commit 15320a8f46e18d867d24eac0a76348631ef3cdda Author: Federico Aponte Date: Sun Jan 16 01:14:05 2022 +0100 First commit diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..608b178 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,116 @@ +cmake_minimum_required(VERSION 3.10) + +project(vb6_parser) + +set(CMAKE_CXX_STANDARD 17) + +#------------------------------------------------------------------------------ +# see https://google.github.io/googletest/quickstart-cmake.html +include(FetchContent) +FetchContent_Declare( + googletest + URL https://github.com/google/googletest/archive/609281088cfefc76f9d0ce82e1ff6c30cc3591e5.zip +) +# for Windows: prevent overriding the parent project's compiler/linker settings +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) +FetchContent_MakeAvailable(googletest) +#------------------------------------------------------------------------------ + +add_compile_options($<$:-Wall> $<$:-Wextra>) + +enable_testing() + +#find_package(GTest CONFIG REQUIRED) # GoogleTest with vcpkg +find_package(Threads REQUIRED) +find_package(Boost REQUIRED QUIET COMPONENTS system) + +add_library(vb6_parser_lib + src/raw_ast_printer.cpp + src/raw_ast_printer.hpp + src/color_console.cpp + src/color_console.hpp + src/cpp_ast_printer.cpp + src/cpp_ast_printer.hpp + src/vb6_ast.hpp + src/vb6_ast_adapt.hpp + src/vb6_config.hpp + src/vb6_error_handler.hpp + src/vb6_parser.cpp + src/vb6_parser.hpp + src/vb6_parser_def.hpp + src/vb6_parser_functions.cpp + src/vb6_parser_helper.cpp + src/vb6_parser_keywords.hpp + src/vb6_parser_operators.hpp + src/vb6_parser_statements.cpp + src/vb6_parser_statements_def.hpp + src/vb6_ast_printer.cpp + src/vb6_ast_printer.hpp + src/visual_basic_x3.hpp +) + +target_compile_definitions(vb6_parser_lib +PRIVATE + -DBOOST_MPL_CFG_NO_PREPROCESSED_HEADERS + -DBOOST_MPL_LIMIT_LIST_SIZE=30 +) + +add_executable(vb6_parser + src/vb6_parser_main.cpp + src/vb6_test1.cpp + src/vb6_test2.cpp +) + +target_compile_definitions(vb6_parser +PRIVATE + -DBOOST_MPL_CFG_NO_PREPROCESSED_HEADERS + -DBOOST_MPL_LIMIT_LIST_SIZE=30 +) + +target_link_libraries(vb6_parser +PRIVATE + vb6_parser_lib + Boost::system + Threads::Threads +) + +# ---- test + +add_executable(vb6_parser_test + src/test_gosub.cpp + src/test_grammar_helper.hpp + src/vb6_parser_statements_test.cpp + src/vb6_parser_test.cpp + src/vb6_parser_test_main.cpp +) + +target_compile_definitions(vb6_parser_test +PRIVATE + -DBOOST_MPL_CFG_NO_PREPROCESSED_HEADERS + -DBOOST_MPL_LIMIT_LIST_SIZE=30 +) + +target_link_libraries(vb6_parser_test +PRIVATE + vb6_parser_lib + gtest_main + Boost::system + Threads::Threads +) +#[[ +# GoogleTest with vcpkg, probably not a good idea +target_link_libraries(vb6_parser_test +PRIVATE + vb6_parser_lib + GTest::gtest + GTest::gtest_main + GTest::gmock + GTest::gmock_main + Boost::system + Threads::Threads +) +#]] + +include(GoogleTest) +gtest_discover_tests(vb6_parser_test) +#add_test(AllTestsInMain vb6_parser_test) diff --git a/README.md b/README.md new file mode 100644 index 0000000..ba1c52a --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +# vb6_parser + +A parsing engine for Microsoft's Visual Basic 6 programming language. + +## Introduction + +This is a parser for the [Visual Basic 6](https://en.wikipedia.org/wiki/Visual_Basic) programming language, implemented using the [Boost Spirit x3](https://www.boost.org/doc/libs/develop/libs/spirit/doc/x3/html/index.html) library. + +https://github.com/fedapo/experiments.git + +## Building + +## Usage + +## History + +## Acknowledgements + +- Boost Spirit X3 +- GTest +- CMake +- GCC +- Clang +- MSYS2 +- Vcpkg +- The C++ Committee diff --git a/data/long_source.bas b/data/long_source.bas new file mode 100644 index 0000000..48ce023 --- /dev/null +++ b/data/long_source.bas @@ -0,0 +1,131 @@ +Attribute ModuleName = "MyForm" +Attribute ProgID = "ca-fe-de-ad" +Option Explicit +Option Compare Text +Option Compare Binary +' first comment +Rem second comment +' long_source.bas + +Dim g_logger0 +Dim a_long As Long + +Const str As String = "una stringa" +Const a_bool = True +Const an_object = Nothing + +' declaration of global constants with no type +Const an_integer = 1234%, a_long = 1234&, a_hex_long = &Hcafedead&, an_oct_long = &01234&, an_integer2 = 1234 +Const a_single_float = 1234!, a_double_float = 1234#, a_float = 2.8 + +' declaration of global variables with type +Dim g_logger As Long +Dim name As New Form + +Type PatRecord + name As String + age As Integer +End Type + +Enum PatTypes + inpatient + outpatient +End Enum + +Global g_logger As Long, v1, XRes As New Object, ptr As Module.MyRec, g_active As Boolean + +Const e As Single = 2.8, pi As Double = 3.14, u As Integer = -1 + +Private Const PI As Double = 3.1415 +Private Declare Sub BeepVB Lib "kernel32.dll" Alias "Beep" (ByVal time As Long, ByVal xx As Single) +Private Declare Function BeepVB Lib "kernel32.dll" Alias "Beep" (ByVal time As Long, ByVal xx As Single) As Long +Const e As Double = 2.8, pi As Double = 3.14, u As Integer = -1 +Global g_logger As Long, v1, XRes As Object, ptr As MyRec, g_active As Boolean +Private Declare Sub PFoo Lib "mylib.dll" Alias "PFoo" (ByVal val As Long) + +Enum MyEnum1 + c1 = 0 + c2 = 1 +End Enum + +Public Type MyRecord1 + v1 As String + v2 As Long +End Type + +Public Event OnChange(ByVal Text As String) + +Sub foo(Optional ByVal name As String = "pippo") +End Sub + +Sub foo(ByVal name As String, ByRef val As Integer) + Dim x As Long + x = 4 +End Sub + +Private Function OneFunc(ByVal name As String, ByRef val As Integer) As Integer +End Function + +Private Function NoParamFunc() As Object +End Function + +Private Sub my_sub(ByRef str As String, ByVal valid As Boolean) +End Sub + +Private Sub my_sub(ByRef str As String, ByVal valid As Boolean, Optional ByVal flag As Boolean = True) + var1.func().pnt1.X = 34 +End Sub + +Private Sub my_sub(ByRef str As String, Optional ByVal valid As Boolean = false) + ' This is comment line 1 + ' Comment line 2 + Set var1 = " ciao\" + + Let var2 = 54.7 + + var3 = Func("descr", 54.7) + + str = CStr(i) + + Dim var1 As String, var2 As Integer + + ReDim var1(15) + + Exit Function + + GoSub label1 + + On Error Resume Next + +label1: + Call Foo(13) + Foo "Sea", Nothing, False + + RaiseEvent OnChange("hi!") + + With obj + ' we miss the dot notation for subroutines and functions + 'Call Module1.foo(True, .Name) + Call foo(True, .Name) + End With +End Sub + +Sub Func(ByRef obj As Canvas) + Dim i As Integer + i = 23 + + Dim pnt As Point3d + pnt.x = 1.0 + pnt.y = 1.2 + ' we miss the dot notation for subroutines and functions + 'Call obj.Plot(pnt) + + While cond(34, i) + Print str + For i = 1 To 100 Step 2 + Dim str As String + str = CStr(i) + Call Print(str) + Next i + Wend +End Sub diff --git a/data/prova_form.frm b/data/prova_form.frm new file mode 100644 index 0000000..31c52f5 --- /dev/null +++ b/data/prova_form.frm @@ -0,0 +1,39 @@ +VERSION 5.00 +Begin VB.Form prova_form + Caption = "prova_form" + ClientHeight = 3030 + ClientLeft = 120 + ClientTop = 450 + ClientWidth = 4560 + LinkTopic = "Form1" + ScaleHeight = 3030 + ScaleWidth = 4560 + StartUpPosition = 3 'Windows Default + Begin VB.CommandButton Command1 + Caption = "Command1" + Height = 375 + Index = 0 + Left = 360 + TabIndex = 0 + Top = 240 + Width = 1095 + End +End +Attribute VB_Name = "prova_form" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = True +Attribute VB_Exposed = False +Option Explicit + +Public id As Integer + +Private Sub Command1_Click(Index As Integer) + +End Sub + +Private Sub Form_Load() + Debug.Print "== prova_form.Load ==" + + Debug.Print id +End Sub diff --git a/data/prova_module.bas b/data/prova_module.bas new file mode 100644 index 0000000..f000ecf --- /dev/null +++ b/data/prova_module.bas @@ -0,0 +1,99 @@ +Attribute VB_Name = "prova_module" +Option Explicit + +Enum MyEnum1 + v0 = False ' this is ok only because it can be cast to an integer + v1 = 1 + v2 = "2" ' this is ok only because it can be cast to an integer + v3 +End Enum + +Type MyRec1 + f1 As Integer + f2 As String +End Type + +Type MyRec2 + f1 As Integer + f2 As String + f3 As MyRec1 +End Type + +Private myprop As String + +Property Get prova_property() As String + prova_property = myprop +End Property + +Property Let prova_property(v As String) ' using "Let" because it is a built-in type? + myprop = v +End Property + +Sub prova_data_member_access() + Debug.Print "== prova_data_member_access ==" + + Dim obj1 As prova_module.MyRec1 + + obj1.f1 = 45 + obj1.f2 = "prova" + + Dim obj2 As prova_module.MyRec2 + + obj2.f1 = 1 + obj2.f2 = "xxx" + obj2.f3.f1 = 2 + obj2.f3.f2 = "yyy" +End Sub + +Function func1() As Integer + Debug.Print "== func1 ==" + func1 = 45 +End Function + +Function func2() As prova_module.MyRec1 + Debug.Print "== func2 ==" + func2.f1 = 45 + func2.f2 = "prova" +End Function + +Sub Main() + prova_data_member_access + + Dim frm As prova_project.prova_form + Set frm = New prova_form + + frm.id = 345 + + VB.Global.Load frm.Command1(1) ' loads a form or control into memory + + frm.Command1(1).Caption = "Nuovo" + frm.Command1(1).Top = 750 + frm.Command1(1).Left = 750 + frm.Command1(1).Width = 1000 + frm.Command1(1).Height = 400 + frm.Command1(1).Visible = True + frm.Command1(1).Enabled = True + + 'VB.Global.Load frm ' loads a form or control into memory + + frm.Show + + func1 + + ' Compile Error: Expected Sub, Function, or Property + 'func2.f1 + 'func2().f1 + + Debug.Print func2.f1 + Debug.Print func2().f2 + + Debug.Print MyEnum1.v0 + Debug.Print MyEnum1.v1 + Debug.Print MyEnum1.v2 + Debug.Print MyEnum1.v3 + + prova_property = "xyxy" + Debug.Print prova_property + + prova_form.Show +End Sub diff --git a/data/prova_project.vbp b/data/prova_project.vbp new file mode 100644 index 0000000..fe6ee59 --- /dev/null +++ b/data/prova_project.vbp @@ -0,0 +1,34 @@ +Type=Exe +Form=prova_form.frm +Reference=*\G{00020430-0000-0000-C000-000000000046}#2.0#0#C:\Windows\SysWOW64\stdole2.tlb#OLE Automation +Module=prova_module; prova_module.bas +IconForm="prova_form" +Startup="Sub Main" +HelpFile="" +Title="prova_project" +Command32="" +Name="prova_project" +HelpContextID="0" +CompatibleMode="0" +MajorVer=1 +MinorVer=0 +RevisionVer=0 +AutoIncrementVer=0 +ServerSupportFiles=0 +VersionCompanyName="csh" +CompilationType=0 +OptimizationType=0 +FavorPentiumPro(tm)=0 +CodeViewDebugInfo=0 +NoAliasing=0 +BoundsCheck=0 +OverflowCheck=0 +FlPointCheck=0 +FDIVCheck=0 +UnroundedFP=0 +StartMode=0 +Unattended=0 +Retained=0 +ThreadPerObject=0 +MaxNumberOfThreads=1 +DebugStartupOption=0 diff --git a/data/test.bas b/data/test.bas new file mode 100644 index 0000000..0eda6df --- /dev/null +++ b/data/test.bas @@ -0,0 +1,65 @@ +Attribute ModuleName = "MyForm" +Attribute ProgID = "a1-b2-c3-d4" +' test.bas +Option Explicit +' compare options +Option Compare Text +Option Compare Binary +' array base options +Option Base 0 +Option Base 1 + +' test.bas + +Global g_v1 As String, g_v2 As Integer +Public g_v3 As Long +Private g_v4 As Single + +' we miss the declaration of arrays +'Dim arr0() As String +'Dim arr1(10) As Integer +'Dim arr2(10, 10) As Long + +' what about this? +'Dim values As Integer * 10 + +Enum Colors + Black = 0 + White = 1 + Blue = 2 + Red = 3 + Green = 4 +End Enum + +Public Type Point3d + x As Double + y As Double +End Type + +Sub Func(ByRef obj As Canvas) + Dim i As Integer + i = 23 + + Dim pnt As Point3d + pnt.x = 1.0 + pnt.y = 1.2 + ' we miss the dot notation for subroutines and functions + 'Call obj.Plot(pnt) + + If check(87) Then + Print "check1" + ElseIf check(92) Then + Print "check2" + Else + Print "check3" + End If + + While cond(34, i) + Print str + For i = 1 To 100 Step 2 + Dim str As String + str = CStr(i) + Call Print(str) + Next i + Wend +End Sub diff --git a/docs/ast_vs_grammar_table.txt b/docs/ast_vs_grammar_table.txt new file mode 100644 index 0000000..6a9082e --- /dev/null +++ b/docs/ast_vs_grammar_table.txt @@ -0,0 +1,89 @@ +AST ELEMENT GRAMMAR RULE +---------------------------------------------------------------------- +empty_line empty_line +lonely_comment lonely_comment +quoted_string quoted_string +var_identifier basic_identifier +identifier_context identifier_context +type_identifier + +vb6_ast::variable vb6_grammar::single_var_declaration +decorated_variable decorated_variable +vb6_ast::global_var_decls vb6_grammar::global_var_declaration +integer_dec +integer_hex +integer_oct +long_dec +long_hex +long_oct +vb6_ast::const_expr vb6_grammar::const_expression +vb6_ast::const_var +vb6_ast::const_var_stat vb6_grammar::const_var_declaration +vb6_ast::record vb6_grammar::record_declaration +vb6_ast::enum_item +vb6_ast::vb_enum vb6_grammar::enum_declaration +expression expression +func_call functionCall +vb6_ast::func_param vb6_grammar::param_decl +vb6_ast::external_decl vb6_grammar:: +vb6_ast::subHead vb6_grammar::subHead +eventHead vb6_grammar::eventHead +vb6_ast::functionHead vb6_grammar::functionHead +propertyLetHead vb6_grammar::property_letHead +propertySetHead vb6_grammar::property_setHead +propertyGetHead vb6_grammar::property_getHead +vb6_ast::externalSub vb6_grammar::external_sub_decl +vb6_ast::externalFunction vb6_grammar::external_function_decl + +vb6_ast::assignStatement vb6_grammar:: +localVarDeclStat localvardeclStatement +redimStatement redimStatement +exitStatement exitStatement +gotoStatement gotoStatement +onerrorStatement onerrorStatement +resumeStatement resumeStatement +labelStatement labelStatement +callStatement callimplicitStatement +callStatement callexplicitStatement +raiseeventStatement raiseeventStatement + +vb6_ast::whileStatement vb6_grammar::whileStatement +vb6_ast::doStatement vb6_grammar::doStatement +vb6_ast::dowhileStatement vb6_grammar::dowhileStatement +vb6_ast::loopwhileStatement vb6_grammar::loopwhileStatement +vb6_ast::dountilStatement vb6_grammar::dountilStatement +vb6_ast::loopuntilStatement vb6_grammar::loopuntilStatement + +vb6_ast::forStatement vb6_grammar::forStatement +vb6_ast::foreachStatement vb6_grammar::foreachStatement +if_branch +vb6_ast::ifelseStatement vb6_grammar::ifelseStatement +vb6_ast::withStatement vb6_grammar::withStatement +case_relational_expr +case_block case_block +vb6_ast::selectStatement vb6_grammar::selectStatement + +vb6_ast::if_branch ifBranch +vb6_ast::if_branch elsifBranch +vb6_ast::statement_block elseBranch + +vb6_ast::singleStatement vb6_grammar::singleStatement +vb6_ast::statement_block vb6_grammar::statement_block + +vb6_ast::subDef vb6_grammar::subDef +vb6_ast::functionDef vb6_grammar::functionDef +vb6_ast::get_prop vb6_grammar:: +vb6_ast::let_prop vb6_grammar:: +vb6_ast::set_prop vb6_grammar:: + +STRICT_MODULE_STRUCTURE + module_attributes + option_block + declaration + functionList + vb_module + +#vb6_ast::decl_item vb6_grammar::declaration +vb6_ast::declaration vb6_grammar::declaration +module_attribute +vb6_ast::vb_module diff --git a/docs/notes_Spirit.md b/docs/notes_Spirit.md new file mode 100644 index 0000000..f0d68cc --- /dev/null +++ b/docs/notes_Spirit.md @@ -0,0 +1,4 @@ +# Boost Spirit + +https://stackoverflow.com/questions/38039237/parsing-identifiers-except-keywords \ +https://www.codevamping.com/2018/09/identifier-parsing-in-boost-spirit-x3-custom-parser/ diff --git a/docs/notes_vb6.md b/docs/notes_vb6.md new file mode 100644 index 0000000..7953e1c --- /dev/null +++ b/docs/notes_vb6.md @@ -0,0 +1,115 @@ +# VB6 - Visual Basic 6 + +https://en.wikipedia.org/wiki/Visual_Basic_(classic) \ +https://en.wikipedia.org/wiki/BASIC + +BASIC = Beginner's All-purpose Symbolic Instruction Code + +> At least for the people who send me mail about a new language that they're +> designing, the general advice is: do it to learn about how to write a compiler. +> Don't have any expectations that anyone will use it, unless you hook up with +> some sort of organization in a position to push it hard. It's a lottery, and +> some can buy a lot of the tickets. There are plenty of beautiful languages +> (more beautiful than C) that didn't catch on. But someone does win the lottery, +> and doing a language at least teaches you something. \ +> _Dennis Ritchie (1941-2011)_ \ +> _Creator of the C programming language and of Unix_ + +--- +## Press + +Visual Basic 6 Renewed to Run on Windows 8 \ +https://www.infoq.com/news/2012/02/vb6_supported_on_win8 + +--- +## Complete VB6 Grammars + +ANTLR4-based Grammars + +https://github.com/antlr/grammars-v4/tree/master/vb6 + +https://github.com/uwol/vb6parser \ +https://github.com/uwol/vb6parser/blob/master/src/main/antlr4/io/proleap/vb6/VisualBasic6.g4 + +Grammar Zoo: http://slebok.github.io/zoo/index.html + +http://boost.2283326.n4.nabble.com/Parser-operator-Difference-td4675788.html + +--- +## Expression Parsing + +https://en.wikipedia.org/wiki/Operator-precedence_parser + +--- +## CodeProject Articles + +Crafting an interpreter Part 1 - Parsing and Grammars \ +https://www.codeproject.com/Articles/10115/Crafting-an-interpreter-Part-1-Parsing-and-Grammar \ + +Visual Basic 6.0: A giant more powerful than ever\ +https://www.codeproject.com/Articles/710181/Visual-Basic-6-0-A-giant-more-powerful-than-ever + +--- +## Misc + +**Grako** (grammar compiler) is a tool that takes grammars in a variation of EBNF as input, and outputs memoizing (Packrat) PEG parsers in Python. \ +A generator of PEG/Packrat parsers from EBNF grammars. \ +https://pypi.python.org/pypi/grako/ + +**TatSu** is a tool that takes grammars in a variation of EBNF as input, and outputs memoizing (Packrat) PEG parsers in Python. \ +https://pypi.org/project/TatSu/ + +GOLD Parsing System \ +http://goldparser.org/engine/1/vb6/index.htm \ +http://goldparser.org/about/comparison-parsers.htm + +http://www.eclipse.org/gmt/modisco/technologies/VisualBasic/#download + +https://tomassetti.me/how-to-write-a-transpiler/ + +The vb2Py project is developing a suite of conversion tools to aid in translating Visual Basic projects into Python.\ +http://vb2py.sourceforge.net/index.html + +http://vb6awards.blogspot.com/2016/04/microsoft-update-or-open-source-vb6.html + +--- +## VB6 Named parameters + +```vb +Function foo(Optional val1 = 1, Optional val2 = 2, Optional val3 = 3) + MsgBox "val1: " & val1 & " val2: " & val2 & " val3: " & val3 + foo = val3 +End Function + +Private Sub Form_Load() + MsgBox "foo returned: " & foo(val3:=4) +End Sub +``` + +## VB6 Dot notation (x.y) + +Access to members of + +- Modules +- Classes, forms, controls +- Types + +Types of members + +- Data members +- Member functions +- Member subroutines +- Properties + +``` +module.variable +module.function +module.subroutine +module.enum +module.type + +object.variable +object.function +object.subroutine +object.property +``` diff --git a/docs/vb6_attributes.md b/docs/vb6_attributes.md new file mode 100644 index 0000000..b299155 --- /dev/null +++ b/docs/vb6_attributes.md @@ -0,0 +1,114 @@ +# VB6 Attributes + +## Class (.cls) + +```vb +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True + Persistable = 0 'NotPersistable + DataBindingBehavior = 0 'vbNone + DataSourceBehavior = 0 'vbNone + MTSTransactionMode = 0 'NotAnMTSObject +END +Attribute VB_Name = "Interactive" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = True +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +... +``` + +## Form (.frm), Control (.ctl), Property Page (.pag) + +```vb +VERSION 5.00 +Object = "{8DDE6232-1BB0-11D0-81C3-0080C7A2EF7D}#3.0#0"; "flp32a30.ocx" +Begin VB.Form frmCalendar +... +``` + +```vb +VERSION 5.00 +Object = "{8DDE6232-1BB0-11D0-81C3-0080C7A2EF7D}#3.0#0"; "flp32a30.ocx" +Begin VB.UserControl ConfSelection +... +``` + +```vb +VERSION 5.00 +Object = "{8DDE6232-1BB0-11D0-81C3-0080C7A2EF7D}#3.0#0"; "flp32a30.ocx" +Begin VB.PropertyPage PropertyPage1 +... +``` + +## Module (.bas) + +```vb +Attribute VB_Name = "CENTSERV" +... +``` + +## Attributes + +https://christopherjmcclellan.wordpress.com/2015/04/21/vb-attributes-what-are-they-and-why-should-we-use-them/ + +### Module Level Attributes + +```vb +VB_Name = "Interactive" +VB_GlobalNameSpace = False +VB_Creatable = True +VB_PredeclaredId = False +VB_Exposed = False +``` + +### Other Attributes + +There are also a number of attributes that can be applied to module variables (fields), properties, and procedures. + +| Attribute | Description | +| --------- | ----------- | +|VB_VarUserMemId| Determines the order of the variables in the Object Broswer. A value of 0 (zero) declares the variable to be the default member of the class.| +|VB_VarDescription| The value of this attribute will be displayed in the Object Broswer.| +|VB_UserMemId| | +VB_Description| | + +```vb +LmoIEIDXRad.VB_VarHelpID = -1 +Item.VB_UserMemId = 0 +NewEnum.VB_UserMemId = -4 +``` + +There is one more special value for `VB_UserMemId` and that value is -4. +Negative 4 always indicates that the function being marked should return +a [_NewEnum] enumerator. + +```vb +' Header +Attribute VB_Name = "ClassOrModuleName" +Attribute VB_GlobalNameSpace = False ' ignored +Attribute VB_Creatable = False ' ignored +Attribute VB_PredeclaredId = False ' a Value of True creates a default global instance +Attribute VB_Exposed = True ' Controls how the class can be instanced. + +' Module Scoped Variables +Attribute variableName.VB_VarUserMemId = 0 ' Zero indicates that this is the default member of the class. +Attribute variableName.VB_VarDescription = "some string" ' Adds the text to the Object Browser information for this variable. + +' Procedures +Attribute procName.VB_Description = "some string" ' Adds the text to the Object Browser information for the procedure. +Attribute procName.VB_UserMemId = someInteger + ' 0: Makes the function the default member of the class. + ' -4: Specifies that the function returns an Enumerator. +``` + +In a control. + +```vb +Public Sub SpeechMarkBeg() +Attribute SpeechMarkBeg.VB_MemberFlags = "40" + +Public Property Get MaxLength() As Single +Attribute MaxLength.VB_Description = "When the length of the dictation hits this number (in minutes) then the system stops recording and asks the user if he/she wants to proceed." +``` diff --git a/msvc/vb6_parser.sln b/msvc/vb6_parser.sln new file mode 100644 index 0000000..48d9226 --- /dev/null +++ b/msvc/vb6_parser.sln @@ -0,0 +1,57 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.28701.123 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "vb6_parser", "vb6_parser.vcxproj", "{D252565E-F568-11E9-B82C-5AA538984BD8}" + ProjectSection(ProjectDependencies) = postProject + {6808F98C-3361-447B-8F34-F78EFB332316} = {6808F98C-3361-447B-8F34-F78EFB332316} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "vb6_parser_test", "vb6_parser_test.vcxproj", "{B5CDF59F-23D6-4E11-A5AE-F1FEDD1C8F58}" + ProjectSection(ProjectDependencies) = postProject + {6808F98C-3361-447B-8F34-F78EFB332316} = {6808F98C-3361-447B-8F34-F78EFB332316} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "vb6_parser_lib", "vb6_parser_lib.vcxproj", "{6808F98C-3361-447B-8F34-F78EFB332316}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D252565E-F568-11E9-B82C-5AA538984BD8}.Debug|x64.ActiveCfg = Debug|x64 + {D252565E-F568-11E9-B82C-5AA538984BD8}.Debug|x64.Build.0 = Debug|x64 + {D252565E-F568-11E9-B82C-5AA538984BD8}.Debug|x86.ActiveCfg = Debug|Win32 + {D252565E-F568-11E9-B82C-5AA538984BD8}.Debug|x86.Build.0 = Debug|Win32 + {D252565E-F568-11E9-B82C-5AA538984BD8}.Release|x64.ActiveCfg = Release|x64 + {D252565E-F568-11E9-B82C-5AA538984BD8}.Release|x64.Build.0 = Release|x64 + {D252565E-F568-11E9-B82C-5AA538984BD8}.Release|x86.ActiveCfg = Release|Win32 + {D252565E-F568-11E9-B82C-5AA538984BD8}.Release|x86.Build.0 = Release|Win32 + {B5CDF59F-23D6-4E11-A5AE-F1FEDD1C8F58}.Debug|x64.ActiveCfg = Debug|x64 + {B5CDF59F-23D6-4E11-A5AE-F1FEDD1C8F58}.Debug|x64.Build.0 = Debug|x64 + {B5CDF59F-23D6-4E11-A5AE-F1FEDD1C8F58}.Debug|x86.ActiveCfg = Debug|Win32 + {B5CDF59F-23D6-4E11-A5AE-F1FEDD1C8F58}.Debug|x86.Build.0 = Debug|Win32 + {B5CDF59F-23D6-4E11-A5AE-F1FEDD1C8F58}.Release|x64.ActiveCfg = Release|x64 + {B5CDF59F-23D6-4E11-A5AE-F1FEDD1C8F58}.Release|x64.Build.0 = Release|x64 + {B5CDF59F-23D6-4E11-A5AE-F1FEDD1C8F58}.Release|x86.ActiveCfg = Release|Win32 + {B5CDF59F-23D6-4E11-A5AE-F1FEDD1C8F58}.Release|x86.Build.0 = Release|Win32 + {6808F98C-3361-447B-8F34-F78EFB332316}.Debug|x64.ActiveCfg = Debug|x64 + {6808F98C-3361-447B-8F34-F78EFB332316}.Debug|x64.Build.0 = Debug|x64 + {6808F98C-3361-447B-8F34-F78EFB332316}.Debug|x86.ActiveCfg = Debug|Win32 + {6808F98C-3361-447B-8F34-F78EFB332316}.Debug|x86.Build.0 = Debug|Win32 + {6808F98C-3361-447B-8F34-F78EFB332316}.Release|x64.ActiveCfg = Release|x64 + {6808F98C-3361-447B-8F34-F78EFB332316}.Release|x64.Build.0 = Release|x64 + {6808F98C-3361-447B-8F34-F78EFB332316}.Release|x86.ActiveCfg = Release|Win32 + {6808F98C-3361-447B-8F34-F78EFB332316}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {559721B8-C7D6-4E7F-8089-9F93DCD9C915} + EndGlobalSection +EndGlobal diff --git a/msvc/vb6_parser.vcxproj b/msvc/vb6_parser.vcxproj new file mode 100644 index 0000000..b008fb6 --- /dev/null +++ b/msvc/vb6_parser.vcxproj @@ -0,0 +1,154 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {D252565E-F568-11E9-B82C-5AA538984BD8} + 10.0 + + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + + + + true + + + true + + + + MultiThreadedDebugDLL + Level3 + BOOST_MPL_CFG_NO_PREPROCESSED_HEADERS;BOOST_MPL_LIMIT_LIST_SIZE=30;WIN32;_DEBUG;%(PreprocessorDefinitions) + true + Disabled + ProgramDatabase + stdcpp17 + + + MachineX86 + Console + true + + + + + MultiThreadedDLL + Level3 + true + true + BOOST_MPL_CFG_NO_PREPROCESSED_HEADERS;BOOST_MPL_LIMIT_LIST_SIZE=30;WIN32;NDEBUG;%(PreprocessorDefinitions) + true + stdcpp17 + + + MachineX86 + Console + true + true + true + + + + + Level3 + BOOST_MPL_CFG_NO_PREPROCESSED_HEADERS;BOOST_MPL_LIMIT_LIST_SIZE=30;%(PreprocessorDefinitions) + true + stdcpp17 + + + + + Level3 + true + true + true + BOOST_MPL_CFG_NO_PREPROCESSED_HEADERS;BOOST_MPL_LIMIT_LIST_SIZE=30;%(PreprocessorDefinitions) + true + stdcpp17 + + + + + + + + + + + + + + + + {6808f98c-3361-447b-8f34-f78efb332316} + + + + + + \ No newline at end of file diff --git a/msvc/vb6_parser.vcxproj.filters b/msvc/vb6_parser.vcxproj.filters new file mode 100644 index 0000000..f81dda4 --- /dev/null +++ b/msvc/vb6_parser.vcxproj.filters @@ -0,0 +1,42 @@ + + + + + {1BD2A6B2-F569-11E9-802A-5AA538984BD8} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {4319F8BA-F569-11E9-802A-5AA538984BD8} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {2E09A9E8-F569-11E9-B82C-5AA538984BD8} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + \ No newline at end of file diff --git a/msvc/vb6_parser_lib.vcxproj b/msvc/vb6_parser_lib.vcxproj new file mode 100644 index 0000000..d9a867f --- /dev/null +++ b/msvc/vb6_parser_lib.vcxproj @@ -0,0 +1,180 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + + + + + + + + + + + + + + + + + + + + + + + + + 16.0 + Win32Proj + {6808f98c-3361-447b-8f34-f78efb332316} + vb6parserlib + 10.0 + + + + StaticLibrary + true + v143 + Unicode + + + StaticLibrary + false + v143 + true + Unicode + + + StaticLibrary + true + v143 + Unicode + + + StaticLibrary + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + + + + true + + + false + + + true + + + false + + + + Level3 + true + BOOST_MPL_CFG_NO_PREPROCESSED_HEADERS;BOOST_MPL_LIMIT_LIST_SIZE=30;WIN32;_DEBUG;%(PreprocessorDefinitions) + true + stdcpp17 + + + + + true + + + + + Level3 + true + true + true + BOOST_MPL_CFG_NO_PREPROCESSED_HEADERS;BOOST_MPL_LIMIT_LIST_SIZE=30;WIN32;NDEBUG;%(PreprocessorDefinitions) + true + stdcpp17 + + + + + true + true + true + + + + + Level3 + true + BOOST_MPL_CFG_NO_PREPROCESSED_HEADERS;BOOST_MPL_LIMIT_LIST_SIZE=30;_DEBUG;%(PreprocessorDefinitions) + true + stdcpp17 + + + + + true + + + + + Level3 + true + true + true + BOOST_MPL_CFG_NO_PREPROCESSED_HEADERS;BOOST_MPL_LIMIT_LIST_SIZE=30;NDEBUG;%(PreprocessorDefinitions) + true + stdcpp17 + + + + + true + true + true + + + + + + \ No newline at end of file diff --git a/msvc/vb6_parser_lib.vcxproj.filters b/msvc/vb6_parser_lib.vcxproj.filters new file mode 100644 index 0000000..0cf3b33 --- /dev/null +++ b/msvc/vb6_parser_lib.vcxproj.filters @@ -0,0 +1,93 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {4db10dc1-bd75-48ec-a340-cd63bfe93a54} + + + {5fc50242-d0ab-4904-a16b-20faa38be067} + + + {58be8f26-3ece-4851-9850-256d9c32c62e} + + + {017cf1f3-4296-4f1b-b845-08fbee14fac0} + + + + + Source Files\ast_printers + + + Source Files\ast_printers + + + Source Files\ast_printers + + + Source Files\parser + + + Source Files\parser + + + Source Files\parser + + + Source Files\parser + + + + + Header Files\ast_printers + + + Header Files\ast_printers + + + Header Files\ast_printers + + + Header Files\parser + + + Header Files\parser + + + Header Files\parser + + + Header Files\parser + + + Header Files\parser + + + Header Files\parser + + + Header Files\parser + + + Header Files\parser + + + Header Files + + + Header Files + + + \ No newline at end of file diff --git a/msvc/vb6_parser_test.vcxproj b/msvc/vb6_parser_test.vcxproj new file mode 100644 index 0000000..d9bb3f2 --- /dev/null +++ b/msvc/vb6_parser_test.vcxproj @@ -0,0 +1,168 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {b5cdf59f-23d6-4e11-a5ae-f1fedd1c8f58} + vb6parsertest + 10.0 + + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + + + + true + + + false + + + true + + + false + + + + Level3 + true + BOOST_MPL_CFG_NO_PREPROCESSED_HEADERS;BOOST_MPL_LIMIT_LIST_SIZE=30;GTEST_LINKED_AS_SHARED_LIBRARY;WIN32;_DEBUG;%(PreprocessorDefinitions) + true + stdcpp17 + + + Console + true + + + + + Level3 + true + true + true + BOOST_MPL_CFG_NO_PREPROCESSED_HEADERS;BOOST_MPL_LIMIT_LIST_SIZE=30;GTEST_LINKED_AS_SHARED_LIBRARY;WIN32;NDEBUG;%(PreprocessorDefinitions) + true + stdcpp17 + + + Console + true + true + true + + + + + Level3 + true + BOOST_MPL_CFG_NO_PREPROCESSED_HEADERS;BOOST_MPL_LIMIT_LIST_SIZE=30;GTEST_LINKED_AS_SHARED_LIBRARY;_DEBUG;%(PreprocessorDefinitions) + true + stdcpp17 + + + Console + true + + + + + Level3 + true + true + true + BOOST_MPL_CFG_NO_PREPROCESSED_HEADERS;BOOST_MPL_LIMIT_LIST_SIZE=30;GTEST_LINKED_AS_SHARED_LIBRARY;NDEBUG;%(PreprocessorDefinitions) + true + stdcpp17 + + + Console + true + true + true + + + + + + + + + + + + + + + + {6808f98c-3361-447b-8f34-f78efb332316} + + + + + + \ No newline at end of file diff --git a/msvc/vb6_parser_test.vcxproj.filters b/msvc/vb6_parser_test.vcxproj.filters new file mode 100644 index 0000000..3f716a5 --- /dev/null +++ b/msvc/vb6_parser_test.vcxproj.filters @@ -0,0 +1,42 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + \ No newline at end of file diff --git a/src/color_console.cpp b/src/color_console.cpp new file mode 100644 index 0000000..c5a4757 --- /dev/null +++ b/src/color_console.cpp @@ -0,0 +1,91 @@ +//: color_console.cpp + +// vb6_parser +// Copyright (c) 2022 Federico Aponte +// This code is licensed under GNU Software License (see LICENSE.txt for details) + +#include "color_console.hpp" + +#ifdef _MSC_VER +#define WIN32_LEAN_AND_MEAN +#include +#endif + +#include +#include + +/* + bit 0 - foreground blue + bit 1 - foreground green + bit 2 - foreground red + bit 3 - foreground intensity + + bit 4 - background blue + bit 5 - background green + bit 6 - background red + bit 7 - background intensity +*/ + +color_manip setcolor(uint8_t col) +{ + return color_manip(col); +} + +std::ostream& operator<<(std::ostream& os, color_manip c) +{ +#ifdef _MSC_VER + HANDLE hstdout = GetStdHandle(STD_OUTPUT_HANDLE); + + //SetConsoleMode(STD_OUTPUT_HANDLE, ENABLE_VIRTUAL_TERMINAL_INPUT); + + // restore console state + //SetConsoleTextAttribute(hstdout, csbi.wAttributes); + + // save console state + //CONSOLE_SCREEN_BUFFER_INFO csbi; + //GetConsoleScreenBufferInfo(hstdout, &csbi); + + SetConsoleTextAttribute(hstdout, c.color); +#else + // ANSI escape codes could also be used in some terminals + switch(c.color) + { + case 0: os << std::setfill('c') << "\x1b[30;1m"; break; // bold;black + case 1: os << "\x1b[34;1m"; break; // bold;blue + case 2: os << "\x1b[32;1m"; break; // bold;green + case 3: os << "\x1b[36;1m"; break; // bold;cyan + case 4: os << "\x1b[31;1m"; break; // bold;red + case 5: os << "\x1b[35;1m"; break; // bold;magenta + case 14: os << "\x1b[33;1m"; break; // bold;yellow + case 15: os << "\x1b[37;1m"; break; // bold;white + case 7: os << "\x1b[0m"; break; // reset + } +#endif + + return os; +} + +std::ostream& tag_ok(std::ostream& os) +{ + os << "[" << setcolor(0x02) << "ok" << setcolor(0x07) << "]"; + return os; +} + +std::ostream& tag_fail(std::ostream& os) +{ + os << "[" << setcolor(0x04) << "fail" << setcolor(0x07) << "]"; + return os; +} + +void wait_key_pressed() +{ +#ifdef _MSC_VER + HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE); + + // wait for a key to be pressed + WaitForSingleObject(hstdin, INFINITE); + + // what's this for? removes all characters input by the users? + FlushConsoleInputBuffer(hstdin); +#endif +} \ No newline at end of file diff --git a/src/color_console.hpp b/src/color_console.hpp new file mode 100644 index 0000000..dd61963 --- /dev/null +++ b/src/color_console.hpp @@ -0,0 +1,25 @@ +//: color_console.hpp + +// vb6_parser +// Copyright (c) 2022 Federico Aponte +// This code is licensed under GNU Software License (see LICENSE.txt for details) + +#pragma once + +#include +#include + +struct color_manip +{ + uint8_t color; + + explicit color_manip(uint8_t c) : color(c) {} +}; + +color_manip setcolor(uint8_t col); +std::ostream& operator<<(std::ostream&, color_manip c); + +std::ostream& tag_ok(std::ostream&); +std::ostream& tag_fail(std::ostream&); + +void wait_key_pressed(); \ No newline at end of file diff --git a/src/cpp_ast_printer.cpp b/src/cpp_ast_printer.cpp new file mode 100644 index 0000000..5501c31 --- /dev/null +++ b/src/cpp_ast_printer.cpp @@ -0,0 +1,754 @@ +//: cpp_ast_printer.cpp + +// vb6_parser +// Copyright (c) 2022 Federico Aponte +// This code is licensed under GNU Software License (see LICENSE.txt for details) + +#include "cpp_ast_printer.hpp" + +#include +#include + +using namespace std; + +// static +int cpp_ast_printer::indent_size = 4; + +void cpp_ast_printer::operator()(vb6_ast::empty_line const&) const +{ + os << '\n'; +} + +void cpp_ast_printer::operator()(vb6_ast::lonely_comment const& ast) const +{ + os << string(indent, ' '); + os << "//" << ast.content << '\n'; +} + +void cpp_ast_printer::operator()(vb6_ast::identifier_context const& ast) const +{ + if(ast.leading_dot) + os << '.'; + + struct { + void operator()(std::string s) { os << s; } + void operator()(vb6_ast::func_call const& s) { (*p)(s); } + ostream& os; + cpp_ast_printer const* p; + } visitor{ os, this }; + + for(auto& el : ast.elements) + { + boost::apply_visitor(visitor, el.get()); + os << '.'; + } +} + +void cpp_ast_printer::operator()(vb6_ast::variable const& ast) const +{ + //if(ast.library_or_module) + // os << *ast.library_or_module << "::"; + os << (ast.type ? *ast.type : "std::any") << " " << ast.name << ";"; +} + +void cpp_ast_printer::operator()(vb6_ast::decorated_variable const& ast) const +{ + (*this)(ast.ctx); + os << ast.var; +} + +void cpp_ast_printer::operator()(vb6_ast::func_call const& ast) const +{ + os << ast.func_name << "("; + + bool first = true; + for(auto& el : ast.params) + { + if(first) + first = false; + else + os << ", "; + + (*this)(el); + } + + os << ");\n"; +} + +void cpp_ast_printer::operator()(vb6_ast::global_var_decls const& ast) const +{ + print_type(ast.at); + + bool first = true; + for(auto& el : ast.vars) + { + if(first) + first = false; + else + os << ", "; + + (*this)(el); + } + os << '\n'; +} + +void cpp_ast_printer::operator()(vb6_ast::const_expr const& ast) const +{ + struct { + void operator()(float v) { os << v << 'f'; } + void operator()(double v) { os << v; } + void operator()(vb6_ast::long_dec v) { os << v.val; } + void operator()(vb6_ast::long_hex v) { os << showbase << hex << v.val << dec; } + void operator()(vb6_ast::long_oct v) { os << showbase << oct << v.val << dec; } + void operator()(vb6_ast::integer_dec v) { os << v.val; } + void operator()(vb6_ast::integer_hex v) { os << showbase << hex << v.val << dec; } + void operator()(vb6_ast::integer_oct v) { os << showbase << oct << v.val << dec; } + void operator()(bool v) { os << (v ? "true" : "false"); } + void operator()(vb6_ast::quoted_string const& s) { os << "\"" << s << "\""; } + void operator()(vb6_ast::nothing const&) { os << "nullptr"; } + ostream& os; + } visitor{os}; + + boost::apply_visitor(visitor, ast.get()); +} + +void cpp_ast_printer::operator()(vb6_ast::expression const& ast) const +{ + boost::apply_visitor(*this, ast.get()); +} + +void cpp_ast_printer::operator()(vb6_ast::const_var_stat const& cvars) const +{ + os << "Const "; + bool first = true; + for(auto& el : cvars) + { + if(first) + first = false; + else + os << ", "; + + os << (el.var.type ? *el.var.type : "std::any") + << el.var.name << " = "; + (*this)(el.value); + } +} + +void cpp_ast_printer::print_type(vb6_ast::access_type t) const +{ + switch(t) + { + case vb6_ast::access_type::na: break; + case vb6_ast::access_type::dim: os << "Dim "; break; + case vb6_ast::access_type::private_: os << "Private "; break; + case vb6_ast::access_type::public_: os << "Public "; break; + case vb6_ast::access_type::global: os << "Global "; break; + case vb6_ast::access_type::friend_: os << "Friend "; break; + } +} + +void cpp_ast_printer::operator()(vb6_ast::record const& ast) const +{ + print_type(ast.at); + os << "struct " << ast.name << '\n' << "{"; + indent += indent_size; + for(auto& el : ast.members) + { + os << string(indent, ' '); + (*this)(el); + os << ";\n"; + } + indent -= indent_size; + os << "};\n"; +} + +void cpp_ast_printer::operator()(vb6_ast::vb_enum const& ast) const +{ + print_type(ast.at); + os << "enum " << ast.name << '\n' << "{"; + indent += indent_size; + for(auto& el : ast.values) + { + os << string(indent, ' ') << el.first; + if(el.second) + { + os << " = "; + (*this)(*el.second); + } + os << ",\n"; + } + indent -= indent_size; + os << "};\n"; +} + +void cpp_ast_printer::operator()(vb6_ast::func_param const& ast) const +{ + os << (ast.var.type ? *ast.var.type : "std::any"); + + if(!ast.qualifier || *ast.qualifier == vb6_ast::param_qualifier::byref) + os << "&"; + + os << " " << ast.var.name; + + if(ast.defvalue) + { + os << " = "; + (*this)(*ast.defvalue); + } +} + +void cpp_ast_printer::operator()(vb6_ast::externalSub const& ast) const +{ + print_type(ast.at); + os << "__declspec(dllimport)\n" + "void " << ast.name << "("; + bool first = true; + for(auto& el : ast.params) + { + if(first) + first = false; + else + os << ", "; + + (*this)(el); + } + os << ");\n"; +} + +void cpp_ast_printer::operator()(vb6_ast::subHead const& ast) const +{ + print_type(ast.at); + os << "void " << ast.name << "("; + bool first = true; + for(auto& el : ast.params) + { + if(first) + first = false; + else + os << ", "; + + (*this)(el); + } + os << ");\n"; +} + +void cpp_ast_printer::operator()(vb6_ast::eventHead const& ast) const +{ + print_type(ast.at); + os << "void /* event */ " << ast.name << "("; + bool first = true; + for(auto& el : ast.params) + { + if(first) + first = false; + else + os << ", "; + + (*this)(el); + } + os << ");\n"; +} + +void cpp_ast_printer::operator()(vb6_ast::functionHead const& ast) const +{ + print_type(ast.at); + os << ast.return_type << " " << ast.name << "("; + bool first = true; + for(auto& el : ast.params) + { + if(first) + first = false; + else + os << ", "; + + (*this)(el); + } + os << ")\n"; +} + +void cpp_ast_printer::operator()(vb6_ast::propertyLetHead const& ast) const +{ + print_type(ast.at); + os << "void let_" << ast.name << "("; + bool first = true; + for(auto& el : ast.params) + { + if(first) + first = false; + else + os << ", "; + + (*this)(el); + } + os << ")\n"; +} + +void cpp_ast_printer::operator()(vb6_ast::propertySetHead const& ast) const +{ + print_type(ast.at); + os << "void set_" << ast.name << "("; + bool first = true; + for(auto& el : ast.params) + { + if(first) + first = false; + else + os << ", "; + + (*this)(el); + } + os << ")\n"; +} + +void cpp_ast_printer::operator()(vb6_ast::propertyGetHead const& ast) const +{ + print_type(ast.at); + os << ast.return_type << " get_" << ast.name << "("; + bool first = true; + for(auto& el : ast.params) + { + if(first) + first = false; + else + os << ", "; + + (*this)(el); + } + os << ")"; + if(ast.return_type) + os << " As " << *ast.return_type; + os << '\n'; +} + +void cpp_ast_printer::operator()(vb6_ast::subDef const& ast) const +{ + (*this)(ast.header); + print_block(ast.statements); +} + +void cpp_ast_printer::operator()(vb6_ast::functionDef const& ast) const +{ + (*this)(ast.header); + print_block(ast.statements); +} + +void cpp_ast_printer::operator()(vb6_ast::externalFunction const& ast) const +{ + print_type(ast.at); + os << "__declspec(dllimport)\n" + << *ast.return_type << " " << ast.name << "("; + bool first = true; + for(auto& el : ast.params) + { + if(first) + first = false; + else + os << ", "; + + (*this)(el); + } + os << ");\n"; +} + +void cpp_ast_printer::operator()(vb6_ast::declaration const& ast) const +{ + boost::apply_visitor(*this, ast); +} + +void cpp_ast_printer::operator()(vb6_ast::module_attribute) const +{ +} + +void cpp_ast_printer::operator()(vb6_ast::module_option) const +{ +} + +//void cpp_ast_printer::operator()(vb6_ast::STRICT_MODULE_STRUCTURE::module_attributes const&) const +//{ +//} + +void cpp_ast_printer::operator()(vb6_ast::STRICT_MODULE_STRUCTURE::vb_module const& ast) const +{ + for(auto& el : ast.declarations) + { + boost::apply_visitor(*this, el); + } + + for(auto& el : ast.functions) + { + boost::apply_visitor(*this, el); + } +} + +void cpp_ast_printer::operator()(vb6_ast::vb_module const& ast) const +{ + for(auto& el : ast) + { + boost::apply_visitor(*this, el); + } +} + +void cpp_ast_printer::print_block(vb6_ast::statements::statement_block const& block) const +{ + os << string(indent, ' ') << "{\n"; + + indent += indent_size; + for(auto& st : block) + (*this)(st); + indent -= indent_size; + + os << string(indent, ' ') << "}\n"; +} + +void cpp_ast_printer::operator()(vb6_ast::statements::assignStmt const& ast) const +{ + os << string(indent, ' '); + switch(ast.type) + { + case vb6_ast::assignmentType::na: break; + case vb6_ast::assignmentType::set: os << "Set "; break; + case vb6_ast::assignmentType::let: os << "Let "; break; + } + + (*this)(ast.var); + os << " = "; + + (*this)(ast.rhs); + + // print the index of the type in a comment + os << " // " << ast.rhs.get().which() << '\n'; +} + +void cpp_ast_printer::operator()(vb6_ast::statements::exitStmt const& ast) const +{ + os << string(indent, ' '); + switch(ast.type) + { + case vb6_ast::exit_type::function: os << "return ???;"; break; + case vb6_ast::exit_type::sub: os << "return;"; break; + case vb6_ast::exit_type::property: os << "Property"; break; + case vb6_ast::exit_type::while_: os << "break;"; break; + case vb6_ast::exit_type::do_: os << "break;"; break; + case vb6_ast::exit_type::for_: os << "break;"; break; + } + os << '\n'; +} + +void cpp_ast_printer::operator()(vb6_ast::statements::gotoStmt const& ast) const +{ + os << string(indent, ' ') << (ast.type == vb6_ast::gotoType::goto_v ? "GoTo" : "GoSub") + << " " << ast.label << '\n'; +} + +void cpp_ast_printer::operator()(vb6_ast::statements::onerrorStmt const& ast) const +{ + os << string(indent, ' ') << "On Error "; + switch(ast.type) + { + case vb6_ast::onerror_type::goto_0: os << "GoTo 0"; break; + case vb6_ast::onerror_type::goto_neg_1: os << "GoTo -1"; break; + case vb6_ast::onerror_type::goto_label: os << "goto " << ast.label; break; + case vb6_ast::onerror_type::resume_next: os << "Resume Next"; break; + case vb6_ast::onerror_type::exit_sub: os << "Exit Sub"; break; + case vb6_ast::onerror_type::exit_func: os << "Exit Func"; break; + case vb6_ast::onerror_type::exit_property: os << "Exit Property"; break; + } + os << '\n'; +} + +void cpp_ast_printer::operator()(vb6_ast::statements::resumeStmt const& ast) const +{ + os << string(indent, ' ') << "Resume"; + switch(ast.type) + { + case vb6_ast::resume_type::implicit: + break; + case vb6_ast::resume_type::next: + os << " Next"; + break; + case vb6_ast::resume_type::label: + os << " " << boost::get(ast.label_or_line_nr); + break; + case vb6_ast::resume_type::line_nr: + os << " " << boost::get(ast.label_or_line_nr); + break; + } + os << '\n'; +} + +void cpp_ast_printer::operator()(vb6_ast::statements::localVarDeclStmt const& ast) const +{ + os << string(indent, ' '); + + if(ast.type == vb6_ast::localvardeclType::Static) + os << "static "; + + bool first = true; + for(auto& el : ast.vars) + { + if(first) + first = false; + else + os << ", "; + + os << (el.type ? *el.type : "std::any") << " " << el.name; + } + os << ";\n"; +} + +void cpp_ast_printer::operator()(vb6_ast::statements::redimStmt const& ast) const +{ + os << string(indent, ' '); + + if(ast.preserve) + { + // TODO + } + + (*this)(ast.var); + + os << " = new " << "Type" << "["; + + bool first = true; + for(auto& el : ast.newsize) + { + if(first) + first = false; + else + os << ", "; + + (*this)(el); + } + + os << "];\n"; +} + +// vb6_ast::returnStmt + +void cpp_ast_printer::operator()(vb6_ast::statements::callStmt const& ast) const +{ + os << string(indent, ' ') << ast.sub_name << "("; + + bool first = true; + for(auto& el : ast.params) + { + if(first) + first = false; + else + os << ", "; + + (*this)(el); + } + + os << ");\n"; +} + +void cpp_ast_printer::operator()(vb6_ast::statements::raiseeventStmt const& ast) const +{ + os << string(indent, ' ') << ast.event_name << "("; + + bool first = true; + for(auto& el : ast.params) + { + if(first) + first = false; + else + os << ", "; + + (*this)(el); + } + + os << ");\n"; +} + +void cpp_ast_printer::operator()(vb6_ast::statements::labelStmt const& ast) const +{ + os << ast.label << ":\n"; // no indentation for labels +} + +void cpp_ast_printer::operator()(vb6_ast::statements::whileStmt const& ast) const +{ + os << string(indent, ' ') << "while("; + (*this)(ast.condition); + os << ")\n"; + os << string(indent, ' ') << "{\n"; + os << string(indent, ' ') << "}\n"; + os << string(indent, ' ') << "while("; + (*this)(ast.condition); + os << ")\n"; + print_block(ast.block); +} + +void cpp_ast_printer::operator()(vb6_ast::statements::doStmt const& ast) const +{ + os << string(indent, ' ') << "for(;;)" << '\n'; + print_block(ast.block); +} + +void cpp_ast_printer::operator()(vb6_ast::statements::dowhileStmt const& ast) const +{ + os << string(indent, ' ') << "while("; + (*this)(ast.condition); + os << ")\n"; + print_block(ast.block); +} + +void cpp_ast_printer::operator()(vb6_ast::statements::loopwhileStmt const& ast) const +{ + os << string(indent, ' ') << "do\n"; + os << string(indent, ' ') << "{\n"; + + indent += indent_size; + for(auto& st : ast.block) + (*this)(st); + indent -= indent_size; + + os << string(indent, ' ') << "} while("; + (*this)(ast.condition); + os << ")\n"; +} + +void cpp_ast_printer::operator()(vb6_ast::statements::dountilStmt const& ast) const +{ + os << string(indent, ' ') << "while(not ("; + (*this)(ast.condition); + os << "))\n"; + print_block(ast.block); +} + +void cpp_ast_printer::operator()(vb6_ast::statements::loopuntilStmt const& ast) const +{ + os << string(indent, ' ') << "do\n"; + os << string(indent, ' ') << "{\n"; + + indent += indent_size; + for(auto& st : ast.block) + (*this)(st); + indent -= indent_size; + + os << string(indent, ' ') << "} while(not ("; + (*this)(ast.condition); + os << "))\n"; +} + +void cpp_ast_printer::operator()(vb6_ast::statements::forStmt const& ast) const +{ + os << string(indent, ' ') << "for("; + (*this)(ast.for_variable); + os << " = "; + (*this)(ast.from); + os << "; "; + (*this)(ast.for_variable); + os << " <= "; + (*this)(ast.to); + if(ast.step) + { + os << "; "; + (*this)(ast.for_variable); + os << " += "; + (*this)(*ast.step); + } + else + { + os << "; ++"; + (*this)(ast.for_variable); + } + + os << ")\n"; + print_block(ast.block); +} + +void cpp_ast_printer::operator()(vb6_ast::statements::foreachStmt const& ast) const +{ + os << string(indent, ' ') << "for("; + (*this)(ast.for_variable); + os << " : "; + (*this)(ast.container); + os << ")\n"; + print_block(ast.block); +} + +void cpp_ast_printer::operator()(vb6_ast::statements::ifelseStmt const& ast) const +{ + // TODO +#ifdef SIMPLE_IF_STATEMENT + os << string(indent, ' ') << "if("; + //(*this)(ast.first_branch); + os << ")\n"; + print_block(ast.first_branch.block); +#else + bool first = true; + for(auto& el : ast.if_branches) + { + if(first) + { + os << string(indent, ' ') << "if("; + (*this)(el.condition); + os << ")\n"; + } + else + { + os << string(indent, ' ') << "else if("; + (*this)(el.condition); + os << ")\n"; + } + print_block(el.block); + } +#endif + + if(ast.else_branch.has_value()) + { + os << "else\n"; + print_block(ast.else_branch.get()); + } +} + +void cpp_ast_printer::operator()(vb6_ast::statements::withStmt const& ast) const +{ + // TODO + os << string(indent, ' ') << "auto& dummy = "; + (*this)(ast.with_variable); + os << ";\n"; + + // statements + indent += indent_size; + for(auto& st : ast.block) + (*this)(st); + indent -= indent_size; +} + +void cpp_ast_printer::operator()(vb6_ast::statements::case_block const& ast) const +{ + // TODO + os << string(indent, ' ') << "case "; + (*this)(ast.case_expr); + os << ":\n"; + + indent += indent_size; + for(auto& st : ast.block) + (*this)(st); + indent -= indent_size; + + os << "break;\n"; +} + +void cpp_ast_printer::operator()(vb6_ast::statements::selectStmt const& ast) const +{ + // TODO + os << string(indent, ' ') << "switch("; + (*this)(ast.condition); + os << ")\n"; + os << string(indent, ' ') << "{\n"; + + indent += indent_size; + for(auto& st : ast.blocks) + (*this)(st); + indent -= indent_size; + + os << string(indent, ' ') << "}\n"; +} + +void cpp_ast_printer::operator()(vb6_ast::statements::singleStmt const& ast) const +{ + boost::apply_visitor(*this, ast.get()); +} diff --git a/src/cpp_ast_printer.hpp b/src/cpp_ast_printer.hpp new file mode 100644 index 0000000..abb5525 --- /dev/null +++ b/src/cpp_ast_printer.hpp @@ -0,0 +1,82 @@ +//: cpp_ast_printer.hpp + +// vb6_parser +// Copyright (c) 2022 Federico Aponte +// This code is licensed under GNU Software License (see LICENSE.txt for details) + +#pragma once + +#include "vb6_ast.hpp" +#include + +class cpp_ast_printer +{ +public: + explicit cpp_ast_printer(std::ostream& os) : os(os) + { + } + + void operator()(vb6_ast::empty_line const&) const; + void operator()(vb6_ast::lonely_comment const&) const; + void operator()(vb6_ast::identifier_context const&) const; + void operator()(vb6_ast::variable const&) const; + void operator()(vb6_ast::decorated_variable const&) const; + void operator()(vb6_ast::const_expr const&) const; + void operator()(vb6_ast::expression const&) const; + void operator()(vb6_ast::func_call const&) const; + void operator()(vb6_ast::global_var_decls const&) const; + void operator()(vb6_ast::const_var_stat const&) const; + void operator()(vb6_ast::record const&) const; + void operator()(vb6_ast::vb_enum const&) const; + void operator()(vb6_ast::func_param const&) const; + void operator()(vb6_ast::externalSub const&) const; + void operator()(vb6_ast::externalFunction const&) const; + void operator()(vb6_ast::subHead const&) const; + void operator()(vb6_ast::eventHead const&) const; + void operator()(vb6_ast::functionHead const&) const; + void operator()(vb6_ast::propertyLetHead const&) const; + void operator()(vb6_ast::propertySetHead const&) const; + void operator()(vb6_ast::propertyGetHead const&) const; + void operator()(vb6_ast::subDef const&) const; + void operator()(vb6_ast::functionDef const&) const; + + void operator()(vb6_ast::module_attribute) const; + void operator()(vb6_ast::module_option opt) const; + //void operator()(vb6_ast::STRICT_MODULE_STRUCTURE::module_attributes const&) const; + void operator()(vb6_ast::STRICT_MODULE_STRUCTURE::declaration const&) const; + void operator()(vb6_ast::STRICT_MODULE_STRUCTURE::vb_module const&) const; + void operator()(vb6_ast::declaration const&) const; + void operator()(vb6_ast::vb_module const&) const; + + void operator()(vb6_ast::statements::assignStmt const&) const; + void operator()(vb6_ast::statements::exitStmt const&) const; + void operator()(vb6_ast::statements::gotoStmt const&) const; + void operator()(vb6_ast::statements::onerrorStmt const&) const; + void operator()(vb6_ast::statements::resumeStmt const&) const; + void operator()(vb6_ast::statements::localVarDeclStmt const&) const; + void operator()(vb6_ast::statements::redimStmt const&) const; + void operator()(vb6_ast::statements::callStmt const&) const; + void operator()(vb6_ast::statements::raiseeventStmt const&) const; + void operator()(vb6_ast::statements::labelStmt const&) const; + void operator()(vb6_ast::statements::whileStmt const&) const; + void operator()(vb6_ast::statements::doStmt const&) const; + void operator()(vb6_ast::statements::dowhileStmt const&) const; + void operator()(vb6_ast::statements::loopwhileStmt const&) const; + void operator()(vb6_ast::statements::dountilStmt const&) const; + void operator()(vb6_ast::statements::loopuntilStmt const&) const; + void operator()(vb6_ast::statements::forStmt const&) const; + void operator()(vb6_ast::statements::foreachStmt const&) const; + void operator()(vb6_ast::statements::ifelseStmt const&) const; + void operator()(vb6_ast::statements::withStmt const&) const; + void operator()(vb6_ast::statements::case_block const&) const; + void operator()(vb6_ast::statements::selectStmt const&) const; + void operator()(vb6_ast::statements::singleStmt const&) const; + +private: + void print_type(vb6_ast::access_type) const; + void print_block(vb6_ast::statements::statement_block const&) const; + + std::ostream& os; + mutable int indent = 0; + static int indent_size; +}; \ No newline at end of file diff --git a/src/raw_ast_printer.cpp b/src/raw_ast_printer.cpp new file mode 100644 index 0000000..0078c54 --- /dev/null +++ b/src/raw_ast_printer.cpp @@ -0,0 +1,983 @@ +//: raw_ast_printer.cpp + +// vb6_parser +// Copyright (c) 2022 Federico Aponte +// This code is licensed under GNU Software License (see LICENSE.txt for details) + +#include "raw_ast_printer.hpp" +#include + +using namespace std; + +// static +int raw_ast_printer::indent_size = 4; + +void raw_ast_printer::operator()(vb6_ast::empty_line const& ast) const +{ + // (empty_line) + os << string(indent, ' ') << '(' << typeid(ast).name() << ")\n"; +} + +void raw_ast_printer::operator()(vb6_ast::lonely_comment const& ast) const +{ + // (lonely_comment "this is a comment") + os << string(indent, ' ') << '(' << typeid(ast).name() << " \"" << ast.content << "\")\n"; +} + +void raw_ast_printer::operator()(vb6_ast::identifier_context const& ast) const +{ + // (identifier_context no_dot (func_call foo) depth) + os << string(indent, ' ') << '(' << typeid(ast).name(); + + os << (ast.leading_dot ? "dot" : "no_dot"); + + struct { + void operator()(string s) { os << s; } + void operator()(vb6_ast::func_call const& s) { (*p)(s); } + ostream& os; + raw_ast_printer const* p; + } visitor{ os, this }; + + for(auto& el : ast.elements) + { + boost::apply_visitor(visitor, el.get()); + os << " "; + } + + os << ")\n"; +} + +void raw_ast_printer::operator()(vb6_ast::variable const& ast) const +{ + // (variable x Integer no_new) + os << string(indent, ' ') << '(' << typeid(ast).name() + << " " << ast.name; + + if(ast.type) + { + os << " " << *ast.type; + if(ast.construct) + os << " new"; + //if(ast.library_or_module) + // os << *ast.library_or_module << '.'; + } + + os << ")\n"; +} + +void raw_ast_printer::operator()(vb6_ast::decorated_variable const& ast) const +{ + os << string(indent, ' ') << '(' << typeid(ast).name() + << " " << ast.var; + (*this)(ast.ctx); + os << ")\n"; +} + +void raw_ast_printer::operator()(vb6_ast::func_call const& ast) const +{ + os << string(indent, ' ') << '(' << typeid(ast).name() + << " " << ast.func_name; + + bool first = true; + for(auto& el : ast.params) + { + if(first) + first = false; + else + os << ", "; + + (*this)(el); + } + + os << ")\n"; +} + +void raw_ast_printer::operator()(vb6_ast::global_var_decls const& ast) const +{ + os << string(indent, ' ') << '(' << typeid(ast).name(); + + print_type(ast.at); + + bool first = true; + for(auto& el : ast.vars) + { + if(first) + first = false; + else + os << ", "; + + (*this)(el); + } + + os << ")\n"; +} + +void raw_ast_printer::operator()(vb6_ast::const_expr const& ast) const +{ + os << string(indent, ' ') << '(' << typeid(ast).name(); + + struct { + void operator()(float v) { os << v << '!'; } + void operator()(double v) { os << v << '#'; } + void operator()(vb6_ast::long_dec v) { os << v.val << '&'; } + void operator()(vb6_ast::long_hex v) { os << "&H" << noshowbase << hex << v.val << dec << '&'; } + void operator()(vb6_ast::long_oct v) { os << "&0" << noshowbase << oct << v.val << dec << '&'; } + void operator()(vb6_ast::integer_dec v) { os << v.val << '%'; } + void operator()(vb6_ast::integer_hex v) { os << "&H" << noshowbase << hex << v.val << dec << '%'; } + void operator()(vb6_ast::integer_oct v) { os << "&0" << noshowbase << hex << v.val << dec << '%'; } + void operator()(bool v) { os << (v ? "True" : "False"); } + void operator()(vb6_ast::quoted_string const& s) { os << "\"" << s << "\""; } + void operator()(vb6_ast::nothing const&) { os << "Nothing"; } + ostream& os; + } visitor{os}; + + boost::apply_visitor(visitor, ast.get()); + + os << ")\n"; +} + +void raw_ast_printer::operator()(vb6_ast::expression const& ast) const +{ + os << string(indent, ' ') << '(' << typeid(ast).name(); + + boost::apply_visitor(*this, ast.get()); + + os << ")\n"; +} + +void raw_ast_printer::operator()(vb6_ast::const_var_stat const& ast) const +{ + os << string(indent, ' ') << '(' << typeid(ast).name(); + + bool first = true; + for(auto& el : ast) + { + if(first) + first = false; + else + os << ", "; + + os << el.var.name; + //os << " As " << (el.var.type ? *el.var.type : ""); + if(el.var.type) + os << " As " << *el.var.type; + os << " = "; + (*this)(el.value); + } + + os << ")\n"; +} + +void raw_ast_printer::print_type(vb6_ast::access_type t) const +{ + switch(t) + { + case vb6_ast::access_type::na: break; + case vb6_ast::access_type::dim: os << "Dim "; break; + case vb6_ast::access_type::private_: os << "Private "; break; + case vb6_ast::access_type::public_: os << "Public "; break; + case vb6_ast::access_type::global: os << "Global "; break; + case vb6_ast::access_type::friend_: os << "Friend "; break; + } +} + +void raw_ast_printer::operator()(vb6_ast::record const& ast) const +{ + os << string(indent, ' ') << '(' << typeid(ast).name(); + + print_type(ast.at); + os << "Type " << ast.name << '\n'; + indent += indent_size; + for(auto& el : ast.members) + { + os << string(indent, ' '); + (*this)(el); + os << '\n'; + } + indent -= indent_size; + + os << ")\n"; +} + +void raw_ast_printer::operator()(vb6_ast::vb_enum const& ast) const +{ + os << string(indent, ' ') << '(' << typeid(ast).name(); + + print_type(ast.at); + os << ast.name << '\n'; + indent += indent_size; + for(auto& el : ast.values) + { + os << string(indent, ' ') << el.first; + if(el.second) + { + os << " = "; + (*this)(*el.second); + } + os << '\n'; + } + indent -= indent_size; + + os << ")\n"; +} + +void raw_ast_printer::operator()(vb6_ast::func_param const& ast) const +{ + os << string(indent, ' ') << '(' << typeid(ast).name(); + + if(ast.isoptional) + os << "Optional "; + + if(ast.qualifier) + os << (*ast.qualifier == vb6_ast::param_qualifier::byref ? "ByRef" : "ByVal") << " "; + + os << ast.var.name; + //os << " As " << (ast.var.type ? *ast.var.type : ""); + if(ast.var.type) + os << " As " << *ast.var.type; + + if(ast.defvalue) + { + os << " = "; + (*this)(*ast.defvalue); + } + + os << ")\n"; +} + +void raw_ast_printer::operator()(vb6_ast::externalSub const& ast) const +{ + os << string(indent, ' ') << '(' << typeid(ast).name(); + + print_type(ast.at); + os << ast.name << " Lib \"" << ast.lib << "\" Alias \"" << ast.alias << "\" ("; + bool first = true; + for(auto& el : ast.params) + { + if(first) + first = false; + else + os << ", "; + + (*this)(el); + } + + os << ")\n"; +} + +void raw_ast_printer::operator()(vb6_ast::subHead const& ast) const +{ + os << string(indent, ' ') << '(' << typeid(ast).name(); + + print_type(ast.at); + os << "Sub " << ast.name << "("; + bool first = true; + for(auto& el : ast.params) + { + if(first) + first = false; + else + os << ", "; + + (*this)(el); + } + + os << ")\n"; +} + +void raw_ast_printer::operator()(vb6_ast::eventHead const& ast) const +{ + os << string(indent, ' ') << '(' << typeid(ast).name(); + + print_type(ast.at); + os << ast.name << "("; + bool first = true; + for(auto& el : ast.params) + { + if(first) + first = false; + else + os << ", "; + + (*this)(el); + } + + os << ")\n"; +} + +void raw_ast_printer::operator()(vb6_ast::functionHead const& ast) const +{ + os << string(indent, ' ') << '(' << typeid(ast).name(); + + print_type(ast.at); + os << "Function " << ast.name << "("; + bool first = true; + for(auto& el : ast.params) + { + if(first) + first = false; + else + os << ", "; + + (*this)(el); + } + os << ")"; + if(ast.return_type) + os << " As " << *ast.return_type; + + os << ")\n"; +} + +void raw_ast_printer::operator()(vb6_ast::propertyLetHead const& ast) const +{ + os << string(indent, ' ') << '(' << typeid(ast).name(); + + print_type(ast.at); + os << ast.name << "("; + bool first = true; + for(auto& el : ast.params) + { + if(first) + first = false; + else + os << ", "; + + (*this)(el); + } + + os << ")\n"; +} + +void raw_ast_printer::operator()(vb6_ast::propertySetHead const& ast) const +{ + os << string(indent, ' ') << '(' << typeid(ast).name(); + + print_type(ast.at); + os << ast.name << "("; + bool first = true; + for(auto& el : ast.params) + { + if(first) + first = false; + else + os << ", "; + + (*this)(el); + } + + os << ")\n"; +} + +void raw_ast_printer::operator()(vb6_ast::propertyGetHead const& ast) const +{ + os << string(indent, ' ') << '(' << typeid(ast).name(); + + print_type(ast.at); + os << ast.name << "("; + bool first = true; + for(auto& el : ast.params) + { + if(first) + first = false; + else + os << ", "; + + (*this)(el); + } + os << ")"; + if(ast.return_type) + os << " As " << *ast.return_type; + + os << ")\n"; +} + +void raw_ast_printer::operator()(vb6_ast::subDef const& ast) const +{ + os << string(indent, ' ') << '(' << typeid(ast).name(); + + (*this)(ast.header); + indent += indent_size; + for(auto& el : ast.statements) + { + (*this)(el); + } + indent -= indent_size; + + os << ")\n"; +} + +void raw_ast_printer::operator()(vb6_ast::functionDef const& ast) const +{ + os << string(indent, ' ') << '(' << typeid(ast).name(); + + (*this)(ast.header); + indent += indent_size; + for(auto& el : ast.statements) + { + (*this)(el); + } + indent -= indent_size; + + os << ")\n"; +} + +void raw_ast_printer::operator()(vb6_ast::externalFunction const& ast) const +{ + os << string(indent, ' ') << '(' << typeid(ast).name(); + + print_type(ast.at); + os << "Declare Function " << ast.name << " Lib \"" << ast.lib << "\" Alias \"" << ast.alias << "\" ("; + bool first = true; + for(auto& el : ast.params) + { + if(first) + first = false; + else + os << ", "; + + (*this)(el); + } + os << ")"; + if(ast.return_type) + os << " As " << *ast.return_type; + + os << ")\n"; +} + +void raw_ast_printer::operator()(vb6_ast::module_attribute ast) const +{ + os << string(indent, ' ') << '(' << typeid(ast).name(); + + os << " " << ast.first << " " << ast.second; + + os << ")\n"; +} + +void raw_ast_printer::operator()(vb6_ast::module_option ast) const +{ + os << string(indent, ' ') << '(' << typeid(ast).name(); + + switch(ast) + { + case vb6_ast::module_option::explicit_: + os << "Option Explicit"; + break; + case vb6_ast::module_option::base_0: + os << "Option Base 0"; + break; + case vb6_ast::module_option::base_1: + os << "Option Base 1"; + break; + case vb6_ast::module_option::compare_binary: + os << "Option Compare Binary"; + break; + case vb6_ast::module_option::compare_text: + os << "Option Compare Text"; + break; + case vb6_ast::module_option::private_module: + os << "Option Private Module"; + break; + } + + os << ")\n"; +} + +void raw_ast_printer::operator()(vb6_ast::STRICT_MODULE_STRUCTURE::module_attributes const& ast) const +{ + os << string(indent, ' ') << '(' << typeid(ast).name(); + + for(auto& el : ast) + { + os << el.first << " " << el.second << "\"\n"; + } + + os << ")\n"; +} + +void raw_ast_printer::operator()(vb6_ast::STRICT_MODULE_STRUCTURE::declaration const& ast) const +{ + os << string(indent, ' ') << '(' << typeid(ast).name(); + + boost::apply_visitor(*this, ast); + + os << ")\n"; +} + +void raw_ast_printer::operator()(vb6_ast::STRICT_MODULE_STRUCTURE::vb_module const& ast) const +{ + (*this)(ast.attrs); + + os << "'---------------------------------------- options\n"; + + for(auto& el : ast.opts.items) + { +#if 0 + (*this)(el); +#else + boost::apply_visitor(*this, el); +#endif + } + + os << "'---------------------------------------- declarations\n"; + + for(auto& el : ast.declarations) + { + (*this)(el); + } + + os << "'---------------------------------------- functions\n"; + + for(auto& el : ast.functions) + { + boost::apply_visitor(*this, el); + } +} + +void raw_ast_printer::operator()(vb6_ast::declaration const& ast) const +{ + boost::apply_visitor(*this, ast); +} + +void raw_ast_printer::operator()(vb6_ast::vb_module const& ast) const +{ + for(auto& el : ast) + { + boost::apply_visitor(*this, el); + } +} + +void raw_ast_printer::operator()(vb6_ast::statements::assignStmt const& ast) const +{ + os << string(indent, ' '); + switch(ast.type) + { + case vb6_ast::assignmentType::na: break; + case vb6_ast::assignmentType::set: os << "Set "; break; + case vb6_ast::assignmentType::let: os << "Let "; break; + } + + (*this)(ast.var); + os << " = "; + + (*this)(ast.rhs); + + // print the index of the type in a comment + os << " ' " << ast.rhs.get().which() << '\n'; +} + +void raw_ast_printer::operator()(vb6_ast::statements::exitStmt const& ast) const +{ + os << string(indent, ' ') << '(' << typeid(ast).name(); + + switch(ast.type) + { + case vb6_ast::exit_type::function: os << "Function"; break; + case vb6_ast::exit_type::sub: os << "Sub"; break; + case vb6_ast::exit_type::property: os << "Property"; break; + case vb6_ast::exit_type::while_: os << "While"; break; + case vb6_ast::exit_type::do_: os << "Do"; break; + case vb6_ast::exit_type::for_: os << "For"; break; + } + + os << ")\n"; +} + +void raw_ast_printer::operator()(vb6_ast::statements::gotoStmt const& ast) const +{ + os << string(indent, ' ') << '(' << typeid(ast).name(); + + os << ' ' << (ast.type == vb6_ast::gotoType::goto_v ? "GoTo" : "GoSub") + << " " << ast.label + << ")\n"; +} + +void raw_ast_printer::operator()(vb6_ast::statements::onerrorStmt const& ast) const +{ + os << string(indent, ' ') << '(' << typeid(ast).name(); + + switch(ast.type) + { + case vb6_ast::onerror_type::goto_0: os << "GoTo 0"; break; + case vb6_ast::onerror_type::goto_neg_1: os << "GoTo -1"; break; + case vb6_ast::onerror_type::goto_label: os << "GoTo " << ast.label; break; + case vb6_ast::onerror_type::resume_next: os << "Resume Next"; break; + case vb6_ast::onerror_type::exit_sub: os << "Exit Sub"; break; + case vb6_ast::onerror_type::exit_func: os << "Exit Func"; break; + case vb6_ast::onerror_type::exit_property: os << "Exit Property"; break; + } + + os << ")\n"; +} + +void raw_ast_printer::operator()(vb6_ast::statements::resumeStmt const& ast) const +{ + os << string(indent, ' ') << '(' << typeid(ast).name(); + + switch(ast.type) + { + case vb6_ast::resume_type::implicit: + break; + case vb6_ast::resume_type::next: + os << " Next"; + break; + case vb6_ast::resume_type::label: + os << " " << boost::get(ast.label_or_line_nr); + break; + case vb6_ast::resume_type::line_nr: + os << " " << boost::get(ast.label_or_line_nr); + break; + } + + os << ")\n"; +} + +void raw_ast_printer::operator()(vb6_ast::statements::localVarDeclStmt const& ast) const +{ + os << string(indent, ' ') << '(' << typeid(ast).name(); + + if(ast.type == vb6_ast::localvardeclType::Static) + os << "Static "; + else + os << "Dim "; + + bool first = true; + for(auto& el : ast.vars) + { + if(first) + first = false; + else + os << ", "; + + os << el.name; + if(el.type) + { + os << " As "; + if(el.construct) + os << "New "; + os << *el.type; + } + //else + // os << " As "; + } + + os << ")\n"; +} + +void raw_ast_printer::operator()(vb6_ast::statements::redimStmt const& ast) const +{ + os << string(indent, ' ') << '(' << typeid(ast).name(); + + if(ast.preserve) + os << "preserve "; + + (*this)(ast.var); + + os << " "; + + bool first = true; + for(auto& el : ast.newsize) + { + if(first) + first = false; + else + os << ", "; + + (*this)(el); + } + + os << ")\n"; +} + +// vb6_ast::returnStmt + +void raw_ast_printer::operator()(vb6_ast::statements::callStmt const& ast) const +{ + os << string(indent, ' ') << '(' << typeid(ast).name(); + + if(ast.explicit_call) + os << "Call " << ast.sub_name << "("; + else + os << ast.sub_name << " "; + + bool first = true; + for(auto& el : ast.params) + { + if(first) + first = false; + else + os << ", "; + + (*this)(el); + } + + os << ")\n"; +} + +void raw_ast_printer::operator()(vb6_ast::statements::raiseeventStmt const& ast) const +{ + os << string(indent, ' ') << '(' << typeid(ast).name(); + + os << " " << ast.event_name; + + bool first = true; + for(auto& el : ast.params) + { + if(first) + first = false; + else + os << ", "; + + (*this)(el); + } + + os << ")\n"; +} + +void raw_ast_printer::operator()(vb6_ast::statements::labelStmt const& ast) const +{ + os << string(indent, ' ') << '(' << typeid(ast).name() + << " " << ast.label << ")\n"; +} + +void raw_ast_printer::operator()(vb6_ast::statements::whileStmt const& ast) const +{ + os << string(indent, ' ') << '(' << typeid(ast).name(); + + (*this)(ast.condition); + os << '\n'; + + indent += indent_size; + for(auto& st : ast.block) + (*this)(st); + indent -= indent_size; + + os << ")\n"; +} + +void raw_ast_printer::operator()(vb6_ast::statements::doStmt const& ast) const +{ + os << string(indent, ' ') << '(' << typeid(ast).name(); + + indent += indent_size; + for(auto& st : ast.block) + (*this)(st); + indent -= indent_size; + + os << ")\n"; +} + +void raw_ast_printer::operator()(vb6_ast::statements::dowhileStmt const& ast) const +{ + os << string(indent, ' ') << '(' << typeid(ast).name(); + + (*this)(ast.condition); + os << '\n'; + + indent += indent_size; + for(auto& st : ast.block) + (*this)(st); + indent -= indent_size; + + os << ")\n"; +} + +void raw_ast_printer::operator()(vb6_ast::statements::dountilStmt const& ast) const +{ + os << string(indent, ' ') << '(' << typeid(ast).name(); + + (*this)(ast.condition); + os << '\n'; + + indent += indent_size; + for(auto& st : ast.block) + (*this)(st); + indent -= indent_size; + + os << ")\n"; +} + +void raw_ast_printer::operator()(vb6_ast::statements::loopwhileStmt const& ast) const +{ + os << string(indent, ' ') << '(' << typeid(ast).name(); + + indent += indent_size; + for(auto& st : ast.block) + (*this)(st); + indent -= indent_size; + + os << string(indent, ' ') << "Loop While "; + (*this)(ast.condition); + + os << ")\n"; +} + +void raw_ast_printer::operator()(vb6_ast::statements::loopuntilStmt const& ast) const +{ + os << string(indent, ' ') << '(' << typeid(ast).name(); + + indent += indent_size; + for(auto& st : ast.block) + (*this)(st); + indent -= indent_size; + + os << string(indent, ' ') << "Loop Until "; + (*this)(ast.condition); + + os << ")\n"; +} + +void raw_ast_printer::operator()(vb6_ast::statements::forStmt const& ast) const +{ + os << string(indent, ' ') << '(' << typeid(ast).name(); + + (*this)(ast.for_variable); + os << " = "; + (*this)(ast.from); + os << " To "; + (*this)(ast.to); + if(ast.step) + { + os << " Step "; + (*this)(*ast.step); + } + os << '\n'; + + indent += indent_size; + for(auto& el : ast.block) + (*this)(el); + indent -= indent_size; + + os << string(indent, ' ') << "Next "; + (*this)(ast.for_variable); + + os << ")\n"; +} + +void raw_ast_printer::operator()(vb6_ast::statements::foreachStmt const& ast) const +{ + os << string(indent, ' ') << '(' << typeid(ast).name(); + + (*this)(ast.for_variable); + os << " In "; + (*this)(ast.container); + os << '\n'; + + indent += indent_size; + for(auto& el : ast.block) + (*this)(el); + indent -= indent_size; + + os << string(indent, ' ') << "Next "; + (*this)(ast.for_variable); + + os << ")\n"; +} + +void raw_ast_printer::operator()(vb6_ast::statements::ifelseStmt const& ast) const +{ + os << string(indent, ' ') << '(' << typeid(ast).name(); + +#ifdef SIMPLE_IF_STATEMENT + (*this)(ast.first_branch.condition); + + // statements + indent += indent_size; + for(auto& st : ast.first_branch.block) + (*this)(st); + indent -= indent_size; +#else + (*this)(ast.first_branch.condition); + + // statements + indent += indent_size; + for(auto& st : ast.first_branch.block) + (*this)(st); + indent -= indent_size; + + //bool first = true; + for(auto& el : ast.if_branches) + { + os << string(indent, ' '); + //if(first) + //{ + // os << "If "; + // first = false; + //} + //else + os << "ElseIf "; + + (*this)(el.condition); + os << " Then\n"; + + indent += indent_size; + for(auto& st : el.block) + (*this)(st); + indent -= indent_size; + } +#endif + + if(ast.else_branch.has_value()) + { + os << string(indent, ' ') << "Else " << '\n'; + indent += indent_size; + for(auto& el : ast.else_branch.get()) + (*this)(el); + indent -= indent_size; + } + + os << string(indent, ' ') << "End If\n"; +} + +void raw_ast_printer::operator()(vb6_ast::statements::withStmt const& ast) const +{ + os << string(indent, ' ') << '(' << typeid(ast).name(); + + (*this)(ast.with_variable); + os << '\n'; + + // statements + indent += indent_size; + for(auto& st : ast.block) + (*this)(st); + indent -= indent_size; + + os << string(indent, ' ') << "End With\n"; +} + +void raw_ast_printer::operator()(vb6_ast::statements::case_block const& ast) const +{ + os << string(indent, ' ') << '(' << typeid(ast).name(); + + // TODO + (*this)(ast.case_expr); + os << "\n"; + + indent += indent_size; + for(auto& st : ast.block) + (*this)(st); + indent -= indent_size; +} + +void raw_ast_printer::operator()(vb6_ast::statements::selectStmt const& ast) const +{ + os << string(indent, ' ') << '(' << typeid(ast).name(); + + // TODO + (*this)(ast.condition); + os << '\n'; + + indent += indent_size; + for(auto& st : ast.blocks) + (*this)(st); + indent -= indent_size; + + os << string(indent, ' ') << "End Case\n"; +} + +void raw_ast_printer::operator()(vb6_ast::statements::singleStmt const& ast) const +{ + //os << string(indent, ' '); // FED ???? wouldn't it be better to indent here rather than at each statement? + // => problem with some statements, e.g. else within the if-statement + // perhaps the else should be a statement of its own!!! + boost::apply_visitor(*this, ast.get()); +} diff --git a/src/raw_ast_printer.hpp b/src/raw_ast_printer.hpp new file mode 100644 index 0000000..1b10eae --- /dev/null +++ b/src/raw_ast_printer.hpp @@ -0,0 +1,81 @@ +//: raw_ast_printer.hpp + +// vb6_parser +// Copyright (c) 2022 Federico Aponte +// This code is licensed under GNU Software License (see LICENSE.txt for details) + +#pragma once + +#include "vb6_ast.hpp" +#include + +class raw_ast_printer +{ +public: + explicit raw_ast_printer(std::ostream& os) : os(os) + { + } + + void operator()(vb6_ast::empty_line const&) const; + void operator()(vb6_ast::lonely_comment const&) const; + void operator()(vb6_ast::identifier_context const&) const; + void operator()(vb6_ast::variable const&) const; + void operator()(vb6_ast::decorated_variable const&) const; + void operator()(vb6_ast::const_expr const&) const; + void operator()(vb6_ast::expression const&) const; + void operator()(vb6_ast::func_call const&) const; + void operator()(vb6_ast::global_var_decls const&) const; + void operator()(vb6_ast::const_var_stat const&) const; + void operator()(vb6_ast::record const&) const; + void operator()(vb6_ast::vb_enum const&) const; + void operator()(vb6_ast::func_param const&) const; + void operator()(vb6_ast::externalSub const&) const; + void operator()(vb6_ast::externalFunction const&) const; + void operator()(vb6_ast::subHead const&) const; + void operator()(vb6_ast::eventHead const&) const; + void operator()(vb6_ast::functionHead const&) const; + void operator()(vb6_ast::propertyLetHead const&) const; + void operator()(vb6_ast::propertySetHead const&) const; + void operator()(vb6_ast::propertyGetHead const&) const; + void operator()(vb6_ast::subDef const&) const; + void operator()(vb6_ast::functionDef const&) const; + + void operator()(vb6_ast::module_attribute) const; + void operator()(vb6_ast::module_option) const; + void operator()(vb6_ast::STRICT_MODULE_STRUCTURE::module_attributes const&) const; + void operator()(vb6_ast::STRICT_MODULE_STRUCTURE::declaration const&) const; + void operator()(vb6_ast::STRICT_MODULE_STRUCTURE::vb_module const&) const; + void operator()(vb6_ast::declaration const&) const; + void operator()(vb6_ast::vb_module const&) const; + + void operator()(vb6_ast::statements::assignStmt const&) const; + void operator()(vb6_ast::statements::exitStmt const&) const; + void operator()(vb6_ast::statements::gotoStmt const&) const; + void operator()(vb6_ast::statements::onerrorStmt const&) const; + void operator()(vb6_ast::statements::resumeStmt const&) const; + void operator()(vb6_ast::statements::localVarDeclStmt const&) const; + void operator()(vb6_ast::statements::redimStmt const&) const; + void operator()(vb6_ast::statements::callStmt const&) const; + void operator()(vb6_ast::statements::raiseeventStmt const&) const; + void operator()(vb6_ast::statements::labelStmt const&) const; + void operator()(vb6_ast::statements::whileStmt const&) const; + void operator()(vb6_ast::statements::doStmt const&) const; + void operator()(vb6_ast::statements::dowhileStmt const&) const; + void operator()(vb6_ast::statements::loopwhileStmt const&) const; + void operator()(vb6_ast::statements::dountilStmt const&) const; + void operator()(vb6_ast::statements::loopuntilStmt const&) const; + void operator()(vb6_ast::statements::forStmt const&) const; + void operator()(vb6_ast::statements::foreachStmt const&) const; + void operator()(vb6_ast::statements::ifelseStmt const&) const; + void operator()(vb6_ast::statements::withStmt const&) const; + void operator()(vb6_ast::statements::case_block const&) const; + void operator()(vb6_ast::statements::selectStmt const&) const; + void operator()(vb6_ast::statements::singleStmt const&) const; + +private: + void print_type(vb6_ast::access_type) const; + + std::ostream& os; + mutable int indent = 0; + static int indent_size; +}; \ No newline at end of file diff --git a/src/test_gosub.cpp b/src/test_gosub.cpp new file mode 100644 index 0000000..b72b792 --- /dev/null +++ b/src/test_gosub.cpp @@ -0,0 +1,50 @@ +// + +#include +#include + +#define VB6_GOSUB(sub) if(!setjmp(_gs)){goto sub;} +#define VB6_RETURN longjmp(_gs,1) + +using namespace std; + +/* +Sub foo() + GoSub mysub1 + + Exit Sub +mysub1: + Debug.Print "mysub1" + Return + +mysub2: + Debug.Print "mysub2" + Return +End Sub + +#define GOSUB(sub) if(!setjmp(_gs)){goto sub;} +#define RETURN longjmp(_gs,1) +*/ + +void test_gosub(ostream& os) +{ + os << "---- begin " << __func__ << " ----\n"; + + jmp_buf _gs; + VB6_GOSUB(sub1); + + os << " after calling subroutine 1\n"; + + VB6_GOSUB(sub2); + + os << " after calling subroutine 2\n"; + + return; +sub1: + os << " in subroutine 1\n"; + VB6_RETURN; + +sub2: + os << " in subroutine 2\n"; + VB6_RETURN; +} diff --git a/src/test_grammar_helper.hpp b/src/test_grammar_helper.hpp new file mode 100644 index 0000000..cdd49c1 --- /dev/null +++ b/src/test_grammar_helper.hpp @@ -0,0 +1,116 @@ +//: test_grammar_helper.hpp + +// vb6_parser +// Copyright (c) 2022 Federico Aponte +// This code is licensed under GNU Software License (see LICENSE.txt for details) + +#pragma once + +#include "color_console.hpp" +#include "vb6_config.hpp" + +#include + +#include + +#include +#include +#include + +template +void test_grammar(std::ostream& os, std::string_view testName, ruleType rule, + std::string_view fragment, attrType& attr) +{ + namespace x3 = boost::spirit::x3; + + os << "--------\n" << testName << "\n--------\n"; + + auto it1 = cbegin(fragment); + auto const it2 = cend(fragment); + + try + { + std::stringstream out; + + vb6_grammar::error_handler_type error_handler(it1, it2, out, "source.bas"); + + auto const parser = + // we pass our error handler to the parser so we can access + // it later on in our on_error and on_success handlers + x3::with(std::ref(error_handler)) + [ + rule + ]; + + // NOTE: + // see https://www.codevamping.com/2018/12/spirit-x3-file-organization/ + // "Be sure to call phrase_parse without a namespace. + // Do not call it like x3::phrase_parse. + // Doing so turns off Argument Dependent Lookup. Evil ensues." + // It appears that using a namespace is not a problem here, + // but the article is interesting. + bool res = x3::phrase_parse(it1, it2, parser, vb6_grammar::skip, attr); + + if(res) + { + os << tag_ok << '\n'; + if(it1 != it2) + os << " but we stopped at " << std::string(it1, it2) << '\n'; + } + else + os << tag_fail << " - stopped at: " << std::string(it1, it2) << '\n'; + } + catch(x3::expectation_failure& e) + { + os << tag_fail << " - " << e.what() << " - " << (e.where() - it1) << " - " << e.which() << '\n'; + } +} + +template +void test_grammar(std::string_view fragment, ruleType rule, attrType& attr) +{ + namespace x3 = boost::spirit::x3; + + auto it1 = cbegin(fragment); + auto const it2 = cend(fragment); + + std::stringstream out; + + vb6_grammar::error_handler_type error_handler(it1, it2, out, "source.bas"); + + auto const parser = + // we pass our error handler to the parser so we can access + // it later on in our on_error and on_success handlers + x3::with(std::ref(error_handler)) + [ + rule + ]; + + ASSERT_TRUE(x3::phrase_parse(it1, it2, parser, vb6_grammar::skip, attr)) + << "stopped at: " << std::string(it1, it2); + + EXPECT_EQ(it1, it2); +} + +template +void test_grammar_false(std::string_view fragment, ruleType rule, attrType& attr) +{ + namespace x3 = boost::spirit::x3; + + auto it1 = cbegin(fragment); + auto const it2 = cend(fragment); + + std::stringstream out; + + vb6_grammar::error_handler_type error_handler(it1, it2, out, "source.bas"); + + auto const parser = + // we pass our error handler to the parser so we can access + // it later on in our on_error and on_success handlers + x3::with(std::ref(error_handler)) + [ + rule + ]; + + ASSERT_FALSE(x3::phrase_parse(it1, it2, parser, vb6_grammar::skip, attr)); +} diff --git a/src/vb6_ast.hpp b/src/vb6_ast.hpp new file mode 100644 index 0000000..3c4b55f --- /dev/null +++ b/src/vb6_ast.hpp @@ -0,0 +1,716 @@ +//: vb6_ast.hpp + +// vb6_parser +// Copyright (c) 2022 Federico Aponte +// This code is licensed under GNU Software License (see LICENSE.txt for details) + +#pragma once + +#include +#include +#include + +#include +#include +#include + +namespace vb6_ast { + +namespace x3 = boost::spirit::x3; + +enum class NativeType +{ + Unspecified, + Boolean, + Byte, + Integer, + Long, + Single, + Double, + Currency, + Date, + String, + Object, + Variant, + NonNative +}; + +enum class rel_operator_type +{ + less, + greater, + less_equal, + greater_equal, + equal, + not_equal +}; + +enum class operator_type +{ + less, + greater, + less_equal, + greater_equal, + equal, + not_equal, + plus, + minus, + mult, + div, + div_int, + exp, + diff, + assign, + amp, + mod, + imp, + not_, // unary + and_, + xor_, + is, + like +}; + +enum class access_type +{ + na, + dim, + global, + public_, + private_, + friend_ +}; + +enum class param_qualifier +{ + byval, + byref +}; + +enum class assignmentType +{ + na = 0, + let = 1, + set = 2 +}; + +enum class localvardeclType +{ + Dim = 0, + Static = 1 +}; + +enum class gotoType +{ + goto_v = 0, + gosub_v = 1 +}; + +enum class onerror_type +{ + goto_0, // disables enabled error handler in the current procedureand resets it to Nothing + goto_neg_1, // disables enabled exception in the current procedure and resets it to Nothing + goto_label, + exit_sub, + exit_func, + exit_property, + resume_next +}; + +enum class resume_type +{ + implicit, + next, + line_nr, + label +}; + +enum class exit_type +{ + sub, + function, + property, + while_, + do_, + for_ +}; + +enum class module_option +{ + explicit_, + base_0, + base_1, + compare_text, + compare_binary, + private_module +}; + +struct func_call; + +struct nothing +{ +}; + +struct empty_line +{ +}; + +struct lonely_comment //: std::string +{ + std::string content; +}; + +struct quoted_string : std::string +{ +}; + +using var_identifier = std::string; + +struct identifier_context : x3::position_tagged +{ + bool leading_dot = false; + std::vector, std::string>> elements; +}; + +//using simple_type_identifier = std::pair, std::string>; +//using complex_type_identifier = std::pair, std::string>; +using simple_type_identifier = std::string; +using complex_type_identifier = std::string; + +// TODO use this as the class to encode the type in a declaration for variables and parameters +struct type_identifier : x3::position_tagged +{ + NativeType type = NativeType::NonNative; + + boost::optional library_or_module; + std::string nonnative_type; +}; + +// Ex.: frm As VB.Form +// Ex.: frm As New MyLib.MyClass +struct variable : x3::position_tagged +{ + std::string name; + bool construct = false; // has the 'New' specifier + //boost::optional library_or_module; + //simple_type_identifier type; // not sure... + boost::optional type; // not sure... +}; + +struct decorated_variable : x3::position_tagged +{ + identifier_context ctx; + var_identifier var; +}; + +// Ex.: Private g_FinalName As String, g_FinalType As Integer +struct global_var_decls : x3::position_tagged +{ + access_type at = access_type::na; + bool with_events = false; + std::vector vars; +}; + +struct integer_dec { short val; }; +struct integer_hex { short val; }; +struct integer_oct { short val; }; +struct long_dec { int val; }; +struct long_hex { int val; }; +struct long_oct { int val; }; + +using const_expr = x3::variant; + +// Ex.: x As Integer = 100 +struct const_var : x3::position_tagged +{ + variable var; + const_expr value; +}; + +struct const_var_stat : std::vector +{ +}; + +// Ex.: Public Type MyPoint: x As Integer: y As Integer: End Type +struct record : x3::position_tagged +{ + access_type at = access_type::na; + std::string name; + std::vector members; +}; + +// FED ???? instead of a const_expr consider using an integer (how large?) for the value +using enum_item = std::pair>; + +// Ex.: Public Enum ComprKind: NoCompr = 0: Jpeg = 1: Tiff = 2: End Enum +struct vb_enum : x3::position_tagged +{ + access_type at = access_type::na; + std::string name; + std::vector values; +}; + +struct expression : x3::variant< + const_expr, + decorated_variable, + x3::forward_ast> +{ + using base_type::base_type; + using base_type::operator=; +}; + +struct func_call : x3::position_tagged +{ + std::string func_name; + std::vector params; +}; + +// Ex.: Optional ByVal flag As Boolean = True +struct func_param : x3::position_tagged +{ + bool isoptional = false; + boost::optional qualifier; // byval, byref + variable var; + boost::optional defvalue; +}; + +struct external_decl : x3::position_tagged +{ + std::string name; + access_type at; + std::vector params; +}; + +// Ex.: Private Sub CalcTotal(ByVal algorithm As Integer) +struct subHead : x3::position_tagged +{ + access_type at = access_type::na; + std::string name; + std::vector params; +}; + +// Ex.: Public Event OnShow(ByVal text As String) +struct eventHead : x3::position_tagged +{ + access_type at; // private, public, friend + std::string name; + std::vector params; +}; + +// Ex.: Private Function GetMagicWord(ByVal spell As String) As String +struct functionHead : x3::position_tagged +{ + access_type at = access_type::na; + std::string name; + std::vector params; + boost::optional return_type; // not sure... +}; + +// Ex.: Public Property Let Title(str As String) +struct propertyLetHead : x3::position_tagged +{ + access_type at = access_type::na; + std::string name; + std::vector params; +}; + +// Ex.: Public Property Set Title(str As String) +struct propertySetHead : x3::position_tagged +{ + access_type at = access_type::na; + std::string name; + std::vector params; +}; + +// Ex.: Public Property Get Title() As String +struct propertyGetHead : x3::position_tagged +{ + access_type at = access_type::na; + std::string name; + std::vector params; // FED ???? does Property Get take parameters? maybe for arrays? + boost::optional return_type; // not sure... +}; + +// Ex.: Private Sub Beep Lib "kernel32" Alias "Beep" (ByVal freq As Integer, ByVal duration As Integer) +struct externalSub : x3::position_tagged +{ + access_type at = access_type::na; + std::string name; + std::string lib; + std::string alias; + std::vector params; +}; + +// Ex.: Private Function Compress Lib "myzip" Alias "Compress" (ByVal str As String) As Integer +struct externalFunction : x3::position_tagged +{ + access_type at = access_type::na; + std::string name; + std::string lib; + std::string alias; + std::vector params; + boost::optional return_type; // not sure... +}; + +// statements (things that go into functions, subroutines and property definitions) +namespace statements { + +struct assignStmt : x3::position_tagged +{ + assignmentType type = assignmentType::na; + decorated_variable var; + expression rhs; +}; + +struct localVarDeclStmt : x3::position_tagged +{ + localvardeclType type; + std::vector vars; +}; + +struct redimStmt : x3::position_tagged +{ + bool preserve = false; + decorated_variable var; + std::vector newsize; +}; + +struct exitStmt : x3::position_tagged +{ + exit_type type; // Sub, Function, Property, For, Do, While, ... +}; + +struct gotoStmt : x3::position_tagged +{ + gotoType type; + std::string label; +}; + +struct onerrorStmt : x3::position_tagged +{ + onerror_type type; + std::string label; +}; + +struct resumeStmt : x3::position_tagged +{ + resume_type type; + x3::variant label_or_line_nr; +}; + +struct labelStmt : x3::position_tagged +{ + std::string label; +}; + +struct callStmt : x3::position_tagged +{ + std::string sub_name; + std::vector params; + bool explicit_call; +}; + +struct raiseeventStmt : x3::position_tagged +{ + std::string event_name; + std::vector params; +}; + +// forward declarations for compound statements +struct whileStmt; +struct doStmt; +struct dowhileStmt; +struct loopwhileStmt; +struct dountilStmt; +struct loopuntilStmt; +struct forStmt; +struct foreachStmt; +struct ifelseStmt; +struct withStmt; +struct selectStmt; + +// FED ???? pazzesco! senza questi #define non compila +//#define BOOST_MPL_CFG_NO_PREPROCESSED_HEADERS +//#define BOOST_MPL_LIMIT_LIST_SIZE 30 +struct singleStmt : x3::variant< + lonely_comment, + empty_line, + assignStmt, + localVarDeclStmt, + redimStmt, + exitStmt, + gotoStmt, + onerrorStmt, + resumeStmt, + labelStmt, + callStmt, + raiseeventStmt, + // compound statements + x3::forward_ast, + x3::forward_ast, + x3::forward_ast, + x3::forward_ast, + x3::forward_ast, + x3::forward_ast, + x3::forward_ast, + x3::forward_ast, + x3::forward_ast, + x3::forward_ast, + x3::forward_ast +> +{ + using base_type::base_type; + using base_type::operator=; +}; + +using statement_block = std::vector; + +// compound statements + +struct whileStmt : x3::position_tagged +{ + expression condition; + statement_block block; +}; + +struct doStmt : x3::position_tagged +{ + statement_block block; +}; + +struct dowhileStmt : x3::position_tagged +{ + expression condition; + statement_block block; +}; + +struct loopwhileStmt : x3::position_tagged +{ + statement_block block; + expression condition; +}; + +struct dountilStmt : x3::position_tagged +{ + expression condition; + statement_block block; +}; + +struct loopuntilStmt : x3::position_tagged +{ + statement_block block; + expression condition; +}; + +struct forStmt : x3::position_tagged +{ + decorated_variable for_variable; + expression from; + expression to; + boost::optional step; + statement_block block; +}; + +// TODO +struct foreachStmt : x3::position_tagged +{ + decorated_variable for_variable; + expression container; + statement_block block; +}; + +struct if_branch : x3::position_tagged +{ + expression condition; + statement_block block; +}; + +// TODO +struct ifelseStmt : x3::position_tagged +{ + //expression condition; + //statement_block block; +#ifdef SIMPLE_IF_STATEMENT + if_branch first_branch; +#else + if_branch first_branch; + std::vector if_branches; +#endif + boost::optional else_branch; +}; + +struct withStmt : x3::position_tagged +{ + decorated_variable with_variable; + statement_block block; +}; + +// TODO +struct case_relational_expr : x3::position_tagged +{ + rel_operator_type rel_op; + expression rexpr; +}; + +// TODO +struct case_block : x3::position_tagged +{ + expression case_expr; + //std::vector>> case_expr_list; + statement_block block; +}; + +struct selectStmt : x3::position_tagged +{ + expression condition; + std::vector blocks; +}; + +} // namespace statements + +struct subDef : x3::position_tagged +{ + subHead header; + statements::statement_block statements; +}; + +struct functionDef : x3::position_tagged +{ + functionHead header; + statements::statement_block statements; +}; + +struct get_prop : x3::position_tagged +{ + std::string name; + access_type at; + std::vector params; + statements::statement_block statements; +}; + +struct let_prop : x3::position_tagged +{ + std::string name; + access_type at; + std::vector params; + statements::statement_block stats; +}; + +struct set_prop : x3::position_tagged +{ + std::string name; + access_type at; + statements::statement_block statements; +}; + +namespace STRICT_MODULE_STRUCTURE +{ + struct module_attributes : x3::position_tagged + , std::map + { + }; + + struct option_block : x3::position_tagged + { + std::vector< +#if 0 + module_option +#else + x3::variant +#endif + > items; + }; + + struct declaration : x3::position_tagged + , x3::variant< + lonely_comment, + empty_line, + global_var_decls, + const_var_stat, + vb_enum, + record, + //external_decl, + externalSub, + externalFunction, + eventHead + > + { + using base_type::base_type; + using base_type::operator=; + }; + + using functionList = std::vector< + x3::variant + >; + + struct vb_module : x3::position_tagged + { + module_attributes attrs; + option_block opts; + std::vector declarations; + functionList functions; + }; +} // STRICT_MODULE_STRUCTURE + +struct declaration : x3::position_tagged + , x3::variant< + global_var_decls, + const_var_stat, + vb_enum, + record, + //external_decl, + externalSub, + externalFunction, + eventHead + > +{ + using base_type::base_type; + using base_type::operator=; +}; + +using module_attribute = std::pair; + +using vb_module = std::vector< + x3::variant< + lonely_comment, + empty_line, + module_attribute, + module_option, + declaration, + functionDef, + subDef + //get_prop, + //let_prop, + //set_prop + > + >; +/* +struct vb_module : x3::position_tagged +{ + std::vector< + x3::variant< + lonely_comment, + empty_line, + module_attribute, + module_option, + declaration, + functionDef, + subDef + //get_prop, + //let_prop, + //set_prop + > + > items; +}; +*/ + +} // namespace vb6_ast \ No newline at end of file diff --git a/src/vb6_ast_adapt.hpp b/src/vb6_ast_adapt.hpp new file mode 100644 index 0000000..d5ded64 --- /dev/null +++ b/src/vb6_ast_adapt.hpp @@ -0,0 +1,285 @@ +//: vb6_ast_adapt.hpp + +// vb6_parser +// Copyright (c) 2022 Federico Aponte +// This code is licensed under GNU Software License (see LICENSE.txt for details) + +#pragma once + +#include "vb6_ast.hpp" + +#include + +// these must be in the global scope! + +BOOST_FUSION_ADAPT_STRUCT(vb6_ast::integer_dec, val) +BOOST_FUSION_ADAPT_STRUCT(vb6_ast::integer_hex, val) +BOOST_FUSION_ADAPT_STRUCT(vb6_ast::integer_oct, val) +BOOST_FUSION_ADAPT_STRUCT(vb6_ast::long_dec, val) +BOOST_FUSION_ADAPT_STRUCT(vb6_ast::long_hex, val) +BOOST_FUSION_ADAPT_STRUCT(vb6_ast::long_oct, val) + +// these are needed when BOOST_SPIRIT_X3_DEBUG is defined +#ifdef BOOST_SPIRIT_X3_DEBUG +BOOST_FUSION_ADAPT_STRUCT(vb6_ast::nothing) +BOOST_FUSION_ADAPT_STRUCT(vb6_ast::empty_line) +#endif + +BOOST_FUSION_ADAPT_STRUCT(vb6_ast::lonely_comment, + content +) + +BOOST_FUSION_ADAPT_STRUCT(vb6_ast::identifier_context, + leading_dot, + elements +) + +BOOST_FUSION_ADAPT_STRUCT(vb6_ast::variable, + name, + construct, + //library_or_module, + type // not sure... +) + +BOOST_FUSION_ADAPT_STRUCT(vb6_ast::decorated_variable, + ctx, + var +) + +BOOST_FUSION_ADAPT_STRUCT(vb6_ast::func_call, + func_name, + params +) + +BOOST_FUSION_ADAPT_STRUCT(vb6_ast::global_var_decls, + at, + with_events, + vars +) + +BOOST_FUSION_ADAPT_STRUCT(vb6_ast::const_var, + var, + value +) + +BOOST_FUSION_ADAPT_STRUCT(vb6_ast::record, + at, + name, + members +) + +BOOST_FUSION_ADAPT_STRUCT(vb6_ast::vb_enum, + at, + name, + values +) + +BOOST_FUSION_ADAPT_STRUCT(vb6_ast::func_param, + isoptional, + qualifier, // byval, byref + var, + defvalue +) + +BOOST_FUSION_ADAPT_STRUCT(vb6_ast::statements::assignStmt, + type, + var, + rhs +) + +BOOST_FUSION_ADAPT_STRUCT(vb6_ast::statements::localVarDeclStmt, + type, + vars +) + +BOOST_FUSION_ADAPT_STRUCT(vb6_ast::statements::redimStmt, + preserve, + var, + newsize +) + +BOOST_FUSION_ADAPT_STRUCT(vb6_ast::statements::exitStmt, + type +) + +BOOST_FUSION_ADAPT_STRUCT(vb6_ast::statements::gotoStmt, + type, + label +) + +BOOST_FUSION_ADAPT_STRUCT(vb6_ast::statements::labelStmt, + label +) + +BOOST_FUSION_ADAPT_STRUCT(vb6_ast::statements::whileStmt, + condition, + block +) + +BOOST_FUSION_ADAPT_STRUCT(vb6_ast::statements::doStmt, + block +) + +BOOST_FUSION_ADAPT_STRUCT(vb6_ast::statements::dowhileStmt, + condition, + block +) + +BOOST_FUSION_ADAPT_STRUCT(vb6_ast::statements::loopwhileStmt, + block, + condition +) + +BOOST_FUSION_ADAPT_STRUCT(vb6_ast::statements::dountilStmt, + condition, + block +) + +BOOST_FUSION_ADAPT_STRUCT(vb6_ast::statements::loopuntilStmt, + block, + condition +) + +BOOST_FUSION_ADAPT_STRUCT(vb6_ast::statements::forStmt, + for_variable, + from, + to, + step, + block +) + +BOOST_FUSION_ADAPT_STRUCT(vb6_ast::statements::foreachStmt, + for_variable, + container, + block +) + +BOOST_FUSION_ADAPT_STRUCT(vb6_ast::statements::if_branch, + condition, + block +) + +#ifdef SIMPLE_IF_STATEMENT +BOOST_FUSION_ADAPT_STRUCT(vb6_ast::statements::ifelseStmt, + first_branch, + else_branch +) +#else +BOOST_FUSION_ADAPT_STRUCT(vb6_ast::statements::ifelseStmt, + first_branch, + if_branches, + else_branch +) +#endif + +BOOST_FUSION_ADAPT_STRUCT(vb6_ast::statements::withStmt, + with_variable, + block +) + +BOOST_FUSION_ADAPT_STRUCT(vb6_ast::statements::case_block, + case_expr, + block +) + +BOOST_FUSION_ADAPT_STRUCT(vb6_ast::statements::selectStmt, + condition, + blocks +) + +BOOST_FUSION_ADAPT_STRUCT(vb6_ast::statements::onerrorStmt, + label, + type +) + +BOOST_FUSION_ADAPT_STRUCT(vb6_ast::statements::resumeStmt, + label_or_line_nr, + type +) + +BOOST_FUSION_ADAPT_STRUCT(vb6_ast::statements::callStmt, + sub_name, + params, + explicit_call +) + +BOOST_FUSION_ADAPT_STRUCT(vb6_ast::statements::raiseeventStmt, + event_name, + params +) + +BOOST_FUSION_ADAPT_STRUCT(vb6_ast::subHead, + at, + name, + params +) + +BOOST_FUSION_ADAPT_STRUCT(vb6_ast::eventHead, + at, + name, + params +) + +BOOST_FUSION_ADAPT_STRUCT(vb6_ast::functionHead, + at, + name, + params, + return_type // not sure... +) + +BOOST_FUSION_ADAPT_STRUCT(vb6_ast::propertyLetHead, + at, + name, + params +) + +BOOST_FUSION_ADAPT_STRUCT(vb6_ast::propertySetHead, + at, + name, + params +) + +BOOST_FUSION_ADAPT_STRUCT(vb6_ast::propertyGetHead, + at, + name, + params, + return_type // not sure... +) + +BOOST_FUSION_ADAPT_STRUCT(vb6_ast::subDef, + header, + statements +) + +BOOST_FUSION_ADAPT_STRUCT(vb6_ast::functionDef, + header, + statements +) + +BOOST_FUSION_ADAPT_STRUCT(vb6_ast::externalSub, + at, + name, + lib, + alias, + params +) + +BOOST_FUSION_ADAPT_STRUCT(vb6_ast::externalFunction, + at, + name, + lib, + alias, + params, + return_type // not sure... +) + +BOOST_FUSION_ADAPT_STRUCT(vb6_ast::STRICT_MODULE_STRUCTURE::option_block, + items +) + +BOOST_FUSION_ADAPT_STRUCT(vb6_ast::STRICT_MODULE_STRUCTURE::vb_module, + attrs, + opts, + declarations, + functions +) \ No newline at end of file diff --git a/src/vb6_ast_printer.cpp b/src/vb6_ast_printer.cpp new file mode 100644 index 0000000..bfe7045 --- /dev/null +++ b/src/vb6_ast_printer.cpp @@ -0,0 +1,892 @@ +//: vb6_ast_printer.cpp + +// vb6_parser +// Copyright (c) 2022 Federico Aponte +// This code is licensed under GNU Software License (see LICENSE.txt for details) + +#include "vb6_ast_printer.hpp" +#include + +using namespace std; + +// static +int vb6_ast_printer::indent_size = 4; + +void vb6_ast_printer::operator()(vb6_ast::empty_line const&) const +{ + os << '\n'; +} + +void vb6_ast_printer::operator()(vb6_ast::lonely_comment const& ast) const +{ + os << string(indent, ' '); + os << "'" << ast.content << '\n'; +} + +void vb6_ast_printer::operator()(vb6_ast::identifier_context const& ast) const +{ + if(ast.leading_dot) + os << '.'; + + struct { + void operator()(std::string s) { os << s; } + void operator()(vb6_ast::func_call const& s) { (*p)(s); } + ostream& os; + vb6_ast_printer const* p; + } visitor{ os, this }; + + for(auto& el : ast.elements) + { + boost::apply_visitor(visitor, el.get()); + os << '.'; + } +} + +void vb6_ast_printer::operator()(vb6_ast::variable const& ast) const +{ + os << ast.name; + if(ast.type) + { + os << " As "; + if(ast.construct) + os << "New "; + //if(ast.library_or_module) + // os << *ast.library_or_module << '.'; + os << *ast.type; + } + //else + // os << " As "; +} + +void vb6_ast_printer::operator()(vb6_ast::decorated_variable const& ast) const +{ + (*this)(ast.ctx); + os << ast.var; +} + +void vb6_ast_printer::operator()(vb6_ast::func_call const& ast) const +{ + os << ast.func_name << "("; + + bool first = true; + for(auto& el : ast.params) + { + if(first) + first = false; + else + os << ", "; + + (*this)(el); + } + + os << ")"; +} + +void vb6_ast_printer::operator()(vb6_ast::global_var_decls const& ast) const +{ + print_type(ast.at); + + bool first = true; + for(auto& el : ast.vars) + { + if(first) + first = false; + else + os << ", "; + + (*this)(el); + } + os << '\n'; +} + +void vb6_ast_printer::operator()(vb6_ast::const_expr const& ast) const +{ + struct { + void operator()(float v) { os << v << '!'; } + void operator()(double v) { os << v << '#'; } + void operator()(vb6_ast::long_dec v) { os << v.val << '&'; } + void operator()(vb6_ast::long_hex v) { os << "&H" << noshowbase << hex << v.val << dec << '&'; } + void operator()(vb6_ast::long_oct v) { os << "&0" << noshowbase << oct << v.val << dec << '&'; } + void operator()(vb6_ast::integer_dec v) { os << v.val << '%'; } + void operator()(vb6_ast::integer_hex v) { os << "&H" << noshowbase << hex << v.val << dec << '%'; } + void operator()(vb6_ast::integer_oct v) { os << "&0" << noshowbase << hex << v.val << dec << '%'; } + void operator()(bool v) { os << (v ? "True" : "False"); } + void operator()(vb6_ast::quoted_string const& s) { os << "\"" << s << "\""; } + void operator()(vb6_ast::nothing const&) { os << "Nothing"; } + ostream& os; + } visitor{os}; + + boost::apply_visitor(visitor, ast.get()); +} + +void vb6_ast_printer::operator()(vb6_ast::expression const& ast) const +{ + boost::apply_visitor(*this, ast.get()); +} + +void vb6_ast_printer::operator()(vb6_ast::const_var_stat const& cvars) const +{ + os << "Const "; + bool first = true; + for(auto& el : cvars) + { + if(first) + first = false; + else + os << ", "; + + os << el.var.name; + //os << " As " << (el.var.type ? *el.var.type : ""); + if(el.var.type) + os << " As " << *el.var.type; + os << " = "; + (*this)(el.value); + } + os << '\n'; +} + +void vb6_ast_printer::print_type(vb6_ast::access_type t) const +{ + switch(t) + { + case vb6_ast::access_type::na: break; + case vb6_ast::access_type::dim: os << "Dim "; break; + case vb6_ast::access_type::private_: os << "Private "; break; + case vb6_ast::access_type::public_: os << "Public "; break; + case vb6_ast::access_type::global: os << "Global "; break; + case vb6_ast::access_type::friend_: os << "Friend "; break; + } +} + +void vb6_ast_printer::operator()(vb6_ast::record const& ast) const +{ + print_type(ast.at); + os << "Type " << ast.name << '\n'; + indent += indent_size; + for(auto& el : ast.members) + { + os << string(indent, ' '); + (*this)(el); + os << '\n'; + } + indent -= indent_size; + os << "End Type\n"; +} + +void vb6_ast_printer::operator()(vb6_ast::vb_enum const& ast) const +{ + print_type(ast.at); + os << "Enum " << ast.name << '\n'; + indent += indent_size; + for(auto& el : ast.values) + { + os << string(indent, ' ') << el.first; + if(el.second) + { + os << " = "; + (*this)(*el.second); + } + os << '\n'; + } + indent -= indent_size; + os << "End Enum\n"; +} + +void vb6_ast_printer::operator()(vb6_ast::func_param const& ast) const +{ + if(ast.isoptional) + os << "Optional "; + + if(ast.qualifier) + os << (*ast.qualifier == vb6_ast::param_qualifier::byref ? "ByRef" : "ByVal") << " "; + + os << ast.var.name; + //os << " As " << (ast.var.type ? *ast.var.type : ""); + if(ast.var.type) + os << " As " << *ast.var.type; + + if(ast.defvalue) + { + os << " = "; + (*this)(*ast.defvalue); + } +} + +void vb6_ast_printer::operator()(vb6_ast::externalSub const& ast) const +{ + print_type(ast.at); + os << "Declare Sub " << ast.name << " Lib \"" << ast.lib << "\" Alias \"" << ast.alias << "\" ("; + bool first = true; + for(auto& el : ast.params) + { + if(first) + first = false; + else + os << ", "; + + (*this)(el); + } + os << ")\n"; +} + +void vb6_ast_printer::operator()(vb6_ast::subHead const& ast) const +{ + print_type(ast.at); + os << "Sub " << ast.name << "("; + bool first = true; + for(auto& el : ast.params) + { + if(first) + first = false; + else + os << ", "; + + (*this)(el); + } + os << ")\n"; +} + +void vb6_ast_printer::operator()(vb6_ast::eventHead const& ast) const +{ + print_type(ast.at); + os << "Event " << ast.name << "("; + bool first = true; + for(auto& el : ast.params) + { + if(first) + first = false; + else + os << ", "; + + (*this)(el); + } + os << ")\n"; +} + +void vb6_ast_printer::operator()(vb6_ast::functionHead const& ast) const +{ + print_type(ast.at); + os << "Function " << ast.name << "("; + bool first = true; + for(auto& el : ast.params) + { + if(first) + first = false; + else + os << ", "; + + (*this)(el); + } + os << ")"; + if(ast.return_type) + os << " As " << *ast.return_type; + os << '\n'; +} + +void vb6_ast_printer::operator()(vb6_ast::propertyLetHead const& ast) const +{ + print_type(ast.at); + os << "Property Let " << ast.name << "("; + bool first = true; + for(auto& el : ast.params) + { + if(first) + first = false; + else + os << ", "; + + (*this)(el); + } + os << ")\n"; +} + +void vb6_ast_printer::operator()(vb6_ast::propertySetHead const& ast) const +{ + print_type(ast.at); + os << "Property Set " << ast.name << "("; + bool first = true; + for(auto& el : ast.params) + { + if(first) + first = false; + else + os << ", "; + + (*this)(el); + } + os << ")\n"; +} + +void vb6_ast_printer::operator()(vb6_ast::propertyGetHead const& ast) const +{ + print_type(ast.at); + os << "Property Get " << ast.name << "("; + bool first = true; + for(auto& el : ast.params) + { + if(first) + first = false; + else + os << ", "; + + (*this)(el); + } + os << ")"; + if(ast.return_type) + os << " As " << *ast.return_type; + os << '\n'; +} + +void vb6_ast_printer::operator()(vb6_ast::subDef const& ast) const +{ + (*this)(ast.header); + indent += indent_size; + for(auto& el : ast.statements) + { + (*this)(el); + } + indent -= indent_size; + os << "End Sub\n"; +} + +void vb6_ast_printer::operator()(vb6_ast::functionDef const& ast) const +{ + (*this)(ast.header); + indent += indent_size; + for(auto& el : ast.statements) + { + (*this)(el); + } + indent -= indent_size; + os << "End Function\n"; +} + +void vb6_ast_printer::operator()(vb6_ast::externalFunction const& ast) const +{ + print_type(ast.at); + os << "Declare Function " << ast.name << " Lib \"" << ast.lib << "\" Alias \"" << ast.alias << "\" ("; + bool first = true; + for(auto& el : ast.params) + { + if(first) + first = false; + else + os << ", "; + + (*this)(el); + } + os << ")"; + if(ast.return_type) + os << " As " << *ast.return_type; + os << '\n'; +} + +void vb6_ast_printer::operator()(vb6_ast::module_attribute attr) const +{ + os << "Attribute " << attr.first << " = \"" << attr.second << "\"\n"; +} + +void vb6_ast_printer::operator()(vb6_ast::module_option opt) const +{ + switch(opt) + { + case vb6_ast::module_option::explicit_: + os << "Option Explicit"; + break; + case vb6_ast::module_option::base_0: + os << "Option Base 0"; + break; + case vb6_ast::module_option::base_1: + os << "Option Base 1"; + break; + case vb6_ast::module_option::compare_binary: + os << "Option Compare Binary"; + break; + case vb6_ast::module_option::compare_text: + os << "Option Compare Text"; + break; + case vb6_ast::module_option::private_module: + os << "Option Private Module"; + break; + } + os << '\n'; +} + +void vb6_ast_printer::operator()(vb6_ast::STRICT_MODULE_STRUCTURE::module_attributes const& ast) const +{ + for(auto& el : ast) + { + os << el.first << " = \"" << el.second << "\"\n"; + } +} + +void vb6_ast_printer::operator()(vb6_ast::STRICT_MODULE_STRUCTURE::declaration const& ast) const +{ + boost::apply_visitor(*this, ast); +} + +void vb6_ast_printer::operator()(vb6_ast::STRICT_MODULE_STRUCTURE::vb_module const& ast) const +{ + (*this)(ast.attrs); + + os << "'---------------------------------------- options\n"; + + for(auto& el : ast.opts.items) + { +#if 0 + (*this)(el); +#else + boost::apply_visitor(*this, el); +#endif + } + + os << "'---------------------------------------- declarations\n"; + + for(auto& el : ast.declarations) + { + (*this)(el); + } + + os << "'---------------------------------------- functions\n"; + + for(auto& el : ast.functions) + { + boost::apply_visitor(*this, el); + } +} + +void vb6_ast_printer::operator()(vb6_ast::declaration const& ast) const +{ + boost::apply_visitor(*this, ast); +} + +void vb6_ast_printer::operator()(vb6_ast::vb_module const& ast) const +{ + for(auto& el : ast) + { + boost::apply_visitor(*this, el); + } +} + +void vb6_ast_printer::operator()(vb6_ast::statements::assignStmt const& ast) const +{ + os << string(indent, ' '); + switch(ast.type) + { + case vb6_ast::assignmentType::na: break; + case vb6_ast::assignmentType::set: os << "Set "; break; + case vb6_ast::assignmentType::let: os << "Let "; break; + } + + (*this)(ast.var); + os << " = "; + + (*this)(ast.rhs); + + // print the index of the type in a comment + os << " ' " << ast.rhs.get().which() << '\n'; +} + +void vb6_ast_printer::operator()(vb6_ast::statements::exitStmt const& ast) const +{ + os << string(indent, ' ') << "Exit "; + switch(ast.type) + { + case vb6_ast::exit_type::function: os << "Function"; break; + case vb6_ast::exit_type::sub: os << "Sub"; break; + case vb6_ast::exit_type::property: os << "Property"; break; + case vb6_ast::exit_type::while_: os << "While"; break; + case vb6_ast::exit_type::do_: os << "Do"; break; + case vb6_ast::exit_type::for_: os << "For"; break; + } + os << '\n'; +} + +void vb6_ast_printer::operator()(vb6_ast::statements::gotoStmt const& ast) const +{ + os << string(indent, ' ') << (ast.type == vb6_ast::gotoType::goto_v ? "GoTo" : "GoSub") + << " " << ast.label << '\n'; +} + +void vb6_ast_printer::operator()(vb6_ast::statements::onerrorStmt const& ast) const +{ + os << string(indent, ' ') << "On Error "; + switch(ast.type) + { + case vb6_ast::onerror_type::goto_0: os << "GoTo 0"; break; + case vb6_ast::onerror_type::goto_neg_1: os << "GoTo -1"; break; + case vb6_ast::onerror_type::goto_label: os << "GoTo " << ast.label; break; + case vb6_ast::onerror_type::resume_next: os << "Resume Next"; break; + case vb6_ast::onerror_type::exit_sub: os << "Exit Sub"; break; + case vb6_ast::onerror_type::exit_func: os << "Exit Func"; break; + case vb6_ast::onerror_type::exit_property: os << "Exit Property"; break; + } + os << '\n'; +} + +void vb6_ast_printer::operator()(vb6_ast::statements::resumeStmt const& ast) const +{ + os << string(indent, ' ') << "Resume"; + switch(ast.type) + { + case vb6_ast::resume_type::implicit: + break; + case vb6_ast::resume_type::next: + os << " Next"; + break; + case vb6_ast::resume_type::label: + os << " " << boost::get(ast.label_or_line_nr); + break; + case vb6_ast::resume_type::line_nr: + os << " " << boost::get(ast.label_or_line_nr); + break; + } + os << '\n'; +} + +void vb6_ast_printer::operator()(vb6_ast::statements::localVarDeclStmt const& ast) const +{ + os << string(indent, ' '); + + if(ast.type == vb6_ast::localvardeclType::Static) + os << "Static "; + else + os << "Dim "; + + bool first = true; + for(auto& el : ast.vars) + { + if(first) + first = false; + else + os << ", "; + + os << el.name; + if(el.type) + { + os << " As "; + if(el.construct) + os << "New "; + os << *el.type; + } + //else + // os << " As "; + } + os << "\n"; +} + +void vb6_ast_printer::operator()(vb6_ast::statements::redimStmt const& ast) const +{ + os << string(indent, ' '); + + os << "ReDim "; + + if(ast.preserve) + os << "Preserve "; + + (*this)(ast.var); + + os << "("; + + bool first = true; + for(auto& el : ast.newsize) + { + if(first) + first = false; + else + os << ", "; + + (*this)(el); + } + + os << ")\n"; +} + +// vb6_ast::returnStmt + +void vb6_ast_printer::operator()(vb6_ast::statements::callStmt const& ast) const +{ + os << string(indent, ' '); + + if(ast.explicit_call) + os << "Call " << ast.sub_name << "("; + else + os << ast.sub_name << " "; + + bool first = true; + for(auto& el : ast.params) + { + if(first) + first = false; + else + os << ", "; + + (*this)(el); + } + + if(ast.explicit_call) + os << ")"; + + os << "\n"; +} + +void vb6_ast_printer::operator()(vb6_ast::statements::raiseeventStmt const& ast) const +{ + os << string(indent, ' '); + + os << "RaiseEvent " << ast.event_name << "("; + + bool first = true; + for(auto& el : ast.params) + { + if(first) + first = false; + else + os << ", "; + + (*this)(el); + } + + os << ")"; + + os << "\n"; +} + +void vb6_ast_printer::operator()(vb6_ast::statements::labelStmt const& ast) const +{ + os << ast.label << ":\n"; // no indentation for labels +} + +void vb6_ast_printer::operator()(vb6_ast::statements::whileStmt const& ast) const +{ + os << string(indent, ' ') << "While "; + (*this)(ast.condition); + os << '\n'; + + indent += indent_size; + for(auto& st : ast.block) + (*this)(st); + indent -= indent_size; + + os << string(indent, ' ') << "Wend\n"; +} + +void vb6_ast_printer::operator()(vb6_ast::statements::doStmt const& ast) const +{ + os << string(indent, ' ') << "Do\n"; + + indent += indent_size; + for(auto& st : ast.block) + (*this)(st); + indent -= indent_size; + + os << string(indent, ' ') << "Loop\n"; +} + +void vb6_ast_printer::operator()(vb6_ast::statements::dowhileStmt const& ast) const +{ + os << string(indent, ' ') << "Do While "; + (*this)(ast.condition); + os << '\n'; + + indent += indent_size; + for(auto& st : ast.block) + (*this)(st); + indent -= indent_size; + + os << string(indent, ' ') << "Loop\n"; +} + +void vb6_ast_printer::operator()(vb6_ast::statements::dountilStmt const& ast) const +{ + os << string(indent, ' ') << "Do Until "; + (*this)(ast.condition); + os << '\n'; + + indent += indent_size; + for(auto& st : ast.block) + (*this)(st); + indent -= indent_size; + + os << string(indent, ' ') << "Loop\n"; +} + +void vb6_ast_printer::operator()(vb6_ast::statements::loopwhileStmt const& ast) const +{ + os << string(indent, ' ') << "Do\n"; + + indent += indent_size; + for(auto& st : ast.block) + (*this)(st); + indent -= indent_size; + + os << string(indent, ' ') << "Loop While "; + (*this)(ast.condition); + os << '\n'; +} + +void vb6_ast_printer::operator()(vb6_ast::statements::loopuntilStmt const& ast) const +{ + os << string(indent, ' ') << "Do\n"; + + indent += indent_size; + for(auto& st : ast.block) + (*this)(st); + indent -= indent_size; + + os << string(indent, ' ') << "Loop Until "; + (*this)(ast.condition); + os << '\n'; +} + +void vb6_ast_printer::operator()(vb6_ast::statements::forStmt const& ast) const +{ + os << string(indent, ' ') << "For "; + (*this)(ast.for_variable); + os << " = "; + (*this)(ast.from); + os << " To "; + (*this)(ast.to); + if(ast.step) + { + os << " Step "; + (*this)(*ast.step); + } + os << '\n'; + + indent += indent_size; + for(auto& el : ast.block) + (*this)(el); + indent -= indent_size; + + os << string(indent, ' ') << "Next "; + (*this)(ast.for_variable); + os << '\n'; +} + +void vb6_ast_printer::operator()(vb6_ast::statements::foreachStmt const& ast) const +{ + os << string(indent, ' ') << "For Each "; + (*this)(ast.for_variable); + os << " In "; + (*this)(ast.container); + os << '\n'; + + indent += indent_size; + for(auto& el : ast.block) + (*this)(el); + indent -= indent_size; + + os << string(indent, ' ') << "Next "; + (*this)(ast.for_variable); + os << '\n'; +} + +void vb6_ast_printer::operator()(vb6_ast::statements::ifelseStmt const& ast) const +{ +#ifdef SIMPLE_IF_STATEMENT + os << "If "; + (*this)(ast.first_branch.condition); + os << " Then\n"; + + // statements + indent += indent_size; + for(auto& st : ast.first_branch.block) + (*this)(st); + indent -= indent_size; +#else + os << "If "; + (*this)(ast.first_branch.condition); + os << " Then\n"; + + // statements + indent += indent_size; + for(auto& st : ast.first_branch.block) + (*this)(st); + indent -= indent_size; + + //bool first = true; + for(auto& el : ast.if_branches) + { + os << string(indent, ' '); + //if(first) + //{ + // os << "If "; + // first = false; + //} + //else + os << "ElseIf "; + + (*this)(el.condition); + os << " Then\n"; + + indent += indent_size; + for(auto& st : el.block) + (*this)(st); + indent -= indent_size; + } +#endif + + if(ast.else_branch.has_value()) + { + os << string(indent, ' ') << "Else " << '\n'; + indent += indent_size; + for(auto& el : ast.else_branch.get()) + (*this)(el); + indent -= indent_size; + } + + os << string(indent, ' ') << "End If\n"; +} + +void vb6_ast_printer::operator()(vb6_ast::statements::withStmt const& ast) const +{ + os << string(indent, ' ') << "With "; + + (*this)(ast.with_variable); + os << '\n'; + + // statements + indent += indent_size; + for(auto& st : ast.block) + (*this)(st); + indent -= indent_size; + + os << string(indent, ' ') << "End With\n"; +} + +void vb6_ast_printer::operator()(vb6_ast::statements::case_block const& ast) const +{ + // TODO + os << string(indent, ' ') << "Case "; + (*this)(ast.case_expr); + os << "\n"; + + indent += indent_size; + for(auto& st : ast.block) + (*this)(st); + indent -= indent_size; +} + +void vb6_ast_printer::operator()(vb6_ast::statements::selectStmt const& ast) const +{ + // TODO + os << string(indent, ' ') << "Select Case "; + (*this)(ast.condition); + os << '\n'; + + indent += indent_size; + for(auto& st : ast.blocks) + (*this)(st); + indent -= indent_size; + + os << string(indent, ' ') << "End Case\n"; +} + +void vb6_ast_printer::operator()(vb6_ast::statements::singleStmt const& ast) const +{ + //os << string(indent, ' '); // FED ???? wouldn't it be better to indent here rather than at each statement? + // => problem with some statements, e.g. else within the if-statement + // perhaps the else should be a statement of its own!!! + boost::apply_visitor(*this, ast.get()); +} diff --git a/src/vb6_ast_printer.hpp b/src/vb6_ast_printer.hpp new file mode 100644 index 0000000..7bddc14 --- /dev/null +++ b/src/vb6_ast_printer.hpp @@ -0,0 +1,81 @@ +//: vb6_ast_printer.hpp + +// vb6_parser +// Copyright (c) 2022 Federico Aponte +// This code is licensed under GNU Software License (see LICENSE.txt for details) + +#pragma once + +#include "vb6_ast.hpp" +#include + +class vb6_ast_printer +{ +public: + explicit vb6_ast_printer(std::ostream& os) : os(os) + { + } + + void operator()(vb6_ast::empty_line const&) const; + void operator()(vb6_ast::lonely_comment const&) const; + void operator()(vb6_ast::identifier_context const&) const; + void operator()(vb6_ast::variable const&) const; + void operator()(vb6_ast::decorated_variable const&) const; + void operator()(vb6_ast::const_expr const&) const; + void operator()(vb6_ast::expression const&) const; + void operator()(vb6_ast::func_call const&) const; + void operator()(vb6_ast::global_var_decls const&) const; + void operator()(vb6_ast::const_var_stat const&) const; + void operator()(vb6_ast::record const&) const; + void operator()(vb6_ast::vb_enum const&) const; + void operator()(vb6_ast::func_param const&) const; + void operator()(vb6_ast::externalSub const&) const; + void operator()(vb6_ast::externalFunction const&) const; + void operator()(vb6_ast::subHead const&) const; + void operator()(vb6_ast::eventHead const&) const; + void operator()(vb6_ast::functionHead const&) const; + void operator()(vb6_ast::propertyLetHead const&) const; + void operator()(vb6_ast::propertySetHead const&) const; + void operator()(vb6_ast::propertyGetHead const&) const; + void operator()(vb6_ast::subDef const&) const; + void operator()(vb6_ast::functionDef const&) const; + + void operator()(vb6_ast::module_attribute) const; + void operator()(vb6_ast::module_option) const; + void operator()(vb6_ast::STRICT_MODULE_STRUCTURE::module_attributes const&) const; + void operator()(vb6_ast::STRICT_MODULE_STRUCTURE::declaration const&) const; + void operator()(vb6_ast::STRICT_MODULE_STRUCTURE::vb_module const&) const; + void operator()(vb6_ast::declaration const&) const; + void operator()(vb6_ast::vb_module const&) const; + + void operator()(vb6_ast::statements::assignStmt const&) const; + void operator()(vb6_ast::statements::exitStmt const&) const; + void operator()(vb6_ast::statements::gotoStmt const&) const; + void operator()(vb6_ast::statements::onerrorStmt const&) const; + void operator()(vb6_ast::statements::resumeStmt const&) const; + void operator()(vb6_ast::statements::localVarDeclStmt const&) const; + void operator()(vb6_ast::statements::redimStmt const&) const; + void operator()(vb6_ast::statements::callStmt const&) const; + void operator()(vb6_ast::statements::raiseeventStmt const&) const; + void operator()(vb6_ast::statements::labelStmt const&) const; + void operator()(vb6_ast::statements::whileStmt const&) const; + void operator()(vb6_ast::statements::doStmt const&) const; + void operator()(vb6_ast::statements::dowhileStmt const&) const; + void operator()(vb6_ast::statements::loopwhileStmt const&) const; + void operator()(vb6_ast::statements::dountilStmt const&) const; + void operator()(vb6_ast::statements::loopuntilStmt const&) const; + void operator()(vb6_ast::statements::forStmt const&) const; + void operator()(vb6_ast::statements::foreachStmt const&) const; + void operator()(vb6_ast::statements::ifelseStmt const&) const; + void operator()(vb6_ast::statements::withStmt const&) const; + void operator()(vb6_ast::statements::case_block const&) const; + void operator()(vb6_ast::statements::selectStmt const&) const; + void operator()(vb6_ast::statements::singleStmt const&) const; + +private: + void print_type(vb6_ast::access_type) const; + + std::ostream& os; + mutable int indent = 0; + static int indent_size; +}; \ No newline at end of file diff --git a/src/vb6_config.hpp b/src/vb6_config.hpp new file mode 100644 index 0000000..29eaafe --- /dev/null +++ b/src/vb6_config.hpp @@ -0,0 +1,35 @@ +//: vb6_config.hpp + +// vb6_parser +// Copyright (c) 2022 Federico Aponte +// This code is licensed under GNU Software License (see LICENSE.txt for details) + +#pragma once + +#include "vb6_error_handler.hpp" +#include "vb6_parser.hpp" // only for having vb6_grammar::skip_type + +#include + +#include + +namespace vb6_grammar { + +namespace x3 = boost::spirit::x3; + +using iterator_type = std::string_view::const_iterator; +//using iterator_type = std::string::const_iterator; + +using phrase_context_type = x3::phrase_parse_context::type; + +using error_handler_type = vb6_error_handler; +//using error_handler_type = x3::unused_type; + +using context_type = x3::context + , phrase_context_type>; +//using context_type = x3::context const +// , x3::unused_type>; + +} \ No newline at end of file diff --git a/src/vb6_error_handler.hpp b/src/vb6_error_handler.hpp new file mode 100644 index 0000000..df0a5b9 --- /dev/null +++ b/src/vb6_error_handler.hpp @@ -0,0 +1,42 @@ +//: vb6_error_handler.hpp + +// vb6_parser +// Copyright (c) 2022 Federico Aponte +// This code is licensed under GNU Software License (see LICENSE.txt for details) + +#pragma once + +#include +#include + +namespace vb6_grammar { + + namespace x3 = boost::spirit::x3; + + // x3::position_tagged + // annotation_base + // error_handler_base + + // our error handler + template + using vb6_error_handler = x3::error_handler; + + // tag used to get our error handler from the context + //using vb6_error_handler_tag = x3::error_handler_tag; + struct vb6_error_handler_tag; + +#if 0 + struct error_handler_base + { + template + x3::error_handler_result on_error(Iterator& /*first*/, Iterator const& /*last*/, + Exception const& x, Context const& context) + { + std::string message = "Error! Expecting: " + x.which() + " here:"; + auto& error_handler = x3::get(context).get(); + error_handler(x.where(), message); + return x3::error_handler_result::fail; + } + }; +#endif +} \ No newline at end of file diff --git a/src/vb6_parser.cpp b/src/vb6_parser.cpp new file mode 100644 index 0000000..70b1eb7 --- /dev/null +++ b/src/vb6_parser.cpp @@ -0,0 +1,38 @@ +//: vb6_parser.cpp + +// vb6_parser +// Copyright (c) 2022 Federico Aponte +// This code is licensed under GNU Software License (see LICENSE.txt for details) + +#include "vb6_config.hpp" +#include "vb6_parser_def.hpp" + +namespace vb6_grammar { + +std::string getParserInfo(); + +/* +template bool parse_rule( + ????_type rule_ + , iterator_type& first, iterator_type const& last + , context_type const& context, ????_type::attribute_type&); +*/ +BOOST_SPIRIT_INSTANTIATE(empty_line_type, iterator_type, context_type) +BOOST_SPIRIT_INSTANTIATE(lonely_comment_type, iterator_type, context_type) +BOOST_SPIRIT_INSTANTIATE(quoted_string_type, iterator_type, context_type) +BOOST_SPIRIT_INSTANTIATE(basic_identifier_type, iterator_type, context_type) +BOOST_SPIRIT_INSTANTIATE(identifier_context_type, iterator_type, context_type) +BOOST_SPIRIT_INSTANTIATE(decorated_variable_type, iterator_type, context_type) +BOOST_SPIRIT_INSTANTIATE(simple_type_identifier_type, iterator_type, context_type) +BOOST_SPIRIT_INSTANTIATE(complex_type_identifier_type, iterator_type, context_type) +BOOST_SPIRIT_INSTANTIATE(type_identifier_type, iterator_type, context_type) +BOOST_SPIRIT_INSTANTIATE(single_var_declaration_type, iterator_type, context_type) +BOOST_SPIRIT_INSTANTIATE(global_var_declaration_type, iterator_type, context_type) +BOOST_SPIRIT_INSTANTIATE(record_declaration_type, iterator_type, context_type) +BOOST_SPIRIT_INSTANTIATE(const_expression_type, iterator_type, context_type) +BOOST_SPIRIT_INSTANTIATE(expression_type, iterator_type, context_type) +BOOST_SPIRIT_INSTANTIATE(enum_declaration_type, iterator_type, context_type) +BOOST_SPIRIT_INSTANTIATE(const_var_declaration_type, iterator_type, context_type) +BOOST_SPIRIT_INSTANTIATE(param_decl_type, iterator_type, context_type) + +} \ No newline at end of file diff --git a/src/vb6_parser.hpp b/src/vb6_parser.hpp new file mode 100644 index 0000000..82913b8 --- /dev/null +++ b/src/vb6_parser.hpp @@ -0,0 +1,379 @@ +//: vb6_parser.hpp + +// vb6_parser +// Copyright (c) 2022 Federico Aponte +// This code is licensed under GNU Software License (see LICENSE.txt for details) + +#pragma once + +#include "vb6_ast.hpp" + +#include + +#include +/* +---- +Function return value +VB: The function's name treated as a variable is assigned the return value of + the function. No explicit return statement needed, though it can be used (Exit Function/Property). +C++: Explicit return value needed. +---- +Macros +---- +Comments +---- +Multi-line statements, continuation symbol _ +---- +On Error GoTo/Resume +---- +Exclamation mark operator +---- +support ParamArray, Array +---- +support AddressOf +support TypeOf +---- +Implicit type variables +---- +*/ + +namespace vb6_grammar { + + std::string getParserInfo(); + + namespace x3 = boost::spirit::x3; + + auto const kwRem = x3::no_case[x3::lit("Rem")]; + + auto const comment = x3::rule("comment") + = (kwRem | '\'') >> x3::no_skip[*(x3::char_ - x3::eol)] >> x3::eol; + //= (kwRem | '\'') >> x3::seek[x3::eol]; + +#if 0 + struct skip_class; + //using skip_type = x3::rule; + using skip_type = x3::rule; + skip_type const skip("vb6.skip"); + //BOOST_SPIRIT_DECLARE(skip_type); + + auto dbg_comment = [](auto& ctx) { std::cout << "comment: " << x3::_attr(ctx) << '\n'; }; + + // x3::blank only matches spaces or tabs, no newlines + auto const skip_def = x3::blank | (comment [dbg_comment]); + //= x3::blank | ('_' >> x3::eol) | comment; + + BOOST_SPIRIT_DEFINE(skip) +#else + using skip_type = x3::ascii::blank_type; + skip_type const skip; +#endif + + struct empty_line_class ;//: x3::annotation_base, error_handler_base {}; + struct lonely_comment_class ;//: x3::annotate_on_success {}; + struct quoted_string_class ;//: x3::annotate_on_success {}; + struct basic_identifier_class ;//: x3::annotate_on_success {}; + struct decorated_variable_class ;//: x3::annotate_on_success {}; + struct identifier_context_class ;//: x3::annotate_on_success {}; + struct simple_type_identifier_class ;//: x3::annotate_on_success {}; + struct complex_type_identifier_class ;//: x3::annotate_on_success {}; + struct type_identifier_class ;//: x3::annotate_on_success {}; + struct single_var_declaration_class ;//: x3::annotate_on_success {}; + struct global_var_declaration_class ;//: x3::annotate_on_success {}; + struct record_declaration_class ;//: x3::annotate_on_success {}; + struct const_expression_class ;//: x3::annotate_on_success {}; + struct expression_class ;//: x3::annotate_on_success {}; + struct enum_declaration_class ;//: x3::annotate_on_success {}; + struct const_var_declaration_class ;//: x3::annotate_on_success {}; + struct param_decl_class ;//: x3::annotate_on_success {}; + struct external_sub_decl_class ;//: x3::annotate_on_success {}; + struct external_function_decl_class ;//: x3::annotate_on_success {}; + struct subHead_class ;//: x3::annotate_on_success {}; + struct eventHead_class ;//: x3::annotate_on_success {}; + struct functionHead_class ;//: x3::annotate_on_success {}; + struct subDef_class ;//: x3::annotate_on_success {}; + struct functionDef_class ;//: x3::annotate_on_success {}; + struct propertyDef_class ;//: x3::annotate_on_success {}; + struct functionCall_class ;//: x3::annotate_on_success {}; + struct property_letHead_class ;//: x3::annotate_on_success {}; + struct property_setHead_class ;//: x3::annotate_on_success {}; + struct property_getHead_class ;//: x3::annotate_on_success {}; + struct attributeDef_class ;//: x3::annotate_on_success {}; + struct option_item_class ;//: x3::annotate_on_success {}; + + namespace statements { + struct singleStmt_class ;//: x3::annotate_on_success {}; + struct statement_block_class ;//: x3::annotate_on_success {}; + struct assignmentStmt_class ;//: x3::annotate_on_success {}; + struct localvardeclStmt_class;//: x3::annotate_on_success {}; + struct redimStmt_class ;//: x3::annotate_on_success {}; + struct exitStmt_class ;//: x3::annotate_on_success {}; + struct gotoStmt_class ;//: x3::annotate_on_success {}; + struct onerrorStmt_class ;//: x3::annotate_on_success {}; + struct resumeStmt_class ;//: x3::annotate_on_success {}; + struct labelStmt_class ;//: x3::annotate_on_success {}; + struct callimplicitStmt_class;//: x3::annotate_on_success {}; + struct callexplicitStmt_class;//: x3::annotate_on_success {}; + struct raiseeventStmt_class ;//: x3::annotate_on_success {}; + + // compound statements + struct whileStmt_class ;//: x3::annotate_on_success {}; + struct doStmt_class ;//: x3::annotate_on_success {}; + struct dowhileStmt_class ;//: x3::annotate_on_success {}; + struct loopwhileStmt_class ;//: x3::annotate_on_success {}; + struct dountilStmt_class ;//: x3::annotate_on_success {}; + struct loopuntilStmt_class ;//: x3::annotate_on_success {}; + struct forStmt_class ;//: x3::annotate_on_success {}; + struct foreachStmt_class ;//: x3::annotate_on_success {}; + struct ifelseStmt_class ;//: x3::annotate_on_success {}; + struct withStmt_class ;//: x3::annotate_on_success {}; + struct selectStmt_class ;//: x3::annotate_on_success {}; + } + + using empty_line_type = x3::rule; + using lonely_comment_type = x3::rule; + using quoted_string_type = x3::rule; + using basic_identifier_type = x3::rule; + using decorated_variable_type = x3::rule; + using identifier_context_type = x3::rule; + using simple_type_identifier_type = x3::rule; + using complex_type_identifier_type = x3::rule; + using type_identifier_type = x3::rule; + using single_var_declaration_type = x3::rule; + using global_var_declaration_type = x3::rule; + using record_declaration_type = x3::rule; + using const_expression_type = x3::rule; + using expression_type = x3::rule; + using enum_declaration_type = x3::rule; + using const_var_declaration_type = x3::rule; + using param_decl_type = x3::rule; + using external_sub_decl_type = x3::rule; + using external_function_decl_type = x3::rule; + using subHead_type = x3::rule; + using eventHead_type = x3::rule; + using functionHead_type = x3::rule; + using property_letHead_type = x3::rule; + using property_setHead_type = x3::rule; + using property_getHead_type = x3::rule; + using subDef_type = x3::rule; + using functionDef_type = x3::rule; + //using propertyDef_type = x3::rule; + using functionCall_type = x3::rule; + using attributeDef_type = x3::rule>; + using option_item_type = x3::rule; + + namespace statements { + using singleStmt_type = x3::rule; + using statement_block_type = x3::rule; + using assignmentStmt_type = x3::rule; + using localvardeclStmt_type = x3::rule; + using redimStmt_type = x3::rule; + using exitStmt_type = x3::rule; + using gotoStmt_type = x3::rule; + using onerrorStmt_type = x3::rule; + using resumeStmt_type = x3::rule; + using labelStmt_type = x3::rule; + using callimplicitStmt_type = x3::rule; + using callexplicitStmt_type = x3::rule; + using raiseeventStmt_type = x3::rule; + + // compound statements + using whileStmt_type = x3::rule; + using doStmt_type = x3::rule; + using dowhileStmt_type = x3::rule; + using loopwhileStmt_type = x3::rule; + using dountilStmt_type = x3::rule; + using loopuntilStmt_type = x3::rule; + using forStmt_type = x3::rule; + using foreachStmt_type = x3::rule; + using ifelseStmt_type = x3::rule; + using withStmt_type = x3::rule; + using selectStmt_type = x3::rule; + + using ifBranch_type = x3::rule; + using elsifBranch_type = x3::rule; + using elseBranch_type = x3::rule; + + ifBranch_type const ifBranch("ifBranch"); + elsifBranch_type const elsifBranch("elsifBranch"); + elseBranch_type const elseBranch("elseBranch"); + + using case_block_type = x3::rule; + case_block_type const case_block("case_block"); + } + + namespace STRICT_MODULE_STRUCTURE + { + struct declaration_class; //: x3::annotation_base, error_handler_base {}; + using declaration_type = x3::rule; + declaration_type const declaration("declaration"); + + struct basModDef_class; //: x3::annotation_base, error_handler_base {}; + using basModDef_type = x3::rule; + basModDef_type const basModDef("basModDef"); + + BOOST_SPIRIT_DECLARE( + declaration_type + , basModDef_type + ); + } + + struct declaration_class; //: x3::annotation_base, error_handler_base {}; + using declaration_type = x3::rule; + declaration_type const declaration("declaration"); + + struct basModDef_class; //: x3::annotation_base, error_handler_base {}; + using basModDef_type = x3::rule; + basModDef_type const basModDef("basModDef"); + + empty_line_type const empty_line ("empty_line"); + lonely_comment_type const lonely_comment ("lonely_comment"); + quoted_string_type const quoted_string ("quoted_string"); + basic_identifier_type const basic_identifier ("basic_identifier"); + decorated_variable_type const decorated_variable ("decorated_variable"); + identifier_context_type const identifier_context ("identifier_context"); + simple_type_identifier_type const simple_type_identifier ("simple_type_identifier"); + complex_type_identifier_type const complex_type_identifier ("complex_type_identifier"); + type_identifier_type const type_identifier ("type_identifier"); + single_var_declaration_type const single_var_declaration ("single_var_declaration"); + global_var_declaration_type const global_var_declaration ("global_var_declaration"); + record_declaration_type const record_declaration ("record_declaration"); + const_expression_type const const_expression ("const_expression"); + expression_type const expression ("expression"); + enum_declaration_type const enum_declaration ("enum_declaration"); + const_var_declaration_type const const_var_declaration ("const_var_declaration"); + param_decl_type const param_decl ("param_decl"); + external_sub_decl_type const external_sub_decl ("external_sub_decl"); + external_function_decl_type const external_function_decl ("external_function_decl"); + subHead_type const subHead ("subHead"); + eventHead_type const eventHead ("eventHead"); + functionHead_type const functionHead ("functionHead"); + property_letHead_type const property_letHead ("property_letHead"); + property_setHead_type const property_setHead ("property_setHead"); + property_getHead_type const property_getHead ("property_getHead"); + subDef_type const subDef ("subDef"); + functionDef_type const functionDef ("functionDef"); + //propertyDef_type const propertyDef ("propertyDef"); + functionCall_type const functionCall ("functionCall"); + attributeDef_type const attributeDef ("attributeDef"); + option_item_type const option_item ("option_item"); + + namespace statements { + singleStmt_type const singleStmt ("singleStmt"); + statement_block_type const statement_block ("statement_block"); + assignmentStmt_type const assignmentStmt ("assignmentStmt"); + localvardeclStmt_type const localvardeclStmt ("localvardeclStmt"); + redimStmt_type const redimStmt ("redimStmt"); + exitStmt_type const exitStmt ("exitStmt"); + gotoStmt_type const gotoStmt ("gotoStmt"); + onerrorStmt_type const onerrorStmt ("onerrorStmt"); + resumeStmt_type const resumeStmt ("resumeStmt"); + labelStmt_type const labelStmt ("labelStmt"); + callimplicitStmt_type const callimplicitStmt ("callimplicitStmt"); + callexplicitStmt_type const callexplicitStmt ("callexplicitStmt"); + raiseeventStmt_type const raiseeventStmt ("raiseeventStmt"); + + // compound statements + whileStmt_type const whileStmt ("whileStmt"); + doStmt_type const doStmt ("doStmt"); + dowhileStmt_type const dowhileStmt ("dowhileStmt"); + loopwhileStmt_type const loopwhileStmt ("loopwhileStmt"); + dountilStmt_type const dountilStmt ("dountilStmt"); + loopuntilStmt_type const loopuntilStmt ("loopuntilStmt"); + forStmt_type const forStmt ("forStmt"); + foreachStmt_type const foreachStmt ("foreachStmt"); + ifelseStmt_type const ifelseStmt ("ifelseStmt"); + withStmt_type const withStmt ("withStmt"); + selectStmt_type const selectStmt ("selectStmt"); + } + + auto const var_identifier = basic_identifier; + auto const record_identifier = basic_identifier; + auto const enum_identifier = basic_identifier; + auto const sub_identifier = basic_identifier; + auto const func_identifier = basic_identifier; + auto const event_identifier = basic_identifier; + auto const prop_identifier = basic_identifier; + auto const lib_name = quoted_string; + auto const alias_name = quoted_string; + + /* + template + bool parse_rule( + ????_type rule_ + , Iterator& first, Iterator const& last + , Context const& context, ????_type::attribute_type& attr); + */ + BOOST_SPIRIT_DECLARE( + empty_line_type + , lonely_comment_type + , quoted_string_type + , basic_identifier_type + , identifier_context_type + , decorated_variable_type + , simple_type_identifier_type + , complex_type_identifier_type + , type_identifier_type + , single_var_declaration_type + , global_var_declaration_type + , record_declaration_type + , const_expression_type + , expression_type + , enum_declaration_type + , const_var_declaration_type + , param_decl_type + , external_sub_decl_type + , external_function_decl_type + , subHead_type + , eventHead_type + , functionHead_type + , property_letHead_type + , property_setHead_type + , property_getHead_type + , subDef_type + , functionDef_type + //, propertyDef_type + , functionCall_type + , attributeDef_type + , option_item_type + , declaration_type + , basModDef_type + ) + + BOOST_SPIRIT_DECLARE( + statements::singleStmt_type + , statements::statement_block_type + , statements::assignmentStmt_type + , statements::localvardeclStmt_type + , statements::redimStmt_type + , statements::exitStmt_type + , statements::gotoStmt_type + , statements::onerrorStmt_type + , statements::resumeStmt_type + , statements::labelStmt_type + , statements::callimplicitStmt_type + , statements::callexplicitStmt_type + , statements::raiseeventStmt_type + ) + + BOOST_SPIRIT_DECLARE( + statements::ifBranch_type + , statements::elsifBranch_type + , statements::elseBranch_type + , statements::case_block_type + ) + + // compound statements + BOOST_SPIRIT_DECLARE( + statements::whileStmt_type + , statements::doStmt_type + , statements::dowhileStmt_type + , statements::loopwhileStmt_type + , statements::dountilStmt_type + , statements::loopuntilStmt_type + , statements::forStmt_type + , statements::foreachStmt_type + , statements::ifelseStmt_type + , statements::withStmt_type + , statements::selectStmt_type + ) +} \ No newline at end of file diff --git a/src/vb6_parser_def.hpp b/src/vb6_parser_def.hpp new file mode 100644 index 0000000..741f1c8 --- /dev/null +++ b/src/vb6_parser_def.hpp @@ -0,0 +1,475 @@ +//: vb6_parser_def.hpp + +// vb6_parser +// Copyright (c) 2022 Federico Aponte +// This code is licensed under GNU Software License (see LICENSE.txt for details) + +#pragma once + +#include "vb6_parser.hpp" +#include "vb6_ast_adapt.hpp" +#include "vb6_parser_keywords.hpp" +#include "vb6_parser_operators.hpp" + +#include +#include +#include + +// http://boost.2283326.n4.nabble.com/Horrible-compiletimes-and-memory-usage-while-compiling-a-parser-with-X3-td4689104.html + +/* +---- +Function return value +VB: The function's name treated as a variable is assigned the return value of + the function. No explicit return statement needed, though it can be used (Exit Function/Property). +C++: Explicit return value needed. +---- +Macros +---- +Comments +---- +Multi-line statements, continuation symbol _ +---- +On Error GoTo/Resume +---- +Exclamation mark operator +---- +support ParamArray, Array +---- +support AddressOf +---- +Implicit type variables +---- +*/ + +namespace vb6_grammar { + + namespace x3 = boost::spirit::x3; + + // the terminator for every VB6 statement + auto const cmdTermin = x3::eol | ':'; + + // keyword groups + auto const kwgEndType = kwEnd > kwType > cmdTermin; + auto const kwgEndEnum = kwEnd > kwEnum > cmdTermin; + auto const kwgEndFunction = kwEnd > kwFunction > cmdTermin; + auto const kwgEndSub = kwEnd > kwSub > cmdTermin; + auto const kwgEndProperty = kwEnd > kwProperty > cmdTermin; + + auto const bool_const = x3::rule("bool_const") + = (kwTrue >> x3::attr(true)) + | (kwFalse >> x3::attr(false)); + + auto const keywords = kwFor | kwEach | kwStep | kwTo | kwNext | kwEnd + | kwWhile | kwWend | kwLoop | kwDo | kwUntil + | kwIf | kwElse | kwElseIf | kwExit | kwSelect | kwCase + | kwPublic| kwPrivate | kwGlobal | kwDim + | kwGet | kwSet | kwLet + | kwOn | kwLocal | kwGoTo | kwGoSub | kwReturn + | kwCall | kwRaiseEvent + | kwSub | kwFunction | kwEvent | kwProperty + | kwByVal | kwByRef | kwAs | kwIn | kwOption; + + auto const reserved = keywords >> !x3::char_("a-zA-Z0-9_£"); + //auto const reserved = keywords >> (x3::char_ - x3::char_("a-zA-Z0-9_£")); + + // FED ???? dovrebbe andare bene, rivedere e pulire + auto const empty_line_def = //x3::omit[x3::no_skip[*x3::blank]] + //>> x3::attr(vb6_ast::empty_line()) >> x3::eol; + x3::omit[x3::no_skip[*x3::blank >> x3::eol]] + >> x3::attr(vb6_ast::empty_line()); + + // line composed only of a comment, no VB6 code + //auto const lonely_comment_def = x3::no_skip[ x3::omit[*x3::blank] + // >> (kwRem | x3::lit('\'')) + // >> *(x3::char_ - x3::eol)] >> x3::eol; + // FED ???? dovrebbe andare bene, rivedere e pulire + auto const lonely_comment_def = x3::no_skip[ x3::omit[*x3::blank] + >> (kwRem | x3::lit('\'')) + >> *(x3::char_ - x3::eol) >> x3::eol]; + + // FED ???? the expression ~x3::char_('"') does not behave as I expect + //auto const quoted_string_def = x3::lexeme['"' >> *(~x3::char_('"')) >> '"']; + auto const quoted_string_def = x3::lexeme['"' >> *(x3::char_ - '"') >> '"']; + + // need to remove the reserved words from the possible identifiers + auto const basic_identifier_def = x3::lexeme[x3::no_case[x3::char_("a-zA-Z_£") >> *x3::char_("a-zA-Z0-9_£")] - reserved]; + //= x3::lexeme[x3::no_case[(x3::alpha | '_' | '£') >> *(x3::alnum | '_' | '£')] - reserved]; + /* + Dim x As String ' simple identifier, variable name + x.y.z = 5 ' composed, variable name + Call foo(x.y.z) ' composed, variable name + Enum types : a = 1 : b = 2 : End Enum ' simple, type name + Type point : x As Double : y As Double : End Type ' simple, type name + Dim x As Module.types ' composed, type name + Call Module.foo() ' composed, sub name + Call .foo() ' composed, sub name, with-statement + a = GetValue().x ' composed + */ + + auto const identifier_context_def = -(opDot >> x3::attr(true)) + >> *((functionCall | basic_identifier) >> opDot); + + auto const param_qualifier = kwByVal | kwByRef; + auto const simple_type_identifier_def = //x3::attr(boost::optional()) >> + ( kwBoolean + | kwByte + | kwInteger + | kwLong + | kwSingle + | kwDouble + | kwCurrency + | kwDate + | kwString + | kwObject + | kwVariant); +/* + auto const simple_type_identifier_def = (kwBoolean >> x3::attr(vb6_ast::NativeType::Boolean)) + | (kwByte >> x3::attr(vb6_ast::NativeType::Byte)) + | (kwInteger >> x3::attr(vb6_ast::NativeType::Integer)) + | (kwLong >> x3::attr(vb6_ast::NativeType::Long)) + | (kwSingle >> x3::attr(vb6_ast::NativeType::Single)) + | (kwDouble >> x3::attr(vb6_ast::NativeType::Double)) + | (kwCurrency >> x3::attr(vb6_ast::NativeType::Currency)) + | (kwDate >> x3::attr(vb6_ast::NativeType::Date)) + | (kwString >> x3::attr(vb6_ast::NativeType::String)) + | (kwObject >> x3::attr(vb6_ast::NativeType::Object)) + | (kwVariant >> x3::attr(vb6_ast::NativeType::Variant)); +*/ + auto const complex_type_identifier_def = x3::omit[-(basic_identifier >> opDot)] >> basic_identifier; + auto const type_identifier_def = (simple_type_identifier | complex_type_identifier); + + auto const array_size_decl = x3::omit["(" >> x3::int_ >> ")"]; + + auto const tmp //= x3::rule>>() + = (kwAs >> kwNew >> x3::attr(true) >> type_identifier); + //| (kwAs >> x3::attr(false) >> type_identifier) + //| (x3::attr(false) >> x3::attr(boost::none)); + + auto const tmp1 //= x3::rule>>() + = kwAs >> kwNew >> x3::attr(true) >> simple_type_identifier; + auto const tmp2 //= x3::rule>>() + = kwAs >> x3::attr(false) >> simple_type_identifier; + + auto const single_var_implicit_declaration_def = var_identifier >> -array_size_decl >> x3::attr(false) >> x3::attr(boost::none); + + auto const single_var_declaration_def = var_identifier >> -array_size_decl >> (tmp1); + //auto const single_var_declaration_def = var_identifier >> -array_size_decl >> (tmp1 | tmp2); + //auto const single_var_declaration_def = var_identifier >> -array_size_decl >> -(kwAs >> -(kwNew >> x3::attr(true)) >> type_identifier); + auto const global_var_declaration_def = ( (kwDim >> x3::attr(vb6_ast::access_type::dim)) + | (kwGlobal >> x3::attr(vb6_ast::access_type::global)) + | (kwPublic >> x3::attr(vb6_ast::access_type::public_)) + | (kwPrivate >> x3::attr(vb6_ast::access_type::private_)) + ) >> -(kwWithEvents >> x3::attr(true)) >> (single_var_declaration % ',') >> cmdTermin; + + auto const private_or_public = (kwPrivate >> x3::attr(vb6_ast::access_type::private_)) + | (kwPublic >> x3::attr(vb6_ast::access_type::public_)) + | (x3::eps >> x3::attr(vb6_ast::access_type::na)); + + auto const record_declaration_def = private_or_public >> kwType >> record_identifier >> cmdTermin + //>> (single_var_declaration % cmdTermin) // this fails, but why? + >> +(single_var_declaration >> cmdTermin) + >> kwgEndType; + + auto const single_float = x3::rule("single_float") + = x3::lexeme[x3::float_ >> '!']; + auto const double_float = x3::rule("double_float") + = x3::lexeme[x3::double_ >> '#']; + auto const long_dec = x3::rule("long_dec") + = x3::lexeme[x3::int_ >> '&']; + auto const long_hex = x3::rule("long_hex") + = x3::lexeme["&H" >> x3::hex >> '&']; + auto const long_oct = x3::rule("long_oct") + = x3::lexeme["&0" >> x3::oct >> '&']; + auto const integer_dec = x3::rule("integer_dec") + = x3::lexeme[x3::short_ >> '%'] + | x3::short_; + auto const integer_hex = x3::rule("integer_hex") + = x3::lexeme["&H" >> x3::hex >> '%']; + auto const integer_oct = x3::rule("integer_oct") + = x3::lexeme["&0" >> x3::oct >> '%']; + auto const currency = x3::rule("currency") + = x3::lexeme[x3::double_ >> '@']; + + x3::real_parser> const float_ = {}; + //x3::real_parser> const double_ = {}; + + auto const const_expression_def = double_float + | single_float + | float_ + | long_dec + | long_hex + | long_oct + | integer_dec + | integer_hex + | integer_oct + | quoted_string + | bool_const + | (kwNothing >> x3::attr(vb6_ast::nothing())); + + namespace expr_take_1 + { + struct factor_class; + struct term_class; + + using factor_type = x3::rule; + using term_type = x3::rule; + + factor_type const factor("factor"); + term_type const term("term"); + + auto const strong_op = opMult | opDiv | opDivint | opMod | opAnd; + auto const weak_op = opPlus | opMinus | opOr; + + auto const term_def = factor >> *(strong_op >> factor); + auto const factor_def = '(' >> expression >> ')' + | const_expression + | functionCall // recursive + | decorated_variable + | (opNot >> factor); + auto const simpleExpression = -(opPlus|opMinus) >> term >> *(weak_op >> term); + + BOOST_SPIRIT_DEFINE( + factor + , term + ) + } // namespace expr_take_1 + + namespace expr_take_2 + { + // https://levelup.gitconnected.com/create-your-own-expression-parser-d1f622077796 + // https://panthema.net/2018/0912-Boost-Spirit-Tutorial/ + + struct expr_class; + struct mulexpr_class; + struct powexpr_class; + struct atom_class; + + using expr_type = x3::rule; + using mulexpr_type = x3::rule; + using powexpr_type = x3::rule; + using atom_type = x3::rule; + + expr_type const expr("expr"); + mulexpr_type const mulexpr("mulexpr"); + powexpr_type const powexpr("powexpr"); + atom_type const atom("atom"); + + auto const addop = opPlus | opMinus; + auto const mulop = opMult | opDiv; + + // expression | RPN + // ------------+--------------- + // 2+3*4 | 2 3 4 * + + // 2*3+4 | 2 3 * 4 + + // (2+3)*4 | 2 3 + 4 * + // 2^3*4+5 | 2 3 ^ 4 * 5 + + // 2+3*4^5 | 2 3 4 5 ^ * + + auto const expr_def = mulexpr >> *(addop >> mulexpr); + auto const mulexpr_def = powexpr >> *(mulop >> powexpr); + auto const powexpr_def = (-(opPlus|opMinus) >> powexpr) + | (atom >> -("^" >> powexpr)); + auto const atom_def = functionCall + | const_expression + | ("(" >> expr >> ")"); + + BOOST_SPIRIT_DEFINE( + expr + , mulexpr + , powexpr + , atom + ) + } // namespace expr_take_3 + + auto const decorated_variable_def = identifier_context >> var_identifier; + + //auto const decorated_functionCall = x3::omit[identifier_context] >> functionCall; + + auto const expression_def = const_expression + | functionCall + | decorated_variable + ; + + auto const enum_declaration_def = private_or_public >> kwEnum >> enum_identifier >> cmdTermin + >> +(basic_identifier >> -(opEqual >> const_expression) >> cmdTermin) // FED ???? basic_identifier? + >> kwgEndEnum; + auto const const_var_declaration_def = -kwPrivate >> kwConst + >> ((single_var_declaration >> opEqual >> const_expression) % ',') + >> cmdTermin; + auto const param_decl_def = -(kwOptional >> x3::attr(true)) >> -param_qualifier + >> single_var_declaration // FED ??? this can have 'New', change it + >> -(opEqual >> const_expression); // default value for optional parameter + + // helper definition + auto const param_list_decl = -(param_decl % ','); + + auto const external_sub_decl_def = private_or_public >> kwDeclare >> kwSub >> sub_identifier + >> kwLib >> lib_name >> -(kwAlias >> alias_name) + >> '(' >> param_list_decl >> ')' + >> cmdTermin; + auto const external_function_decl_def = private_or_public >> kwDeclare >> kwFunction >> func_identifier + >> kwLib >> lib_name >> -(kwAlias >> alias_name) + >> '(' >> param_list_decl >> ')' >> -(kwAs >> type_identifier) + >> cmdTermin; + auto const subHead_def = private_or_public >> kwSub + >> sub_identifier + >> '(' >> param_list_decl >> ')' + >> cmdTermin; + auto const eventHead_def = private_or_public >> kwEvent + >> sub_identifier + >> '(' >> param_list_decl >> ')' + >> cmdTermin; + auto const functionHead_def = private_or_public >> kwFunction >> func_identifier + >> '(' >> param_list_decl >> ')' >> -(kwAs >> type_identifier) + >> cmdTermin; + auto const property_letHead_def = private_or_public >> (kwProperty > kwLet) >> prop_identifier + >> '(' >> param_list_decl >> ')' + >> cmdTermin; + auto const property_setHead_def = private_or_public >> (kwProperty > kwSet) >> prop_identifier + >> '(' >> param_list_decl >> ')' + >> cmdTermin; + auto const property_getHead_def = private_or_public >> (kwProperty > kwGet) >> prop_identifier + >> '(' >> param_list_decl >> ')' >> -(kwAs >> type_identifier) + >> cmdTermin;; + + auto const functionCall_def = func_identifier >> '(' >> -(expression % ',') >> ')'; + + //auto const decorated_functionCall = x3::omit[identifier_context] >> functionCall; + + //auto const decorated_sub_identifier = x3::omit[identifier_context] >> sub_identifier; + + // these are defined after statement_block as they depend on it + + auto const subDef_def = subHead + >> statements::statement_block + >> kwgEndSub; + auto const functionDef_def = functionHead + >> statements::statement_block + >> kwgEndFunction; + //auto const propertyDef_def = (property_getHead | property_letHead | property_setHead) + // >> statements::statement_block + // >> kwgEndProperty; + + auto const attr_name = basic_identifier; + auto const attributeDef_def = kwAttribute + >> attr_name >> opEqual >> quoted_string + >> cmdTermin; + + auto const option_item_def = kwOption > ( (kwExplicit >> x3::attr(vb6_ast::module_option::explicit_)) + | (kwCompare > ((kwText >> x3::attr(vb6_ast::module_option::compare_text)) | + (kwBinary >> x3::attr(vb6_ast::module_option::compare_binary)))) + | (kwBase > ((x3::lit('0') >> x3::attr(vb6_ast::module_option::base_0)) | + (x3::lit('1') >> x3::attr(vb6_ast::module_option::base_1)))) + | ((kwPrivate > kwModule) >> x3::attr(vb6_ast::module_option::private_module)) + ) >> cmdTermin; + + namespace STRICT_MODULE_STRUCTURE + { +#if 0 + auto const option_block = *option_item; +#else + auto const option_block = x3::rule() + = -( *(lonely_comment | empty_line | option_item) + //>> +option_item + ); +#endif + + auto const preamble = *(attributeDef) + >> option_block; + + auto const declaration_def = lonely_comment // critical to have this as the first element + | empty_line + | global_var_declaration + | const_var_declaration + | enum_declaration + | record_declaration + | external_sub_decl + | external_function_decl + | eventHead + ; + + //auto const funcList = x3::rule("funcList") + // = *( subDef + // | functionDef + // //| propertyDef + // ); + + auto const func_subDef = subDef + | functionDef + //| propertyDef + ; + + auto const basModDef_def = preamble + >> (*declaration) + >> (*func_subDef); + auto const clsModDef = preamble >> *declaration >> *func_subDef; + auto const frmModDef = preamble >> /*formDef >>*/ *declaration >> *func_subDef; + auto const ctlModDef = preamble >> /*formDef >>*/ *declaration >> *func_subDef; + + auto const unitDef = basModDef | clsModDef | frmModDef | ctlModDef; + + BOOST_SPIRIT_DEFINE( + declaration + , basModDef + ) + } // namespace STRICT_MODULE_STRUCTURE + + auto const declaration_def = global_var_declaration + | const_var_declaration + | enum_declaration + | record_declaration + | external_sub_decl + | external_function_decl + | eventHead + ; + + auto const func_subDef = subDef + | functionDef + //| propertyDef + ; + + auto const basModDef_def = *(lonely_comment // critical to have this as the first element + | empty_line + | attributeDef + | option_item + | declaration + | func_subDef); + + auto const unitDef = basModDef; + + BOOST_SPIRIT_DEFINE( + empty_line + , lonely_comment + , quoted_string + , basic_identifier + , identifier_context + , decorated_variable + , simple_type_identifier + , complex_type_identifier + , type_identifier + , single_var_declaration + , global_var_declaration + , record_declaration + , const_expression + , expression + , enum_declaration + , const_var_declaration + , param_decl + , external_sub_decl + , external_function_decl + , subHead + , eventHead + , functionHead + , property_letHead + , property_setHead + , property_getHead + , subDef + , functionDef + , functionCall + , attributeDef + , option_item + , declaration + , basModDef + ) + //, propertyDef + } diff --git a/src/vb6_parser_functions.cpp b/src/vb6_parser_functions.cpp new file mode 100644 index 0000000..2a1a75f --- /dev/null +++ b/src/vb6_parser_functions.cpp @@ -0,0 +1,35 @@ +//: vb6_parser_functions.cpp + +// vb6_parser +// Copyright (c) 2022 Federico Aponte +// This code is licensed under GNU Software License (see LICENSE.txt for details) + +#include "vb6_config.hpp" +#include "vb6_parser_def.hpp" + +namespace vb6_grammar { + +/* +template bool parse_rule( + ????_type rule_ + , iterator_type& first, iterator_type const& last + , context_type const& context, ????_type::attribute_type&); +*/ +BOOST_SPIRIT_INSTANTIATE(external_sub_decl_type, iterator_type, context_type) +BOOST_SPIRIT_INSTANTIATE(external_function_decl_type, iterator_type, context_type) +BOOST_SPIRIT_INSTANTIATE(subHead_type, iterator_type, context_type) +BOOST_SPIRIT_INSTANTIATE(eventHead_type, iterator_type, context_type) +BOOST_SPIRIT_INSTANTIATE(functionHead_type, iterator_type, context_type) +BOOST_SPIRIT_INSTANTIATE(property_letHead_type, iterator_type, context_type) +BOOST_SPIRIT_INSTANTIATE(property_setHead_type, iterator_type, context_type) +BOOST_SPIRIT_INSTANTIATE(property_getHead_type, iterator_type, context_type) +BOOST_SPIRIT_INSTANTIATE(functionCall_type, iterator_type, context_type) +BOOST_SPIRIT_INSTANTIATE(declaration_type, iterator_type, context_type) +BOOST_SPIRIT_INSTANTIATE(attributeDef_type, iterator_type, context_type) +BOOST_SPIRIT_INSTANTIATE(option_item_type, iterator_type, context_type) +BOOST_SPIRIT_INSTANTIATE(subDef_type, iterator_type, context_type) +BOOST_SPIRIT_INSTANTIATE(functionDef_type, iterator_type, context_type) +//BOOST_SPIRIT_INSTANTIATE(propertyDef_type, iterator_type, context_type) +BOOST_SPIRIT_INSTANTIATE(basModDef_type, iterator_type, context_type) + +} \ No newline at end of file diff --git a/src/vb6_parser_helper.cpp b/src/vb6_parser_helper.cpp new file mode 100644 index 0000000..3d762c1 --- /dev/null +++ b/src/vb6_parser_helper.cpp @@ -0,0 +1,23 @@ +//: vb6_parser_helper.cpp + +// vb6_parser +// Copyright (c) 2022 Federico Aponte +// This code is licensed under GNU Software License (see LICENSE.txt for details) + +#include + +#include + +namespace vb6_grammar { + +std::string getParserInfo() +{ + using namespace std; + ostringstream os; + os << "SPIRIT_X3_VERSION: " << showbase << hex << SPIRIT_X3_VERSION + << noshowbase << dec << '\n' + << "VB6_PARSER_VERSION: 0.1" << '\n'; + return os.str(); +} + +} \ No newline at end of file diff --git a/src/vb6_parser_keywords.hpp b/src/vb6_parser_keywords.hpp new file mode 100644 index 0000000..4be94e3 --- /dev/null +++ b/src/vb6_parser_keywords.hpp @@ -0,0 +1,153 @@ +//: vb6_parser_keywords.hpp + +// vb6_parser +// Copyright (c) 2022 Federico Aponte +// This code is licensed under GNU Software License (see LICENSE.txt for details) + +#pragma once + +#include + +namespace vb6_grammar { + + namespace x3 = boost::spirit::x3; + + // constants + auto const kwTrue = x3::no_case[x3::lit("True")]; + auto const kwFalse = x3::no_case[x3::lit("False")]; + auto const kwNothing = x3::no_case[x3::lit("Nothing")]; + + // primitive types + auto const kwBoolean = x3::rule("kwBoolean") + = x3::no_case[x3::lit("Boolean")]; + auto const kwByte = x3::rule("kwByte") + = x3::no_case[x3::lit("Byte")]; + auto const kwInteger = x3::rule("kwInteger") + = x3::no_case[x3::lit("Integer")]; + auto const kwLong = x3::rule("kwLong") + = x3::no_case[x3::lit("Long")]; + auto const kwSingle = x3::rule("kwSingle") + = x3::no_case[x3::lit("Single")]; + auto const kwDouble = x3::rule("kwDouble") + = x3::no_case[x3::lit("Double")]; + auto const kwCurrency = x3::rule("kwCurrency") + = x3::no_case[x3::lit("Currency")]; + auto const kwDate = x3::rule("kwDate") + = x3::no_case[x3::lit("Date")]; + auto const kwString = x3::rule("kwString") + = x3::no_case[x3::lit("String")]; + auto const kwObject = x3::rule("kwObject") + = x3::no_case[x3::lit("Object")]; + auto const kwVariant = x3::rule("kwVariant") + = x3::no_case[x3::lit("Variant")]; + // only for external functions and subroutines + // "As Any" disables type checking and allows any data type to be passed in or returned + auto const kwAny = x3::rule("kwAny") + = x3::no_case[x3::lit("Any")]; + + // VB6 keywords + + auto const kwLet = x3::no_case[x3::lit("Let")]; + auto const kwSet = x3::no_case[x3::lit("Set")]; + auto const kwGet = x3::no_case[x3::lit("Get")]; // also for file handling + auto const kwRSet = x3::no_case[x3::lit("RSet")]; + + //auto const kwBegin = x3::no_case[x3::lit("Begin")]; + auto const kwEnd = x3::no_case[x3::lit("End")]; + + auto const kwOn = x3::no_case[x3::lit("On")]; + auto const kwLocal = x3::no_case[x3::lit("Local")]; + auto const kwError = x3::no_case[x3::lit("Error")]; + auto const kwResume = x3::no_case[x3::lit("Resume")]; + + auto const kwEvent = x3::no_case[x3::lit("Event")]; + auto const kwProperty = x3::no_case[x3::lit("Property")]; + + auto const kwParamArray = x3::rule("kwParamArray") + = x3::no_case[x3::lit("ParamArray")]; + auto const kwDefault = x3::rule("kwDefault") + = x3::no_case[x3::lit("Default")]; + auto const kwAddressOf = x3::rule("kwAddressOf") + = x3::no_case[x3::lit("AddressOf")]; + auto const kwStatic = x3::no_case[x3::lit("Static")]; + + auto const kwFriend = x3::no_case[x3::lit("Friend")]; // used only in class modules + + auto const kwIf = x3::no_case[x3::lit("If")]; + auto const kwThen = x3::no_case[x3::lit("Then")]; + auto const kwElseIf = x3::no_case[x3::lit("ElseIf")]; + auto const kwElse = x3::no_case[x3::lit("Else")]; + + auto const kwFor = x3::no_case[x3::lit("For")]; + auto const kwEach = x3::no_case[x3::lit("Each")]; + auto const kwIn = x3::no_case[x3::lit("In")]; + auto const kwTo = x3::no_case[x3::lit("To")]; + auto const kwStep = x3::no_case[x3::lit("Step")]; + auto const kwNext = x3::no_case[x3::lit("Next")]; + + auto const kwWhile = x3::no_case[x3::lit("While")]; + auto const kwWend = x3::no_case[x3::lit("Wend")]; + auto const kwDo = x3::no_case[x3::lit("Do")]; + auto const kwLoop = x3::no_case[x3::lit("Loop")]; + auto const kwUntil = x3::no_case[x3::lit("Until")]; + + auto const kwSelect = x3::no_case[x3::lit("Select")]; + auto const kwCase = x3::no_case[x3::lit("Case")]; + auto const kwIs = x3::no_case[x3::lit("Is")]; + + auto const kwReDim = x3::no_case[x3::lit("ReDim")]; + auto const kwPreserve = x3::no_case[x3::lit("Preserve")]; + auto const kwWithEvents = x3::no_case[x3::lit("WithEvents")]; + auto const kwNew = x3::no_case[x3::lit("New")]; + auto const kwExit = x3::no_case[x3::lit("Exit")]; + auto const kwCall = x3::no_case[x3::lit("Call")]; + auto const kwRaiseEvent = x3::no_case[x3::lit("RaiseEvent")]; + auto const kwGoTo = x3::no_case[x3::lit("GoTo")]; + auto const kwGoSub = x3::no_case[x3::lit("GoSub")]; + auto const kwReturn = x3::no_case[x3::lit("Return")]; + auto const kwWith = x3::no_case[x3::lit("With")]; + auto const kwAttribute = x3::no_case[x3::lit("Attribute")]; + auto const kwTypeOf = x3::no_case[x3::lit("TypeOf")]; + + /*struct GoTo_table : x3::symbols { + GoTo_table() { + add("GoTo", vb6_ast::gotoType::goto_v) + ("GoSub", vb6_ast::gotoType::gosub_v); + } + } const goto_or_gosub;*/ + + auto const kwConst = x3::no_case[x3::lit("Const")]; + auto const kwDim = x3::no_case[x3::lit("Dim")]; + auto const kwType = x3::no_case[x3::lit("Type")]; + auto const kwEnum = x3::no_case[x3::lit("Enum")]; + auto const kwPublic = x3::no_case[x3::lit("Public")]; + auto const kwPrivate = x3::no_case[x3::lit("Private")]; + auto const kwGlobal = x3::no_case[x3::lit("Global")]; + auto const kwSub = x3::no_case[x3::lit("Sub")]; + auto const kwFunction = x3::no_case[x3::lit("Function")]; + auto const kwDeclare = x3::no_case[x3::lit("Declare")]; + auto const kwAs = x3::no_case[x3::lit("As")]; + auto const kwLib = x3::no_case[x3::lit("Lib")]; + auto const kwAlias = x3::no_case[x3::lit("Alias")]; + auto const kwText = x3::no_case[x3::lit("Text")]; // used in Option Compare and file functions + auto const kwBinary = x3::no_case[x3::lit("Binary")]; // used in Option Compare and file functions + + // start of unit + auto const kwOption = x3::no_case[x3::lit("Option")]; + auto const kwExplicit = x3::no_case[x3::lit("Explicit")]; + auto const kwCompare = x3::no_case[x3::lit("Compare")]; + auto const kwBase = x3::no_case[x3::lit("Base")]; + auto const kwModule = x3::no_case[x3::lit("Module")]; + /* NB: + L'istruzione Option Private Module risulta utile solo per applicazioni host + che supportano il caricamento simultaneo di più progetti e riferimenti + tra progetti caricati. Microsoft Excel, ad esempio, consente di + caricare più progetti e Option Private Module può essere utilizzata per + limitare la visibilità tra progetti. Sebbene Visual Basic consenta di + caricare più progetti, i riferimenti tra progetti non sono mai ammessi. + */ + + auto const kwByVal = x3::no_case[x3::lit("ByVal")] >> x3::attr(vb6_ast::param_qualifier::byval); + auto const kwByRef = x3::no_case[x3::lit("ByRef")] >> x3::attr(vb6_ast::param_qualifier::byref); + auto const kwOptional = x3::no_case[x3::lit("Optional")]; +} \ No newline at end of file diff --git a/src/vb6_parser_main.cpp b/src/vb6_parser_main.cpp new file mode 100644 index 0000000..21e2625 --- /dev/null +++ b/src/vb6_parser_main.cpp @@ -0,0 +1,69 @@ +//: vb6_parser_main.cpp + +// vb6_parser +// Copyright (c) 2022 Federico Aponte +// This code is licensed under GNU Software License (see LICENSE.txt for details) + +#include "color_console.hpp" +#include "vb6_parser.hpp" // only for vb6_grammar::getParserInfo() + +#include +#include +#include +#include +#include + +void vb6_test1(std::ostream&); +void vb6_test2(std::ostream&); +void vb6_test_statements(std::ostream&); + +void test_vb6_unit(std::ostream&, std::string_view unit); +void test_gosub(std::ostream&); + +using namespace std; + +void test_vbasic(ostream& os, string const& fname) +{ + ifstream is(fname, ios::binary); + + if(!is) + { + cerr << "Could not open input file: " << fname << '\n'; + return; + } + + // no whitespace skipping on the stream + noskipws(is); //is.unsetf(ios::skipws); + + string unit; + copy(istream_iterator(is), istream_iterator(), + back_inserter(unit)); + + test_vb6_unit(os, unit); +} + +void test_vbasic(ostream& os) +{ + string unit = R"vb(Option Explicit +Dim str As String + +Sub foo(a As Integer, b As Long) +)vb"; + + test_vb6_unit(os, unit); +} + +int main() +{ + cout << vb6_grammar::getParserInfo() << '\n'; + + vb6_test1(cout); + vb6_test_statements(cout); + vb6_test2(cout); + + test_vbasic(cout, "data/test.bas"); + test_vbasic(cout, "data/long_source.bas"); + test_vbasic(cout); + + //test_gosub(cout); +} \ No newline at end of file diff --git a/src/vb6_parser_operators.hpp b/src/vb6_parser_operators.hpp new file mode 100644 index 0000000..937b2ea --- /dev/null +++ b/src/vb6_parser_operators.hpp @@ -0,0 +1,75 @@ +//: vb6_parser_operators.hpp + +// vb6_parser +// Copyright (c) 2022 Federico Aponte +// This code is licensed under GNU Software License (see LICENSE.txt for details) + +#pragma once + +#include + +namespace vb6_grammar { + + namespace x3 = boost::spirit::x3; + + auto const opDot = x3::lit('.'); + auto const opExclamation = x3::lit('!'); + + // arithmetic operators + auto const opExp = x3::lit('^'); + auto const opMult = x3::lit('*'); + auto const opDiv = x3::lit('/'); + auto const opDivint = x3::lit('\\'); + auto const opMod = x3::no_case[x3::lit("Mod")]; + auto const opPlus = x3::lit('+'); + auto const opMinus = x3::lit('-'); + auto const opAmp = x3::lit('&'); + + // relational operators + auto const opLess = x3::lit('<'); + auto const opGreater = x3::lit('>'); + auto const opLessEqual = x3::lit("<="); + auto const opGreaterEqual = x3::lit(">="); + auto const opEqual = x3::lit('='); + auto const opNotEqual = x3::lit("<>"); + + // boolean and logical operators + auto const opNot = x3::no_case[x3::lit("Not")]; + auto const opAnd = x3::no_case[x3::lit("And")]; + auto const opOr = x3::no_case[x3::lit("Or")]; + auto const opXor = x3::no_case[x3::lit("Xor")]; + auto const opEqv = x3::no_case[x3::lit("Eqv")]; // equivalence: (a IMP b) AND (b IMP a) + auto const opImp = x3::no_case[x3::lit("Imp")]; // material implication: NOT a OR b + auto const opIs = x3::no_case[x3::lit("Is")]; + auto const opLike = x3::no_case[x3::lit("Like")]; + + /* + # Arithmetic Operator Precedence Order + ^ + - (unary negation) + *, / + \ + Mod + +, - (binary addition/subtraction) + & + # Comparison Operator Precedence Order + = + <> + < + > + <= + >= + Like, Is + # Logical Operator Precedence Order + Not + And + Or + Xor + Eqv + Imp + # Source + Sams + Teach Yourself Visual Basic 6 in 24 Hours + Appendix A: Operator Precedence + */ +} \ No newline at end of file diff --git a/src/vb6_parser_statements.cpp b/src/vb6_parser_statements.cpp new file mode 100644 index 0000000..0f8aae7 --- /dev/null +++ b/src/vb6_parser_statements.cpp @@ -0,0 +1,50 @@ +//: vb6_parser_statements.cpp + +// vb6_parser +// Copyright (c) 2022 Federico Aponte +// This code is licensed under GNU Software License (see LICENSE.txt for details) + +#include "vb6_config.hpp" +#include "vb6_parser_statements_def.hpp" + +namespace vb6_grammar { + +/* +template bool parse_rule( + ????_type rule_ + , iterator_type& first, iterator_type const& last + , context_type const& context, ????_type::attribute_type&); +*/ +BOOST_SPIRIT_INSTANTIATE(statements::singleStmt_type, iterator_type, context_type) +BOOST_SPIRIT_INSTANTIATE(statements::statement_block_type, iterator_type, context_type) +BOOST_SPIRIT_INSTANTIATE(statements::assignmentStmt_type, iterator_type, context_type) +BOOST_SPIRIT_INSTANTIATE(statements::localvardeclStmt_type, iterator_type, context_type) +BOOST_SPIRIT_INSTANTIATE(statements::redimStmt_type, iterator_type, context_type) +BOOST_SPIRIT_INSTANTIATE(statements::exitStmt_type, iterator_type, context_type) +BOOST_SPIRIT_INSTANTIATE(statements::gotoStmt_type, iterator_type, context_type) +BOOST_SPIRIT_INSTANTIATE(statements::onerrorStmt_type, iterator_type, context_type) +BOOST_SPIRIT_INSTANTIATE(statements::resumeStmt_type, iterator_type, context_type) +BOOST_SPIRIT_INSTANTIATE(statements::labelStmt_type, iterator_type, context_type) +BOOST_SPIRIT_INSTANTIATE(statements::callimplicitStmt_type, iterator_type, context_type) +BOOST_SPIRIT_INSTANTIATE(statements::callexplicitStmt_type, iterator_type, context_type) +BOOST_SPIRIT_INSTANTIATE(statements::raiseeventStmt_type, iterator_type, context_type) + +// compound statements +BOOST_SPIRIT_INSTANTIATE(statements::whileStmt_type, iterator_type, context_type) +BOOST_SPIRIT_INSTANTIATE(statements::doStmt_type, iterator_type, context_type) +BOOST_SPIRIT_INSTANTIATE(statements::dowhileStmt_type, iterator_type, context_type) +BOOST_SPIRIT_INSTANTIATE(statements::loopwhileStmt_type, iterator_type, context_type) +BOOST_SPIRIT_INSTANTIATE(statements::dountilStmt_type, iterator_type, context_type) +BOOST_SPIRIT_INSTANTIATE(statements::loopuntilStmt_type, iterator_type, context_type) +BOOST_SPIRIT_INSTANTIATE(statements::forStmt_type, iterator_type, context_type) +BOOST_SPIRIT_INSTANTIATE(statements::foreachStmt_type, iterator_type, context_type) +BOOST_SPIRIT_INSTANTIATE(statements::ifelseStmt_type, iterator_type, context_type) +BOOST_SPIRIT_INSTANTIATE(statements::withStmt_type, iterator_type, context_type) +BOOST_SPIRIT_INSTANTIATE(statements::selectStmt_type, iterator_type, context_type) + +BOOST_SPIRIT_INSTANTIATE(statements::ifBranch_type, iterator_type, context_type) +BOOST_SPIRIT_INSTANTIATE(statements::elsifBranch_type, iterator_type, context_type) +BOOST_SPIRIT_INSTANTIATE(statements::elseBranch_type, iterator_type, context_type) +BOOST_SPIRIT_INSTANTIATE(statements::case_block_type, iterator_type, context_type) + +} \ No newline at end of file diff --git a/src/vb6_parser_statements_def.hpp b/src/vb6_parser_statements_def.hpp new file mode 100644 index 0000000..9e5c183 --- /dev/null +++ b/src/vb6_parser_statements_def.hpp @@ -0,0 +1,337 @@ +//: vb6_parser_statements_def.hpp + +// vb6_parser +// Copyright (c) 2022 Federico Aponte +// This code is licensed under GNU Software License (see LICENSE.txt for details) + +#pragma once + +#include "vb6_parser.hpp" +#include "vb6_ast_adapt.hpp" +#include "vb6_parser_keywords.hpp" +#include "vb6_parser_operators.hpp" + +#include +#include +#include + +// http://boost.2283326.n4.nabble.com/Horrible-compiletimes-and-memory-usage-while-compiling-a-parser-with-X3-td4689104.html + +/* +---- +Function return value +VB: The function's name treated as a variable is assigned the return value of + the function. No explicit return statement needed, though it can be used (Exit Function/Property). +C++: Explicit return value needed. +---- +Macros +---- +Comments +---- +Multi-line statements, continuation symbol _ +---- +On Error GoTo/Resume +---- +Exclamation mark operator +---- +support ParamArray, Array +---- +support AddressOf +---- +Implicit type variables +---- +*/ + +namespace vb6_grammar { + + namespace x3 = boost::spirit::x3; + + // the terminator for every VB6 statement + auto const cmdTermin = x3::eol | ':'; + + // keyword groups + auto const kwgEndIf = kwEnd > kwIf > cmdTermin; + auto const kwgEndWith = kwEnd > kwWith > cmdTermin; + auto const kwgEndSelect = kwEnd > kwSelect > cmdTermin; + + // statements + namespace statements { +#if 0 + // FED ???? there seems to be a limit here + // I cannot define this rule with more than 21 alternatives! + auto const singleStmt_def = lonely_comment // critical to have this as the first element + | empty_line + | assignmentStmt + | localvardeclStmt + | redimStmt + | exitStmt + | gotoStmt + | onerrorStmt + //| resumeStmt + | labelStmt + | callimplicitStmt + | callexplicitStmt + | raiseeventStmt + | whileStmt + | doStmt + | dowhileStmt + | loopwhileStmt + | dountilStmt + | loopuntilStmt + | forStmt + //| foreachStmt + | ifelseStmt + //| selectStmt + | withStmt + ; +#else + auto const singleStmt1 = assignmentStmt + | localvardeclStmt + | redimStmt + | exitStmt + | gotoStmt + | onerrorStmt + | resumeStmt + | labelStmt + | callimplicitStmt + | callexplicitStmt + | raiseeventStmt + ; + auto const singleStmt2 = whileStmt + | doStmt + | dowhileStmt + | loopwhileStmt + | dountilStmt + | loopuntilStmt + | forStmt + | foreachStmt + | ifelseStmt + | selectStmt + | withStmt + ; + auto const singleStmt_def = lonely_comment // critical to have this as the first element + | empty_line + | singleStmt1 + | singleStmt2; +#endif + + auto const statement_block_def = *singleStmt; + + auto const assignmentStmt_def = ( (kwSet >> x3::attr(vb6_ast::assignmentType::set)) + | (kwLet >> x3::attr(vb6_ast::assignmentType::let)) + | (x3::eps >> x3::attr(vb6_ast::assignmentType::na)) + ) >> decorated_variable >> opEqual >> expression >> cmdTermin; + + auto const localvardeclStmt_def = ( kwDim >> x3::attr(vb6_ast::localvardeclType::Dim) + | kwStatic >> x3::attr(vb6_ast::localvardeclType::Static) + ) + >> (single_var_declaration % ',') + >> cmdTermin; + + // TODO redim statements must be able to act on several variables + auto const redimStmt_def = kwReDim >> -(kwPreserve >> x3::attr(true)) + //>> ((decorated_variable > '(' > (expression % ',') > ')') % ',') + >> (decorated_variable > '(' > (expression % ',') > ')') + >> cmdTermin; + + auto const exitStmt_def = kwExit > ( kwSub >> x3::attr(vb6_ast::exit_type::sub) + | kwFunction >> x3::attr(vb6_ast::exit_type::function) + | kwProperty >> x3::attr(vb6_ast::exit_type::property) + | kwDo >> x3::attr(vb6_ast::exit_type::do_) + | kwWhile >> x3::attr(vb6_ast::exit_type::while_) + | kwFor >> x3::attr(vb6_ast::exit_type::for_) + ) + >> cmdTermin; + + auto const gotoLabel = basic_identifier; + + auto const gotoStmt_def = ( (kwGoTo >> x3::attr(vb6_ast::gotoType::goto_v)) + | (kwGoSub >> x3::attr(vb6_ast::gotoType::gosub_v)) + ) > gotoLabel >> cmdTermin; + + auto const onerrorStmt_def = (kwOn > kwError) + >> ( (kwResume > kwNext >> x3::attr(std::string()) >> x3::attr(vb6_ast::onerror_type::resume_next)) + | (kwGoTo >> ('0' >> x3::attr(std::string()) >> x3::attr(vb6_ast::onerror_type::goto_0))) + | (kwGoTo >> ("-1" >> x3::attr(std::string()) >> x3::attr(vb6_ast::onerror_type::goto_neg_1))) + | (kwGoTo >> (gotoLabel >> x3::attr(vb6_ast::onerror_type::goto_label))) + | (kwExit >> (kwSub >> x3::attr(std::string()) >> x3::attr(vb6_ast::onerror_type::exit_sub))) + | (kwExit >> (kwFunction >> x3::attr(std::string()) >> x3::attr(vb6_ast::onerror_type::exit_func))) + | (kwExit >> (kwProperty >> x3::attr(std::string()) >> x3::attr(vb6_ast::onerror_type::exit_property))) + ) >> cmdTermin; + + auto const resumeStmt_def = kwResume + >> ( (kwNext >> x3::attr(0) >> x3::attr(vb6_ast::resume_type::next)) + | ( gotoLabel >> x3::attr(vb6_ast::resume_type::label)) + | ( x3::int_ >> x3::attr(vb6_ast::resume_type::line_nr)) + | (x3::eps >> x3::attr(0) >> x3::attr(vb6_ast::resume_type::implicit)) + ) + >> cmdTermin; + + auto const labelStmt_def = gotoLabel >> x3::lit(':') >> cmdTermin; + + auto const callimplicitStmt_def = sub_identifier >> -(expression % ',') >> x3::attr(false) >> cmdTermin; + auto const callexplicitStmt_def //= kwCall >> functionCall >> x3::attr(true) >> cmdTermin; + //= ( kwCall >> x3::attr() + // | kWRaiseEvent >> x3::attr() + // ) >> sub_identifier + = kwCall >> sub_identifier + >> '(' >> -(expression % ',') >> ')' >> x3::attr(true) >> cmdTermin; + + // raising an event is very similar to calling a subroutine + auto const raiseeventStmt_def = kwRaiseEvent >> event_identifier >> '(' >> -(expression % ',') >> ')' >> cmdTermin; + + // compound statements + + auto const whileStmt_def = kwWhile >> expression >> cmdTermin + >> statement_block + >> kwWend + >> cmdTermin; + + // infinite loop + auto const doStmt_def = kwDo >> cmdTermin + >> statement_block + >> kwLoop + >> cmdTermin; + + auto const dowhileStmt_def = kwDo >> kwWhile >> expression >> cmdTermin + >> statement_block + >> kwLoop + >> cmdTermin; + + auto const loopwhileStmt_def = kwDo >> cmdTermin + >> statement_block + >> kwLoop >> kwWhile >> expression + >> cmdTermin; + + auto const dountilStmt_def = kwDo >> kwUntil >> expression >> cmdTermin + >> statement_block + >> kwLoop + >> cmdTermin; + + auto const loopuntilStmt_def = kwDo >> cmdTermin + >> statement_block + >> kwLoop >> kwUntil >> expression + >> cmdTermin; + + // TODO match the variable after 'Next' with the one of the loop + auto const forStmt_def = kwFor >> decorated_variable >> opEqual + >> expression >> kwTo >> expression + >> -(kwStep >> expression) >> cmdTermin + >> statement_block + >> kwNext >> -x3::omit[decorated_variable] + >> cmdTermin; + + // TODO match the variable after 'Next' with the one of the loop + auto const foreachStmt_def = kwFor >> kwEach >> decorated_variable + >> kwIn >> expression >> cmdTermin + >> statement_block + >> kwNext >> -x3::omit[decorated_variable] + >> cmdTermin; + + auto const ifBranch_def = x3::rule("ifBranch") + = kwIf >> expression + >> kwThen >> cmdTermin + >> statement_block; + auto const elsifBranch_def = x3::rule("elsifBranch") + = kwElseIf >> expression + >> kwThen >> cmdTermin + >> statement_block; + auto const elseBranch_def = x3::rule("elseBranch") + = kwElse >> cmdTermin + >> statement_block; + + //auto const ifelseifBranches = x3::rule>() + // = ifBranch >> (*elsifBranch); + + auto const ifelseStmt_def = //ifelseifBranches + ifBranch + >> (*elsifBranch) + >> (-elseBranch) + >> kwgEndIf; + + // TODO with-statement should also take a function returning a reference to an object + auto const withStmt_def = kwWith >> decorated_variable + //>> -(kwNew >> type_identifier) // FED ???? + >> cmdTermin + >> statement_block + >> kwgEndWith; + + // 'Is' is legal only when used with the 6 relational operators + // it must appear, alone, on the left side of the operator with + // an expression on the right side + auto const case_relational_expr = kwIs >> ( (opLess >> x3::attr(vb6_ast::rel_operator_type::less)) + | (opGreater >> x3::attr(vb6_ast::rel_operator_type::greater)) + | (opLessEqual >> x3::attr(vb6_ast::rel_operator_type::less_equal)) + | (opGreaterEqual >> x3::attr(vb6_ast::rel_operator_type::greater_equal)) + | (opNotEqual >> x3::attr(vb6_ast::rel_operator_type::not_equal)) + | (opEqual >> x3::attr(vb6_ast::rel_operator_type::equal)) + ) + >> expression; + + // TODO + /* + Select Case frm.Type + Case 0: + Print 0 + Case 1: + Print 1 + Case 1 To 4, 7 To 9, 11, 13, Is > MaxNumber + Case Else: + Print "Default" + End Select + */ + auto const case_block_def = kwCase >> expression >> cmdTermin + >> statement_block; + auto const case_block2_def = kwCase >> ( kwElse + | (( (expression >> -(kwTo >> expression)) + | case_relational_expr + ) % ',') + ) + >> cmdTermin + >> statement_block; + + // TODO select-statement + auto const selectStmt_def = (kwSelect > kwCase) + >> expression >> cmdTermin + >> (*case_block) + >> kwgEndSelect; + } // namespace statements + + BOOST_SPIRIT_DEFINE( + statements::ifBranch + , statements::elsifBranch + , statements::elseBranch + , statements::case_block + ) + + BOOST_SPIRIT_DEFINE( + statements::singleStmt + , statements::statement_block + , statements::assignmentStmt + , statements::localvardeclStmt + , statements::redimStmt + , statements::exitStmt + , statements::gotoStmt + , statements::onerrorStmt + , statements::resumeStmt + , statements::labelStmt + , statements::callimplicitStmt + , statements::callexplicitStmt + , statements::raiseeventStmt + ) + + // compound statements + BOOST_SPIRIT_DEFINE( + statements::whileStmt + , statements::doStmt + , statements::dowhileStmt + , statements::loopwhileStmt + , statements::dountilStmt + , statements::loopuntilStmt + , statements::forStmt + , statements::foreachStmt + , statements::ifelseStmt + , statements::withStmt + , statements::selectStmt + ) +} \ No newline at end of file diff --git a/src/vb6_parser_statements_test.cpp b/src/vb6_parser_statements_test.cpp new file mode 100644 index 0000000..7baadd0 --- /dev/null +++ b/src/vb6_parser_statements_test.cpp @@ -0,0 +1,508 @@ +//: vb6_parser_statements_test.cpp + +// vb6_parser +// Copyright (c) 2022 Federico Aponte +// This code is licensed under GNU Software License (see LICENSE.txt for details) + +//#define BOOST_SPIRIT_X3_DEBUG + +#include "test_grammar_helper.hpp" +#include "vb6_parser.hpp" + +#include + +#include +#include +#include + +using namespace std; +namespace x3 = boost::spirit::x3; + +GTEST_TEST(vb6_parser_statements, Assignment_0) +{ + vb6_ast::statements::assignStmt st; + test_grammar("Set var1 = \" ciao\"\r\n", vb6_grammar::statements::assignmentStmt, st); + + EXPECT_EQ(st.type, vb6_ast::assignmentType::set); + EXPECT_EQ(st.var.var, "var1"); + EXPECT_FALSE(st.var.ctx.leading_dot); + EXPECT_TRUE(st.var.ctx.elements.empty()); + EXPECT_EQ(boost::get( + boost::get(st.rhs.get()).get() + ), " ciao"); +} + +GTEST_TEST(vb6_parser_statements, Assignment_1) +{ + vb6_ast::statements::assignStmt st; + test_grammar("Let var2 = 54.7\r\n", vb6_grammar::statements::assignmentStmt, st); + + EXPECT_EQ(st.type, vb6_ast::assignmentType::let); + EXPECT_EQ(st.var.var, "var2"); + EXPECT_FALSE(st.var.ctx.leading_dot); + EXPECT_TRUE(st.var.ctx.elements.empty()); + EXPECT_EQ(boost::get( + boost::get(st.rhs.get()).get() + ), 54.7f); +} + +GTEST_TEST(vb6_parser_statements, Assignment_2) +{ + vb6_ast::statements::assignStmt st; + test_grammar("var3 = Func(\"descr\", 54.7)\r\n", vb6_grammar::statements::assignmentStmt, st); + + EXPECT_EQ(st.type, vb6_ast::assignmentType::na); + EXPECT_EQ(st.var.var, "var3"); + EXPECT_FALSE(st.var.ctx.leading_dot); + EXPECT_TRUE(st.var.ctx.elements.empty()); + auto& func = boost::get>( + st.rhs.get() + ).get(); + EXPECT_EQ(func.func_name, "Func"); +} + +GTEST_TEST(vb6_parser_statements, Assignment_3) +{ + vb6_ast::statements::assignStmt st; + test_grammar("str = CStr(i)\r\n", vb6_grammar::statements::assignmentStmt, st); + + EXPECT_EQ(st.type, vb6_ast::assignmentType::na); + EXPECT_EQ(st.var.var, "str"); + EXPECT_FALSE(st.var.ctx.leading_dot); + EXPECT_TRUE(st.var.ctx.elements.empty()); +} + +GTEST_TEST(vb6_parser_statements, LocalVarDecl_1) +{ + vb6_ast::statements::localVarDeclStmt st; + test_grammar("Dim var As New Form\r\n", vb6_grammar::statements::localvardeclStmt, st); + + EXPECT_EQ(st.type, vb6_ast::localvardeclType::Dim); + + ASSERT_EQ(st.vars.size(), 1); + + EXPECT_TRUE(st.vars[0].construct); + EXPECT_EQ(st.vars[0].name, "var"); + ASSERT_TRUE(st.vars[0].type); + EXPECT_EQ(*st.vars[0].type, "Form"); +} + +GTEST_TEST(vb6_parser_statements, LocalVarDecl_2) +{ + vb6_ast::statements::localVarDeclStmt st; + test_grammar("Static var1 As String, var2 As Integer\r\n", vb6_grammar::statements::localvardeclStmt, st); + + EXPECT_EQ(st.type, vb6_ast::localvardeclType::Static); + + ASSERT_EQ(st.vars.size(), 2); + + EXPECT_FALSE(st.vars[0].construct); + EXPECT_EQ(st.vars[0].name, "var1"); + ASSERT_TRUE(st.vars[0].type); + EXPECT_EQ(*st.vars[0].type, "String"); + + EXPECT_FALSE(st.vars[1].construct); + EXPECT_EQ(st.vars[1].name, "var2"); + ASSERT_TRUE(st.vars[1].type); + EXPECT_EQ(*st.vars[1].type, "Integer"); +} + +GTEST_TEST(vb6_parser_statements, ReDim) +{ + vb6_ast::statements::redimStmt st; + test_grammar("ReDim var1(15)\r\n", vb6_grammar::statements::redimStmt, st); + //test_grammar("ReDim var1(2*L + 1)\r\n", vb6_grammar::statements::redimStmt, st); + EXPECT_FALSE(st.preserve); + EXPECT_EQ(st.var.var, "var1"); +} + +GTEST_TEST(vb6_parser_statements, Exit) +{ + vb6_ast::statements::exitStmt st; + test_grammar("Exit Function\r\n", vb6_grammar::statements::exitStmt, st); + + EXPECT_EQ(st.type, vb6_ast::exit_type::function); +} + +GTEST_TEST(vb6_parser_statements, GoTo) +{ + vb6_ast::statements::gotoStmt st; + test_grammar("GoSub label1\r\n", vb6_grammar::statements::gotoStmt, st); + + EXPECT_EQ(st.type, vb6_ast::gotoType::gosub_v); + EXPECT_EQ(st.label, "label1"); +} + +GTEST_TEST(vb6_parser_statements, OnError) +{ + vb6_ast::statements::onerrorStmt st; + test_grammar("On Error Resume Next\r\n", vb6_grammar::statements::onerrorStmt, st); + + EXPECT_EQ(st.type, vb6_ast::onerror_type::resume_next); + EXPECT_TRUE(st.label.empty()); +} + +GTEST_TEST(vb6_parser_statements, Resume) +{ + vb6_ast::statements::resumeStmt st; + + test_grammar("Resume\r\n", vb6_grammar::statements::resumeStmt, st); + EXPECT_EQ(st.type, vb6_ast::resume_type::implicit); + + test_grammar("Resume Next\r\n", vb6_grammar::statements::resumeStmt, st); + EXPECT_EQ(st.type, vb6_ast::resume_type::next); + + test_grammar("Resume 0\r\n", vb6_grammar::statements::resumeStmt, st); + EXPECT_EQ(st.type, vb6_ast::resume_type::line_nr); + EXPECT_EQ(boost::get(st.label_or_line_nr), 0); + + test_grammar("Resume resume_point\r\n", vb6_grammar::statements::resumeStmt, st); + EXPECT_EQ(st.type, vb6_ast::resume_type::label); + EXPECT_EQ(boost::get(st.label_or_line_nr), "resume_point"); +} + +GTEST_TEST(vb6_parser_statements, Label) +{ + vb6_ast::statements::labelStmt st; + test_grammar("label1:\r\n", vb6_grammar::statements::labelStmt, st); + + EXPECT_EQ(st.label, "label1"); +} + +GTEST_TEST(vb6_parser_statements, Call_explicit) +{ + vb6_ast::statements::callStmt st; + test_grammar("Call Foo(13)\r\n", vb6_grammar::statements::callexplicitStmt, st); + + EXPECT_TRUE(st.explicit_call); + EXPECT_EQ(st.sub_name, "Foo"); + EXPECT_EQ(st.params.size(), 1); + + test_grammar_false("Call Foo 13\r\n", vb6_grammar::statements::callexplicitStmt, st); +} + +GTEST_TEST(vb6_parser_statements, Call_implicit) +{ + vb6_ast::statements::callStmt st; + test_grammar("Foo \"Sea\", Nothing, False\r\n", vb6_grammar::statements::callimplicitStmt, st); + + EXPECT_FALSE(st.explicit_call); + EXPECT_EQ(st.sub_name, "Foo"); + EXPECT_EQ(st.params.size(), 3); +} + +GTEST_TEST(vb6_parser_statements, RaiseEvent) +{ + vb6_ast::statements::raiseeventStmt st; + test_grammar("RaiseEvent OnChange(\"hi!\")\r\n", + vb6_grammar::statements::raiseeventStmt, st); +} + +GTEST_TEST(vb6_parser_statements, statement_block) +{ + vb6_ast::statements::statement_block st; + test_grammar( + R"vb(Set var1 = " ciao" + Let var2 = 54.7 + var3 = Func("descr", 54.7) + str = CStr(i) + Dim var As New Form + Static var1 As String, var2 As Integer + ReDim var1(15) + Exit Function + GoSub label1 + On Error Resume Next + Resume Next + label1: + Call Foo(13) + Foo "Sea", Nothing, False + RaiseEvent OnChange("hi!") + )vb", vb6_grammar::statements::statement_block, st); + + EXPECT_EQ(st.size(), 15); + EXPECT_EQ(st[0].get().type(), typeid(vb6_ast::statements::assignStmt)); + EXPECT_EQ(st[1].get().type(), typeid(vb6_ast::statements::assignStmt)); + EXPECT_EQ(st[2].get().type(), typeid(vb6_ast::statements::assignStmt)); + EXPECT_EQ(st[3].get().type(), typeid(vb6_ast::statements::assignStmt)); + EXPECT_EQ(st[4].get().type(), typeid(vb6_ast::statements::localVarDeclStmt)); + EXPECT_EQ(st[5].get().type(), typeid(vb6_ast::statements::localVarDeclStmt)); + EXPECT_EQ(st[6].get().type(), typeid(vb6_ast::statements::redimStmt)); + EXPECT_EQ(st[7].get().type(), typeid(vb6_ast::statements::exitStmt)); + EXPECT_EQ(st[8].get().type(), typeid(vb6_ast::statements::gotoStmt)); + EXPECT_EQ(st[9].get().type(), typeid(vb6_ast::statements::onerrorStmt)); + EXPECT_EQ(st[10].get().type(), typeid(vb6_ast::statements::resumeStmt)); + EXPECT_EQ(st[11].get().type(), typeid(vb6_ast::statements::labelStmt)); + EXPECT_EQ(st[12].get().type(), typeid(vb6_ast::statements::callStmt)); + EXPECT_EQ(st[13].get().type(), typeid(vb6_ast::statements::callStmt)); + EXPECT_EQ(st[14].get().type(), typeid(vb6_ast::statements::raiseeventStmt)); +} + +GTEST_TEST(vb6_parser_compound_statements, With) +{ + vb6_ast::statements::withStmt st; + test_grammar( + R"vb(With obj + 'Call Module1.foo(True, .Name) + Call foo(True, .Name) + End With + )vb", vb6_grammar::statements::withStmt, st); + + EXPECT_FALSE(st.with_variable.ctx.leading_dot); + EXPECT_TRUE(st.with_variable.ctx.elements.empty()); + EXPECT_EQ(st.with_variable.var, "obj"); + EXPECT_EQ(st.block.size(), 2); +} + +GTEST_TEST(vb6_parser_compound_statements, While) +{ + vb6_ast::statements::whileStmt st; + test_grammar( + R"vb(While cond(34, i) + Print str + Wend + )vb", + vb6_grammar::statements::whileStmt, st); + auto& tmp = boost::get>(st.condition.get()).get(); + EXPECT_EQ(tmp.func_name, "cond"); + EXPECT_EQ(tmp.params.size(), 2); + EXPECT_EQ(st.block.size(), 1); +} + +GTEST_TEST(vb6_parser_compound_statements, Do) +{ + vb6_ast::statements::doStmt st; + test_grammar( + R"vb(Do + Print str + Loop + )vb", + vb6_grammar::statements::doStmt, st); + EXPECT_EQ(st.block.size(), 1); +} + +GTEST_TEST(vb6_parser_compound_statements, DoWhile) +{ + vb6_ast::statements::dowhileStmt st; + test_grammar( + R"vb(Do While cond(34, i) + Print str + Loop + )vb", + vb6_grammar::statements::dowhileStmt, st); + auto& tmp = boost::get>(st.condition.get()).get(); + EXPECT_EQ(tmp.func_name, "cond"); + EXPECT_EQ(tmp.params.size(), 2); + EXPECT_EQ(st.block.size(), 1); +} + +GTEST_TEST(vb6_parser_compound_statements, LoopWhile) +{ + vb6_ast::statements::loopwhileStmt st; + test_grammar( + R"vb(Do + Print str + Loop While cond(34, i) + )vb", + vb6_grammar::statements::loopwhileStmt, st); + auto& tmp = boost::get>(st.condition.get()).get(); + EXPECT_EQ(tmp.func_name, "cond"); + EXPECT_EQ(tmp.params.size(), 2); + EXPECT_EQ(st.block.size(), 1); +} + +GTEST_TEST(vb6_parser_compound_statements, DoUntil) +{ + vb6_ast::statements::dountilStmt st; + test_grammar( + R"vb(Do Until cond(34, i) + Print str + Loop + )vb", + vb6_grammar::statements::dountilStmt, st); + auto& tmp = boost::get>(st.condition.get()).get(); + EXPECT_EQ(tmp.func_name, "cond"); + EXPECT_EQ(tmp.params.size(), 2); + EXPECT_EQ(st.block.size(), 1); +} + +GTEST_TEST(vb6_parser_compound_statements, LoopUntil) +{ + vb6_ast::statements::loopuntilStmt st; + test_grammar( + R"vb(Do + Print str + Loop Until cond(34, i) + )vb", + vb6_grammar::statements::loopuntilStmt, st); + auto& tmp = boost::get>(st.condition.get()).get(); + EXPECT_EQ(tmp.func_name, "cond"); + EXPECT_EQ(tmp.params.size(), 2); + EXPECT_EQ(st.block.size(), 1); +} + +GTEST_TEST(vb6_parser_compound_statements, For) +{ + vb6_ast::statements::forStmt st; + test_grammar( + R"vb(For i = 1 To 100 Step 2 + Dim str As String + str = CStr(i) + Print str + Next i + )vb", + vb6_grammar::statements::forStmt, st); + EXPECT_EQ(st.for_variable.var, "i"); + EXPECT_FALSE(st.for_variable.ctx.leading_dot); + EXPECT_TRUE(st.for_variable.ctx.elements.empty()); + + auto& tmp1 = boost::get(st.from.get()).get(); + auto& tmp2 = boost::get(st.to.get()).get(); + auto& tmp3 = boost::get(st.step.get()).get(); + + EXPECT_EQ(boost::get(tmp1).val, 1); + EXPECT_EQ(boost::get(tmp2).val, 100); + EXPECT_EQ(boost::get(tmp3).val, 2); + + EXPECT_EQ(st.block.size(), 3); +} + +GTEST_TEST(vb6_parser_compound_statements, ForEach) +{ + vb6_ast::statements::foreachStmt st; + test_grammar( + R"vb(For Each el In Items + Call Print(el) + Next el + )vb", + vb6_grammar::statements::foreachStmt, st); + EXPECT_EQ(st.for_variable.var, "el"); + EXPECT_FALSE(st.for_variable.ctx.leading_dot); + EXPECT_TRUE(st.for_variable.ctx.elements.empty()); + + auto& tmp1 = boost::get(st.container.get()); + + EXPECT_TRUE(tmp1.ctx.elements.empty()); + EXPECT_EQ(tmp1.var, "Items"); + + EXPECT_EQ(st.block.size(), 1); +} + +GTEST_TEST(vb6_parser_compound_statements, IfElse_substatements) +{ + vb6_ast::statements::if_branch ast1; + test_grammar( + R"vb(If cond1 Then + Dim str As String + str = CStr(i) + Print str + )vb", + vb6_grammar::statements::ifBranch, ast1); + + auto& tmp1 = boost::get(ast1.condition.get()); + + EXPECT_EQ(tmp1.var, "cond1"); + EXPECT_EQ(ast1.block.size(), 3); + + vb6_ast::statements::if_branch ast2; + test_grammar( + R"vb(ElseIf cond2 Then + Dim str As String + str = CStr(i) + Print str + )vb", + vb6_grammar::statements::elsifBranch, ast2); + + auto& tmp2 = boost::get(ast2.condition.get()); + + EXPECT_EQ(tmp2.var, "cond2"); + EXPECT_EQ(ast2.block.size(), 3); + + vb6_ast::statements::statement_block ast3; + test_grammar( + R"vb(Else + Print str + 'End If + )vb", + vb6_grammar::statements::elseBranch, ast3); + + EXPECT_EQ(ast3.size(), 2); +} + +GTEST_TEST(vb6_parser_compound_statements, IfElse) +{ + vb6_ast::statements::ifelseStmt ast; + test_grammar( + R"vb(If cond1 Then + Print "1" + ElseIf cond2 Then + Print "2" + ElseIf cond3 Then + Print "3" + Else + Print "4" + End If + )vb", + vb6_grammar::statements::ifelseStmt, ast); + +#ifdef SIMPLE_IF_STATEMENT + EXPECT_EQ(ast.first_branch.block.size(), 5); + auto& tmp1 = boost::get(ast.first_branch.condition.get()); + EXPECT_EQ(tmp1.var, "cond1"); +#else + EXPECT_EQ(ast.first_branch.block.size(), 1); + auto& tmp0 = boost::get(ast.first_branch.condition.get()); + EXPECT_EQ(tmp0.var, "cond1"); + + ASSERT_EQ(ast.if_branches.size(), 2); + + EXPECT_EQ(ast.if_branches[0].block.size(), 1); + auto& tmp1 = boost::get(ast.if_branches[0].condition.get()); + EXPECT_EQ(tmp1.var, "cond2"); + + EXPECT_EQ(ast.if_branches[1].block.size(), 1); + auto& tmp2 = boost::get(ast.if_branches[1].condition.get()); + EXPECT_EQ(tmp2.var, "cond3"); +#endif + + ASSERT_TRUE(ast.else_branch.has_value()); + EXPECT_EQ(ast.else_branch.get().size(), 1); +} + +GTEST_TEST(vb6_parser_compound_statements, Select) +{ + vb6_ast::statements::selectStmt st; + test_grammar( + R"vb(Select Case frm.Type + Case 0 + Dim str As String + Call Print(0) + Case 1 + ' case 1 + Dim str As String + 'Case 1 To 4, 7 To 9, 11, 13, Is > MaxNumber + ' Print "Difficult" + 'Case Else + ' Print "Default" + End Select + )vb", vb6_grammar::statements::selectStmt, st); + + ASSERT_EQ(st.condition.get().type(), typeid(vb6_ast::decorated_variable)); + EXPECT_EQ(boost::get(st.condition.get()).var, "Type"); + EXPECT_EQ(st.blocks.size(), 2); + + ASSERT_EQ(st.blocks[0].case_expr.get().type(), typeid(vb6_ast::const_expr)); + EXPECT_EQ(boost::get( + boost::get(st.blocks[0].case_expr.get()).get() + ).val, 0); + ASSERT_EQ(st.blocks[0].block.size(), 2); + EXPECT_EQ(st.blocks[0].block[0].get().type(), typeid(vb6_ast::statements::localVarDeclStmt)); + EXPECT_EQ(st.blocks[0].block[1].get().type(), typeid(vb6_ast::statements::callStmt)); + + ASSERT_EQ(st.blocks[1].case_expr.get().type(), typeid(vb6_ast::const_expr)); + EXPECT_EQ(boost::get( + boost::get(st.blocks[1].case_expr.get()).get() + ).val, 1); + ASSERT_EQ(st.blocks[1].block.size(), 6); + EXPECT_EQ(st.blocks[1].block[0].get().type(), typeid(vb6_ast::lonely_comment)); + EXPECT_EQ(st.blocks[1].block[1].get().type(), typeid(vb6_ast::statements::localVarDeclStmt)); +} diff --git a/src/vb6_parser_test.cpp b/src/vb6_parser_test.cpp new file mode 100644 index 0000000..c8b78bc --- /dev/null +++ b/src/vb6_parser_test.cpp @@ -0,0 +1,813 @@ +//: vb6_parser_test.cpp + +// vb6_parser +// Copyright (c) 2022 Federico Aponte +// This code is licensed under GNU Software License (see LICENSE.txt for details) + +//#define BOOST_SPIRIT_X3_DEBUG + +#include "test_grammar_helper.hpp" +#include "vb6_parser.hpp" +#include "vb6_ast_printer.hpp" + +#include +#include + +#include +#include +#include + +using namespace std; +namespace x3 = boost::spirit::x3; + +GTEST_TEST(vb6_parser_simple, lonely_comment) +{ + vector ast; + test_grammar( + "' This is comment line 1\r\n' Comment line 2\r\n", + *vb6_grammar::lonely_comment, ast); + + ASSERT_EQ(ast.size(), 2); + + EXPECT_EQ(ast[0].content, " This is comment line 1"); + EXPECT_EQ(ast[1].content, " Comment line 2"); +} + +GTEST_TEST(vb6_parser_simple, empty_lines) +{ + vector< + boost::variant + > ast; + // critical to have lonely_comment first in the parsing rule + auto const G = *(vb6_grammar::lonely_comment | vb6_grammar::empty_line); + auto str = "' comment1\r\n" + "\r\n" + "' comment2\r\n"; + test_grammar(str, G, ast); + + ASSERT_EQ(ast.size(), 3); + + EXPECT_EQ(boost::get(ast[0]).content, " comment1"); + EXPECT_NO_THROW(boost::get(ast[1])); + EXPECT_EQ(boost::get(ast[2]).content, " comment2"); +} + +GTEST_TEST(vb6_parser_simple, quoted_string) +{ + string str; + test_grammar("\"Quoted string.\"", vb6_grammar::quoted_string, str); + EXPECT_EQ(str, "Quoted string."); +} + +GTEST_TEST(vb6_parser_simple, basic_identifier) +{ + string id; + test_grammar("iden_tifier", vb6_grammar::basic_identifier, id); + EXPECT_EQ(id, "iden_tifier"); +} + +GTEST_TEST(vb6_parser_simple, var_identifier) +{ + string var; + test_grammar("g_logger", vb6_grammar::var_identifier, var); + EXPECT_EQ(var, "g_logger"); + + var.clear(); + test_grammar("forth ", vb6_grammar::sub_identifier, var); + EXPECT_EQ(var, "forth"); + + var.clear(); + test_grammar("subroutine ", vb6_grammar::sub_identifier, var); + EXPECT_EQ(var, "subroutine"); + + /* + string code = "sub "; + string_view code_sv = code; + auto it1 = cbegin(code_sv); + auto const it2 = cend(code_sv); + ASSERT_FALSE(boost::spirit::x3::phrase_parse(it1, it2, vb6_grammar::sub_identifier, vb6_grammar::skip, var)); + EXPECT_EQ(it1, begin(code_sv)); + */ +} + +GTEST_TEST(vb6_parser_simple, type_identifier) +{ + string type; + test_grammar("Long", vb6_grammar::type_identifier, type); + EXPECT_EQ(type, "Long"); +} + +GTEST_TEST(vb6_parser_simple, complex_type_identifier) +{ + string type; + test_grammar("VB.Form", vb6_grammar::complex_type_identifier, type); + EXPECT_EQ(type, "VB.Form"); +} + +GTEST_TEST(vb6_parser_simple, const_expression_non_numeric) +{ + vb6_ast::const_expr ast; + string str; + + str = "\"una stringa\""s; + test_grammar(str, vb6_grammar::const_expression, ast); + EXPECT_EQ(boost::get(ast.get()), "una stringa"); + + str = "True"s; + test_grammar(str, vb6_grammar::const_expression, ast); + EXPECT_EQ(boost::get(ast.get()), true); + + str = "Nothing"s; + test_grammar(str, vb6_grammar::const_expression, ast); + EXPECT_NO_THROW(boost::get(ast.get())); +} + +GTEST_TEST(vb6_parser_simple, const_expression_integers) +{ + vb6_ast::const_expr ast; + string str; + + str = "1234%"s; + test_grammar(str, vb6_grammar::const_expression, ast); + EXPECT_EQ(boost::get(ast.get()).val, 1234); + + str = "1234&"s; + test_grammar(str, vb6_grammar::const_expression, ast); + EXPECT_EQ(boost::get(ast.get()).val, 1234); + + str = "&Hcafedead&"s; + test_grammar(str, vb6_grammar::const_expression, ast); + EXPECT_EQ(boost::get(ast.get()).val, 0xcafedead); + + str = "&01234&"s; + test_grammar(str, vb6_grammar::const_expression, ast); + EXPECT_EQ(boost::get(ast.get()).val, 01234); + + str = "1234"s; + test_grammar(str, vb6_grammar::const_expression, ast); + EXPECT_EQ(boost::get(ast.get()).val, 1234); +} + +GTEST_TEST(vb6_parser_simple, const_expression_floats) +{ + vb6_ast::const_expr ast; + string str; + + str = "1234!"s; + test_grammar(str, vb6_grammar::const_expression, ast); + EXPECT_EQ(boost::get(ast.get()), 1234.0f); + + str = "1234#"s; + test_grammar(str, vb6_grammar::const_expression, ast); + EXPECT_EQ(boost::get(ast.get()), 1234.0); + + str = "2.8"s; + test_grammar(str, vb6_grammar::const_expression, ast); + EXPECT_EQ(boost::get(ast.get()), 2.8f); +} + +GTEST_TEST(vb6_parser_simple, sample_expression) +{ + vb6_ast::expression ast; + test_grammar("foo1(foo2(3, M.x_coord), True)", vb6_grammar::expression, ast); + EXPECT_EQ(ast.get().type(), typeid(x3::forward_ast)); + EXPECT_EQ(boost::get>(ast.get()).get().func_name, "foo1"); +} + +GTEST_TEST(vb6_parser_simple, single_var_declaration) +{ + vb6_ast::variable P1; + test_grammar("g_logger As Long", vb6_grammar::single_var_declaration, P1); + EXPECT_EQ(P1.name, "g_logger"); + EXPECT_FALSE(P1.construct); + EXPECT_TRUE(P1.type); + if(P1.type) + { + EXPECT_EQ(*P1.type, "Long"); + } + + vb6_ast::variable P2; + test_grammar("name As New String", vb6_grammar::single_var_declaration, P2); + EXPECT_EQ(P2.name, "name"); + EXPECT_TRUE(P2.construct); + EXPECT_TRUE(P2.type); + if(P2.type) + { + EXPECT_EQ(*P2.type, "String"); + } +} + +GTEST_TEST(vb6_parser_tests, record_declaration) +{ + vb6_ast::record rec; + test_grammar("Type PatRecord\r\n name As String\r\n age As Integer\r\nEnd Type\r\n", + vb6_grammar::record_declaration, rec); + + EXPECT_EQ(rec.name, "PatRecord"); + EXPECT_EQ(rec.at, vb6_ast::access_type::na); + ASSERT_EQ(rec.members.size(), 2); + + EXPECT_EQ(rec.members[0].name, "name"); + EXPECT_TRUE(rec.members[0].type); + if(rec.members[0].type) + { + EXPECT_EQ(*rec.members[0].type, "String"); + } + EXPECT_FALSE(rec.members[0].construct); + + EXPECT_EQ(rec.members[1].name, "age"); + EXPECT_TRUE(rec.members[1].type); + if(rec.members[1].type) + { + EXPECT_EQ(*rec.members[1].type, "Integer"); + } + EXPECT_FALSE(rec.members[1].construct); +} + +GTEST_TEST(vb6_parser_tests, enum_declaration) +{ + vb6_ast::vb_enum enum1; + test_grammar("Enum PatTypes\r\n inpatient\r\n outpatient\r\nEnd Enum\r\n", + vb6_grammar::enum_declaration, enum1); + + EXPECT_EQ(enum1.name, "PatTypes"); + EXPECT_EQ(enum1.at, vb6_ast::access_type::na); + ASSERT_EQ(enum1.values.size(), 2); + + EXPECT_EQ(enum1.values[0].first, "inpatient"); + EXPECT_FALSE(enum1.values[0].second); + + EXPECT_EQ(enum1.values[1].first, "outpatient"); + EXPECT_FALSE(enum1.values[1].second); +} + +GTEST_TEST(vb6_parser_tests, global_var_declaration) +{ + auto str = "Global g_logger As Long, v1, XRes As New Object, ptr As Module.MyRec, g_active As Boolean\r\n"s; + vb6_ast::global_var_decls vars; + test_grammar(str, vb6_grammar::global_var_declaration, vars); + + EXPECT_EQ(vars.at, vb6_ast::access_type::global); + EXPECT_FALSE(vars.with_events); + ASSERT_EQ(vars.vars.size(), 5); + + EXPECT_EQ(vars.vars[0].name, "g_logger"); + EXPECT_FALSE(vars.vars[0].construct); + EXPECT_TRUE(vars.vars[0].type); + if(vars.vars[0].type) + { + EXPECT_EQ(*vars.vars[0].type, "Long"); + } + + EXPECT_EQ(vars.vars[1].name, "v1"); + EXPECT_FALSE(vars.vars[1].construct); + EXPECT_FALSE(vars.vars[1].type); + + EXPECT_EQ(vars.vars[2].name, "XRes"); + EXPECT_TRUE(vars.vars[2].construct); + EXPECT_TRUE(vars.vars[2].type); + if(vars.vars[2].type) + { + EXPECT_EQ(*vars.vars[2].type, "Object"); + } + + EXPECT_EQ(vars.vars[3].name, "ptr"); + EXPECT_FALSE(vars.vars[3].construct); + EXPECT_TRUE(vars.vars[3].type); + if(vars.vars[3].type) + { + EXPECT_EQ(*vars.vars[3].type, "MyRec"); + } + + EXPECT_EQ(vars.vars[4].name, "g_active"); + EXPECT_FALSE(vars.vars[4].construct); + EXPECT_TRUE(vars.vars[4].type); + if(vars.vars[4].type) + { + EXPECT_EQ(*vars.vars[4].type, "Boolean"); + } +} + +GTEST_TEST(vb6_parser_tests, const_var_declaration1) +{ + auto cstr = "Const e As Single = 2.8, pi As Double = 3.14, u As Integer = -1\r\n"s; + vb6_ast::const_var_stat cvars; + test_grammar(cstr, vb6_grammar::const_var_declaration, cvars); + + ASSERT_EQ(cvars.size(), 3); + + EXPECT_EQ(cvars[0].var.name, "e"); + if(cvars[0].var.type) + { + EXPECT_EQ(*cvars[0].var.type, "Single"); + } + EXPECT_FALSE(cvars[0].var.construct); + EXPECT_EQ(boost::get(cvars[0].value.get()), 2.8f); + + EXPECT_EQ(cvars[1].var.name, "pi"); + if(cvars[1].var.type) + { + EXPECT_EQ(*cvars[1].var.type, "Double"); + } + EXPECT_FALSE(cvars[1].var.construct); + EXPECT_EQ(boost::get(cvars[1].value.get()), 3.14f); + + EXPECT_EQ(cvars[2].var.name, "u"); + if(cvars[2].var.type) + { + EXPECT_EQ(*cvars[2].var.type, "Integer"); + } + EXPECT_FALSE(cvars[2].var.construct); + EXPECT_EQ(boost::get(cvars[2].value.get()).val, -1); +} + +GTEST_TEST(vb6_parser_tests, const_var_declaration2) +{ + vb6_ast::const_var_stat cvars; + test_grammar("Private Const PI As Double = 3.1415\r\n", + vb6_grammar::const_var_declaration, cvars); + + ASSERT_EQ(cvars.size(), 1); + + EXPECT_EQ(cvars[0].var.name, "PI"); + if(cvars[0].var.type) + { + EXPECT_EQ(*cvars[0].var.type, "Double"); + } + EXPECT_FALSE(cvars[0].var.construct); + EXPECT_EQ(boost::get(cvars[0].value.get()), 3.1415f); +} + +GTEST_TEST(vb6_parser_tests, param_decl) +{ + vb6_ast::func_param fp; + test_grammar("Optional ByVal name As String = \"pippo\"", vb6_grammar::param_decl, fp); + + EXPECT_TRUE(fp.isoptional); + EXPECT_TRUE(fp.qualifier); + if(fp.qualifier) + { + EXPECT_EQ(*fp.qualifier, vb6_ast::param_qualifier::byval); + } + EXPECT_EQ(fp.var.name, "name"); + EXPECT_FALSE(fp.var.construct); + EXPECT_TRUE(fp.var.type); + if(fp.var.type) + { + EXPECT_EQ(*fp.var.type, "String"); + } + EXPECT_TRUE(fp.defvalue); + if(fp.defvalue) + { + EXPECT_EQ(boost::get(fp.defvalue.get()), "pippo"); + } +} + +GTEST_TEST(vb6_parser_tests, param_list_decl) +{ + vector fps; + test_grammar("ByVal name As String, ByRef val As Integer", + -(vb6_grammar::param_decl % ','), fps); + + ASSERT_EQ(fps.size(), 2); + + EXPECT_FALSE(fps[0].isoptional); + EXPECT_TRUE(fps[0].qualifier); + if(fps[0].qualifier) + { + EXPECT_EQ(*fps[0].qualifier, vb6_ast::param_qualifier::byval); + } + EXPECT_EQ(fps[0].var.name, "name"); + EXPECT_FALSE(fps[0].var.construct); + EXPECT_TRUE(fps[0].var.type); + if(fps[0].var.type) + { + EXPECT_EQ(*fps[0].var.type, "String"); + } + EXPECT_FALSE(fps[0].defvalue); + + EXPECT_FALSE(fps[1].isoptional); + EXPECT_TRUE(fps[1].qualifier); + if(fps[1].qualifier) + { + EXPECT_EQ(*fps[1].qualifier, vb6_ast::param_qualifier::byref); + } + EXPECT_EQ(fps[1].var.name, "val"); + EXPECT_FALSE(fps[1].var.construct); + EXPECT_TRUE(fps[1].var.type); + if(fps[1].var.type) + { + EXPECT_EQ(*fps[1].var.type, "Integer"); + } + EXPECT_FALSE(fps[1].defvalue); +} + +GTEST_TEST(vb6_parser_tests, event_declaration) +{ + vb6_ast::eventHead event_decl; + test_grammar("Public Event OnChange(ByVal Text As String)\r\n", + vb6_grammar::eventHead, event_decl); + + EXPECT_EQ(event_decl.at, vb6_ast::access_type::public_); + EXPECT_EQ(event_decl.name, "OnChange"); + EXPECT_EQ(event_decl.params.size(), 1); +} + +GTEST_TEST(vb6_parser_tests, function_head) +{ + vb6_ast::functionHead fh; + test_grammar( + "Private Function OneFunc(ByVal name As String, ByRef val As Integer) As Integer\r\n", + vb6_grammar::functionHead, fh); + + EXPECT_EQ(fh.at, vb6_ast::access_type::private_); + EXPECT_EQ(fh.name, "OneFunc"); + EXPECT_EQ(fh.params.size(), 2); + ASSERT_TRUE(fh.return_type); + EXPECT_EQ(*fh.return_type, "Integer"); +} + +GTEST_TEST(vb6_parser_tests, function_head_no_params) +{ + vb6_ast::functionHead fh; + test_grammar( + "Private Function NoParamFunc() As Object\r\n", vb6_grammar::functionHead, fh); + + EXPECT_EQ(fh.at, vb6_ast::access_type::private_); + EXPECT_EQ(fh.name, "NoParamFunc"); + EXPECT_TRUE(fh.params.empty()); + ASSERT_TRUE(fh.return_type); + EXPECT_EQ(*fh.return_type, "Object"); +} + +GTEST_TEST(vb6_parser_tests, subroutine_head) +{ + vb6_ast::subHead sh; + test_grammar( + "Private Sub my_sub(ByRef str As String, ByVal valid As Boolean)\r\n", + vb6_grammar::subHead, sh); + + EXPECT_EQ(sh.at, vb6_ast::access_type::private_); + EXPECT_EQ(sh.name, "my_sub"); + EXPECT_EQ(sh.params.size(), 2); +} + +GTEST_TEST(vb6_parser_tests, subroutine_head2) +{ + auto str = "Private Sub my_sub(ByRef str As String, ByVal valid As Boolean, Optional ByVal flag As Boolean = True)\r\n"s; + vb6_ast::subHead sh; + test_grammar(str, vb6_grammar::subHead, sh); + + EXPECT_EQ(sh.at, vb6_ast::access_type::private_); + EXPECT_EQ(sh.name, "my_sub"); + EXPECT_EQ(sh.params.size(), 3); +} + +GTEST_TEST(vb6_parser_tests, subroutine_head_with_optional_params) +{ + vb6_ast::subHead sh; + test_grammar( + "Private Sub my_sub(ByRef str As String, Optional ByVal valid As Boolean = false)\r\n", + vb6_grammar::subHead, sh); + + EXPECT_EQ(sh.at, vb6_ast::access_type::private_); + EXPECT_EQ(sh.name, "my_sub"); + ASSERT_EQ(sh.params.size(), 2); + + EXPECT_TRUE(sh.params[0].qualifier.has_value()); + if (sh.params[0].qualifier) + EXPECT_EQ(sh.params[0].qualifier.get(), vb6_ast::param_qualifier::byref); + EXPECT_EQ(sh.params[0].var.name, "str"); + if(sh.params[0].var.type) + { + EXPECT_EQ(*sh.params[0].var.type, "String"); + } + EXPECT_FALSE(sh.params[0].var.construct); + EXPECT_FALSE(sh.params[0].isoptional); + EXPECT_FALSE(sh.params[0].defvalue); + + EXPECT_TRUE(sh.params[1].qualifier); + if(sh.params[1].qualifier) + { + EXPECT_EQ(sh.params[1].qualifier.get(), vb6_ast::param_qualifier::byval); + } + EXPECT_EQ(sh.params[1].var.name, "valid"); + if(sh.params[1].var.type) + { + EXPECT_EQ(*sh.params[1].var.type, "Boolean"); + } + EXPECT_FALSE(sh.params[1].var.construct); + EXPECT_TRUE(sh.params[1].isoptional); + EXPECT_TRUE(sh.params[1].defvalue); + if(sh.params[0].defvalue) + { + EXPECT_EQ(boost::get(*sh.params[0].defvalue), false); + } +} + +GTEST_TEST(vb6_parser_tests, property_let_head) +{ + auto str = "Public Property Let Width(ByVal w As Integer)\r\n"s; + vb6_ast::propertyLetHead ast; + test_grammar(str, vb6_grammar::property_letHead, ast); + + EXPECT_EQ(ast.at, vb6_ast::access_type::public_); + EXPECT_EQ(ast.name, "Width"); + EXPECT_EQ(ast.params.size(), 1); +} + +GTEST_TEST(vb6_parser_tests, property_get_head) +{ + auto str = "Public Property Get Width() As Integer\r\n"s; + vb6_ast::propertyGetHead ast; + test_grammar(str, vb6_grammar::property_getHead, ast); + + EXPECT_EQ(ast.at, vb6_ast::access_type::public_); + EXPECT_EQ(ast.name, "Width"); + EXPECT_EQ(ast.params.size(), 0); + ASSERT_TRUE(ast.return_type); + EXPECT_EQ(*ast.return_type, "Integer"); +} + +GTEST_TEST(vb6_parser_tests, dll_subroutine_declaration) +{ + vb6_ast::externalSub extsub; + auto str = "Private Declare Sub BeepVB Lib \"kernel32.dll\" Alias \"Beep\" (ByVal time As Long, ByVal xx As Single)\r\n"s; + test_grammar(str, vb6_grammar::external_sub_decl, extsub); + + EXPECT_EQ(extsub.at, vb6_ast::access_type::private_); + EXPECT_EQ(extsub.name, "BeepVB"); + EXPECT_EQ(extsub.alias, "Beep"); + EXPECT_EQ(extsub.params.size(), 2); + EXPECT_EQ(extsub.lib, "kernel32.dll"); +} + +GTEST_TEST(vb6_parser_tests, dll_function_declaration) +{ + vb6_ast::externalFunction extfunc; + auto str = "Private Declare Function BeepVB Lib \"kernel32.dll\" Alias \"Beep\" (ByVal time As Long, ByVal xx As Single) As Long\r\n"s; + test_grammar(str, vb6_grammar::external_function_decl, extfunc); + + EXPECT_EQ(extfunc.at, vb6_ast::access_type::private_); + EXPECT_EQ(extfunc.name, "BeepVB"); + EXPECT_TRUE(extfunc.return_type); + if(extfunc.return_type) + { + EXPECT_EQ(*extfunc.return_type, "Long"); + } + EXPECT_EQ(extfunc.alias, "Beep"); + EXPECT_EQ(extfunc.params.size(), 2); + EXPECT_EQ(extfunc.lib, "kernel32.dll"); +} + +GTEST_TEST(vb6_parser_tests, identifier_context) +{ + vb6_ast::identifier_context ctx; + test_grammar("var1.func().pnt1.", vb6_grammar::identifier_context, ctx); + + EXPECT_FALSE(ctx.leading_dot); + ASSERT_EQ(ctx.elements.size(), 3); + EXPECT_EQ(boost::get(ctx.elements[0].get()), "var1"); + auto& tmp = boost::get>(ctx.elements[1].get()).get(); + EXPECT_EQ(tmp.func_name, "func"); + EXPECT_TRUE(tmp.params.empty()); + EXPECT_EQ(boost::get(ctx.elements[2].get()), "pnt1"); +} + +GTEST_TEST(vb6_parser_tests, decorated_variable) +{ + vb6_ast::decorated_variable dec_var; + test_grammar("var1.func().pnt1.X", vb6_grammar::decorated_variable, dec_var); + + EXPECT_FALSE(dec_var.ctx.leading_dot); + ASSERT_EQ(dec_var.ctx.elements.size(), 3); + EXPECT_EQ(boost::get(dec_var.ctx.elements[0].get()), "var1"); + auto& tmp = boost::get>(dec_var.ctx.elements[1].get()).get(); + EXPECT_EQ(tmp.func_name, "func"); + EXPECT_TRUE(tmp.params.empty()); + EXPECT_EQ(boost::get(dec_var.ctx.elements[2].get()), "pnt1"); + EXPECT_EQ(dec_var.var, "X"); +} + +GTEST_TEST(vb6_parser_tests, attribute_block) +{ + vb6_ast::STRICT_MODULE_STRUCTURE::module_attributes attrs; + auto str = "Attribute ModuleName = \"MyForm\"\r\n" + "Attribute ProgID = \"00-00-00-00\"\r\n"s; + test_grammar(str, *vb6_grammar::attributeDef, attrs); + + EXPECT_EQ(attrs.size(), 2); + + auto it1 = attrs.find("ModuleName"); + EXPECT_NE(it1, attrs.cend()); + if(it1 != attrs.cend()) + { + EXPECT_EQ(it1->second, "MyForm"); + } + + auto it2 = attrs.find("ProgID"); + EXPECT_NE(it2, attrs.cend()); + if(it2 != attrs.cend()) + { + EXPECT_EQ(it2->second, "00-00-00-00"); + } +} + +GTEST_TEST(vb6_parser_tests, attributes) +{ + vb6_ast::STRICT_MODULE_STRUCTURE::module_attributes ast; + auto str = R"vb(Attribute ModuleName = "MyForm" + Attribute ProgID = "00-00-00-00" + )vb"s; + //test_grammar(str, vb6_grammar::preamble, ast); + test_grammar(str, *vb6_grammar::attributeDef, ast); + + EXPECT_EQ(ast.size(), 2); + + auto const it1 = ast.find("ModuleName"); + EXPECT_NE(it1, ast.cend()); + if(it1 != ast.cend()) + { + EXPECT_EQ(it1->second, "MyForm"); + } + + auto const it2 = ast.find("ProgID"); + EXPECT_NE(it2, ast.cend()); + if(it2 != ast.cend()) + { + EXPECT_EQ(it2->second, "00-00-00-00"); + } +} + +GTEST_TEST(vb6_parser_tests, options) +{ + vector ast; + auto str = R"vb(Option Explicit + Option Base 0 + )vb"s; + test_grammar(str, *vb6_grammar::option_item, ast); + + ASSERT_EQ(ast.size(), 2); + + EXPECT_EQ(ast[0], vb6_ast::module_option::explicit_); + EXPECT_EQ(ast[1], vb6_ast::module_option::base_0); +} + +#if 0 +GTEST_TEST(vb6_parser_tests, declaration_block) +{ + auto const declarations + = boost::spirit::x3::rule>("declaration_block") + = *vb6_grammar::STRICT_MODULE_STRUCTURE::declaration; + + vector decls; + auto str = "Const e As Double = 2.8, pi As Double = 3.14, u As Integer = -1\r\n" + "Global g_logger As Long, v1, XRes As Object, ptr As MyRec, g_active As Boolean\r\n" + "Private Declare Sub PFoo Lib \"mylib.dll\" Alias \"PFoo\" (ByVal val As Long)\r\n" + "Enum MyEnum1\r\n c1 = 0\r\n c2 = 1\r\nEnd Enum\r\n" + "Public Type MyRecord1\r\n v1 As String\r\n v2 As Long\r\nEnd Type\r\n"s; + test_grammar(str, declarations, decls); + + ASSERT_EQ(decls.size(), 5); + + EXPECT_NO_THROW(boost::get(decls[0].get())); + EXPECT_NO_THROW(boost::get(decls[1].get())); + EXPECT_NO_THROW(boost::get(decls[2].get())); + EXPECT_NO_THROW(boost::get(decls[3].get())); + EXPECT_NO_THROW(boost::get(decls[4].get())); +} + +GTEST_TEST(vb6_parser_tests, bas_unit_STRICT_MODULE_STRUCTURE) +{ + vb6_ast::STRICT_MODULE_STRUCTURE::vb_module ast; + auto str = R"vb(Attribute ModuleName = "MyForm" + Attribute ProgID = "00-00-00-00" + Option Explicit + Option Base 0 + + ' declarations + Const u As Integer = 1234 + Global g_logger As Long + Enum MyEnum1 + c1 = 0 + c2 = 1 + End Enum + Sub my_sub(ByRef str As String) + End Sub + Function my_fun(ByRef str As String) As Long + End Function + )vb"s; + test_grammar(str, vb6_grammar::STRICT_MODULE_STRUCTURE::basModDef, ast); + + vb6_ast_printer P(cout); + P(ast); + + EXPECT_EQ(ast.attrs.size(), 2); + + auto const it1 = ast.attrs.find("ModuleName"); + EXPECT_NE(it1, ast.attrs.cend()); + if(it1 != ast.attrs.cend()) + { + EXPECT_EQ(it1->second, "MyForm"); + } + + auto const it2 = ast.attrs.find("ProgID"); + EXPECT_NE(it2, ast.attrs.cend()); + if(it2 != ast.attrs.cend()) + { + EXPECT_EQ(it2->second, "00-00-00-00"); + } + + EXPECT_EQ(ast.opts.items.size(), 4); + if(ast.opts.items.size() == 4) + { +#if 0 + EXPECT_EQ(ast.opts.items[0], vb6_ast::module_option::explicit_); + EXPECT_EQ(ast.opts.items[1], vb6_ast::module_option::base_0); +#else + EXPECT_EQ(boost::get(ast.opts.items[0].get()), vb6_ast::module_option::explicit_); + EXPECT_EQ(boost::get(ast.opts.items[1].get()), vb6_ast::module_option::base_0); +#endif + EXPECT_NO_THROW(boost::get(ast.opts.items[2].get())); + EXPECT_NO_THROW(boost::get(ast.opts.items[3].get())); + } + + EXPECT_EQ(ast.declarations.size(), 3); + if(ast.declarations.size() == 3) + { + EXPECT_NO_THROW(boost::get(ast.declarations[0].get())); + EXPECT_NO_THROW(boost::get(ast.declarations[1].get())); + EXPECT_NO_THROW(boost::get(ast.declarations[2].get())); + } + + EXPECT_EQ(ast.functions.size(), 2); + if(ast.functions.size() == 2) + { + EXPECT_NO_THROW(boost::get(ast.functions[0].get())); + EXPECT_NO_THROW(boost::get(ast.functions[1].get())); + } +} +#endif + +GTEST_TEST(vb6_parser_tests, bas_unit) +{ + vb6_ast::vb_module ast; + auto str = R"vb(Attribute ModuleName = "MyForm" + Attribute ProgID = "00-00-00-00" + Option Explicit + Option Base 0 + + ' declarations + Const u As Integer = 1234 + Global g_logger As Long + Enum MyEnum1 + c1 = 0 + c2 = 1 + End Enum + Sub my_sub(ByRef str As String) + End Sub + Function my_fun(ByRef str As String) As Long + End Function + )vb"s; + test_grammar(str, vb6_grammar::basModDef, ast); + + vb6_ast_printer P(cout); + P(ast); + + ASSERT_EQ(ast.size(), 11); + + EXPECT_EQ(boost::get(ast[0].get()).first, "ModuleName"); + EXPECT_EQ(boost::get(ast[1].get()).first, "ProgID"); + + EXPECT_EQ(boost::get(ast[2].get()), vb6_ast::module_option::explicit_); + EXPECT_EQ(boost::get(ast[3].get()), vb6_ast::module_option::base_0); + + EXPECT_NO_THROW(boost::get(ast[4].get())); + EXPECT_NO_THROW(boost::get(ast[5].get())); + + EXPECT_NO_THROW(boost::get(boost::get(ast[6].get()))); + EXPECT_NO_THROW(boost::get(boost::get(ast[7].get()))); + EXPECT_NO_THROW(boost::get(boost::get(ast[8].get()))); + + EXPECT_NO_THROW(boost::get(ast[9].get())); + EXPECT_NO_THROW(boost::get(ast[10].get())); +} + +GTEST_TEST(vb6_parser_tests, trailing_comment) +{ + auto str = "Global g_var As Long ' how can we catch a trailing comment?\r\n"s; + vb6_ast::global_var_decls vars; + test_grammar(str, vb6_grammar::global_var_declaration, vars); + + EXPECT_EQ(vars.at, vb6_ast::access_type::global); + EXPECT_FALSE(vars.with_events); + ASSERT_EQ(vars.vars.size(), 1); + + EXPECT_EQ(vars.vars[0].name, "g_var"); + EXPECT_FALSE(vars.vars[0].construct); + EXPECT_TRUE(vars.vars[0].type); + if(vars.vars[0].type) + { + EXPECT_EQ(*vars.vars[0].type, "Long"); + } +} diff --git a/src/vb6_parser_test_main.cpp b/src/vb6_parser_test_main.cpp new file mode 100644 index 0000000..5fda93e --- /dev/null +++ b/src/vb6_parser_test_main.cpp @@ -0,0 +1,29 @@ +//: vb6_parser_test_main.cpp + +// vb6_parser +// Copyright (c) 2022 Federico Aponte +// This code is licensed under GNU Software License (see LICENSE.txt for details) + +#include + +#include + +int main(int argc, char* argv[]) +{ + testing::InitGoogleTest(&argc, argv); + + // --gtest_filter = Test_Cases1* + // --gtest_repeat = 1000 + // --gtest_break_on_failure + // --gtest_shuffle + // --gtest_random_seed=SEED + // --gtest_color=no + // --gtest_print_time=0 + // --gtest_print_utf8=0 + // --gtest_output=xml:path_to_output_file + // --gtest_output=json:path_to_output_file + //::test::GTEST_FLAG(list_tests) = true; + //::testing::GTEST_FLAG(filter) = "*empty_lines*"; + + return RUN_ALL_TESTS(); +} \ No newline at end of file diff --git a/src/vb6_test1.cpp b/src/vb6_test1.cpp new file mode 100644 index 0000000..10c0ef9 --- /dev/null +++ b/src/vb6_test1.cpp @@ -0,0 +1,200 @@ +//: vb6_test1.cpp + +// vb6_parser +// Copyright (c) 2022 Federico Aponte +// This code is licensed under GNU Software License (see LICENSE.txt for details) + +//#define BOOST_SPIRIT_X3_DEBUG + +#include "test_grammar_helper.hpp" +#include "vb6_parser.hpp" +#include "vb6_ast_printer.hpp" + +#include +#include +#include + +using namespace std; +//namespace x3 = boost::spirit::x3; + +void test_vb6_unit(ostream& os, std::string_view unit) +{ + os << "---- " << __func__ << " ----\n"; + +#if 0 + vb6_ast::STRICT_MODULE_STRUCTURE::vb_module ast; + auto const G = vb6_grammar::STRICT_MODULE_STRUCTURE::basModDef; +#else + vb6_ast::vb_module ast; + auto const G = vb6_grammar::basModDef; +#endif + + test_grammar(os, "test_vb6_unit", G, unit, ast); + + vb6_ast_printer P(os); + P(ast); +} + +void vb6_test1(ostream& os) +{ + string var; + test_grammar(os, "var_identifier", vb6_grammar::var_identifier, "g_logger", var); + os << var << '\n'; + + string type; + test_grammar(os, "type_identifier", vb6_grammar::type_identifier, "Long", type); + os << type << '\n'; + + vb6_ast_printer Print(os); + + vb6_ast::expression expr; + test_grammar(os, "expression", vb6_grammar::expression, "foo1(foo2(3, M.x_coord), True)", expr); + Print(expr); + os << '\n'; + + vb6_ast::variable P1; + test_grammar(os, "single_var_declaration", vb6_grammar::single_var_declaration, "g_logger As Long", P1); + Print(P1); + os << '\n'; + + vb6_ast::variable res1; + test_grammar(os, "single_var_declaration", vb6_grammar::single_var_declaration, "name As String", res1); + Print(res1); + os << '\n'; + + vb6_ast::record rec1; + test_grammar(os, "record_declaration", vb6_grammar::record_declaration, + "Type PatRecord\r\n name As String\r\n age As Integer\r\nEnd Type\r\n", rec1); + Print(rec1); + + vb6_ast::vb_enum enum1; + test_grammar(os, "enum_declaration", vb6_grammar::enum_declaration, + "Enum PatTypes\r\n inpatient\r\n outpatient\r\nEnd Enum\r\n", enum1); + Print(enum1); + + auto str = "Global g_logger As Long, v1, XRes As New Object, ptr As Module.MyRec, g_active As Boolean\r\n"s; + vb6_ast::global_var_decls vars; + test_grammar(os, "global_var_declaration", vb6_grammar::global_var_declaration, str, vars); + Print(vars); + + auto cstr = "Const bActivate As Boolean = False" + ", keyword As String = \"Module\"" + ", e As Single = 2.8" + ", pi As Double = 3.14" + ", u As Integer = -1\r\n"s; + vb6_ast::const_var_stat cvars; + test_grammar(os, "const_var_declaration", vb6_grammar::const_var_declaration, cstr, cvars); + Print(cvars); + + vb6_ast::const_var_stat cvar; + test_grammar(os, "const_var_declaration", vb6_grammar::const_var_declaration, + "Private Const PI As Double = 3.1415\r\n", cvar); + Print(cvar); + + vb6_ast::func_param res2; + test_grammar(os, "param_decl", vb6_grammar::param_decl, "Optional ByVal name As String = \"pippo\"", res2); + Print(res2); + os << '\n'; + + vector res3; + test_grammar(os, "param_list_decl", -(vb6_grammar::param_decl % ','), + "ByVal name As String, ByRef val As Integer", res3); + for(auto& el : res3) + { + Print(el); + os << ", "; + } + os << '\n'; + + vb6_ast::eventHead event_decl; + test_grammar(os, "event declaration", vb6_grammar::eventHead, + "Public Event OnChange(ByVal Text As String)\r\n", event_decl); + Print(event_decl); + + vb6_ast::functionHead func_header1; + test_grammar(os, "function head", vb6_grammar::functionHead, + "Private Function OneFunc(ByVal name As String, ByRef val As Integer) As Integer\r\n", func_header1); + Print(func_header1); + + vb6_ast::functionHead func_header2; + test_grammar(os, "function head (no params)", vb6_grammar::functionHead, + "Private Function NoParamFunc() As Object\r\n", func_header2); + Print(func_header2); + + vb6_ast::subHead sub_header1; + test_grammar(os, "subroutine_head", vb6_grammar::subHead, + "Private Sub my_sub(ByRef str As String, ByVal valid As Boolean)\r\n", sub_header1); + Print(sub_header1); + + auto str2 = "Private Sub my_sub(ByRef str As String, ByVal valid As Boolean, Optional ByVal flag As Boolean = True)\r\n"s; + vb6_ast::subHead sh; + test_grammar(os, "subroutine_head2", vb6_grammar::subHead, str2, sh); + Print(sh); + + vb6_ast::subHead sub_header2; + test_grammar(os, "subroutine head with optional params", vb6_grammar::subHead, + "Private Sub my_sub(ByRef str As String, Optional ByVal valid As Boolean = false)\r\n", sub_header2); + Print(sub_header2); + + vector comments; + test_grammar(os, "lonely_comment", *vb6_grammar::lonely_comment, + "' This is comment line 1\r\n' Comment line 2\r\n", comments); + for(auto& el : comments) + Print(el); + + vb6_ast::subDef sub1; + test_grammar(os, "subroutine definition 1", vb6_grammar::subDef, + "Private Sub my_sub(ByRef str As String, ByVal valid As Boolean)\r\nEnd Sub\r\n", sub1); + Print(sub1); + + vb6_ast::subDef sub2; + test_grammar(os, "subroutine definition 2", vb6_grammar::subDef, + R"vb(Public Sub my_sub(ByRef p1 As String, ByVal p2 As Boolean, p3 As Long) + ' this is a comment + Dim x As Long, y As String, frm As New VB.Form + Set x = Module1.GetField("size").Value + y = "word" + 'On Error GoTo err_handler + Dim z As Variant + 'ReDim arr(256, 256) + Call routine1() + Call routine2(34, "ciao") + Call routine3(1.5, Module1.GetField("name1").Value) + 'Call subroutine3(1.5, Value) + routine1 + routine2 34, "ciao" + routine3 1.5, Module1.GetField("name1").Value + Exit Sub + err_handler: + GoTo final + final: + End Sub + )vb", sub2); + Print(sub2); + + //x3::error_handler err_hndlr(it1, it2, out, "dummy.cpp"); + + // we pass our error handler to the parser so we can access + // it later on in our on_error and on_sucess handlers + //auto const parser = x3::with(std::ref(err_hndlr))[vb6_grammar::functionDef]; + + vb6_ast::functionDef func1; + test_grammar(os, "function definition", vb6_grammar::functionDef, + R"vb(Private Function foo(ByRef p1 As String, ByVal p2 As Boolean, p3 As Long) As Long + Static s_State As String + Set x = "sss" + foo = 5 + End Function + )vb", func1); + Print(func1); + + vb6_ast::functionDef func_def1; + test_grammar(os, "function definition (no params)", vb6_grammar::functionDef, + "Private Function NoParamFunc() As Object\r\nNoParamFunc = Nothing\r\nEnd Function\r\n", func_def1); + Print(func_def1); + + vb6_ast::functionDef func2; + test_grammar(os, "function definition (1 line)", vb6_grammar::functionDef, + "Function foo(str As String) As String: foo = \"ciao\": End Function\r\n", func2); + Print(func2); +} diff --git a/src/vb6_test2.cpp b/src/vb6_test2.cpp new file mode 100644 index 0000000..6b08e33 --- /dev/null +++ b/src/vb6_test2.cpp @@ -0,0 +1,259 @@ +//: vb6_test2.cpp + +// vb6_parser +// Copyright (c) 2022 Federico Aponte +// This code is licensed under GNU Software License (see LICENSE.txt for details) + +//#define BOOST_SPIRIT_X3_DEBUG + +#include "test_grammar_helper.hpp" +#include "vb6_parser.hpp" +#include "vb6_ast_printer.hpp" + +#include +#include +#include + +using namespace std; +//namespace x3 = boost::spirit::x3; + +void vb6_test_statements(ostream& os) +{ + vb6_ast_printer Print(os); + + vb6_ast::statements::assignStmt P2; + test_grammar(os, "assignmentStmt 0", vb6_grammar::statements::assignmentStmt, + "Set var1 = \" ciao\"\r\n", P2); + Print(P2); + + vb6_ast::statements::assignStmt P3; + test_grammar(os, "assignmentStmt 1", vb6_grammar::statements::assignmentStmt, + "Let var2 = 54.7\r\n", P3); + Print(P3); + + vb6_ast::statements::assignStmt P4; + test_grammar(os, "assignmentStmt 2", vb6_grammar::statements::assignmentStmt, + "var3 = Func(\"descr\", 54.7)\r\n", P4); + Print(P4); + + vb6_ast::statements::assignStmt P5; + test_grammar(os, "assignmentStmt 3", vb6_grammar::statements::assignmentStmt, + "str = CStr(i)\r\n", P5); + Print(P5); + + vb6_ast::statements::localVarDeclStmt localVarDecl; + test_grammar(os, "localvardeclStmt", vb6_grammar::statements::localvardeclStmt, + "Dim var1 As String, var2 As Integer\r\n", localVarDecl); + Print(localVarDecl); + + vb6_ast::statements::redimStmt redim; + test_grammar(os, "redimStmt", vb6_grammar::statements::redimStmt, + "ReDim var1(15)\r\n", redim); + Print(redim); + + vb6_ast::statements::exitStmt exitSt; + test_grammar(os, "exitStmt", vb6_grammar::statements::exitStmt, + "Exit Function\r\n", exitSt); + Print(exitSt); + + vb6_ast::statements::gotoStmt gotoSt; + test_grammar(os, "gotoStmt", vb6_grammar::statements::gotoStmt, + "GoSub label1\r\n", gotoSt); + Print(gotoSt); + + vb6_ast::statements::onerrorStmt onerrorSt; + test_grammar(os, "onerrorStmt", vb6_grammar::statements::onerrorStmt, + "On Error Resume Next\r\n", onerrorSt); + Print(onerrorSt); + + vb6_ast::statements::labelStmt labelSt; + test_grammar(os, "labelStmt", vb6_grammar::statements::labelStmt, + "label1:\r\n", labelSt); + Print(labelSt); + + vb6_ast::statements::callStmt callSt1; + test_grammar(os, "callexplicitStmt", vb6_grammar::statements::callexplicitStmt, + "Call Foo(13)\r\n", callSt1); + Print(callSt1); + + vb6_ast::statements::callStmt callSt2; + test_grammar(os, "callimplicitStmt", vb6_grammar::statements::callimplicitStmt, + "Foo \"Sea\", Nothing, False\r\n", callSt2); + Print(callSt2); + + vb6_ast::statements::raiseeventStmt raise_ev; + test_grammar(os, "raise_event_statement", vb6_grammar::statements::raiseeventStmt, + "RaiseEvent OnChange(\"hi!\")\r\n", raise_ev); + + vb6_ast::statements::withStmt with_stat; + test_grammar(os, "with_statement", vb6_grammar::statements::withStmt, + R"vb(With obj + 'Call Module1.foo(True, .Name) + Call foo(True, .Name) + End With + )vb", with_stat); + Print(with_stat); + + vb6_ast::statements::whileStmt while_stat; + test_grammar(os, "while_statement", vb6_grammar::statements::whileStmt, + R"vb(While cond(34, i) + Print str + Wend + )vb", while_stat); + Print(while_stat); + + vb6_ast::statements::dowhileStmt dowhile_stat; + test_grammar(os, "dowhile_statement", vb6_grammar::statements::dowhileStmt, + R"vb(Do While cond(34, i) + Print str + Loop + )vb", dowhile_stat); + Print(dowhile_stat); + + vb6_ast::statements::loopwhileStmt loopwhile_st; + test_grammar(os, "loopwhile_statement", vb6_grammar::statements::loopwhileStmt, + R"vb(Do + Print str + Loop While cond(34, i) + )vb", loopwhile_st); + Print(loopwhile_st); + + vb6_ast::statements::dountilStmt dountil_st; + test_grammar(os, "dountil_statement", vb6_grammar::statements::dountilStmt, + R"vb(Do Until cond(34, i) + Print str + Loop + )vb", dountil_st); + Print(dountil_st); + + vb6_ast::statements::loopuntilStmt loopuntil_st; + test_grammar(os, "loopuntil_statement", vb6_grammar::statements::loopuntilStmt, + R"vb(Do + Print str + Loop Until cond(34, i) + )vb", loopuntil_st); + Print(loopuntil_st); + + vb6_ast::statements::forStmt forloop_stat; + test_grammar(os, "for_statement", vb6_grammar::statements::forStmt, + R"vb(For i = 1 To 100 Step 2 + Dim str As String + str = CStr(i) + Print str + Next i + )vb", forloop_stat); + Print(forloop_stat); + + vb6_ast::statements::foreachStmt foreach_stat; + test_grammar(os, "foreach_statement", vb6_grammar::statements::foreachStmt, + R"vb(For Each el In Items + Print el + Next el + )vb", foreach_stat); + Print(foreach_stat); + + std::vector branches; + //vb6_ast::if_branch branches; + test_grammar(os, "elseif_branch", *vb6_grammar::statements::elsifBranch, + R"vb(ElseIf func2(Nothing) Then + proc2a "elseif branch", 2 + ElseIf func3(Nothing) Then + proc2b "elseif branch", 2 + proc2c "elseif branch", 2 + )vb", branches); + for(auto& el : branches) + { + os << "ElseIf "; + Print(el.condition); + os << " Then\n"; + + // statements + for(auto& st : el.block) + Print(st); + } + + vb6_ast::statements::ifelseStmt ifelse_stat1; + test_grammar(os, "ifelse_statement", vb6_grammar::statements::ifelseStmt, + R"vb(If func1("name", True, 45.8, -45) Then + proc1 "if branch", 1 + ElseIf func2(Nothing) Then + proc2 "elseif branch", 2 + Else + proc3 "else branch", 3 + End If + )vb", ifelse_stat1); + Print(ifelse_stat1); + + vb6_ast::statements::selectStmt select_stat; + test_grammar(os, "select_statement", vb6_grammar::statements::selectStmt, + R"vb(Select Case frm.Type + Case 0 + Dim str As String + Call Print(0) + Case 1 + ' case 1 + Dim str As String + 'Case 1 To 4, 7 To 9, 11, 13, Is > MaxNumber + ' Print "Difficult" + 'Case Else + ' Print "Default" + End Select + )vb", select_stat); + Print(select_stat); +} + +void vb6_test2(ostream& os) +{ + vb6_ast_printer Print(os); + + vb6_ast::record rec; + auto str3 = "Private Type myrec\r\n name As String\r\n age As Integer\r\nEnd Type\r\n"s; + test_grammar(os, "record_declaration", vb6_grammar::record_declaration, str3, rec); + Print(rec); + + vb6_ast::vb_enum enum_res; + auto str4 = "Private Enum ComprKind\r\n NoCompr = 0\r\n Jpeg = 1\r\n Tiff\r\nEnd Enum\r\n"s; + test_grammar(os, "enum_declaration", vb6_grammar::enum_declaration, str4, enum_res); + Print(enum_res); + + vb6_ast::externalSub extsub; + auto str5 = "Private Declare Sub Beep Lib \"kernel32.dll\" Alias \"Beep\" (ByVal time As Long, ByVal xx As Single)\r\n"s; + test_grammar(os, "DLL subroutine declaration", vb6_grammar::external_sub_decl, str5, extsub); + Print(extsub); + + vb6_ast::externalFunction extfunc; + auto str6 = "Private Declare Function Beep Lib \"kernel32.dll\" Alias \"Beep\" (ByVal time As Long, ByVal xx As Single) As Long\r\n"s; + test_grammar(os, "DLL function declaration", vb6_grammar::external_function_decl, str6, extfunc); + Print(extfunc); + + auto const declarations //= x3::rule>() + = *vb6_grammar::STRICT_MODULE_STRUCTURE::declaration; + +#if 0 + vector decls; + auto str7 = "Const e As Double = 2.8, pi As Double = 3.14, u As Integer = -1\r\n" + "Global g_logger As Long, v1, XRes As Object, ptr As MyRec, g_active As Boolean\r\n" + "Private Declare Sub PFoo Lib \"mylib.dll\" Alias \"PFoo\" (ByVal val As Long)\r\n" + "Enum MyEnum1\r\n c1 = 0\r\n c2 = 1\r\nEnd Enum\r\n" + "Public Type MyRecord1\r\n v1 As String\r\n v2 As Long\r\nEnd Type\r\n"s; + test_grammar(os, "Declaration block", declarations, str7, decls); + os << str7 << '\n'; + for(auto& el : decls) + Print(el); + os << '\n'; +#endif + + vb6_ast::identifier_context ctx; + test_grammar(os, "identifier_context", vb6_grammar::identifier_context, "var1.func().pnt1.", ctx); + Print(ctx); + os << '\n'; + + vb6_ast::decorated_variable dec_var; + test_grammar(os, "decorated_variable", vb6_grammar::decorated_variable, "var1.func().pnt1.X", dec_var); + Print(dec_var); + os << '\n'; + + // Dim obj As VB.Form + // pnt.x = 4.5 + // Call Module1.func1(45) +} diff --git a/src/visual_basic_x3.hpp b/src/visual_basic_x3.hpp new file mode 100644 index 0000000..3343d2b --- /dev/null +++ b/src/visual_basic_x3.hpp @@ -0,0 +1,364 @@ +//: visual_basic_x3.hpp + +// vb6_parser +// Copyright (c) 2022 Federico Aponte +// This code is licensed under GNU Software License (see LICENSE.txt for details) + +#pragma once + +#include "color_console.hpp" +#include "vb6_ast.hpp" +#include "vb6_ast_adapt.hpp" +#include "vb6_parser.hpp" + +#include +#include + +#include +#include +#include +#include + +/* +http://stackoverflow.com/questions/2366097/what-does-putting-an-exclamation-point-in-front-of-an-object-reference-varia +https://support.microsoft.com/en-us/kb/129287 +http://bytecomb.com/the-bang-exclamation-operator-in-vba/ +*/ + +/* +http://ciere.com/cppnow15/x3_docs/ +http://ciere.com/cppnow15/ + +Parser Directory Structure + * fun + * ast.hpp + * ast_adapted.hpp + * common.hpp + * expression.hpp + * expression_def.hpp + * src + * expression.cpp + * test +*/ + +/* +PARSING +http://stackoverflow.com/questions/3455456/what-kinds-of-patterns-could-i-enforce-on-the-code-to-make-it-easier-to-translat/3460977#3460977 +http://www.semanticdesigns.com/Products/DMS/LifeAfterParsing.html +http://www.semanticdesigns.com/Products/DMS/DMSToolkit.html +https://support.microsoft.com/en-us/kb/216388 +http://www.tangiblesoftwaresolutions.com/Product_Details/VB_to_CPlusPlus_Converter_Details.html +https://sourceforge.net/projects/vbtocconv/ +http://www.vbto.net/ +https://ubuntuforums.org/showthread.php?t=1590707 +http://edndoc.esri.com/arcobjects/9.0/ArcGISDevHelp/DevelopmentEnvs/Cpp/ArcGIS%20Development/sampleconversions.htm +http://ezbasic.sourceforge.net/ +https://github.com/antlr/grammars-v4/blob/master/vb6/VisualBasic6.g4 +http://slebok.github.io/zoo/index.html + +MIX +http://powerbasic.com/index.php +http://www.codeproject.com/Articles/710181/Visual-Basic-A-giant-more-powerful-than-ever +https://www.indiegogo.com/projects/a-replacement-to-visual-basic-6-ide-and-compiler#/ +http://www.ioprogrammo.it/index.php?topic=20151.0;wap2 +http://vb.mvps.org/ +http://www.semanticdesigns.com/Products/Services/VB6Migration.html?Home=LegacyMigration +http://betselection.cc/resources/vb6-replacements/ +https://blog.xojo.com/2013/06/19/a-modern-alternative-to-visual-basic/ +http://www.tiobe.com/index.php/content/paperinfo/tpci/index.html +https://www.indiegogo.com/projects/a-replacement-to-visual-basic-6-ide-and-compiler#/ +http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/7462243-provide-a-visual-basic-6-community-edition-to-al + +FRX +http://www.vbforums.com/showthread.php?221901-frx-file-format +http://planet-source-code.com/vb/scripts/ShowCode.asp?txtCodeId=5539&lngWId=1 +https://forum.powerbasic.com/forum/user-to-user-discussions/special-interest-groups/vb-conversion/59957-extract-files-from-vb6-frx-discussion +https://forum.powerbasic.com/forum/user-to-user-discussions/source-code/59945-extract-files-from-visual-basic-vb5-vb6-frx-form-resource-files + +putref_ -> Set +put_ -> Let +get_ -> Get +*/ + +/* +== Exclamation Point (!) Operator == +Use the ! operator only on a class or interface as a dictionary access operator. +The class or interface must have a default property that accepts a single String argument. +The identifier immediately following the ! operator becomes the string argument to the default property. +*/ + +/* +== Implicit types == + + * the type depends on a suffix + * there are 6 suffixes: $ % & @ ! # + * variables with a suffix cannot be explicitly declared (e.g. Dim name$ As String is forbiddden) + * £ is a valid character for names just like a-z and _ (aaargh) + + my_str$ = "ciao" ' String + my_short% = 34 ' Integer + my_long& = 65537 ' Long + my_float! = 3.1 ' Single + my_double# = 1.23 ' Double + my_currency@ = 23.56 ' Currency + + Debug.Print "my_str$ -> "; TypeName(my_str$) ' String + Debug.Print "my_short% -> "; TypeName(my_short%) ' Integer + Debug.Print "my_long& -> "; TypeName(my_long&) ' Long + Debug.Print "my_float! -> "; TypeName(my_float!) ' Single + Debug.Print "my_double# -> "; TypeName(my_double#) ' Double + Debug.Print "my_currency@ -> "; TypeName(my_currency@) ' Currency +*/ + +/* +http://www.vb6.us/tutorials/error-handling + +On [Local] Error { GoTo [ line | 0 | -1 ] | Resume Next } + +-> "On Local Error" is a hold over from previous versions of VB + +GoTo line Enables the error-handling routine that starts at the line + specified in the required line argument. The line argument is any + line label or line number. If a run-time error occurs, control + branches to the specified line, making the error handler active. + The specified line must be in the same procedure as the On Error + statement, or a compile-time error will occur. + This form of the On Error statement redirects program execution + to the line label specified. When you use this form of On Error, + a block of error handling code is constructed following the label. +GoTo 0 Disables enabled error handler in the current procedure and + resets it to Nothing. +GoTo -1 Disables enabled exception in the current procedure and resets it + to Nothing. +Resume Next Specifies that when a run-time error occurs, control goes to the + statement immediately following the statement where the error + occurred, and execution continues from that point. Use this form + rather than On Error GoTo when accessing objects. + This form of the On Error statement tells VB to continue with the + line of code following the line where the error occurred. With + this type of error trap, you would normally test for an error at + selected points in the program code where you anticipate that an + error may occur. + +Private Sub cmd1_Click() + On Error GoTo ErrHandler + + Dim val As Long + val = CLng("we34") + + Debug.Print val +ErrHandler: + Debug.Print "cmd1_Click -> " & VBA.Information.Err.Number & " - " & VBA.Information.Err.Description +End Sub + +Private Sub cmd2_Click() + On Error Resume Next + + Dim val As Long + val = CLng("we34") ' fails with type mismatch + + Debug.Print val + + ' this succeeds, but the error stays because the operation above failed + val = CLng("12") + + Debug.Print "cmd2_Click -> " & VBA.Information.Err.Number & " - " & VBA.Information.Err.Description +End Sub + +Private Sub cmd3_Click() + ' the error is apperently cleared at the beginning of each sub or function + Debug.Print "cmd3_Click -> " & VBA.Information.Err.Number & " - " & VBA.Information.Err.Description +End Sub +*/ + +/* +http://stackoverflow.com/questions/9290731/what-is-the-effect-of-a-semicolon-at-the-end-of-a-line +http://www.vb6.us/tutorials/understanding-semicolans-and-print-method + + --> Print is more of a pseudo-method that the compiler handles directly. + This is why it does not work within a With-block, etc. + +Print is a fundamental BASIC statement that dates back to the first days of the +language in the mid-1960s. Print is used to display lines of data on a form, +picture box, printer, and the immediate (Debug) window; it can also be used to +write records of data to a file. In VB, Print is implemented as a method. + +The general format for the Print method is: + +[object.]Print [expressionlist] + +where object refers to one of the objects mentioned above (Form, PictureBox, +Debug window, Printer) and expressionlist refers to a list of one or more +numeric or string expressions to print. + +Items in the expression list may be separated with semicolons (;) or commas (,). +A semicolon or comma in the expression list determines where the next output begins: + +; (semicolon) means print immediately after the last value. +, (comma) means print at the start of the next "print zone". + +Items in the expression list of a Print statement that are separated by +semicolons print immediately after one another. In the statement + +Print "Hello,"; strName; "How are you today?" +*/ + +/* +vbrun100.Dll -> msvbvm50.dll -> msvbvm60.dll +*/ +struct vb6_keywords_ : boost::spirit::x3::symbols +{ + vb6_keywords_() + { + add("Option", 0) ("Explicit", 0) ("Compare", 0) ("Module", 0) ("Open", 0) ("Close", 0) ("Put", 0) + ("Access", 0) ("Line", 0) ("Read", 0) ("Print", 0) ("Write", 0) ("Input", 0) ("Output", 0) ("Random", 0) ("Append", 0) ("Text", 0) + ("Binary", 0) ("Lock", 0) ("Shared", 0) ("Tab", 0) ("On", 0) ("Error", 0) ("Resume", 0) ("Const", 0) ("True", 0) ("False", 0) + + // data types + ("Boolean", 0) ("Byte", 0) ("Integer", 0) ("Long", 0) ("Single", 0) ("Double", 0) + ("Currency", 0) ("Date", 0) ("String", 0) ("Variant", 0) ("Object", 0) ("Any", 0) + + ("Event", 0) ("Sub", 0) ("Function", 0) ("Propery", 0) ("Get", 0) ("Let", 0) + ("Set", 0) ("Declare", 0) ("As", 0) ("Lib", 0) ("Alias", 0) ("Begin", 0) ("End", 0) ("ByVal", 0) + ("ByRef", 0) ("ParamArray", 0) ("Optional", 0) /*("Default", 0)*/ ("AddressOf", 0) ("Static", 0) + ("Public", 0) ("Private", 0) ("Global", 0) ("Friend", 0) ("If", 0) ("Then", 0) ("ElseIf", 0) ("Else", 0) + ("For", 0) ("Each", 0) ("In", 0) ("To", 0) ("Step", 0) ("Next", 0) + ("Debug.Assert", 0) ("Debug.Print", 0) ("Stop", 0) + ("While", 0) ("Wend", 0) ("Do", 0) ("Loop", 0) ("Until", 0) ("Select", 0) ("Case", 0) ("Type", 0) + ("Enum", 0) ("Dim", 0) ("ReDim", 0) ("Preserve", 0) ("Erase", 0) ("WithEvents", 0) ("Implements", 0) + ("New", 0) ("Exit", 0) ("GoTo", 0) ("Call", 0) ("Rem", 0) ("Like", 0) + ("GoSub", 0) ("RaiseEvent", 0) ("With", 0) ("Nothing", 0) ("Attribute", 0) ("TypeOf", 0) + ("Not", 0) ("And", 0) ("Or", 0) ("Xor", 0) ("Is", 0) ("Mod", 0) ("Imp", 0) + ; + } +}; + +//inline vb6_keywords_ vb6_keywords; + +/* +normally, the syntax is + + Open "filename.txt" for MODE [Lock] as #FILENO + +whereas MODE is one of Input, Output, Random, Append, Binary (Random is default) +if you don't specify Lock, it normally shouldn't lock except on modes output and append. +don't forget to Close #FILENO after operation. +you might want to take a look at http://www.profsr.com/vb/vbless08.htm +*/ + +/* +Attribute VB_Name = "clsBulge" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = True +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +Attribute VB_Description = "Some text here" +Attribute VB_Ext_KEY = "SavedWithClassBuilder" ,"Yes" +Attribute VB_Ext_KEY = "Member0" ,"collBulges" +Attribute VB_Ext_KEY = "Top_Level" ,"Yes" + +' Header +Attribute VB_Name = "ClassOrModuleName" +Attribute VB_GlobalNameSpace = False ' ignored +Attribute VB_Creatable = False ' ignored +Attribute VB_PredeclaredId = False ' a Value of True creates a default global instance +Attribute VB_Exposed = True ' Controls how the class can be instanced. + +' Module Scoped Variables +Attribute variableName.VB_VarUserMemId = 0 ' Zero indicates that this is the default member of the class. +Attribute variableName.VB_VarDescription = "some string" ' Adds the text to the Object Browser information for this variable. + +' Procedures +Attribute procName.VB_Description = "some string" ' Adds the text to the Object Browser information for the procedure. +Attribute procName.VB_UserMemId = someInteger + ' 0: Makes the function the default member of the class. + ' -4: Specifies that the function returns an Enumerator. + +https://christopherjmcclellan.wordpress.com/2015/04/21/vb-attributes-what-are-they-and-why-should-we-use-them/ +https://stackoverflow.com/questions/33648764/what-does-the-attribute-keyword-do-in-vb6 +*/ + +namespace vb6_grammar { + + namespace x3 = boost::spirit::x3; + + /* + Sub ComboLoad(ByVal ctrl As VB.Control, ByVal D As String, ByVal active As Boolean, ByVal CodeVal As String) + Function CodeText(ByVal s As String) As String + + Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long + Declare Sub xxx Lib "yyy" Alias "zzz" (ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) + + Preprocessor!!!! + + #Const x = 6 + #If x = 5 Then + ... + #ElseIf + ... + #End If + */ + + /* + == built-in functions, classes, constants == + + Open Close Put Get - For Binary Random Append Read Write - As Binary Text + + == libraries (modules) embedded in the language == + + VBA.Information -> Err VarType VarName + VBA.Conversion -> Val Str CStr CInt CLong, Error + VBA.Interaction -> CreateObject IIf MsgBox + VBA.Strings -> StrConv Split Join InStr LCase UCase Len LenB Left$ Right$ Mid$ Space$ + VBA.FileSystem -> FreeFile Kill + VBA._HiddenModule -> VarPtr, StrPtr, ObjPtr + VB -> Load App + + == built-in constants == + + VbStrConv -> vbUnicode + */ + + // not really keywords, these are subroutines, functions and constants that + // are built into the language + + // file handling + auto const kwOpen = x3::rule("kwOpen") + = x3::no_case[x3::lit("Open")]; + auto const kwClose = x3::rule("kwClose") + = x3::no_case[x3::lit("Close")]; + auto const kwPut = x3::rule("kwPut") + = x3::no_case[x3::lit("Put")]; + auto const kwAccess = x3::rule("kwAccess") + = x3::no_case[x3::lit("Access")]; + auto const kwLine = x3::rule("kwLine") + = x3::no_case[x3::lit("Line")]; + auto const kwRead = x3::rule("kwRead") + = x3::no_case[x3::lit("Read")]; + auto const kwWrite = x3::rule("kwWrite") + = x3::no_case[x3::lit("Write")]; + auto const kwInput = x3::rule("kwInput") + = x3::no_case[x3::lit("Input")]; + auto const kwOutput = x3::rule("kwOutput") + = x3::no_case[x3::lit("Output")]; + auto const kwRandom = x3::rule("kwRandom") + = x3::no_case[x3::lit("Random")]; + auto const kwAppend = x3::rule("kwAppend") + = x3::no_case[x3::lit("Append")]; + auto const kwLock = x3::rule("kwLock") + = x3::no_case[x3::lit("Lock")]; + auto const kwShared = x3::rule("kwShared") + = x3::no_case[x3::lit("Shared")]; + auto const kwSeek = x3::rule("kwSeek") + = x3::no_case[x3::lit("Seek")]; + auto const kwPrint = x3::rule("kwPrint") + = x3::no_case[x3::lit("Print")]; + auto const kwTab = x3::rule("kwTab") + = x3::no_case[x3::lit("Tab")]; + + auto const kwDebugAssert = x3::rule() + = x3::no_case[x3::lit("Debug.Assert")]; + auto const kwDebugPrint = x3::rule() + = x3::no_case[x3::lit("Debug.Print")]; + +} // namespace vb6_grammar \ No newline at end of file