diff --git a/CMakeLists.txt b/CMakeLists.txt index 2c25e27..6d983d1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,148 +1,66 @@ -cmake_minimum_required(VERSION 3.10) +cmake_minimum_required(VERSION 3.28) project(vb6_parser) -set(CMAKE_CXX_STANDARD 20) - -#------------------------------------------------------------------------------ -# see https://google.github.io/googletest/quickstart-cmake.html -include(FetchContent) -FetchContent_Declare( - googletest - URL https://github.com/google/googletest/archive/7df7853ea02371f6d24ccf4a0cf9e16553d23d05.zip -) -# for Windows: prevent overriding the parent project's compiler/linker settings -set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) -FetchContent_MakeAvailable(googletest) -#------------------------------------------------------------------------------ +set(CMAKE_CXX_STANDARD 23) add_compile_options($<$:-Wall> $<$:-Wextra>) -#find_package(GTest CONFIG REQUIRED) # GoogleTest with vcpkg -find_package(ut CONFIG REQUIRED) +find_package(Boost REQUIRED COMPONENTS system) find_package(Threads REQUIRED) -find_package(Boost REQUIRED QUIET COMPONENTS system) + +add_compile_definitions( + BOOST_MPL_CFG_NO_PREPROCESSED_HEADERS + BOOST_MPL_LIMIT_LIST_SIZE=30 +) 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 + src/raw_ast_printer.cpp + src/color_console.cpp + src/cpp_ast_printer.cpp + src/vb6_parser.cpp + src/vb6_parser_functions.cpp + src/vb6_parser_helper.cpp + src/vb6_parser_statements.cpp + src/vb6_ast_printer.cpp + + src/raw_ast_printer.hpp + src/color_console.hpp + 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.hpp + src/vb6_parser_def.hpp + src/vb6_parser_keywords.hpp + src/vb6_parser_operators.hpp + src/vb6_parser_statements_def.hpp + 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 -) +target_include_directories(vb6_parser_lib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src) target_link_libraries(vb6_parser_lib PRIVATE - Boost::system + Boost::system ) 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 + src/vb6_parser_main.cpp + src/vb6_test1.cpp + src/vb6_test2.cpp ) target_link_libraries(vb6_parser PRIVATE - vb6_parser_lib - Boost::system - Threads::Threads + vb6_parser_lib + Boost::system + Threads::Threads ) -# ---- test - -add_executable(vb6_parser.ut - #src/test_gosub.cpp - #src/test_grammar_helper_gtest.hpp - #src/vb6_parser_statements_test.cpp - src/vb6_parser.ut.cpp -) - -target_compile_definitions(vb6_parser.ut -PRIVATE - -DBOOST_MPL_CFG_NO_PREPROCESSED_HEADERS - -DBOOST_MPL_LIMIT_LIST_SIZE=30 -) - -target_link_libraries(vb6_parser.ut -PRIVATE - vb6_parser_lib - Boost::ut - Boost::system - Threads::Threads -) - -add_executable(vb6_parser_test - src/test_gosub.cpp - src/test_grammar_helper_gtest.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 -) -#]] - -set_target_properties(vb6_parser.ut PROPERTIES EXCLUDE_FROM_ALL 1) - include(CTest) #enable_testing() -include(GoogleTest) -gtest_discover_tests(vb6_parser_test) - -add_test(AllTestsInMain vb6_parser_test) +add_subdirectory(test) diff --git a/CMakePresets.json b/CMakePresets.json index a501d9b..3b3142b 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -2,16 +2,26 @@ "version": 3, "configurePresets": [ { - "name": "vcpkg", - "displayName": "vcpkg Config", - "description": "Build using vcpkg as a package manager", - "generator": "Unix Makefiles", - "toolchainFile": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake", - "binaryDir": "${sourceDir}/build", + "name": "default", + "generator": "Ninja", "cacheVariables": { "CMAKE_BUILD_TYPE": "Release", "CMAKE_EXPORT_COMPILE_COMMANDS": true } + }, + { + "name": "vcpkg", + "inherits": "default", + "toolchainFile": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake", + "binaryDir": "${sourceDir}/build" + }, + { + "name": "clang", + "inherits": "vcpkg", + "cacheVariables": { + "CMAKE_C_COMPILER": "clang", + "CMAKE_CXX_COMPILER": "clang++" + } } ], "buildPresets": [ diff --git a/README.md b/README.md index 6ff66d8..e53fdbb 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # vb6-parser -A parsing engine for Microsoft's Visual Basic 6 programming language based on the Boost.Spirit library. +A parsing engine for Microsoft's Visual Basic 6 programming language based on the Boost.Spirit X3 library. ## Introduction @@ -10,10 +10,9 @@ https://github.com/fedapo/vb6-parser.git ## Building with CMake -```shell -> mkdir build -> cmake . -B build/ -> cmake --build build/ +```sh +cmake -B build +cmake --build build ``` ## Building with Microsoft Visual Studio @@ -22,10 +21,12 @@ Open the file `vb_parser.sln` with Microsoft Visual Studio. Build all. ## Usage -The project produces a library, vb_parser_lib, used for the moment only by two executables. +The project produces a library, vb_parser_lib, used by these executables. -- vb6_parser -- vb6_parser_test +- `vb6_parser` +- `vb6_parser.doctest` +- `vb6_parser.gtest` +- `vb6_parser.ut` These run a series of tests to ensure the parser runs correctly. @@ -37,6 +38,8 @@ These run a series of tests to ensure the parser runs correctly. - Boost Spirit X3 - GTest +- Doctest +- Boost.UT - CMake - GCC - Clang diff --git a/docs/notes_vb6.md b/docs/notes_vb6.md index 7953e1c..5eba03c 100644 --- a/docs/notes_vb6.md +++ b/docs/notes_vb6.md @@ -21,6 +21,9 @@ BASIC = Beginner's All-purpose Symbolic Instruction Code Visual Basic 6 Renewed to Run on Windows 8 \ https://www.infoq.com/news/2012/02/vb6_supported_on_win8 +https://www.indiegogo.com/projects/a-replacement-to-visual-basic-6-ide-and-compiler#/ +https://www.codeproject.com/Articles/710181/Visual-Basic-6-0-A-giant-more-powerful-than-ever + --- ## Complete VB6 Grammars diff --git a/src/test_grammar_helper.hpp b/src/test_grammar_helper.hpp index f62e348..8be17d1 100644 --- a/src/test_grammar_helper.hpp +++ b/src/test_grammar_helper.hpp @@ -12,7 +12,7 @@ #include #include -#include +#include #include template @@ -53,10 +53,10 @@ void test_grammar(std::ostream& os, std::string_view testName, ruleType rule, { os << tag_ok << '\n'; if(it1 != it2) - os << " but we stopped at " << std::string(it1, it2) << '\n'; + os << " but we stopped at " << std::string_view(it1, it2) << '\n'; } else - os << tag_fail << " - stopped at: " << std::string(it1, it2) << '\n'; + os << tag_fail << " - stopped at: " << std::string_view(it1, it2) << '\n'; } catch(x3::expectation_failure& e) { diff --git a/src/test_grammar_helper_gtest.hpp b/src/test_grammar_helper_gtest.hpp deleted file mode 100644 index 072bfe2..0000000 --- a/src/test_grammar_helper_gtest.hpp +++ /dev/null @@ -1,44 +0,0 @@ -//: 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::string_view fragment, ruleType rule, attrType& attr, bool expected = true) -{ - 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) == expected) - << "stopped at: " << std::string(it1, it2); - - EXPECT_EQ(it1, it2); -} diff --git a/src/vb6_parser.ut.cpp b/src/vb6_parser.ut.cpp deleted file mode 100644 index 1444656..0000000 --- a/src/vb6_parser.ut.cpp +++ /dev/null @@ -1,853 +0,0 @@ -//: vb6_parser.ut.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 -#pragma GCC diagnostic ignored "-Wunused-variable" - -#include "test_grammar_helper_ut.hpp" -#include "vb6_parser.hpp" -#include "vb6_ast_printer.hpp" - -#include -#include - -#include -#include -#include - -using namespace std; -namespace x3 = boost::spirit::x3; -namespace ut = boost::ut; - -void log_compiler_info(std::ostream& os) -{ - // how to tell that AppleClang and not plain Clang has been used? - // __APPLE_CC__ does not work as it's always defined -#if defined(__clang__) - os << "Compiler: Clang " << __clang_major__ << "." << __clang_minor__ << "." << __clang_patchlevel__<< '\n'; -#elif defined(__GNUC__) - os << "Compiler: GCC " << __GNUC__ << "." << __GNUC_MINOR__ << "." << __GNUC_PATCHLEVEL__<< '\n'; -#elif defined(__MSC_VER) - os << "Compiler: MSC " << __MSC_VER << '\n'; -#endif -} - -int main([[maybe_unused]] int argc, [[maybe_unused]] char* argv[]) -{ - using namespace boost::ut; - - log_compiler_info(cout); - - [[maybe_unused]] ut::suite vb6_parser_simple = [] - { - "lonely_comment"_test = [] - { - vector ast; - test_grammar( - "' This is comment line 1\r\n' Comment line 2\r\n", - *vb6_grammar::lonely_comment, ast); - - cout << ast.size() << "\n"; - ut::expect(ut::eq(ast.size(), 2)); - - ut::expect(ast[0].content == " This is comment line 1"); - ut::expect(ast[1].content == " Comment line 2"); - }; - - "empty_lines"_test = [] - { - 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); - - ut::expect(ast.size() == 3); - - ut::expect(boost::get(ast[0]).content == " comment1"); - ut::expect(ut::nothrow([ast](){boost::get(ast[1]);})); - ut::expect(boost::get(ast[2]).content == " comment2"); - }; - - "quoted_string"_test = [] - { - string str; - test_grammar("\"Quoted string.\"", vb6_grammar::quoted_string, str); - ut::expect(str == "Quoted string."); - }; - - "basic_identifier"_test = [] - { - string id; - test_grammar("iden_tifier", vb6_grammar::basic_identifier, id); - ut::expect(id == "iden_tifier"); - }; - - "var_identifier"_test = [] - { - string var; - test_grammar("g_logger", vb6_grammar::var_identifier, var); - ut::expect(var == "g_logger"); - - var.clear(); - test_grammar("forth ", vb6_grammar::sub_identifier, var); - ut::expect(var == "forth"); - - var.clear(); - test_grammar("subroutine ", vb6_grammar::sub_identifier, var); - ut::expect(var == "subroutine"); - - /* - string code = "sub "; - string_view code_sv = code; - auto it1 = cbegin(code_sv); - auto const it2 = cend(code_sv); - ut::expect(!boost::spirit::x3::phrase_parse(it1, it2, vb6_grammar::sub_identifier, vb6_grammar::skip, var)); - ut::expect(it1 == begin(code_sv)); - */ - }; - - "type_identifier"_test = [] - { - string type; - test_grammar("Long", vb6_grammar::type_identifier, type); - ut::expect(that % type == "Long"s); - }; - - "complex_type_identifier"_test = [] - { - string type; - test_grammar("VB.Form", vb6_grammar::complex_type_identifier, type); - ut::expect(that % type == "VB.Form"s); - }; - - "const_expression_non_numeric"_test = [] - { - vb6_ast::const_expr ast; - string str; - - str = "\"una stringa\""s; - test_grammar(str, vb6_grammar::const_expression, ast); - ut::expect(that % boost::get(ast.get()) == "una stringa"s); - - str = "True"s; - test_grammar(str, vb6_grammar::const_expression, ast); - ut::expect(boost::get(ast.get())); - - str = "Nothing"s; - test_grammar(str, vb6_grammar::const_expression, ast); - ut::expect(ut::nothrow([ast](){boost::get(ast.get());})); - }; - - "const_expression_integers"_test = [] - { - vb6_ast::const_expr ast; - string str; - - str = "1234%"s; - test_grammar(str, vb6_grammar::const_expression, ast); - ut::expect(that % boost::get(ast.get()).val == 1234); - - str = "1234&"s; - test_grammar(str, vb6_grammar::const_expression, ast); - ut::expect(that % boost::get(ast.get()).val == 1234); - - str = "&Hcafedead&"s; - test_grammar(str, vb6_grammar::const_expression, ast); - ut::expect(that % boost::get(ast.get()).val == 0xcafedead); - - str = "&01234&"s; - test_grammar(str, vb6_grammar::const_expression, ast); - ut::expect(that % boost::get(ast.get()).val == 01234); - - str = "1234"s; - test_grammar(str, vb6_grammar::const_expression, ast); - ut::expect(that % boost::get(ast.get()).val == 1234); - }; - - "const_expression_floats"_test = [] - { - vb6_ast::const_expr ast; - string str; - - str = "1234!"s; - test_grammar(str, vb6_grammar::const_expression, ast); - ut::expect(boost::get(ast.get()) == 1234.0f); - - str = "1234#"s; - test_grammar(str, vb6_grammar::const_expression, ast); - ut::expect(boost::get(ast.get()) == 1234.0); - - str = "2.8"s; - test_grammar(str, vb6_grammar::const_expression, ast); - ut::expect(boost::get(ast.get()) == 2.8f); - }; - - "sample_expression"_test = [] - { - vb6_ast::expression ast; - test_grammar("foo1(foo2(3, M.x_coord), True)", vb6_grammar::expression, ast); - ut::expect(ast.get().type() == typeid(x3::forward_ast)); - ut::expect(boost::get>(ast.get()).get().func_name == "foo1"); - }; - - "single_var_declaration"_test = [] - { - vb6_ast::variable P1; - test_grammar("g_logger As Long", vb6_grammar::single_var_declaration, P1); - ut::expect(P1.name == "g_logger"); - ut::expect(!P1.construct); - ut::expect(P1.type.has_value()); - if(P1.type) - { - ut::expect(*P1.type == "Long"); - } - - vb6_ast::variable P2; - test_grammar("name As New String", vb6_grammar::single_var_declaration, P2); - ut::expect(P2.name == "name"); - ut::expect(P2.construct); - ut::expect(P2.type.has_value()); - if(P2.type) - { - ut::expect(*P2.type == "String"); - } - }; - }; - - [[maybe_unused]] ut::suite vb6_parser_tests = [] - { - "record_declaration"_test = [] - { - 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); - - ut::expect(rec.name == "PatRecord"); - ut::expect(rec.at == vb6_ast::access_type::na); - ut::expect(rec.members.size() == 2); - - ut::expect(rec.members[0].name == "name"); - ut::expect(rec.members[0].type.has_value()); - if(rec.members[0].type) - { - ut::expect(*rec.members[0].type == "String"); - } - ut::expect(!rec.members[0].construct); - - ut::expect(rec.members[1].name == "age"); - ut::expect(rec.members[1].type.has_value()); - if(rec.members[1].type) - { - ut::expect(*rec.members[1].type == "Integer"); - } - ut::expect(!rec.members[1].construct); - }; - - "enum_declaration"_test = [] - { - 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); - - ut::expect(enum1.name == "PatTypes"); - ut::expect(enum1.at == vb6_ast::access_type::na); - ut::expect((enum1.values.size() == 2) >> ut::fatal); - - ut::expect(enum1.values[0].first == "inpatient"); - ut::expect(!enum1.values[0].second); - - ut::expect(enum1.values[1].first == "outpatient"); - ut::expect(!enum1.values[1].second); - }; - - "global_var_declaration"_test = [] - { - 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); - - ut::expect(vars.at == vb6_ast::access_type::global); - ut::expect(!vars.with_events); - ut::expect((vars.vars.size() == 5) >> ut::fatal); - - cout << "size: " << vars.vars.size() << "\n"; - - ut::expect(vars.vars[0].name == "g_logger"); - ut::expect(!vars.vars[0].construct); - ut::expect(vars.vars[0].type.has_value()); - if(vars.vars[0].type) - { - ut::expect(*vars.vars[0].type == "Long"); - } - - ut::expect(vars.vars[1].name == "v1"); - ut::expect(!vars.vars[1].construct); - ut::expect(!vars.vars[1].type.has_value()); - - ut::expect(vars.vars[2].name == "XRes"); - ut::expect(vars.vars[2].construct); - ut::expect(vars.vars[2].type.has_value()); - if(vars.vars[2].type) - { - ut::expect(*vars.vars[2].type == "Object"); - } - - ut::expect(vars.vars[3].name == "ptr"); - ut::expect(!vars.vars[3].construct); - ut::expect(vars.vars[3].type.has_value()); - if(vars.vars[3].type) - { - ut::expect(*vars.vars[3].type == "MyRec"); - } - - ut::expect(vars.vars[4].name == "g_active"); - ut::expect(!vars.vars[4].construct); - ut::expect(vars.vars[4].type.has_value()); - if(vars.vars[4].type) - { - ut::expect(*vars.vars[4].type == "Boolean"); - } - }; - - "const_var_declaration1"_test = [] - { - 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); - - ut::expect((cvars.size() == 3) >> ut::fatal); - - cout << "size: " << cvars.size() << "\n"; - - ut::expect(cvars[0].var.name == "e"); - if(cvars[0].var.type) - { - ut::expect(*cvars[0].var.type == "Single"); - } - ut::expect(!cvars[0].var.construct); - ut::expect(boost::get(cvars[0].value.get()) == 2.8f); - - ut::expect(cvars[1].var.name == "pi"); - if(cvars[1].var.type) - { - ut::expect(*cvars[1].var.type == "Double"); - } - ut::expect(!cvars[1].var.construct); - ut::expect(boost::get(cvars[1].value.get()) == 3.14f); - - ut::expect(cvars[2].var.name == "u"); - if(cvars[2].var.type) - { - ut::expect(*cvars[2].var.type == "Integer"); - } - ut::expect(!cvars[2].var.construct); - ut::expect(boost::get(cvars[2].value.get()).val == -1); - }; - - "const_var_declaration2"_test = [] - { - vb6_ast::const_var_stat cvars; - test_grammar("Private Const PI As Double = 3.1415\r\n", - vb6_grammar::const_var_declaration, cvars); - - ut::expect((cvars.size() == 1) >> ut::fatal); - - cout << "size: " << cvars.size() << "\n"; - - ut::expect(cvars[0].var.name == "PI"); - if(cvars[0].var.type) - { - ut::expect(*cvars[0].var.type == "Double"); - } - ut::expect(!cvars[0].var.construct); - ut::expect(boost::get(cvars[0].value.get()) == 3.1415f); - }; - - "param_decl"_test = [] - { - vb6_ast::func_param fp; - test_grammar("Optional ByVal name As String = \"pippo\"", vb6_grammar::param_decl, fp); - - ut::expect(fp.isoptional); - ut::expect(fp.qualifier.has_value()); - if(fp.qualifier) - { - ut::expect(*fp.qualifier == vb6_ast::param_qualifier::byval); - } - ut::expect(fp.var.name == "name"); - ut::expect(!fp.var.construct); - ut::expect(fp.var.type.has_value()); - if(fp.var.type) - { - ut::expect(*fp.var.type == "String"); - } - ut::expect(fp.defvalue.has_value()); - if(fp.defvalue) - { - ut::expect(boost::get(fp.defvalue.get()) == "pippo"); - } - }; - - "param_list_decl"_test = [] - { - vector fps; - test_grammar("ByVal name As String, ByRef val As Integer", - -(vb6_grammar::param_decl % ','), fps); - - cout << "size: " << fps.size() << "\n"; - - ut::expect((fps.size() == 2) >> ut::fatal); - - ut::expect(!fps[0].isoptional); - ut::expect(fps[0].qualifier.has_value()); - if(fps[0].qualifier) - { - ut::expect(*fps[0].qualifier == vb6_ast::param_qualifier::byval); - } - ut::expect(fps[0].var.name == "name"); - ut::expect(!fps[0].var.construct); - ut::expect(fps[0].var.type.has_value()); - if(fps[0].var.type) - { - ut::expect(*fps[0].var.type == "String"); - } - ut::expect(!fps[0].defvalue); - - ut::expect(!fps[1].isoptional); - ut::expect(fps[1].qualifier.has_value()); - if(fps[1].qualifier) - { - ut::expect(*fps[1].qualifier == vb6_ast::param_qualifier::byref); - } - ut::expect(fps[1].var.name == "val"); - ut::expect(!fps[1].var.construct); - ut::expect(fps[1].var.type.has_value()); - if(fps[1].var.type) - { - ut::expect(*fps[1].var.type == "Integer"); - } - ut::expect(!fps[1].defvalue); - }; - - "event_declaration"_test = [] - { - vb6_ast::eventHead event_decl; - test_grammar("Public Event OnChange(ByVal Text As String)\r\n", - vb6_grammar::eventHead, event_decl); - - ut::expect(event_decl.at == vb6_ast::access_type::public_); - ut::expect(event_decl.name == "OnChange"); - ut::expect(event_decl.params.size() == 1); - }; - - "function_head"_test = [] - { - 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); - - ut::expect(fh.at == vb6_ast::access_type::private_); - ut::expect(fh.name == "OneFunc"); - ut::expect(fh.params.size() == 2); - ut::expect(fh.return_type.has_value()); - ut::expect(*fh.return_type == "Integer"); - }; - - "function_head_no_params"_test = [] - { - vb6_ast::functionHead fh; - test_grammar( - "Private Function NoParamFunc() As Object\r\n", vb6_grammar::functionHead, fh); - - ut::expect(fh.at == vb6_ast::access_type::private_); - ut::expect(fh.name == "NoParamFunc"); - ut::expect(fh.params.empty()); - ut::expect((fh.return_type.has_value()) >> ut::fatal); - ut::expect(*fh.return_type == "Object"); - }; - - "subroutine_head"_test = [] - { - vb6_ast::subHead sh; - test_grammar( - "Private Sub my_sub(ByRef str As String, ByVal valid As Boolean)\r\n", - vb6_grammar::subHead, sh); - - ut::expect(sh.at == vb6_ast::access_type::private_); - ut::expect(sh.name == "my_sub"); - ut::expect(sh.params.size() == 2); - }; - - "subroutine_head2"_test = [] - { - 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); - - ut::expect(sh.at == vb6_ast::access_type::private_); - ut::expect(sh.name == "my_sub"); - ut::expect(sh.params.size() == 3); - }; - - "subroutine_head_with_optional_params"_test = [] - { - 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); - - ut::expect(sh.at == vb6_ast::access_type::private_); - ut::expect(sh.name == "my_sub"); - ut::expect((sh.params.size() == 2) >> ut::fatal); - - ut::expect(sh.params[0].qualifier.has_value()); - if (sh.params[0].qualifier) - ut::expect(sh.params[0].qualifier.get() == vb6_ast::param_qualifier::byref); - ut::expect(sh.params[0].var.name == "str"); - if(sh.params[0].var.type) - { - ut::expect(*sh.params[0].var.type == "String"); - } - ut::expect(!sh.params[0].var.construct); - ut::expect(!sh.params[0].isoptional); - ut::expect(!sh.params[0].defvalue); - - ut::expect(sh.params[1].qualifier.has_value()); - if(sh.params[1].qualifier) - { - ut::expect(sh.params[1].qualifier.get() == vb6_ast::param_qualifier::byval); - } - ut::expect(sh.params[1].var.name == "valid"); - if(sh.params[1].var.type) - { - ut::expect(*sh.params[1].var.type == "Boolean"); - } - ut::expect(!sh.params[1].var.construct); - ut::expect(sh.params[1].isoptional); - ut::expect(sh.params[1].defvalue.has_value()); - if(sh.params[0].defvalue) - { - ut::expect(boost::get(*sh.params[0].defvalue) == false); - } - }; - - "property_let_head"_test = [] - { - 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); - - ut::expect(ast.at == vb6_ast::access_type::public_); - ut::expect(ast.name == "Width"); - ut::expect(ast.params.size() == 1); - }; - - "property_get_head"_test = [] - { - auto str = "Public Property Get Width() As Integer\r\n"s; - vb6_ast::propertyGetHead ast; - test_grammar(str, vb6_grammar::property_getHead, ast); - - ut::expect(ast.at == vb6_ast::access_type::public_); - ut::expect(ast.name == "Width"); - ut::expect(ast.params.size() == 0); - ut::expect(ast.return_type >> fatal); - ut::expect(*ast.return_type == "Integer"); - }; - - "dll_subroutine_declaration"_test = [] - { - 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); - - ut::expect(extsub.at == vb6_ast::access_type::private_); - ut::expect(extsub.name == "BeepVB"); - ut::expect(extsub.alias == "Beep"); - ut::expect(extsub.params.size() == 2); - ut::expect(extsub.lib == "kernel32.dll"); - }; - - "dll_function_declaration"_test = [] - { - 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); - - ut::expect(extfunc.at == vb6_ast::access_type::private_); - ut::expect(extfunc.name == "BeepVB"); - ut::expect(extfunc.return_type.has_value()); - if(extfunc.return_type) - { - ut::expect(*extfunc.return_type == "Long"); - } - ut::expect(extfunc.alias == "Beep"); - ut::expect(extfunc.params.size() == 2); - ut::expect(extfunc.lib == "kernel32.dll"); - }; - - "identifier_context"_test = [] - { - vb6_ast::identifier_context ctx; - test_grammar("var1.func().pnt1.", vb6_grammar::identifier_context, ctx); - - ut::expect(!ctx.leading_dot); - ut::expect((ctx.elements.size() == 3) >> ut::fatal); - ut::expect(boost::get(ctx.elements[0].get()) == "var1"); - auto& tmp = boost::get>(ctx.elements[1].get()).get(); - ut::expect(tmp.func_name == "func"); - ut::expect(tmp.params.empty()); - ut::expect(boost::get(ctx.elements[2].get()) == "pnt1"); - }; - - "decorated_variable"_test = [] - { - vb6_ast::decorated_variable dec_var; - test_grammar("var1.func().pnt1.X", vb6_grammar::decorated_variable, dec_var); - - ut::expect(!dec_var.ctx.leading_dot); - ut::expect((dec_var.ctx.elements.size() == 3) >> ut::fatal); - ut::expect(boost::get(dec_var.ctx.elements[0].get()) == "var1"); - auto& tmp = boost::get>(dec_var.ctx.elements[1].get()).get(); - ut::expect(tmp.func_name == "func"); - ut::expect(tmp.params.empty()); - ut::expect(boost::get(dec_var.ctx.elements[2].get()) == "pnt1"); - ut::expect(dec_var.var == "X"); - }; - - "attribute_block"_test = [] - { - 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); - - ut::expect(attrs.size() == 2); - - auto it1 = attrs.find("ModuleName"); - ut::expect(it1 != attrs.cend()); - if(it1 != attrs.cend()) - { - ut::expect(it1->second == "MyForm"); - } - - auto it2 = attrs.find("ProgID"); - ut::expect(it2 != attrs.cend()); - if(it2 != attrs.cend()) - { - ut::expect(it2->second == "00-00-00-00"); - } - }; - - "attributes"_test = [] - { - 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); - - ut::expect(ast.size() == 2); - - auto const it1 = ast.find("ModuleName"); - ut::expect(it1 != ast.cend()); - if(it1 != ast.cend()) - { - ut::expect(it1->second == "MyForm"); - } - - auto const it2 = ast.find("ProgID"); - ut::expect(it2 != ast.cend()); - if(it2 != ast.cend()) - { - ut::expect(it2->second == "00-00-00-00"); - } - }; - - "options"_test = [] - { - vector ast; - auto str = R"vb(Option Explicit - Option Base 0 - )vb"s; - test_grammar(str, *vb6_grammar::option_item, ast); - - ut::expect((ast.size() == 2) >> ut::fatal); - - ut::expect(ast[0] == vb6_ast::module_option::explicit_); - ut::expect(ast[1] == vb6_ast::module_option::base_0); - }; - -#if 0 - "declaration_block"_test = [] - { - 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); - - ut::expect((decls.size() == 5) >> ut::fatal); - - ut::expect(ut::nothrow([ast](){boost::get(decls[0].get();}))); - ut::expect(ut::nothrow([ast](){boost::get(decls[1].get();}))); - ut::expect(ut::nothrow([ast](){boost::get(decls[2].get();}))); - ut::expect(ut::nothrow([ast](){boost::get(decls[3].get();}))); - ut::expect(ut::nothrow([ast](){boost::get(decls[4].get();}))); - }; - - "bas_unit_STRICT_MODULE_STRUCTURE"_test = [] - { - 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); - - ut::expect(ast.attrs.size() == 2); - - auto const it1 = ast.attrs.find("ModuleName"); - EXPECT_NE(it1, ast.attrs.cend()); - if(it1 != ast.attrs.cend()) - { - ut::expect(it1->second == "MyForm"); - } - - auto const it2 = ast.attrs.find("ProgID"); - EXPECT_NE(it2, ast.attrs.cend()); - if(it2 != ast.attrs.cend()) - { - ut::expect(it2->second == "00-00-00-00"); - } - - ut::expect(ast.opts.items.size() == 4); - if(ast.opts.items.size() == 4) - { - #if 0 - ut::expect(ast.opts.items[0] == vb6_ast::module_option::explicit_); - ut::expect(ast.opts.items[1] == vb6_ast::module_option::base_0); - #else - ut::expect(boost::get(ast.opts.items[0].get()) == vb6_ast::module_option::explicit_); - ut::expect(boost::get(ast.opts.items[1].get()) == vb6_ast::module_option::base_0); - #endif - ut::expect(ut::nothrow([ast](){boost::get(ast.opts.items[2].get();}))); - ut::expect(ut::nothrow([ast](){boost::get(ast.opts.items[3].get();}))); - } - - ut::expect(ast.declarations.size() == 3); - if(ast.declarations.size() == 3) - { - ut::expect(ut::nothrow([ast](){boost::get(ast.declarations[0].get();}))); - ut::expect(ut::nothrow([ast](){boost::get(ast.declarations[1].get();}))); - ut::expect(ut::nothrow([ast](){boost::get(ast.declarations[2].get();}))); - } - - ut::expect(ast.functions.size() == 2); - if(ast.functions.size() == 2) - { - ut::expect(ut::nothrow([ast](){boost::get(ast.functions[0].get();}))); - ut::expect(ut::nothrow([ast](){boost::get(ast.functions[1].get();}))); - } - }; - #endif - - "bas_unit"_test = [] - { - 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); - - ut::expect(ut::eq(ast.size(), 11) >> ut::fatal); - - ut::expect(boost::get(ast[0].get()).first == "ModuleName"); - ut::expect(boost::get(ast[1].get()).first == "ProgID"); - - ut::expect(boost::get(ast[2].get()) == vb6_ast::module_option::explicit_); - ut::expect(boost::get(ast[3].get()) == vb6_ast::module_option::base_0); - - ut::expect(ut::nothrow([ast](){boost::get(ast[4].get());})); - ut::expect(ut::nothrow([ast](){boost::get(ast[5].get());})); - - ut::expect(ut::nothrow([ast](){boost::get(boost::get(ast[6].get()));})); - ut::expect(ut::nothrow([ast](){boost::get(boost::get(ast[7].get()));})); - ut::expect(ut::nothrow([ast](){boost::get(boost::get(ast[8].get()));})); - - ut::expect(ut::nothrow([ast](){boost::get(ast[9].get());})); - ut::expect(ut::nothrow([ast](){boost::get(ast[10].get());})); - }; - - "trailing_comment"_test = [] - { - 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); - - ut::expect(vars.at == vb6_ast::access_type::global); - ut::expect(!vars.with_events); - ut::expect((vars.vars.size() == 1) >> ut::fatal); - - ut::expect(vars.vars[0].name == "g_var"); - ut::expect(!vars.vars[0].construct); - ut::expect(vars.vars[0].type.has_value()); - if(vars.vars[0].type) - { - ut::expect(*vars.vars[0].type == "Long"); - } - }; - }; - - bool res = boost::ut::cfg.run(); - return res ? 0 : -1; -} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..5276dd4 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,64 @@ +cmake_minimum_required(VERSION 3.28) + +find_package(Boost REQUIRED COMPONENTS system) +find_package(doctest CONFIG REQUIRED) +find_package(GTest CONFIG REQUIRED) +find_package(ut CONFIG REQUIRED) +find_package(Threads REQUIRED) + +add_executable(vb6_parser.ut + vb6_parser.ut.cpp +) + +target_link_libraries(vb6_parser.ut +PRIVATE + vb6_parser_lib + Boost::system + Boost::ut + Threads::Threads +) + +# -------------------------------- + +add_executable(vb6_parser.doctest + vb6_parser.doctest.cpp +) + +target_link_libraries(vb6_parser.doctest +PRIVATE + vb6_parser_lib + Boost::system + doctest::doctest + Threads::Threads +) + +# -------------------------------- + +add_executable(vb6_parser.gtest + test_gosub.cpp + vb6_parser_statements.gtest.cpp + vb6_parser.gtest.cpp + vb6_parser_test_main.cpp +) + +target_link_libraries(vb6_parser.gtest +PRIVATE + vb6_parser_lib + GTest::gtest + GTest::gtest_main + GTest::gmock + GTest::gmock_main + Boost::system + Threads::Threads +) + +# -------------------------------- + +add_test(NAME vb6_parser.ut COMMAND vb6_parser.ut) + +include(GoogleTest) +gtest_discover_tests(vb6_parser.gtest) + +#add_test(NAME vb6_parser.doctest COMMAND vb6_parser.doctest) +include(doctest) +doctest_discover_tests(vb6_parser.doctest) \ No newline at end of file diff --git a/src/test_gosub.cpp b/test/test_gosub.cpp similarity index 100% rename from src/test_gosub.cpp rename to test/test_gosub.cpp diff --git a/src/test_grammar_helper_ut.hpp b/test/test_grammar_helper_ut.hpp similarity index 70% rename from src/test_grammar_helper_ut.hpp rename to test/test_grammar_helper_ut.hpp index c61ed26..580921d 100644 --- a/src/test_grammar_helper_ut.hpp +++ b/test/test_grammar_helper_ut.hpp @@ -10,14 +10,13 @@ #include "vb6_config.hpp" #include -#include #include -#include #include +#include template -void test_grammar(std::string_view fragment, ruleType rule, attrType& attr, bool expected = true) +std::pair test_grammar(std::string_view fragment, ruleType rule, attrType& attr) { namespace x3 = boost::spirit::x3; @@ -36,10 +35,7 @@ void test_grammar(std::string_view fragment, ruleType rule, attrType& attr, bool rule ]; - using namespace boost::ut; + bool res = x3::phrase_parse(it1, it2, parser, vb6_grammar::skip, attr); - expect((x3::phrase_parse(it1, it2, parser, vb6_grammar::skip, attr) == expected) >> fatal) - << "stopped at: " << std::string(it1, it2); - - expect(it1 == it2); + return std::make_pair(res, std::string_view(it1, it2)); } diff --git a/test/vb6_parser.doctest.cpp b/test/vb6_parser.doctest.cpp new file mode 100644 index 0000000..773d3cc --- /dev/null +++ b/test/vb6_parser.doctest.cpp @@ -0,0 +1,1001 @@ +//: vb6_parser.ut.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 +#pragma GCC diagnostic ignored "-Wunused-variable" + +#include "test_grammar_helper_ut.hpp" +#include "vb6_parser.hpp" +#include "vb6_ast_printer.hpp" + +#include +#define DOCTEST_CONFIG_IMPLEMENT +#include + +#include +#include +#include +#include + +using namespace std; +namespace x3 = boost::spirit::x3; + +void log_compiler_info(std::ostream& os) +{ + // how to tell that AppleClang and not plain Clang has been used? + // __APPLE_CC__ does not work as it's always defined +#if defined(__clang__) + os << "Compiler: Clang " << __clang_major__ << "." << __clang_minor__ << "." << __clang_patchlevel__<< '\n'; +#elif defined(__GNUC__) + os << "Compiler: GCC " << __GNUC__ << "." << __GNUC_MINOR__ << "." << __GNUC_PATCHLEVEL__<< '\n'; +#elif defined(__MSC_VER) + os << "Compiler: MSC " << __MSC_VER << '\n'; +#endif +} + +DOCTEST_TEST_SUITE_BEGIN("vb6_parser_simple"); + +DOCTEST_TEST_CASE("lonely_comment") +{ + vector ast; + auto [res, sv] = test_grammar( + "' This is comment line 1\r\n' Comment line 2\r\n", + *vb6_grammar::lonely_comment, ast); + REQUIRE_MESSAGE(res, "stopped at: " << sv); + CHECK(sv.empty()); + + REQUIRE_EQ(ast.size(), 2); + + CHECK_EQ(ast[0].content, " This is comment line 1"); + CHECK_EQ(ast[1].content, " Comment line 2"); +} + +DOCTEST_TEST_CASE("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"; + auto [res, sv] = test_grammar(str, G, ast); + REQUIRE_MESSAGE(res, "stopped at: " << sv); + CHECK(sv.empty()); + + REQUIRE_EQ(ast.size(), 3); + + CHECK_EQ(boost::get(ast[0]).content, " comment1"); + REQUIRE_NOTHROW([ast](){boost::get(ast[1]);}); + CHECK_EQ(boost::get(ast[2]).content, " comment2"); +} + +DOCTEST_TEST_CASE("quoted_string") +{ + string str; + auto [res, sv] = test_grammar("\"Quoted string.\"", vb6_grammar::quoted_string, str); + REQUIRE_MESSAGE(res, "stopped at: " << sv); + CHECK(sv.empty()); + CHECK_EQ(str, "Quoted string."); +} + +DOCTEST_TEST_CASE("basic_identifier") +{ + string id; + auto [res, sv] = test_grammar("iden_tifier", vb6_grammar::basic_identifier, id); + REQUIRE_MESSAGE(res, "stopped at: " << sv); + CHECK(sv.empty()); + CHECK_EQ(id, "iden_tifier"); +} + +DOCTEST_TEST_CASE("var_identifier") +{ + string var; + + DOCTEST_SUBCASE("1") + { + auto [res, sv] = test_grammar("g_logger", vb6_grammar::var_identifier, var); + REQUIRE_MESSAGE(res, "stopped at: " << sv); + CHECK(sv.empty()); + CHECK_EQ(var, "g_logger"); + } + + DOCTEST_SUBCASE("2") + { + auto [res, sv] = test_grammar("forth ", vb6_grammar::sub_identifier, var); + REQUIRE_MESSAGE(res, "stopped at: " << sv); + CHECK(sv.empty()); + CHECK_EQ(var, "forth"); + } + + DOCTEST_SUBCASE("3") + { + string var; + auto [res, sv] = test_grammar("subroutine ", vb6_grammar::sub_identifier, var); + REQUIRE_MESSAGE(res, "stopped at: " << sv); + CHECK(sv.empty()); + CHECK_EQ(var, "subroutine"); + } + + /* + string code = "sub "; + string_view code_sv = code; + auto it1 = cbegin(code_sv); + auto const it2 = cend(code_sv); + CHECK(!boost::spirit::x3::phrase_parse(it1, it2, vb6_grammar::sub_identifier, vb6_grammar::skip, var)); + CHECK_EQ(it1, begin(code_sv)); + */ +} + +DOCTEST_TEST_CASE("type_identifier") +{ + string type; + auto [res, sv] = test_grammar("Long", vb6_grammar::type_identifier, type); + REQUIRE_MESSAGE(res, "stopped at: " << sv); + CHECK(sv.empty()); + CHECK_EQ(type, "Long"s); +} + +DOCTEST_TEST_CASE("complex_type_identifier") +{ + string type; + auto [res, sv] = test_grammar("VB.Form", vb6_grammar::complex_type_identifier, type); + REQUIRE_MESSAGE(res, "stopped at: " << sv); + CHECK(sv.empty()); + CHECK_EQ(type, "VB.Form"s); +} + +DOCTEST_TEST_CASE("const_expression_non_numeric") +{ + vb6_ast::const_expr ast; + string str; + + DOCTEST_SUBCASE("1") + { + str = "\"una stringa\""s; + auto [res, sv] = test_grammar(str, vb6_grammar::const_expression, ast); + REQUIRE_MESSAGE(res, "stopped at: " << sv); + CHECK(sv.empty()); + CHECK_EQ(boost::get(ast.get()), "una stringa"s); + } + + DOCTEST_SUBCASE("2") + { + str = "True"s; + auto [res, sv] = test_grammar(str, vb6_grammar::const_expression, ast); + REQUIRE_MESSAGE(res, "stopped at: " << sv); + CHECK(sv.empty()); + CHECK(boost::get(ast.get())); + } + + DOCTEST_SUBCASE("3") + { + str = "Nothing"s; + auto [res, sv] = test_grammar(str, vb6_grammar::const_expression, ast); + REQUIRE_MESSAGE(res, "stopped at: " << sv); + CHECK(sv.empty()); + REQUIRE_NOTHROW([ast](){boost::get(ast.get());}); + } +} + +DOCTEST_TEST_CASE("const_expression_integers") +{ + vb6_ast::const_expr ast; + string str; + + DOCTEST_SUBCASE("1") + { + str = "1234%"s; + auto [res, sv] = test_grammar(str, vb6_grammar::const_expression, ast); + REQUIRE_MESSAGE(res, "stopped at: " << sv); + CHECK(sv.empty()); + CHECK_EQ(boost::get(ast.get()).val, 1234); + } + + DOCTEST_SUBCASE("2") + { + str = "1234&"s; + auto [res, sv] = test_grammar(str, vb6_grammar::const_expression, ast); + REQUIRE_MESSAGE(res, "stopped at: " << sv); + CHECK(sv.empty()); + CHECK_EQ(boost::get(ast.get()).val, 1234); + } + + DOCTEST_SUBCASE("3") + { + str = "&Hcafedead&"s; + auto [res, sv] = test_grammar(str, vb6_grammar::const_expression, ast); + REQUIRE_MESSAGE(res, "stopped at: " << sv); + CHECK(sv.empty()); + CHECK_EQ(boost::get(ast.get()).val, 0xcafedead); + } + + DOCTEST_SUBCASE("4") + { + str = "&01234&"s; + auto [res, sv] = test_grammar(str, vb6_grammar::const_expression, ast); + REQUIRE_MESSAGE(res, "stopped at: " << sv); + CHECK(sv.empty()); + CHECK_EQ(boost::get(ast.get()).val, 01234); + } + + DOCTEST_SUBCASE("5") + { + str = "1234"s; + auto [res, sv] = test_grammar(str, vb6_grammar::const_expression, ast); + REQUIRE_MESSAGE(res, "stopped at: " << sv); + CHECK(sv.empty()); + CHECK_EQ(boost::get(ast.get()).val, 1234); + } +} + +DOCTEST_TEST_CASE("const_expression_floats") +{ + vb6_ast::const_expr ast; + string str; + + DOCTEST_SUBCASE("1") + { + str = "1234!"s; + auto [res, sv] = test_grammar(str, vb6_grammar::const_expression, ast); + REQUIRE_MESSAGE(res, "stopped at: " << sv); + CHECK(sv.empty()); + CHECK_EQ(boost::get(ast.get()), 1234.0f); + } + + DOCTEST_SUBCASE("2") + { + str = "1234#"s; + auto [res, sv] = test_grammar(str, vb6_grammar::const_expression, ast); + REQUIRE_MESSAGE(res, "stopped at: " << sv); + CHECK(sv.empty()); + CHECK_EQ(boost::get(ast.get()), 1234.0); + } + + DOCTEST_SUBCASE("1") + { + str = "2.8"s; + auto [res, sv] = test_grammar(str, vb6_grammar::const_expression, ast); + REQUIRE_MESSAGE(res, "stopped at: " << sv); + CHECK(sv.empty()); + CHECK_EQ(boost::get(ast.get()), 2.8f); + } +} + +DOCTEST_TEST_CASE("sample_expression") +{ + vb6_ast::expression ast; + auto [res, sv] = test_grammar("foo1(foo2(3, M.x_coord), True)", vb6_grammar::expression, ast); + REQUIRE_MESSAGE(res, "stopped at: " << sv); + CHECK(sv.empty()); + CHECK_EQ(ast.get().type(), typeid(x3::forward_ast)); + CHECK_EQ(boost::get>(ast.get()).get().func_name, "foo1"); +} + +DOCTEST_TEST_CASE("single_var_declaration") +{ + vb6_ast::variable P1; + auto [res, sv] = test_grammar("g_logger As Long", vb6_grammar::single_var_declaration, P1); + REQUIRE_MESSAGE(res, "stopped at: " << sv); + CHECK(sv.empty()); + CHECK_EQ(P1.name, "g_logger"); + CHECK(!P1.construct); + CHECK(P1.type.has_value()); + if(P1.type) + { + CHECK_EQ(*P1.type, "Long"); + } +} + +DOCTEST_TEST_CASE("single_var_declaration2") +{ + vb6_ast::variable P2; + auto [res, sv] = test_grammar("name As New String", vb6_grammar::single_var_declaration, P2); + REQUIRE_MESSAGE(res, "stopped at: " << sv); + CHECK(sv.empty()); + CHECK_EQ(P2.name, "name"); + CHECK(P2.construct); + CHECK(P2.type.has_value()); + if(P2.type) + { + CHECK_EQ(*P2.type, "String"); + } +} + +DOCTEST_TEST_SUITE_END(); + +DOCTEST_TEST_SUITE_BEGIN("vb6_parser_tests"); + +DOCTEST_TEST_CASE("record_declaration") +{ + vb6_ast::record rec; + auto [res, sv] = test_grammar("Type PatRecord\r\n name As String\r\n age As Integer\r\nEnd Type\r\n", + vb6_grammar::record_declaration, rec); + REQUIRE_MESSAGE(res, "stopped at: " << sv); + CHECK(sv.empty()); + + CHECK_EQ(rec.name, "PatRecord"); + CHECK_EQ(rec.at, vb6_ast::access_type::na); + REQUIRE_EQ(rec.members.size(), 2); + + CHECK_EQ(rec.members[0].name, "name"); + CHECK(rec.members[0].type.has_value()); + if(rec.members[0].type) + { + CHECK_EQ(*rec.members[0].type, "String"); + } + CHECK(!rec.members[0].construct); + + CHECK_EQ(rec.members[1].name, "age"); + CHECK(rec.members[1].type.has_value()); + if(rec.members[1].type) + { + CHECK_EQ(*rec.members[1].type, "Integer"); + } + CHECK(!rec.members[1].construct); +} + +DOCTEST_TEST_CASE("enum_declaration") +{ + vb6_ast::vb_enum enum1; + auto [res, sv] = test_grammar("Enum PatTypes\r\n inpatient\r\n outpatient\r\nEnd Enum\r\n", + vb6_grammar::enum_declaration, enum1); + REQUIRE_MESSAGE(res, "stopped at: " << sv); + CHECK(sv.empty()); + + CHECK_EQ(enum1.name, "PatTypes"); + CHECK_EQ(enum1.at, vb6_ast::access_type::na); + REQUIRE_EQ(enum1.values.size(), 2); + + CHECK_EQ(enum1.values[0].first, "inpatient"); + CHECK(!enum1.values[0].second); + + CHECK_EQ(enum1.values[1].first, "outpatient"); + CHECK(!enum1.values[1].second); +} + +DOCTEST_TEST_CASE("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; + auto [res, sv] = test_grammar(str, vb6_grammar::global_var_declaration, vars); + REQUIRE_MESSAGE(res, "stopped at: " << sv); + CHECK(sv.empty()); + + CHECK_EQ(vars.at, vb6_ast::access_type::global); + CHECK(!vars.with_events); + REQUIRE_EQ(vars.vars.size(), 5); + + cout << "size: " << vars.vars.size() << "\n"; + + CHECK_EQ(vars.vars[0].name, "g_logger"); + CHECK(!vars.vars[0].construct); + CHECK(vars.vars[0].type.has_value()); + if(vars.vars[0].type) + { + CHECK_EQ(*vars.vars[0].type, "Long"); + } + + CHECK_EQ(vars.vars[1].name, "v1"); + CHECK(!vars.vars[1].construct); + CHECK(!vars.vars[1].type.has_value()); + + CHECK_EQ(vars.vars[2].name, "XRes"); + CHECK(vars.vars[2].construct); + CHECK(vars.vars[2].type.has_value()); + if(vars.vars[2].type) + { + CHECK_EQ(*vars.vars[2].type, "Object"); + } + + CHECK_EQ(vars.vars[3].name, "ptr"); + CHECK(!vars.vars[3].construct); + CHECK(vars.vars[3].type.has_value()); + if(vars.vars[3].type) + { + CHECK_EQ(*vars.vars[3].type, "MyRec"); + } + + CHECK_EQ(vars.vars[4].name, "g_active"); + CHECK(!vars.vars[4].construct); + CHECK(vars.vars[4].type.has_value()); + if(vars.vars[4].type) + { + CHECK_EQ(*vars.vars[4].type, "Boolean"); + } +} + +DOCTEST_TEST_CASE("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; + auto [res, sv] = test_grammar(cstr, vb6_grammar::const_var_declaration, cvars); + REQUIRE_MESSAGE(res, "stopped at: " << sv); + CHECK(sv.empty()); + + REQUIRE_EQ(cvars.size(), 3); + + cout << "size: " << cvars.size() << "\n"; + + CHECK_EQ(cvars[0].var.name, "e"); + if(cvars[0].var.type) + { + CHECK_EQ(*cvars[0].var.type, "Single"); + } + CHECK(!cvars[0].var.construct); + CHECK_EQ(boost::get(cvars[0].value.get()), 2.8f); + + CHECK_EQ(cvars[1].var.name, "pi"); + if(cvars[1].var.type) + { + CHECK_EQ(*cvars[1].var.type, "Double"); + } + CHECK(!cvars[1].var.construct); + CHECK_EQ(boost::get(cvars[1].value.get()), 3.14f); + + CHECK_EQ(cvars[2].var.name, "u"); + if(cvars[2].var.type) + { + CHECK_EQ(*cvars[2].var.type, "Integer"); + } + CHECK(!cvars[2].var.construct); + CHECK_EQ(boost::get(cvars[2].value.get()).val, -1); +} + +DOCTEST_TEST_CASE("const_var_declaration2") +{ + vb6_ast::const_var_stat cvars; + auto [res, sv] = test_grammar("Private Const PI As Double = 3.1415\r\n", + vb6_grammar::const_var_declaration, cvars); + REQUIRE_MESSAGE(res, "stopped at: " << sv); + CHECK(sv.empty()); + + REQUIRE_EQ(cvars.size(), 1); + + cout << "size: " << cvars.size() << "\n"; + + CHECK_EQ(cvars[0].var.name, "PI"); + if(cvars[0].var.type) + { + CHECK_EQ(*cvars[0].var.type, "Double"); + } + CHECK(!cvars[0].var.construct); + CHECK_EQ(boost::get(cvars[0].value.get()), 3.1415f); +} + +DOCTEST_TEST_CASE("param_decl") +{ + vb6_ast::func_param fp; + auto [res, sv] = test_grammar("Optional ByVal name As String = \"pippo\"", vb6_grammar::param_decl, fp); + REQUIRE_MESSAGE(res, "stopped at: " << sv); + CHECK(sv.empty()); + + CHECK(fp.isoptional); + CHECK(fp.qualifier.has_value()); + if(fp.qualifier) + { + CHECK_EQ(*fp.qualifier, vb6_ast::param_qualifier::byval); + } + CHECK_EQ(fp.var.name, "name"); + CHECK(!fp.var.construct); + CHECK(fp.var.type.has_value()); + if(fp.var.type) + { + CHECK_EQ(*fp.var.type, "String"); + } + CHECK(fp.defvalue.has_value()); + if(fp.defvalue) + { + CHECK_EQ(boost::get(fp.defvalue.get()), "pippo"); + } +} + +DOCTEST_TEST_CASE("param_list_decl") +{ + vector fps; + auto [res, sv] = test_grammar("ByVal name As String, ByRef val As Integer", + -(vb6_grammar::param_decl % ','), fps); + REQUIRE_MESSAGE(res, "stopped at: " << sv); + CHECK(sv.empty()); + + cout << "size: " << fps.size() << "\n"; + + REQUIRE_EQ(fps.size(), 2); + + CHECK(!fps[0].isoptional); + CHECK(fps[0].qualifier.has_value()); + if(fps[0].qualifier) + { + CHECK_EQ(*fps[0].qualifier, vb6_ast::param_qualifier::byval); + } + CHECK_EQ(fps[0].var.name, "name"); + CHECK(!fps[0].var.construct); + CHECK(fps[0].var.type.has_value()); + if(fps[0].var.type) + { + CHECK_EQ(*fps[0].var.type, "String"); + } + CHECK(!fps[0].defvalue); + + CHECK(!fps[1].isoptional); + CHECK(fps[1].qualifier.has_value()); + if(fps[1].qualifier) + { + CHECK_EQ(*fps[1].qualifier, vb6_ast::param_qualifier::byref); + } + CHECK_EQ(fps[1].var.name, "val"); + CHECK(!fps[1].var.construct); + CHECK(fps[1].var.type.has_value()); + if(fps[1].var.type) + { + CHECK_EQ(*fps[1].var.type, "Integer"); + } + CHECK(!fps[1].defvalue); +} + +DOCTEST_TEST_CASE("event_declaration") +{ + vb6_ast::eventHead event_decl; + auto [res, sv] = test_grammar("Public Event OnChange(ByVal Text As String)\r\n", + vb6_grammar::eventHead, event_decl); + REQUIRE_MESSAGE(res, "stopped at: " << sv); + CHECK(sv.empty()); + + CHECK_EQ(event_decl.at, vb6_ast::access_type::public_); + CHECK_EQ(event_decl.name, "OnChange"); + CHECK_EQ(event_decl.params.size(), 1); +} + +DOCTEST_TEST_CASE("function_head") +{ + vb6_ast::functionHead fh; + auto [res, sv] = test_grammar( + "Private Function OneFunc(ByVal name As String, ByRef val As Integer) As Integer\r\n", + vb6_grammar::functionHead, fh); + REQUIRE_MESSAGE(res, "stopped at: " << sv); + CHECK(sv.empty()); + + CHECK_EQ(fh.at, vb6_ast::access_type::private_); + CHECK_EQ(fh.name, "OneFunc"); + CHECK_EQ(fh.params.size(), 2); + REQUIRE(fh.return_type.has_value()); + CHECK_EQ(*fh.return_type, "Integer"); +} + +DOCTEST_TEST_CASE("function_head_no_params") +{ + vb6_ast::functionHead fh; + auto [res, sv] = test_grammar( + "Private Function NoParamFunc() As Object\r\n", vb6_grammar::functionHead, fh); + REQUIRE_MESSAGE(res, "stopped at: " << sv); + CHECK(sv.empty()); + + CHECK_EQ(fh.at, vb6_ast::access_type::private_); + CHECK_EQ(fh.name, "NoParamFunc"); + CHECK(fh.params.empty()); + REQUIRE(fh.return_type.has_value()); + CHECK_EQ(*fh.return_type, "Object"); +} + +DOCTEST_TEST_CASE("subroutine_head") +{ + vb6_ast::subHead sh; + auto [res, sv] = test_grammar( + "Private Sub my_sub(ByRef str As String, ByVal valid As Boolean)\r\n", + vb6_grammar::subHead, sh); + REQUIRE_MESSAGE(res, "stopped at: " << sv); + CHECK(sv.empty()); + + CHECK_EQ(sh.at, vb6_ast::access_type::private_); + CHECK_EQ(sh.name, "my_sub"); + CHECK_EQ(sh.params.size(), 2); +} + +DOCTEST_TEST_CASE("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; + auto [res, sv] = test_grammar(str, vb6_grammar::subHead, sh); + REQUIRE_MESSAGE(res, "stopped at: " << sv); + CHECK(sv.empty()); + + CHECK_EQ(sh.at, vb6_ast::access_type::private_); + CHECK_EQ(sh.name, "my_sub"); + CHECK_EQ(sh.params.size(), 3); +} + +DOCTEST_TEST_CASE("subroutine_head_with_optional_params") +{ + vb6_ast::subHead sh; + auto [res, sv] = test_grammar( + "Private Sub my_sub(ByRef str As String, Optional ByVal valid As Boolean = false)\r\n", + vb6_grammar::subHead, sh); + REQUIRE_MESSAGE(res, "stopped at: " << sv); + CHECK(sv.empty()); + + CHECK_EQ(sh.at, vb6_ast::access_type::private_); + CHECK_EQ(sh.name, "my_sub"); + REQUIRE_EQ(sh.params.size(), 2); + + CHECK(sh.params[0].qualifier.has_value()); + if (sh.params[0].qualifier) + CHECK_EQ(sh.params[0].qualifier.get(), vb6_ast::param_qualifier::byref); + CHECK_EQ(sh.params[0].var.name, "str"); + if(sh.params[0].var.type) + { + CHECK_EQ(*sh.params[0].var.type, "String"); + } + CHECK(!sh.params[0].var.construct); + CHECK(!sh.params[0].isoptional); + CHECK(!sh.params[0].defvalue); + + CHECK(sh.params[1].qualifier.has_value()); + if(sh.params[1].qualifier) + { + CHECK_EQ(sh.params[1].qualifier.get(), vb6_ast::param_qualifier::byval); + } + CHECK_EQ(sh.params[1].var.name, "valid"); + if(sh.params[1].var.type) + { + CHECK_EQ(*sh.params[1].var.type, "Boolean"); + } + CHECK(!sh.params[1].var.construct); + CHECK(sh.params[1].isoptional); + CHECK(sh.params[1].defvalue.has_value()); + if(sh.params[0].defvalue) + { + CHECK_EQ(boost::get(*sh.params[0].defvalue), false); + } +} + +DOCTEST_TEST_CASE("property_let_head") +{ + auto str = "Public Property Let Width(ByVal w As Integer)\r\n"s; + vb6_ast::propertyLetHead ast; + auto [res, sv] = test_grammar(str, vb6_grammar::property_letHead, ast); + REQUIRE_MESSAGE(res, "stopped at: " << sv); + CHECK(sv.empty()); + + CHECK_EQ(ast.at, vb6_ast::access_type::public_); + CHECK_EQ(ast.name, "Width"); + CHECK_EQ(ast.params.size(), 1); +} + +DOCTEST_TEST_CASE("property_get_head") +{ + auto str = "Public Property Get Width() As Integer\r\n"s; + vb6_ast::propertyGetHead ast; + auto [res, sv] = test_grammar(str, vb6_grammar::property_getHead, ast); + REQUIRE_MESSAGE(res, "stopped at: " << sv); + CHECK(sv.empty()); + + CHECK_EQ(ast.at, vb6_ast::access_type::public_); + CHECK_EQ(ast.name, "Width"); + CHECK_EQ(ast.params.size(), 0); + REQUIRE(ast.return_type); + CHECK_EQ(*ast.return_type, "Integer"); +} + +DOCTEST_TEST_CASE("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; + auto [res, sv] = test_grammar(str, vb6_grammar::external_sub_decl, extsub); + REQUIRE_MESSAGE(res, "stopped at: " << sv); + CHECK(sv.empty()); + + CHECK_EQ(extsub.at, vb6_ast::access_type::private_); + CHECK_EQ(extsub.name, "BeepVB"); + CHECK_EQ(extsub.alias, "Beep"); + CHECK_EQ(extsub.params.size(), 2); + CHECK_EQ(extsub.lib, "kernel32.dll"); +} + +DOCTEST_TEST_CASE("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; + auto [res, sv] = test_grammar(str, vb6_grammar::external_function_decl, extfunc); + REQUIRE_MESSAGE(res, "stopped at: " << sv); + CHECK(sv.empty()); + + CHECK_EQ(extfunc.at, vb6_ast::access_type::private_); + CHECK_EQ(extfunc.name, "BeepVB"); + CHECK(extfunc.return_type.has_value()); + if(extfunc.return_type) + { + CHECK_EQ(*extfunc.return_type, "Long"); + } + CHECK_EQ(extfunc.alias, "Beep"); + CHECK_EQ(extfunc.params.size(), 2); + CHECK_EQ(extfunc.lib, "kernel32.dll"); +} + +DOCTEST_TEST_CASE("identifier_context") +{ + vb6_ast::identifier_context ctx; + auto [res, sv] = test_grammar("var1.func().pnt1.", vb6_grammar::identifier_context, ctx); + REQUIRE_MESSAGE(res, "stopped at: " << sv); + CHECK(sv.empty()); + + CHECK(!ctx.leading_dot); + REQUIRE_EQ(ctx.elements.size(), 3); + CHECK_EQ(boost::get(ctx.elements[0].get()), "var1"); + auto& tmp = boost::get>(ctx.elements[1].get()).get(); + CHECK_EQ(tmp.func_name, "func"); + CHECK(tmp.params.empty()); + CHECK_EQ(boost::get(ctx.elements[2].get()), "pnt1"); +} + +DOCTEST_TEST_CASE("decorated_variable") +{ + vb6_ast::decorated_variable dec_var; + auto [res, sv] = test_grammar("var1.func().pnt1.X", vb6_grammar::decorated_variable, dec_var); + REQUIRE_MESSAGE(res, "stopped at: " << sv); + CHECK(sv.empty()); + + CHECK(!dec_var.ctx.leading_dot); + REQUIRE_EQ(dec_var.ctx.elements.size(), 3); + CHECK_EQ(boost::get(dec_var.ctx.elements[0].get()), "var1"); + auto& tmp = boost::get>(dec_var.ctx.elements[1].get()).get(); + CHECK_EQ(tmp.func_name, "func"); + CHECK(tmp.params.empty()); + CHECK_EQ(boost::get(dec_var.ctx.elements[2].get()), "pnt1"); + CHECK_EQ(dec_var.var, "X"); +} + +DOCTEST_TEST_CASE("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; + auto [res, sv] = test_grammar(str, *vb6_grammar::attributeDef, attrs); + REQUIRE_MESSAGE(res, "stopped at: " << sv); + CHECK(sv.empty()); + + CHECK_EQ(attrs.size(), 2); + + auto it1 = attrs.find("ModuleName"); + CHECK(it1 != attrs.cend()); + if(it1 != attrs.cend()) + { + CHECK_EQ(it1->second, "MyForm"); + } + + auto it2 = attrs.find("ProgID"); + CHECK(it2 != attrs.cend()); + if(it2 != attrs.cend()) + { + CHECK_EQ(it2->second, "00-00-00-00"); + } +} + +DOCTEST_TEST_CASE("attributes") +{ + vb6_ast::STRICT_MODULE_STRUCTURE::module_attributes ast; + auto str = R"vb(Attribute ModuleName = "MyForm" + Attribute ProgID = "00-00-00-00" + )vb"s; + //auto [res, sv] = test_grammar(str, vb6_grammar::preamble, ast); + auto [res, sv] = test_grammar(str, *vb6_grammar::attributeDef, ast); + REQUIRE_MESSAGE(res, "stopped at: " << sv); + CHECK(sv.empty()); + + CHECK_EQ(ast.size(), 2); + + auto const it1 = ast.find("ModuleName"); + CHECK(it1 != ast.cend()); + if(it1 != ast.cend()) + { + CHECK_EQ(it1->second, "MyForm"); + } + + auto const it2 = ast.find("ProgID"); + CHECK(it2 != ast.cend()); + if(it2 != ast.cend()) + { + CHECK_EQ(it2->second, "00-00-00-00"); + } +} + +DOCTEST_TEST_CASE("options") +{ + vector ast; + auto str = R"vb(Option Explicit + Option Base 0 + )vb"s; + auto [res, sv] = test_grammar(str, *vb6_grammar::option_item, ast); + REQUIRE_MESSAGE(res, "stopped at: " << sv); + CHECK(sv.empty()); + + REQUIRE_EQ(ast.size(), 2); + + CHECK_EQ(ast[0], vb6_ast::module_option::explicit_); + CHECK_EQ(ast[1], vb6_ast::module_option::base_0); +} + +#if 0 +DOCTEST_TEST_CASE("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; + auto [res, sv] = test_grammar(str, declarations, decls); + REQUIRE_MESSAGE(res, "stopped at: " << sv); + CHECK(sv.empty()); + + REQUIRE_EQ(decls.size(), 5); + + REQUIRE_NOTHROW([ast](){boost::get(decls[0].get();})); + REQUIRE_NOTHROW([ast](){boost::get(decls[1].get();})); + REQUIRE_NOTHROW([ast](){boost::get(decls[2].get();})); + REQUIRE_NOTHROW([ast](){boost::get(decls[3].get();})); + REQUIRE_NOTHROW([ast](){boost::get(decls[4].get();}))); +} + +DOCTEST_TEST_CASE("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; + auto [res, sv] = test_grammar(str, vb6_grammar::STRICT_MODULE_STRUCTURE::basModDef, ast); + REQUIRE_MESSAGE(res, "stopped at: " << sv); + CHECK(sv.empty()); + + vb6_ast_printer P(cout); + P(ast); + + CHECK_EQ(ast.attrs.size(), 2); + + auto const it1 = ast.attrs.find("ModuleName"); + EXPECT_NE(it1, ast.attrs.cend()); + if(it1 != ast.attrs.cend()) + { + CHECK_EQ(it1->second, "MyForm"); + } + + auto const it2 = ast.attrs.find("ProgID"); + EXPECT_NE(it2, ast.attrs.cend()); + if(it2 != ast.attrs.cend()) + { + CHECK_EQ(it2->second, "00-00-00-00"); + } + + CHECK_EQ(ast.opts.items.size(), 4); + if(ast.opts.items.size() == 4) + { +#if 0 + CHECK_EQ(ast.opts.items[0], vb6_ast::module_option::explicit_); + CHECK_EQ(ast.opts.items[1], vb6_ast::module_option::base_0); +#else + CHECK_EQ(boost::get(ast.opts.items[0].get()), vb6_ast::module_option::explicit_); + CHECK_EQ(boost::get(ast.opts.items[1].get()), vb6_ast::module_option::base_0); +#endif + REQUIRE_NOTHROW([ast](){boost::get(ast.opts.items[2].get();})); + REQUIRE_NOTHROW([ast](){boost::get(ast.opts.items[3].get();})); + } + + CHECK_EQ(ast.declarations.size(), 3); + if(ast.declarations.size() == 3) + { + REQUIRE_NOTHROW([ast](){boost::get(ast.declarations[0].get();})); + REQUIRE_NOTHROW([ast](){boost::get(ast.declarations[1].get();})); + REQUIRE_NOTHROW([ast](){boost::get(ast.declarations[2].get();})); + } + + CHECK_EQ(ast.functions.size(), 2); + if(ast.functions.size() == 2) + { + REQUIRE_NOTHROW([ast](){boost::get(ast.functions[0].get();}))); + REQUIRE_NOTHROW([ast](){boost::get(ast.functions[1].get();}))); + } +} +#endif + +DOCTEST_TEST_CASE("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; + auto [res, sv] = test_grammar(str, vb6_grammar::basModDef, ast); + REQUIRE_MESSAGE(res, "stopped at: " << sv); + CHECK(sv.empty()); + + vb6_ast_printer P(cout); + P(ast); + + REQUIRE_EQ(ast.size(), 11); + + CHECK_EQ(boost::get(ast[0].get()).first, "ModuleName"); + CHECK_EQ(boost::get(ast[1].get()).first, "ProgID"); + + CHECK_EQ(boost::get(ast[2].get()), vb6_ast::module_option::explicit_); + CHECK_EQ(boost::get(ast[3].get()), vb6_ast::module_option::base_0); + + REQUIRE_NOTHROW([ast](){boost::get(ast[4].get());}); + REQUIRE_NOTHROW([ast](){boost::get(ast[5].get());}); + + REQUIRE_NOTHROW([ast](){boost::get(boost::get(ast[6].get()));}); + REQUIRE_NOTHROW([ast](){boost::get(boost::get(ast[7].get()));}); + REQUIRE_NOTHROW([ast](){boost::get(boost::get(ast[8].get()));}); + + REQUIRE_NOTHROW([ast](){boost::get(ast[9].get());}); + REQUIRE_NOTHROW([ast](){boost::get(ast[10].get());}); +} + +DOCTEST_TEST_CASE("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; + auto [res, sv] = test_grammar(str, vb6_grammar::global_var_declaration, vars); + REQUIRE_MESSAGE(res, "stopped at: " << sv); + CHECK(sv.empty()); + + CHECK_EQ(vars.at, vb6_ast::access_type::global); + CHECK(!vars.with_events); + REQUIRE_EQ(vars.vars.size(), 1); + + CHECK_EQ(vars.vars[0].name, "g_var"); + CHECK(!vars.vars[0].construct); + CHECK(vars.vars[0].type.has_value()); + if(vars.vars[0].type) + { + CHECK_EQ(*vars.vars[0].type, "Long"); + } +} + +DOCTEST_TEST_SUITE_END(); + +int main(int argc, char* argv[]) +{ + log_compiler_info(cout); + + doctest::Context context; + context.applyCommandLine(argc, argv); + + // overrides + context.setOption("no-breaks", true); // don't break in the debugger when assertions fail + + return context.run(); +} diff --git a/src/vb6_parser_test.cpp b/test/vb6_parser.gtest.cpp similarity index 69% rename from src/vb6_parser_test.cpp rename to test/vb6_parser.gtest.cpp index 9cd7659..57dacab 100644 --- a/src/vb6_parser_test.cpp +++ b/test/vb6_parser.gtest.cpp @@ -6,7 +6,7 @@ //#define BOOST_SPIRIT_X3_DEBUG -#include "test_grammar_helper_gtest.hpp" +#include "test_grammar_helper_ut.hpp" #include "vb6_parser.hpp" #include "vb6_ast_printer.hpp" @@ -23,9 +23,11 @@ namespace x3 = boost::spirit::x3; GTEST_TEST(vb6_parser_simple, lonely_comment) { vector ast; - test_grammar( + auto [res, sv] = test_grammar( "' This is comment line 1\r\n' Comment line 2\r\n", *vb6_grammar::lonely_comment, ast); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); ASSERT_EQ(ast.size(), 2); @@ -43,7 +45,9 @@ GTEST_TEST(vb6_parser_simple, empty_lines) auto str = "' comment1\r\n" "\r\n" "' comment2\r\n"; - test_grammar(str, G, ast); + auto [res, sv] = test_grammar(str, G, ast); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); ASSERT_EQ(ast.size(), 3); @@ -55,30 +59,49 @@ GTEST_TEST(vb6_parser_simple, empty_lines) GTEST_TEST(vb6_parser_simple, quoted_string) { string str; - test_grammar("\"Quoted string.\"", vb6_grammar::quoted_string, str); + auto [res, sv] = test_grammar("\"Quoted string.\"", vb6_grammar::quoted_string, str); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); + EXPECT_EQ(str, "Quoted string."); } GTEST_TEST(vb6_parser_simple, basic_identifier) { string id; - test_grammar("iden_tifier", vb6_grammar::basic_identifier, id); + auto [res, sv] = test_grammar("iden_tifier", vb6_grammar::basic_identifier, id); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); + 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"); + { + string var; + auto [res, sv] = test_grammar("g_logger", vb6_grammar::var_identifier, var); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); - var.clear(); - test_grammar("forth ", vb6_grammar::sub_identifier, var); - EXPECT_EQ(var, "forth"); + EXPECT_EQ(var, "g_logger"); + } + { + string var; + auto [res, sv] = test_grammar("forth ", vb6_grammar::sub_identifier, var); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); - var.clear(); - test_grammar("subroutine ", vb6_grammar::sub_identifier, var); - EXPECT_EQ(var, "subroutine"); + EXPECT_EQ(var, "forth"); + } + { + string var; + auto [res, sv] = test_grammar("subroutine ", vb6_grammar::sub_identifier, var); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); + + EXPECT_EQ(var, "subroutine"); + } /* string code = "sub "; @@ -93,14 +116,20 @@ GTEST_TEST(vb6_parser_simple, var_identifier) GTEST_TEST(vb6_parser_simple, type_identifier) { string type; - test_grammar("Long", vb6_grammar::type_identifier, type); + auto [res, sv] = test_grammar("Long", vb6_grammar::type_identifier, type); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); + EXPECT_EQ(type, "Long"); } GTEST_TEST(vb6_parser_simple, complex_type_identifier) { string type; - test_grammar("VB.Form", vb6_grammar::complex_type_identifier, type); + auto [res, sv] = test_grammar("VB.Form", vb6_grammar::complex_type_identifier, type); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); + EXPECT_EQ(type, "VB.Form"); } @@ -109,17 +138,30 @@ 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 = "\"una stringa\""s; + auto [res, sv] = test_grammar(str, vb6_grammar::const_expression, ast); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); - str = "True"s; - test_grammar(str, vb6_grammar::const_expression, ast); - EXPECT_EQ(boost::get(ast.get()), true); + EXPECT_EQ(boost::get(ast.get()), "una stringa"); + } + { + str = "True"s; + auto [res, sv] = test_grammar(str, vb6_grammar::const_expression, ast); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); - str = "Nothing"s; - test_grammar(str, vb6_grammar::const_expression, ast); - EXPECT_NO_THROW(boost::get(ast.get())); + EXPECT_EQ(boost::get(ast.get()), true); + } + { + str = "Nothing"s; + auto [res, sv] = test_grammar(str, vb6_grammar::const_expression, ast); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); + + EXPECT_NO_THROW(boost::get(ast.get())); + } } GTEST_TEST(vb6_parser_simple, const_expression_integers) @@ -127,25 +169,46 @@ 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; + auto [res, sv] = test_grammar(str, vb6_grammar::const_expression, ast); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); - str = "1234&"s; - test_grammar(str, vb6_grammar::const_expression, ast); - EXPECT_EQ(boost::get(ast.get()).val, 1234); + EXPECT_EQ(boost::get(ast.get()).val, 1234); + } + { + str = "1234&"s; + auto [res, sv] = test_grammar(str, vb6_grammar::const_expression, ast); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); - str = "&Hcafedead&"s; - test_grammar(str, vb6_grammar::const_expression, ast); - EXPECT_EQ(boost::get(ast.get()).val, 0xcafedead); + EXPECT_EQ(boost::get(ast.get()).val, 1234); + } + { + str = "&Hcafedead&"s; + auto [res, sv] = test_grammar(str, vb6_grammar::const_expression, ast); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); - str = "&01234&"s; - test_grammar(str, vb6_grammar::const_expression, ast); - EXPECT_EQ(boost::get(ast.get()).val, 01234); + EXPECT_EQ(boost::get(ast.get()).val, 0xcafedead); + } + { + str = "&01234&"s; + auto [res, sv] = test_grammar(str, vb6_grammar::const_expression, ast); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); - str = "1234"s; - test_grammar(str, vb6_grammar::const_expression, ast); - EXPECT_EQ(boost::get(ast.get()).val, 1234); + EXPECT_EQ(boost::get(ast.get()).val, 01234); + } + { + str = "1234"s; + auto [res, sv] = test_grammar(str, vb6_grammar::const_expression, ast); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); + + EXPECT_EQ(boost::get(ast.get()).val, 1234); + } } GTEST_TEST(vb6_parser_simple, const_expression_floats) @@ -153,55 +216,82 @@ 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; + auto [res, sv] = test_grammar(str, vb6_grammar::const_expression, ast); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); - str = "1234#"s; - test_grammar(str, vb6_grammar::const_expression, ast); - EXPECT_EQ(boost::get(ast.get()), 1234.0); + EXPECT_EQ(boost::get(ast.get()), 1234.0f); + } + { + str = "1234#"s; + auto [res, sv] = test_grammar(str, vb6_grammar::const_expression, ast); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); - str = "2.8"s; - test_grammar(str, vb6_grammar::const_expression, ast); - EXPECT_EQ(boost::get(ast.get()), 2.8f); + EXPECT_EQ(boost::get(ast.get()), 1234.0); + } + { + str = "2.8"s; + auto [res, sv] = test_grammar(str, vb6_grammar::const_expression, ast); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); + + 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); + auto [res, sv] = test_grammar("foo1(foo2(3, M.x_coord), True)", vb6_grammar::expression, ast); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); + 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 P1; + auto [res, sv] = test_grammar("g_logger As Long", vb6_grammar::single_var_declaration, P1); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); - 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(P1.name, "g_logger"); + EXPECT_FALSE(P1.construct); + EXPECT_TRUE(P1.type); + if(P1.type) + { + EXPECT_EQ(*P1.type, "Long"); + } + } { - EXPECT_EQ(*P2.type, "String"); + vb6_ast::variable P2; + auto [res, sv] = test_grammar("name As New String", vb6_grammar::single_var_declaration, P2); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); + + 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", + auto [res, sv] = test_grammar("Type PatRecord\r\n name As String\r\n age As Integer\r\nEnd Type\r\n", vb6_grammar::record_declaration, rec); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); EXPECT_EQ(rec.name, "PatRecord"); EXPECT_EQ(rec.at, vb6_ast::access_type::na); @@ -227,8 +317,10 @@ GTEST_TEST(vb6_parser_tests, record_declaration) 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", + auto [res, sv] = test_grammar("Enum PatTypes\r\n inpatient\r\n outpatient\r\nEnd Enum\r\n", vb6_grammar::enum_declaration, enum1); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); EXPECT_EQ(enum1.name, "PatTypes"); EXPECT_EQ(enum1.at, vb6_ast::access_type::na); @@ -245,7 +337,9 @@ 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); + auto [res, sv] = test_grammar(str, vb6_grammar::global_var_declaration, vars); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); EXPECT_EQ(vars.at, vb6_ast::access_type::global); EXPECT_FALSE(vars.with_events); @@ -292,7 +386,9 @@ 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); + auto [res, sv] = test_grammar(cstr, vb6_grammar::const_var_declaration, cvars); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); ASSERT_EQ(cvars.size(), 3); @@ -324,8 +420,10 @@ GTEST_TEST(vb6_parser_tests, const_var_declaration1) 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", + auto [res, sv] = test_grammar("Private Const PI As Double = 3.1415\r\n", vb6_grammar::const_var_declaration, cvars); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); ASSERT_EQ(cvars.size(), 1); @@ -341,7 +439,9 @@ GTEST_TEST(vb6_parser_tests, const_var_declaration2) 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); + auto [res, sv] = test_grammar("Optional ByVal name As String = \"pippo\"", vb6_grammar::param_decl, fp); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); EXPECT_TRUE(fp.isoptional); EXPECT_TRUE(fp.qualifier); @@ -366,8 +466,10 @@ GTEST_TEST(vb6_parser_tests, param_decl) GTEST_TEST(vb6_parser_tests, param_list_decl) { vector fps; - test_grammar("ByVal name As String, ByRef val As Integer", + auto [res, sv] = test_grammar("ByVal name As String, ByRef val As Integer", -(vb6_grammar::param_decl % ','), fps); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); ASSERT_EQ(fps.size(), 2); @@ -405,8 +507,10 @@ GTEST_TEST(vb6_parser_tests, param_list_decl) GTEST_TEST(vb6_parser_tests, event_declaration) { vb6_ast::eventHead event_decl; - test_grammar("Public Event OnChange(ByVal Text As String)\r\n", + auto [res, sv] = test_grammar("Public Event OnChange(ByVal Text As String)\r\n", vb6_grammar::eventHead, event_decl); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); EXPECT_EQ(event_decl.at, vb6_ast::access_type::public_); EXPECT_EQ(event_decl.name, "OnChange"); @@ -416,9 +520,11 @@ GTEST_TEST(vb6_parser_tests, event_declaration) GTEST_TEST(vb6_parser_tests, function_head) { vb6_ast::functionHead fh; - test_grammar( + auto [res, sv] = test_grammar( "Private Function OneFunc(ByVal name As String, ByRef val As Integer) As Integer\r\n", vb6_grammar::functionHead, fh); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); EXPECT_EQ(fh.at, vb6_ast::access_type::private_); EXPECT_EQ(fh.name, "OneFunc"); @@ -430,8 +536,10 @@ GTEST_TEST(vb6_parser_tests, function_head) GTEST_TEST(vb6_parser_tests, function_head_no_params) { vb6_ast::functionHead fh; - test_grammar( + auto [res, sv] = test_grammar( "Private Function NoParamFunc() As Object\r\n", vb6_grammar::functionHead, fh); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); EXPECT_EQ(fh.at, vb6_ast::access_type::private_); EXPECT_EQ(fh.name, "NoParamFunc"); @@ -443,9 +551,11 @@ GTEST_TEST(vb6_parser_tests, function_head_no_params) GTEST_TEST(vb6_parser_tests, subroutine_head) { vb6_ast::subHead sh; - test_grammar( + auto [res, sv] = test_grammar( "Private Sub my_sub(ByRef str As String, ByVal valid As Boolean)\r\n", vb6_grammar::subHead, sh); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); EXPECT_EQ(sh.at, vb6_ast::access_type::private_); EXPECT_EQ(sh.name, "my_sub"); @@ -456,7 +566,9 @@ 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); + auto [res, sv] = test_grammar(str, vb6_grammar::subHead, sh); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); EXPECT_EQ(sh.at, vb6_ast::access_type::private_); EXPECT_EQ(sh.name, "my_sub"); @@ -466,17 +578,21 @@ GTEST_TEST(vb6_parser_tests, subroutine_head2) GTEST_TEST(vb6_parser_tests, subroutine_head_with_optional_params) { vb6_ast::subHead sh; - test_grammar( + auto [res, sv] = test_grammar( "Private Sub my_sub(ByRef str As String, Optional ByVal valid As Boolean = false)\r\n", vb6_grammar::subHead, sh); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); 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) + 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) { @@ -509,7 +625,9 @@ 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); + auto [res, sv] = test_grammar(str, vb6_grammar::property_letHead, ast); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); EXPECT_EQ(ast.at, vb6_ast::access_type::public_); EXPECT_EQ(ast.name, "Width"); @@ -520,7 +638,9 @@ 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); + auto [res, sv] = test_grammar(str, vb6_grammar::property_getHead, ast); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); EXPECT_EQ(ast.at, vb6_ast::access_type::public_); EXPECT_EQ(ast.name, "Width"); @@ -533,7 +653,9 @@ 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); + auto [res, sv] = test_grammar(str, vb6_grammar::external_sub_decl, extsub); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); EXPECT_EQ(extsub.at, vb6_ast::access_type::private_); EXPECT_EQ(extsub.name, "BeepVB"); @@ -546,7 +668,9 @@ 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); + auto [res, sv] = test_grammar(str, vb6_grammar::external_function_decl, extfunc); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); EXPECT_EQ(extfunc.at, vb6_ast::access_type::private_); EXPECT_EQ(extfunc.name, "BeepVB"); @@ -563,7 +687,9 @@ GTEST_TEST(vb6_parser_tests, dll_function_declaration) GTEST_TEST(vb6_parser_tests, identifier_context) { vb6_ast::identifier_context ctx; - test_grammar("var1.func().pnt1.", vb6_grammar::identifier_context, ctx); + auto [res, sv] = test_grammar("var1.func().pnt1.", vb6_grammar::identifier_context, ctx); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); EXPECT_FALSE(ctx.leading_dot); ASSERT_EQ(ctx.elements.size(), 3); @@ -577,7 +703,9 @@ GTEST_TEST(vb6_parser_tests, identifier_context) 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); + auto [res, sv] = test_grammar("var1.func().pnt1.X", vb6_grammar::decorated_variable, dec_var); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); EXPECT_FALSE(dec_var.ctx.leading_dot); ASSERT_EQ(dec_var.ctx.elements.size(), 3); @@ -594,7 +722,9 @@ 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); + auto [res, sv] = test_grammar(str, *vb6_grammar::attributeDef, attrs); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); EXPECT_EQ(attrs.size(), 2); @@ -619,8 +749,10 @@ GTEST_TEST(vb6_parser_tests, attributes) 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); + //auto [res, sv] = test_grammar(str, vb6_grammar::preamble, ast); + auto [res, sv] = test_grammar(str, *vb6_grammar::attributeDef, ast); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); EXPECT_EQ(ast.size(), 2); @@ -645,7 +777,9 @@ GTEST_TEST(vb6_parser_tests, options) auto str = R"vb(Option Explicit Option Base 0 )vb"s; - test_grammar(str, *vb6_grammar::option_item, ast); + auto [res, sv] = test_grammar(str, *vb6_grammar::option_item, ast); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); ASSERT_EQ(ast.size(), 2); @@ -666,7 +800,9 @@ GTEST_TEST(vb6_parser_tests, declaration_block) "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); + auto [res, sv] = test_grammar(str, declarations, decls); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); ASSERT_EQ(decls.size(), 5); @@ -697,7 +833,9 @@ GTEST_TEST(vb6_parser_tests, bas_unit_STRICT_MODULE_STRUCTURE) Function my_fun(ByRef str As String) As Long End Function )vb"s; - test_grammar(str, vb6_grammar::STRICT_MODULE_STRUCTURE::basModDef, ast); + auto [res, sv] = test_grammar(str, vb6_grammar::STRICT_MODULE_STRUCTURE::basModDef, ast); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); vb6_ast_printer P(cout); P(ast); @@ -769,7 +907,9 @@ GTEST_TEST(vb6_parser_tests, bas_unit) Function my_fun(ByRef str As String) As Long End Function )vb"s; - test_grammar(str, vb6_grammar::basModDef, ast); + auto [res, sv] = test_grammar(str, vb6_grammar::basModDef, ast); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); vb6_ast_printer P(cout); P(ast); @@ -797,7 +937,9 @@ 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); + auto [res, sv] = test_grammar(str, vb6_grammar::global_var_declaration, vars); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); EXPECT_EQ(vars.at, vb6_ast::access_type::global); EXPECT_FALSE(vars.with_events); diff --git a/test/vb6_parser.ut.cpp b/test/vb6_parser.ut.cpp new file mode 100644 index 0000000..b63c2cb --- /dev/null +++ b/test/vb6_parser.ut.cpp @@ -0,0 +1,996 @@ +//: vb6_parser.ut.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 +#pragma GCC diagnostic ignored "-Wunused-variable" + +#include "test_grammar_helper_ut.hpp" +#include "vb6_parser.hpp" +#include "vb6_ast_printer.hpp" + +#include +#include + +#include +#include +#include + +namespace x3 = boost::spirit::x3; +namespace ut = boost::ut; + +using namespace std; +// need for ut's user-defined literals and overloaded operators +using namespace boost::ut; + +void log_compiler_info(std::ostream& os) +{ + // how to tell that AppleClang and not plain Clang has been used? + // __APPLE_CC__ does not work as it's always defined +#if defined(__clang__) + os << "Compiler: Clang " << __clang_major__ << "." << __clang_minor__ << "." << __clang_patchlevel__<< '\n'; +#elif defined(__GNUC__) + os << "Compiler: GCC " << __GNUC__ << "." << __GNUC_MINOR__ << "." << __GNUC_PATCHLEVEL__<< '\n'; +#elif defined(__MSC_VER) + os << "Compiler: MSC " << __MSC_VER << '\n'; +#endif +} + +ut::suite<"vb6_parser_simple"> _ = [] +{ + ut::test("lonely_comment") = [] + { + vector ast; + auto [res, sv] = test_grammar( + "' This is comment line 1\r\n' Comment line 2\r\n", + *vb6_grammar::lonely_comment, ast); + ut::expect((res) >> fatal) << "stopped at: " << sv; + ut::expect(sv.empty()); + + //cout << ast.size() << "\n"; + ut::expect(ut::eq(ast.size(), 2)); + + ut::expect(ast[0].content == " This is comment line 1"); + ut::expect(ast[1].content == " Comment line 2"); + }; + + ut::test("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"; + auto [res, sv] = test_grammar(str, G, ast); + ut::expect((res) >> fatal) << "stopped at: " << sv; + ut::expect(sv.empty()); + + ut::expect(ast.size() == 3); + + ut::expect(boost::get(ast[0]).content == " comment1"); + ut::expect(ut::nothrow([ast](){boost::get(ast[1]);})); + ut::expect(boost::get(ast[2]).content == " comment2"); + }; + + ut::test("quoted_string") = [] + { + string str; + auto [res, sv] = test_grammar("\"Quoted string.\"", vb6_grammar::quoted_string, str); + ut::expect((res) >> fatal) << "stopped at: " << sv; + ut::expect(sv.empty()); + ut::expect(str == "Quoted string."); + }; + + ut::test("basic_identifier") = [] + { + string id; + auto [res, sv] = test_grammar("iden_tifier", vb6_grammar::basic_identifier, id); + ut::expect((res) >> fatal) << "stopped at: " << sv; + ut::expect(sv.empty()); + ut::expect(id == "iden_tifier"); + }; + + ut::test("var_identifier") = [] + { + string var; + + ut::test("1") = [&var] + { + auto [res, sv] = test_grammar("g_logger", vb6_grammar::var_identifier, var); + ut::expect((res) >> fatal) << "stopped at: " << sv; + ut::expect(sv.empty()); + ut::expect(var == "g_logger"); + }; + + ut::test("2") = [&var] + { + auto [res, sv] = test_grammar("forth ", vb6_grammar::sub_identifier, var); + ut::expect((res) >> fatal) << "stopped at: " << sv; + ut::expect(sv.empty()); + ut::expect(var == "forth"); + }; + + ut::test("3") = [&var] + { + auto [res, sv] = test_grammar("subroutine ", vb6_grammar::sub_identifier, var); + ut::expect((res) >> fatal) << "stopped at: " << sv; + ut::expect(sv.empty()); + ut::expect(var == "subroutine"); + }; + + /* + string code = "sub "; + string_view code_sv = code; + auto it1 = cbegin(code_sv); + auto const it2 = cend(code_sv); + ut::expect(!boost::spirit::x3::phrase_parse(it1, it2, vb6_grammar::sub_identifier, vb6_grammar::skip, var)); + ut::expect(it1 == begin(code_sv)); + */ + }; + + ut::test("type_identifier") = [] + { + string type; + auto [res, sv] = test_grammar("Long", vb6_grammar::type_identifier, type); + ut::expect((res) >> fatal) << "stopped at: " << sv; + ut::expect(sv.empty()); + ut::expect(that % type == "Long"s); + }; + + ut::test("complex_type_identifier") = [] + { + string type; + auto [res, sv] = test_grammar("VB.Form", vb6_grammar::complex_type_identifier, type); + ut::expect((res) >> fatal) << "stopped at: " << sv; + ut::expect(sv.empty()); + ut::expect(that % type == "VB.Form"s); + }; + + ut::test("const_expression_non_numeric") = [] + { + vb6_ast::const_expr ast; + + ut::test("1") = [&ast] + { + string str = "\"una stringa\""s; + auto [res, sv] = test_grammar(str, vb6_grammar::const_expression, ast); + ut::expect((res) >> fatal) << "stopped at: " << sv; + ut::expect(sv.empty()); + ut::expect(that % boost::get(ast.get()) == "una stringa"s); + }; + + ut::test("2") = [&ast] + { + string str = "True"s; + auto [res, sv] = test_grammar(str, vb6_grammar::const_expression, ast); + ut::expect((res) >> fatal) << "stopped at: " << sv; + ut::expect(sv.empty()); + ut::expect(boost::get(ast.get())); + }; + + ut::test("3") = [&ast] + { + string str = "Nothing"s; + auto [res, sv] = test_grammar(str, vb6_grammar::const_expression, ast); + ut::expect((res) >> fatal) << "stopped at: " << sv; + ut::expect(sv.empty()); + ut::expect(ut::nothrow([ast](){boost::get(ast.get());})); + }; + }; + + ut::test("const_expression_integers") = [] + { + vb6_ast::const_expr ast; + + ut::test("1") = [&ast] + { + string str = "1234%"s; + auto [res, sv] = test_grammar(str, vb6_grammar::const_expression, ast); + ut::expect((res) >> fatal) << "stopped at: " << sv; + ut::expect(sv.empty()); + ut::expect(that % boost::get(ast.get()).val == 1234); + }; + + ut::test("2") = [&ast] + { + string str = "1234&"s; + auto [res, sv] = test_grammar(str, vb6_grammar::const_expression, ast); + ut::expect((res) >> fatal) << "stopped at: " << sv; + ut::expect(sv.empty()); + ut::expect(that % boost::get(ast.get()).val == 1234); + }; + + ut::test("3") = [&ast] + { + string str = "&Hcafedead&"s; + auto [res, sv] = test_grammar(str, vb6_grammar::const_expression, ast); + ut::expect((res) >> fatal) << "stopped at: " << sv; + ut::expect(sv.empty()); + ut::expect(that % boost::get(ast.get()).val == 0xcafedead); + }; + + ut::test("4") = [&ast] + { + string str = "&01234&"s; + auto [res, sv] = test_grammar(str, vb6_grammar::const_expression, ast); + ut::expect((res) >> fatal) << "stopped at: " << sv; + ut::expect(sv.empty()); + ut::expect(that % boost::get(ast.get()).val == 01234); + }; + + ut::test("5") = [&ast] + { + string str = "1234"s; + auto [res, sv] = test_grammar(str, vb6_grammar::const_expression, ast); + ut::expect((res) >> fatal) << "stopped at: " << sv; + ut::expect(sv.empty()); + ut::expect(that % boost::get(ast.get()).val == 1234); + }; + }; + + ut::test("const_expression_floats") = [] + { + vb6_ast::const_expr ast; + + ut::test("1") = [&ast] + { + string str = "1234!"s; + auto [res, sv] = test_grammar(str, vb6_grammar::const_expression, ast); + ut::expect((res) >> fatal) << "stopped at: " << sv; + ut::expect(sv.empty()); + ut::expect(boost::get(ast.get()) == 1234.0f); + }; + + ut::test("2") = [&ast] + { + string str = "1234#"s; + auto [res, sv] = test_grammar(str, vb6_grammar::const_expression, ast); + ut::expect((res) >> fatal) << "stopped at: " << sv; + ut::expect(sv.empty()); + ut::expect(boost::get(ast.get()) == 1234.0); + }; + + ut::test("3") = [&ast] + { + string str = "2.8"s; + auto [res, sv] = test_grammar(str, vb6_grammar::const_expression, ast); + ut::expect((res) >> fatal) << "stopped at: " << sv; + ut::expect(sv.empty()); + ut::expect(boost::get(ast.get()) == 2.8f); + }; + }; + + ut::test("sample_expression") = [] + { + vb6_ast::expression ast; + auto [res, sv] = test_grammar("foo1(foo2(3, M.x_coord), True)", vb6_grammar::expression, ast); + ut::expect((res) >> fatal) << "stopped at: " << sv; + ut::expect(sv.empty()); + ut::expect(ast.get().type() == typeid(x3::forward_ast)); + ut::expect(boost::get>(ast.get()).get().func_name == "foo1"); + }; + + ut::test("single_var_declaration") = [] + { + ut::test("1") = [] + { + vb6_ast::variable P1; + auto [res, sv] = test_grammar("g_logger As Long", vb6_grammar::single_var_declaration, P1); + ut::expect((res) >> fatal) << "stopped at: " << sv; + ut::expect(sv.empty()); + ut::expect(P1.name == "g_logger"); + ut::expect(!P1.construct); + ut::expect(P1.type.has_value()); + if(P1.type) + { + ut::expect(*P1.type == "Long"); + } + }; + + ut::test("2") = [] + { + vb6_ast::variable P2; + auto [res, sv] = test_grammar("name As New String", vb6_grammar::single_var_declaration, P2); + ut::expect((res) >> fatal) << "stopped at: " << sv; + ut::expect(sv.empty()); + ut::expect(P2.name == "name"); + ut::expect(P2.construct); + ut::expect(P2.type.has_value()); + if(P2.type) + { + ut::expect(*P2.type == "String"); + } + }; + }; +}; + +ut::suite<"vb6_parser_tests"> __ = [] +{ + ut::test("record_declaration") = [] + { + vb6_ast::record rec; + auto [res, sv] = test_grammar("Type PatRecord\r\n name As String\r\n age As Integer\r\nEnd Type\r\n", + vb6_grammar::record_declaration, rec); + ut::expect((res) >> fatal) << "stopped at: " << sv; + ut::expect(sv.empty()); + + ut::expect(rec.name == "PatRecord"); + ut::expect(rec.at == vb6_ast::access_type::na); + ut::expect(rec.members.size() == 2); + + ut::expect(rec.members[0].name == "name"); + ut::expect(rec.members[0].type.has_value()); + if(rec.members[0].type) + { + ut::expect(*rec.members[0].type == "String"); + } + ut::expect(!rec.members[0].construct); + + ut::expect(rec.members[1].name == "age"); + ut::expect(rec.members[1].type.has_value()); + if(rec.members[1].type) + { + ut::expect(*rec.members[1].type == "Integer"); + } + ut::expect(!rec.members[1].construct); + }; + + ut::test("enum_declaration") = [] + { + vb6_ast::vb_enum enum1; + auto [res, sv] = test_grammar("Enum PatTypes\r\n inpatient\r\n outpatient\r\nEnd Enum\r\n", + vb6_grammar::enum_declaration, enum1); + ut::expect((res) >> fatal) << "stopped at: " << sv; + ut::expect(sv.empty()); + + ut::expect(enum1.name == "PatTypes"); + ut::expect(enum1.at == vb6_ast::access_type::na); + ut::expect((enum1.values.size() == 2) >> ut::fatal); + + ut::expect(enum1.values[0].first == "inpatient"); + ut::expect(!enum1.values[0].second); + + ut::expect(enum1.values[1].first == "outpatient"); + ut::expect(!enum1.values[1].second); + }; + + ut::test("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; + auto [res, sv] = test_grammar(str, vb6_grammar::global_var_declaration, vars); + ut::expect((res) >> fatal) << "stopped at: " << sv; + ut::expect(sv.empty()); + + ut::expect(vars.at == vb6_ast::access_type::global); + ut::expect(!vars.with_events); + ut::expect((vars.vars.size() == 5) >> ut::fatal); + + //cout << "size: " << vars.vars.size() << "\n"; + + ut::expect(vars.vars[0].name == "g_logger"); + ut::expect(!vars.vars[0].construct); + ut::expect(vars.vars[0].type.has_value()); + if(vars.vars[0].type) + { + ut::expect(*vars.vars[0].type == "Long"); + } + + ut::expect(vars.vars[1].name == "v1"); + ut::expect(!vars.vars[1].construct); + ut::expect(!vars.vars[1].type.has_value()); + + ut::expect(vars.vars[2].name == "XRes"); + ut::expect(vars.vars[2].construct); + ut::expect(vars.vars[2].type.has_value()); + if(vars.vars[2].type) + { + ut::expect(*vars.vars[2].type == "Object"); + } + + ut::expect(vars.vars[3].name == "ptr"); + ut::expect(!vars.vars[3].construct); + ut::expect(vars.vars[3].type.has_value()); + if(vars.vars[3].type) + { + ut::expect(*vars.vars[3].type == "MyRec"); + } + + ut::expect(vars.vars[4].name == "g_active"); + ut::expect(!vars.vars[4].construct); + ut::expect(vars.vars[4].type.has_value()); + if(vars.vars[4].type) + { + ut::expect(*vars.vars[4].type == "Boolean"); + } + }; + + ut::test("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; + auto [res, sv] = test_grammar(cstr, vb6_grammar::const_var_declaration, cvars); + ut::expect((res) >> fatal) << "stopped at: " << sv; + ut::expect(sv.empty()); + + ut::expect((cvars.size() == 3) >> ut::fatal); + + //cout << "size: " << cvars.size() << "\n"; + + ut::expect(cvars[0].var.name == "e"); + if(cvars[0].var.type) + { + ut::expect(*cvars[0].var.type == "Single"); + } + ut::expect(!cvars[0].var.construct); + ut::expect(boost::get(cvars[0].value.get()) == 2.8f); + + ut::expect(cvars[1].var.name == "pi"); + if(cvars[1].var.type) + { + ut::expect(*cvars[1].var.type == "Double"); + } + ut::expect(!cvars[1].var.construct); + ut::expect(boost::get(cvars[1].value.get()) == 3.14f); + + ut::expect(cvars[2].var.name == "u"); + if(cvars[2].var.type) + { + ut::expect(*cvars[2].var.type == "Integer"); + } + ut::expect(!cvars[2].var.construct); + ut::expect(boost::get(cvars[2].value.get()).val == -1); + }; + + ut::test("const_var_declaration2") = [] + { + vb6_ast::const_var_stat cvars; + auto [res, sv] = test_grammar("Private Const PI As Double = 3.1415\r\n", + vb6_grammar::const_var_declaration, cvars); + ut::expect((res) >> fatal) << "stopped at: " << sv; + ut::expect(sv.empty()); + + ut::expect((cvars.size() == 1) >> ut::fatal); + + //cout << "size: " << cvars.size() << "\n"; + + ut::expect(cvars[0].var.name == "PI"); + if(cvars[0].var.type) + { + ut::expect(*cvars[0].var.type == "Double"); + } + ut::expect(!cvars[0].var.construct); + ut::expect(boost::get(cvars[0].value.get()) == 3.1415f); + }; + + ut::test("param_decl") = [] + { + vb6_ast::func_param fp; + auto [res, sv] = test_grammar("Optional ByVal name As String = \"pippo\"", vb6_grammar::param_decl, fp); + ut::expect((res) >> fatal) << "stopped at: " << sv; + ut::expect(sv.empty()); + + ut::expect(fp.isoptional); + ut::expect(fp.qualifier.has_value()); + if(fp.qualifier) + { + ut::expect(*fp.qualifier == vb6_ast::param_qualifier::byval); + } + ut::expect(fp.var.name == "name"); + ut::expect(!fp.var.construct); + ut::expect(fp.var.type.has_value()); + if(fp.var.type) + { + ut::expect(*fp.var.type == "String"); + } + ut::expect(fp.defvalue.has_value()); + if(fp.defvalue) + { + ut::expect(boost::get(fp.defvalue.get()) == "pippo"); + } + }; + + ut::test("param_list_decl") = [] + { + vector fps; + auto [res, sv] = test_grammar("ByVal name As String, ByRef val As Integer", + -(vb6_grammar::param_decl % ','), fps); + ut::expect((res) >> fatal) << "stopped at: " << sv; + ut::expect(sv.empty()); + + //cout << "size: " << fps.size() << "\n"; + + ut::expect((fps.size() == 2) >> ut::fatal); + + ut::expect(!fps[0].isoptional); + ut::expect(fps[0].qualifier.has_value()); + if(fps[0].qualifier) + { + ut::expect(*fps[0].qualifier == vb6_ast::param_qualifier::byval); + } + ut::expect(fps[0].var.name == "name"); + ut::expect(!fps[0].var.construct); + ut::expect(fps[0].var.type.has_value()); + if(fps[0].var.type) + { + ut::expect(*fps[0].var.type == "String"); + } + ut::expect(!fps[0].defvalue); + + ut::expect(!fps[1].isoptional); + ut::expect(fps[1].qualifier.has_value()); + if(fps[1].qualifier) + { + ut::expect(*fps[1].qualifier == vb6_ast::param_qualifier::byref); + } + ut::expect(fps[1].var.name == "val"); + ut::expect(!fps[1].var.construct); + ut::expect(fps[1].var.type.has_value()); + if(fps[1].var.type) + { + ut::expect(*fps[1].var.type == "Integer"); + } + ut::expect(!fps[1].defvalue); + }; + + ut::test("event_declaration") = [] + { + vb6_ast::eventHead event_decl; + auto [res, sv] = test_grammar("Public Event OnChange(ByVal Text As String)\r\n", + vb6_grammar::eventHead, event_decl); + ut::expect((res) >> fatal) << "stopped at: " << sv; + ut::expect(sv.empty()); + + ut::expect(event_decl.at == vb6_ast::access_type::public_); + ut::expect(event_decl.name == "OnChange"); + ut::expect(event_decl.params.size() == 1); + }; + + ut::test("function_head") = [] + { + vb6_ast::functionHead fh; + auto [res, sv] = test_grammar( + "Private Function OneFunc(ByVal name As String, ByRef val As Integer) As Integer\r\n", + vb6_grammar::functionHead, fh); + ut::expect((res) >> fatal) << "stopped at: " << sv; + ut::expect(sv.empty()); + + ut::expect(fh.at == vb6_ast::access_type::private_); + ut::expect(fh.name == "OneFunc"); + ut::expect(fh.params.size() == 2); + ut::expect(fh.return_type.has_value()); + ut::expect(*fh.return_type == "Integer"); + }; + + ut::test("function_head_no_params") = [] + { + vb6_ast::functionHead fh; + auto [res, sv] = test_grammar( + "Private Function NoParamFunc() As Object\r\n", vb6_grammar::functionHead, fh); + ut::expect((res) >> fatal) << "stopped at: " << sv; + ut::expect(sv.empty()); + + ut::expect(fh.at == vb6_ast::access_type::private_); + ut::expect(fh.name == "NoParamFunc"); + ut::expect(fh.params.empty()); + ut::expect((fh.return_type.has_value()) >> ut::fatal); + ut::expect(*fh.return_type == "Object"); + }; + + ut::test("subroutine_head") = [] + { + vb6_ast::subHead sh; + auto [res, sv] = test_grammar( + "Private Sub my_sub(ByRef str As String, ByVal valid As Boolean)\r\n", + vb6_grammar::subHead, sh); + ut::expect((res) >> fatal) << "stopped at: " << sv; + ut::expect(sv.empty()); + + ut::expect(sh.at == vb6_ast::access_type::private_); + ut::expect(sh.name == "my_sub"); + ut::expect(sh.params.size() == 2); + }; + + ut::test("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; + auto [res, sv] = test_grammar(str, vb6_grammar::subHead, sh); + ut::expect((res) >> fatal) << "stopped at: " << sv; + ut::expect(sv.empty()); + + ut::expect(sh.at == vb6_ast::access_type::private_); + ut::expect(sh.name == "my_sub"); + ut::expect(sh.params.size() == 3); + }; + + ut::test("subroutine_head_with_optional_params") = [] + { + vb6_ast::subHead sh; + auto [res, sv] = test_grammar( + "Private Sub my_sub(ByRef str As String, Optional ByVal valid As Boolean = false)\r\n", + vb6_grammar::subHead, sh); + ut::expect((res) >> fatal) << "stopped at: " << sv; + ut::expect(sv.empty()); + + ut::expect(sh.at == vb6_ast::access_type::private_); + ut::expect(sh.name == "my_sub"); + ut::expect((sh.params.size() == 2) >> ut::fatal); + + ut::expect(sh.params[0].qualifier.has_value()); + if (sh.params[0].qualifier) + ut::expect(sh.params[0].qualifier.get() == vb6_ast::param_qualifier::byref); + ut::expect(sh.params[0].var.name == "str"); + if(sh.params[0].var.type) + { + ut::expect(*sh.params[0].var.type == "String"); + } + ut::expect(!sh.params[0].var.construct); + ut::expect(!sh.params[0].isoptional); + ut::expect(!sh.params[0].defvalue); + + ut::expect(sh.params[1].qualifier.has_value()); + if(sh.params[1].qualifier) + { + ut::expect(sh.params[1].qualifier.get() == vb6_ast::param_qualifier::byval); + } + ut::expect(sh.params[1].var.name == "valid"); + if(sh.params[1].var.type) + { + ut::expect(*sh.params[1].var.type == "Boolean"); + } + ut::expect(!sh.params[1].var.construct); + ut::expect(sh.params[1].isoptional); + ut::expect(sh.params[1].defvalue.has_value()); + if(sh.params[0].defvalue) + { + ut::expect(boost::get(*sh.params[0].defvalue) == false); + } + }; + + ut::test("property_let_head") = [] + { + auto str = "Public Property Let Width(ByVal w As Integer)\r\n"s; + vb6_ast::propertyLetHead ast; + auto [res, sv] = test_grammar(str, vb6_grammar::property_letHead, ast); + ut::expect((res) >> fatal) << "stopped at: " << sv; + ut::expect(sv.empty()); + + ut::expect(ast.at == vb6_ast::access_type::public_); + ut::expect(ast.name == "Width"); + ut::expect(ast.params.size() == 1); + }; + + ut::test("property_get_head") = [] + { + auto str = "Public Property Get Width() As Integer\r\n"s; + vb6_ast::propertyGetHead ast; + auto [res, sv] = test_grammar(str, vb6_grammar::property_getHead, ast); + ut::expect((res) >> fatal) << "stopped at: " << sv; + ut::expect(sv.empty()); + + ut::expect(ast.at == vb6_ast::access_type::public_); + ut::expect(ast.name == "Width"); + ut::expect(ast.params.size() == 0); + ut::expect(ast.return_type >> fatal); + ut::expect(*ast.return_type == "Integer"); + }; + + ut::test("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; + auto [res, sv] = test_grammar(str, vb6_grammar::external_sub_decl, extsub); + ut::expect((res) >> fatal) << "stopped at: " << sv; + ut::expect(sv.empty()); + + ut::expect(extsub.at == vb6_ast::access_type::private_); + ut::expect(extsub.name == "BeepVB"); + ut::expect(extsub.alias == "Beep"); + ut::expect(extsub.params.size() == 2); + ut::expect(extsub.lib == "kernel32.dll"); + }; + + ut::test("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; + auto [res, sv] = test_grammar(str, vb6_grammar::external_function_decl, extfunc); + ut::expect((res) >> fatal) << "stopped at: " << sv; + ut::expect(sv.empty()); + + ut::expect(extfunc.at == vb6_ast::access_type::private_); + ut::expect(extfunc.name == "BeepVB"); + ut::expect(extfunc.return_type.has_value()); + if(extfunc.return_type) + { + ut::expect(*extfunc.return_type == "Long"); + } + ut::expect(extfunc.alias == "Beep"); + ut::expect(extfunc.params.size() == 2); + ut::expect(extfunc.lib == "kernel32.dll"); + }; + + ut::test("identifier_context") = [] + { + vb6_ast::identifier_context ctx; + auto [res, sv] = test_grammar("var1.func().pnt1.", vb6_grammar::identifier_context, ctx); + ut::expect((res) >> fatal) << "stopped at: " << sv; + ut::expect(sv.empty()); + + ut::expect(!ctx.leading_dot); + ut::expect((ctx.elements.size() == 3) >> ut::fatal); + ut::expect(boost::get(ctx.elements[0].get()) == "var1"); + auto& tmp = boost::get>(ctx.elements[1].get()).get(); + ut::expect(tmp.func_name == "func"); + ut::expect(tmp.params.empty()); + ut::expect(boost::get(ctx.elements[2].get()) == "pnt1"); + }; + + ut::test("decorated_variable") = [] + { + vb6_ast::decorated_variable dec_var; + auto [res, sv] = test_grammar("var1.func().pnt1.X", vb6_grammar::decorated_variable, dec_var); + ut::expect((res) >> fatal) << "stopped at: " << sv; + ut::expect(sv.empty()); + + ut::expect(!dec_var.ctx.leading_dot); + ut::expect((dec_var.ctx.elements.size() == 3) >> ut::fatal); + ut::expect(boost::get(dec_var.ctx.elements[0].get()) == "var1"); + auto& tmp = boost::get>(dec_var.ctx.elements[1].get()).get(); + ut::expect(tmp.func_name == "func"); + ut::expect(tmp.params.empty()); + ut::expect(boost::get(dec_var.ctx.elements[2].get()) == "pnt1"); + ut::expect(dec_var.var == "X"); + }; + + ut::test("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; + auto [res, sv] = test_grammar(str, *vb6_grammar::attributeDef, attrs); + ut::expect((res) >> fatal) << "stopped at: " << sv; + ut::expect(sv.empty()); + + ut::expect(attrs.size() == 2); + + auto it1 = attrs.find("ModuleName"); + ut::expect(it1 != attrs.cend()); + if(it1 != attrs.cend()) + { + ut::expect(it1->second == "MyForm"); + } + + auto it2 = attrs.find("ProgID"); + ut::expect(it2 != attrs.cend()); + if(it2 != attrs.cend()) + { + ut::expect(it2->second == "00-00-00-00"); + } + }; + + ut::test("attributes") = [] + { + vb6_ast::STRICT_MODULE_STRUCTURE::module_attributes ast; + auto str = R"vb(Attribute ModuleName = "MyForm" + Attribute ProgID = "00-00-00-00" + )vb"s; + //auto [res, sv] = test_grammar(str, vb6_grammar::preamble, ast); + auto [res, sv] = test_grammar(str, *vb6_grammar::attributeDef, ast); + ut::expect((res) >> fatal) << "stopped at: " << sv; + ut::expect(sv.empty()); + + ut::expect(ast.size() == 2); + + auto const it1 = ast.find("ModuleName"); + ut::expect(it1 != ast.cend()); + if(it1 != ast.cend()) + { + ut::expect(it1->second == "MyForm"); + } + + auto const it2 = ast.find("ProgID"); + ut::expect(it2 != ast.cend()); + if(it2 != ast.cend()) + { + ut::expect(it2->second == "00-00-00-00"); + } + }; + + ut::test("options") = [] + { + vector ast; + auto str = R"vb(Option Explicit + Option Base 0 + )vb"s; + auto [res, sv] = test_grammar(str, *vb6_grammar::option_item, ast); + ut::expect((res) >> fatal) << "stopped at: " << sv; + ut::expect(sv.empty()); + + ut::expect((ast.size() == 2) >> ut::fatal); + + ut::expect(ast[0] == vb6_ast::module_option::explicit_); + ut::expect(ast[1] == vb6_ast::module_option::base_0); + }; + +#if 0 + ut::test("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; + auto [res, sv] = test_grammar(str, declarations, decls); + ut::expect((res) >> fatal) << "stopped at: " << sv; + ut::expect(sv.empty()); + + ut::expect((decls.size() == 5) >> ut::fatal); + + ut::expect(ut::nothrow([ast](){boost::get(decls[0].get();}))); + ut::expect(ut::nothrow([ast](){boost::get(decls[1].get();}))); + ut::expect(ut::nothrow([ast](){boost::get(decls[2].get();}))); + ut::expect(ut::nothrow([ast](){boost::get(decls[3].get();}))); + ut::expect(ut::nothrow([ast](){boost::get(decls[4].get();}))); + }; + + ut::test("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; + auto [res, sv] = test_grammar(str, vb6_grammar::STRICT_MODULE_STRUCTURE::basModDef, ast); + ut::expect((res) >> fatal) << "stopped at: " << sv; + ut::expect(sv.empty()); + + vb6_ast_printer P(cout); + P(ast); + + ut::expect(ast.attrs.size() == 2); + + auto const it1 = ast.attrs.find("ModuleName"); + EXPECT_NE(it1, ast.attrs.cend()); + if(it1 != ast.attrs.cend()) + { + ut::expect(it1->second == "MyForm"); + } + + auto const it2 = ast.attrs.find("ProgID"); + EXPECT_NE(it2, ast.attrs.cend()); + if(it2 != ast.attrs.cend()) + { + ut::expect(it2->second == "00-00-00-00"); + } + + ut::expect(ast.opts.items.size() == 4); + if(ast.opts.items.size() == 4) + { + #if 0 + ut::expect(ast.opts.items[0] == vb6_ast::module_option::explicit_); + ut::expect(ast.opts.items[1] == vb6_ast::module_option::base_0); + #else + ut::expect(boost::get(ast.opts.items[0].get()) == vb6_ast::module_option::explicit_); + ut::expect(boost::get(ast.opts.items[1].get()) == vb6_ast::module_option::base_0); + #endif + ut::expect(ut::nothrow([ast](){boost::get(ast.opts.items[2].get();}))); + ut::expect(ut::nothrow([ast](){boost::get(ast.opts.items[3].get();}))); + } + + ut::expect(ast.declarations.size() == 3); + if(ast.declarations.size() == 3) + { + ut::expect(ut::nothrow([ast](){boost::get(ast.declarations[0].get();}))); + ut::expect(ut::nothrow([ast](){boost::get(ast.declarations[1].get();}))); + ut::expect(ut::nothrow([ast](){boost::get(ast.declarations[2].get();}))); + } + + ut::expect(ast.functions.size() == 2); + if(ast.functions.size() == 2) + { + ut::expect(ut::nothrow([ast](){boost::get(ast.functions[0].get();}))); + ut::expect(ut::nothrow([ast](){boost::get(ast.functions[1].get();}))); + } + }; + #endif + + ut::test("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; + auto [res, sv] = test_grammar(str, vb6_grammar::basModDef, ast); + ut::expect((res) >> fatal) << "stopped at: " << sv; + ut::expect(sv.empty()); + + vb6_ast_printer P(cout); + P(ast); + + ut::expect(ut::eq(ast.size(), 11) >> ut::fatal); + + ut::expect(boost::get(ast[0].get()).first == "ModuleName"); + ut::expect(boost::get(ast[1].get()).first == "ProgID"); + + ut::expect(boost::get(ast[2].get()) == vb6_ast::module_option::explicit_); + ut::expect(boost::get(ast[3].get()) == vb6_ast::module_option::base_0); + + ut::expect(ut::nothrow([ast](){boost::get(ast[4].get());})); + ut::expect(ut::nothrow([ast](){boost::get(ast[5].get());})); + + ut::expect(ut::nothrow([ast](){boost::get(boost::get(ast[6].get()));})); + ut::expect(ut::nothrow([ast](){boost::get(boost::get(ast[7].get()));})); + ut::expect(ut::nothrow([ast](){boost::get(boost::get(ast[8].get()));})); + + ut::expect(ut::nothrow([ast](){boost::get(ast[9].get());})); + ut::expect(ut::nothrow([ast](){boost::get(ast[10].get());})); + }; + + ut::test("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; + auto [res, sv] = test_grammar(str, vb6_grammar::global_var_declaration, vars); + ut::expect((res) >> fatal) << "stopped at: " << sv; + ut::expect(sv.empty()); + + ut::expect(vars.at == vb6_ast::access_type::global); + ut::expect(!vars.with_events); + ut::expect((vars.vars.size() == 1) >> ut::fatal); + + ut::expect(vars.vars[0].name == "g_var"); + ut::expect(!vars.vars[0].construct); + ut::expect(vars.vars[0].type.has_value()); + if(vars.vars[0].type) + { + ut::expect(*vars.vars[0].type == "Long"); + } + }; +}; + +int main([[maybe_unused]] int argc, [[maybe_unused]] char* argv[]) +{ + log_compiler_info(std::cout); + + bool res = boost::ut::cfg.run(); + return res ? 0 : -1; +} diff --git a/src/vb6_parser_statements_test.cpp b/test/vb6_parser_statements.gtest.cpp similarity index 66% rename from src/vb6_parser_statements_test.cpp rename to test/vb6_parser_statements.gtest.cpp index 790d06a..55e02a0 100644 --- a/src/vb6_parser_statements_test.cpp +++ b/test/vb6_parser_statements.gtest.cpp @@ -6,7 +6,7 @@ //#define BOOST_SPIRIT_X3_DEBUG -#include "test_grammar_helper_gtest.hpp" +#include "test_grammar_helper_ut.hpp" #include "vb6_parser.hpp" #include @@ -21,7 +21,9 @@ 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); + auto [res, sv] = test_grammar("Set var1 = \" ciao\"\r\n", vb6_grammar::statements::assignmentStmt, st); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); EXPECT_EQ(st.type, vb6_ast::assignmentType::set); EXPECT_EQ(st.var.var, "var1"); @@ -35,7 +37,9 @@ GTEST_TEST(vb6_parser_statements, Assignment_0) 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); + auto [res, sv] = test_grammar("Let var2 = 54.7\r\n", vb6_grammar::statements::assignmentStmt, st); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); EXPECT_EQ(st.type, vb6_ast::assignmentType::let); EXPECT_EQ(st.var.var, "var2"); @@ -49,7 +53,9 @@ GTEST_TEST(vb6_parser_statements, Assignment_1) 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); + auto [res, sv] = test_grammar("var3 = Func(\"descr\", 54.7)\r\n", vb6_grammar::statements::assignmentStmt, st); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); EXPECT_EQ(st.type, vb6_ast::assignmentType::na); EXPECT_EQ(st.var.var, "var3"); @@ -64,7 +70,9 @@ GTEST_TEST(vb6_parser_statements, Assignment_2) GTEST_TEST(vb6_parser_statements, Assignment_3) { vb6_ast::statements::assignStmt st; - test_grammar("str = CStr(i)\r\n", vb6_grammar::statements::assignmentStmt, st); + auto [res, sv] = test_grammar("str = CStr(i)\r\n", vb6_grammar::statements::assignmentStmt, st); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); EXPECT_EQ(st.type, vb6_ast::assignmentType::na); EXPECT_EQ(st.var.var, "str"); @@ -75,7 +83,9 @@ GTEST_TEST(vb6_parser_statements, Assignment_3) 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); + auto [res, sv] = test_grammar("Dim var As New Form\r\n", vb6_grammar::statements::localvardeclStmt, st); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); EXPECT_EQ(st.type, vb6_ast::localvardeclType::Dim); @@ -90,7 +100,9 @@ GTEST_TEST(vb6_parser_statements, LocalVarDecl_1) 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); + auto [res, sv] = test_grammar("Static var1 As String, var2 As Integer\r\n", vb6_grammar::statements::localvardeclStmt, st); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); EXPECT_EQ(st.type, vb6_ast::localvardeclType::Static); @@ -110,8 +122,11 @@ GTEST_TEST(vb6_parser_statements, LocalVarDecl_2) 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); + auto [res, sv] = test_grammar("ReDim var1(15)\r\n", vb6_grammar::statements::redimStmt, st); + //auto [res, sv] = test_grammar("ReDim var1(2*L + 1)\r\n", vb6_grammar::statements::redimStmt, st); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); + EXPECT_FALSE(st.preserve); EXPECT_EQ(st.var.var, "var1"); } @@ -119,7 +134,9 @@ GTEST_TEST(vb6_parser_statements, ReDim) GTEST_TEST(vb6_parser_statements, Exit) { vb6_ast::statements::exitStmt st; - test_grammar("Exit Function\r\n", vb6_grammar::statements::exitStmt, st); + auto [res, sv] = test_grammar("Exit Function\r\n", vb6_grammar::statements::exitStmt, st); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); EXPECT_EQ(st.type, vb6_ast::exit_type::function); } @@ -127,7 +144,9 @@ GTEST_TEST(vb6_parser_statements, Exit) GTEST_TEST(vb6_parser_statements, GoTo) { vb6_ast::statements::gotoStmt st; - test_grammar("GoSub label1\r\n", vb6_grammar::statements::gotoStmt, st); + auto [res, sv] = test_grammar("GoSub label1\r\n", vb6_grammar::statements::gotoStmt, st); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); EXPECT_EQ(st.type, vb6_ast::gotoType::gosub_v); EXPECT_EQ(st.label, "label1"); @@ -136,7 +155,9 @@ GTEST_TEST(vb6_parser_statements, GoTo) GTEST_TEST(vb6_parser_statements, OnError) { vb6_ast::statements::onerrorStmt st; - test_grammar("On Error Resume Next\r\n", vb6_grammar::statements::onerrorStmt, st); + auto [res, sv] = test_grammar("On Error Resume Next\r\n", vb6_grammar::statements::onerrorStmt, st); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); EXPECT_EQ(st.type, vb6_ast::onerror_type::resume_next); EXPECT_TRUE(st.label.empty()); @@ -146,25 +167,40 @@ 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"); + { + auto [res, sv] = test_grammar("Resume\r\n", vb6_grammar::statements::resumeStmt, st); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); + EXPECT_EQ(st.type, vb6_ast::resume_type::implicit); + } + { + auto [res, sv] = test_grammar("Resume Next\r\n", vb6_grammar::statements::resumeStmt, st); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); + EXPECT_EQ(st.type, vb6_ast::resume_type::next); + } + { + auto [res, sv] = test_grammar("Resume 0\r\n", vb6_grammar::statements::resumeStmt, st); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); + EXPECT_EQ(st.type, vb6_ast::resume_type::line_nr); + EXPECT_EQ(boost::get(st.label_or_line_nr), 0); + } + { + auto [res, sv] = test_grammar("Resume resume_point\r\n", vb6_grammar::statements::resumeStmt, st); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); + 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); + auto [res, sv] = test_grammar("label1:\r\n", vb6_grammar::statements::labelStmt, st); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); EXPECT_EQ(st.label, "label1"); } @@ -172,19 +208,27 @@ GTEST_TEST(vb6_parser_statements, Label) GTEST_TEST(vb6_parser_statements, Call_explicit) { vb6_ast::statements::callStmt st; - test_grammar("Call Foo(13)\r\n", vb6_grammar::statements::callexplicitStmt, st); + auto [res, sv] = test_grammar("Call Foo(13)\r\n", vb6_grammar::statements::callexplicitStmt, st); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); EXPECT_TRUE(st.explicit_call); EXPECT_EQ(st.sub_name, "Foo"); EXPECT_EQ(st.params.size(), 1); - test_grammar("Call Foo 13\r\n", vb6_grammar::statements::callexplicitStmt, st, false); + { + auto [res, sv] = test_grammar("Call Foo 13\r\n", vb6_grammar::statements::callexplicitStmt, st); + ASSERT_FALSE(res) << "stopped at: " << sv; + //EXPECT_TRUE(sv.empty()); + } } 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); + auto [res, sv] = test_grammar("Foo \"Sea\", Nothing, False\r\n", vb6_grammar::statements::callimplicitStmt, st); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); EXPECT_FALSE(st.explicit_call); EXPECT_EQ(st.sub_name, "Foo"); @@ -194,14 +238,16 @@ GTEST_TEST(vb6_parser_statements, Call_implicit) GTEST_TEST(vb6_parser_statements, RaiseEvent) { vb6_ast::statements::raiseeventStmt st; - test_grammar("RaiseEvent OnChange(\"hi!\")\r\n", + auto [res, sv] = test_grammar("RaiseEvent OnChange(\"hi!\")\r\n", vb6_grammar::statements::raiseeventStmt, st); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); } GTEST_TEST(vb6_parser_statements, statement_block) { vb6_ast::statements::statement_block st; - test_grammar( + auto [res, sv] = test_grammar( R"vb(Set var1 = " ciao" Let var2 = 54.7 var3 = Func("descr", 54.7) @@ -218,8 +264,10 @@ GTEST_TEST(vb6_parser_statements, statement_block) Foo "Sea", Nothing, False RaiseEvent OnChange("hi!") )vb", vb6_grammar::statements::statement_block, st); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); - EXPECT_EQ(st.size(), 15); + ASSERT_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)); @@ -240,12 +288,14 @@ GTEST_TEST(vb6_parser_statements, statement_block) GTEST_TEST(vb6_parser_compound_statements, With) { vb6_ast::statements::withStmt st; - test_grammar( + auto [res, sv] = test_grammar( R"vb(With obj 'Call Module1.foo(True, .Name) Call foo(True, .Name) End With )vb", vb6_grammar::statements::withStmt, st); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); EXPECT_FALSE(st.with_variable.ctx.leading_dot); EXPECT_TRUE(st.with_variable.ctx.elements.empty()); @@ -256,12 +306,15 @@ GTEST_TEST(vb6_parser_compound_statements, With) GTEST_TEST(vb6_parser_compound_statements, While) { vb6_ast::statements::whileStmt st; - test_grammar( + auto [res, sv] = test_grammar( R"vb(While cond(34, i) Print str Wend )vb", vb6_grammar::statements::whileStmt, st); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); + auto& tmp = boost::get>(st.condition.get()).get(); EXPECT_EQ(tmp.func_name, "cond"); EXPECT_EQ(tmp.params.size(), 2); @@ -271,24 +324,30 @@ GTEST_TEST(vb6_parser_compound_statements, While) GTEST_TEST(vb6_parser_compound_statements, Do) { vb6_ast::statements::doStmt st; - test_grammar( + auto [res, sv] = test_grammar( R"vb(Do Print str Loop )vb", vb6_grammar::statements::doStmt, st); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); + EXPECT_EQ(st.block.size(), 1); } GTEST_TEST(vb6_parser_compound_statements, DoWhile) { vb6_ast::statements::dowhileStmt st; - test_grammar( + auto [res, sv] = test_grammar( R"vb(Do While cond(34, i) Print str Loop )vb", vb6_grammar::statements::dowhileStmt, st); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); + auto& tmp = boost::get>(st.condition.get()).get(); EXPECT_EQ(tmp.func_name, "cond"); EXPECT_EQ(tmp.params.size(), 2); @@ -298,12 +357,15 @@ GTEST_TEST(vb6_parser_compound_statements, DoWhile) GTEST_TEST(vb6_parser_compound_statements, LoopWhile) { vb6_ast::statements::loopwhileStmt st; - test_grammar( + auto [res, sv] = test_grammar( R"vb(Do Print str Loop While cond(34, i) )vb", vb6_grammar::statements::loopwhileStmt, st); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); + auto& tmp = boost::get>(st.condition.get()).get(); EXPECT_EQ(tmp.func_name, "cond"); EXPECT_EQ(tmp.params.size(), 2); @@ -313,12 +375,15 @@ GTEST_TEST(vb6_parser_compound_statements, LoopWhile) GTEST_TEST(vb6_parser_compound_statements, DoUntil) { vb6_ast::statements::dountilStmt st; - test_grammar( + auto [res, sv] = test_grammar( R"vb(Do Until cond(34, i) Print str Loop )vb", vb6_grammar::statements::dountilStmt, st); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); + auto& tmp = boost::get>(st.condition.get()).get(); EXPECT_EQ(tmp.func_name, "cond"); EXPECT_EQ(tmp.params.size(), 2); @@ -328,12 +393,15 @@ GTEST_TEST(vb6_parser_compound_statements, DoUntil) GTEST_TEST(vb6_parser_compound_statements, LoopUntil) { vb6_ast::statements::loopuntilStmt st; - test_grammar( + auto [res, sv] = test_grammar( R"vb(Do Print str Loop Until cond(34, i) )vb", vb6_grammar::statements::loopuntilStmt, st); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); + auto& tmp = boost::get>(st.condition.get()).get(); EXPECT_EQ(tmp.func_name, "cond"); EXPECT_EQ(tmp.params.size(), 2); @@ -343,7 +411,7 @@ GTEST_TEST(vb6_parser_compound_statements, LoopUntil) GTEST_TEST(vb6_parser_compound_statements, For) { vb6_ast::statements::forStmt st; - test_grammar( + auto [res, sv] = test_grammar( R"vb(For i = 1 To 100 Step 2 Dim str As String str = CStr(i) @@ -351,6 +419,9 @@ GTEST_TEST(vb6_parser_compound_statements, For) Next i )vb", vb6_grammar::statements::forStmt, st); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); + EXPECT_EQ(st.for_variable.var, "i"); EXPECT_FALSE(st.for_variable.ctx.leading_dot); EXPECT_TRUE(st.for_variable.ctx.elements.empty()); @@ -369,12 +440,15 @@ GTEST_TEST(vb6_parser_compound_statements, For) GTEST_TEST(vb6_parser_compound_statements, ForEach) { vb6_ast::statements::foreachStmt st; - test_grammar( + auto [res, sv] = test_grammar( R"vb(For Each el In Items Call Print(el) Next el )vb", vb6_grammar::statements::foreachStmt, st); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); + EXPECT_EQ(st.for_variable.var, "el"); EXPECT_FALSE(st.for_variable.ctx.leading_dot); EXPECT_TRUE(st.for_variable.ctx.elements.empty()); @@ -389,49 +463,59 @@ GTEST_TEST(vb6_parser_compound_statements, ForEach) 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); + { + vb6_ast::statements::if_branch ast1; + auto [res, sv] = test_grammar( + R"vb(If cond1 Then + Dim str As String + str = CStr(i) + Print str + )vb", + vb6_grammar::statements::ifBranch, ast1); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); - auto& tmp1 = boost::get(ast1.condition.get()); + auto& tmp1 = boost::get(ast1.condition.get()); - EXPECT_EQ(tmp1.var, "cond1"); - EXPECT_EQ(ast1.block.size(), 3); + EXPECT_EQ(tmp1.var, "cond1"); + EXPECT_EQ(ast1.block.size(), 3); + } + { + vb6_ast::statements::if_branch ast2; + auto [res, sv] = test_grammar( + R"vb(ElseIf cond2 Then + Dim str As String + str = CStr(i) + Print str + )vb", + vb6_grammar::statements::elsifBranch, ast2); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); - 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()); - auto& tmp2 = boost::get(ast2.condition.get()); + EXPECT_EQ(tmp2.var, "cond2"); + EXPECT_EQ(ast2.block.size(), 3); + } + { + vb6_ast::statements::statement_block ast3; + auto [res, sv] = test_grammar( + R"vb(Else + Print str + 'End If + )vb", + vb6_grammar::statements::elseBranch, ast3); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); - 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); + EXPECT_EQ(ast3.size(), 2); + } } GTEST_TEST(vb6_parser_compound_statements, IfElse) { vb6_ast::statements::ifelseStmt ast; - test_grammar( + auto [res, sv] = test_grammar( R"vb(If cond1 Then Print "1" ElseIf cond2 Then @@ -443,6 +527,8 @@ GTEST_TEST(vb6_parser_compound_statements, IfElse) End If )vb", vb6_grammar::statements::ifelseStmt, ast); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); #ifdef SIMPLE_IF_STATEMENT EXPECT_EQ(ast.first_branch.block.size(), 5); @@ -471,7 +557,7 @@ GTEST_TEST(vb6_parser_compound_statements, IfElse) GTEST_TEST(vb6_parser_compound_statements, Select) { vb6_ast::statements::selectStmt st; - test_grammar( + auto [res, sv] = test_grammar( R"vb(Select Case frm.Type Case 0 Dim str As String @@ -485,6 +571,8 @@ GTEST_TEST(vb6_parser_compound_statements, Select) ' Print "Default" End Select )vb", vb6_grammar::statements::selectStmt, st); + ASSERT_TRUE(res) << "stopped at: " << sv; + EXPECT_TRUE(sv.empty()); ASSERT_EQ(st.condition.get().type(), typeid(vb6_ast::decorated_variable)); EXPECT_EQ(boost::get(st.condition.get()).var, "Type"); diff --git a/src/vb6_parser_test_main.cpp b/test/vb6_parser_test_main.cpp similarity index 100% rename from src/vb6_parser_test_main.cpp rename to test/vb6_parser_test_main.cpp