From aba386a11c3b99dfe33d30dec586d69a18de3978 Mon Sep 17 00:00:00 2001 From: Federico Aponte Date: Tue, 21 Jun 2022 23:13:15 +0200 Subject: [PATCH] Some changes --- .gitignore | 3 + CMakeLists.txt | 44 +- CMakePresets.json | 32 ++ src/test_grammar_helper.hpp | 51 -- src/test_grammar_helper_gtest.hpp | 44 ++ src/test_grammar_helper_ut.hpp | 45 ++ src/vb6_parser.ut.cpp | 853 +++++++++++++++++++++++++++++ src/vb6_parser_statements_test.cpp | 4 +- src/vb6_parser_test.cpp | 2 +- src/vb6_parser_test_main.cpp | 27 +- 10 files changed, 1034 insertions(+), 71 deletions(-) create mode 100644 .gitignore create mode 100644 CMakePresets.json create mode 100644 src/test_grammar_helper_gtest.hpp create mode 100644 src/test_grammar_helper_ut.hpp create mode 100644 src/vb6_parser.ut.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3174148 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.vs/ +.vscode/ +build/ \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 608b178..2c25e27 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,14 +2,14 @@ cmake_minimum_required(VERSION 3.10) project(vb6_parser) -set(CMAKE_CXX_STANDARD 17) +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/609281088cfefc76f9d0ce82e1ff6c30cc3591e5.zip + 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) @@ -18,9 +18,8 @@ FetchContent_MakeAvailable(googletest) add_compile_options($<$:-Wall> $<$:-Wextra>) -enable_testing() - #find_package(GTest CONFIG REQUIRED) # GoogleTest with vcpkg +find_package(ut CONFIG REQUIRED) find_package(Threads REQUIRED) find_package(Boost REQUIRED QUIET COMPONENTS system) @@ -55,6 +54,11 @@ PRIVATE -DBOOST_MPL_LIMIT_LIST_SIZE=30 ) +target_link_libraries(vb6_parser_lib +PRIVATE + Boost::system +) + add_executable(vb6_parser src/vb6_parser_main.cpp src/vb6_test1.cpp @@ -76,9 +80,30 @@ PRIVATE # ---- 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.hpp + src/test_grammar_helper_gtest.hpp src/vb6_parser_statements_test.cpp src/vb6_parser_test.cpp src/vb6_parser_test_main.cpp @@ -97,6 +122,7 @@ PRIVATE Boost::system Threads::Threads ) + #[[ # GoogleTest with vcpkg, probably not a good idea target_link_libraries(vb6_parser_test @@ -111,6 +137,12 @@ PRIVATE ) #]] +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_test(AllTestsInMain vb6_parser_test) diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 0000000..a501d9b --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,32 @@ +{ + "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", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "CMAKE_EXPORT_COMPILE_COMMANDS": true + } + } + ], + "buildPresets": [ + { + "name": "vcpkg", + "configurePreset": "vcpkg", + "jobs": 16 + } + ], + "testPresets": [ + { + "name": "default", + "configurePreset": "vcpkg", + "output": { "outputOnFailure": true }, + "execution": { "noTestsAction": "error", "stopOnFailure": true } + } + ] +} diff --git a/src/test_grammar_helper.hpp b/src/test_grammar_helper.hpp index cdd49c1..f62e348 100644 --- a/src/test_grammar_helper.hpp +++ b/src/test_grammar_helper.hpp @@ -9,8 +9,6 @@ #include "color_console.hpp" #include "vb6_config.hpp" -#include - #include #include @@ -65,52 +63,3 @@ void test_grammar(std::ostream& os, std::string_view testName, ruleType rule, os << tag_fail << " - " << e.what() << " - " << (e.where() - it1) << " - " << e.which() << '\n'; } } - -template -void test_grammar(std::string_view fragment, ruleType rule, attrType& attr) -{ - namespace x3 = boost::spirit::x3; - - auto it1 = cbegin(fragment); - auto const it2 = cend(fragment); - - std::stringstream out; - - vb6_grammar::error_handler_type error_handler(it1, it2, out, "source.bas"); - - auto const parser = - // we pass our error handler to the parser so we can access - // it later on in our on_error and on_success handlers - x3::with(std::ref(error_handler)) - [ - rule - ]; - - ASSERT_TRUE(x3::phrase_parse(it1, it2, parser, vb6_grammar::skip, attr)) - << "stopped at: " << std::string(it1, it2); - - EXPECT_EQ(it1, it2); -} - -template -void test_grammar_false(std::string_view fragment, ruleType rule, attrType& attr) -{ - namespace x3 = boost::spirit::x3; - - auto it1 = cbegin(fragment); - auto const it2 = cend(fragment); - - std::stringstream out; - - vb6_grammar::error_handler_type error_handler(it1, it2, out, "source.bas"); - - auto const parser = - // we pass our error handler to the parser so we can access - // it later on in our on_error and on_success handlers - x3::with(std::ref(error_handler)) - [ - rule - ]; - - ASSERT_FALSE(x3::phrase_parse(it1, it2, parser, vb6_grammar::skip, attr)); -} diff --git a/src/test_grammar_helper_gtest.hpp b/src/test_grammar_helper_gtest.hpp new file mode 100644 index 0000000..072bfe2 --- /dev/null +++ b/src/test_grammar_helper_gtest.hpp @@ -0,0 +1,44 @@ +//: 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/test_grammar_helper_ut.hpp b/src/test_grammar_helper_ut.hpp new file mode 100644 index 0000000..c61ed26 --- /dev/null +++ b/src/test_grammar_helper_ut.hpp @@ -0,0 +1,45 @@ +//: test_grammar_ut.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 + ]; + + using namespace boost::ut; + + expect((x3::phrase_parse(it1, it2, parser, vb6_grammar::skip, attr) == expected) >> fatal) + << "stopped at: " << std::string(it1, it2); + + expect(it1 == it2); +} diff --git a/src/vb6_parser.ut.cpp b/src/vb6_parser.ut.cpp new file mode 100644 index 0000000..1444656 --- /dev/null +++ b/src/vb6_parser.ut.cpp @@ -0,0 +1,853 @@ +//: 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/src/vb6_parser_statements_test.cpp b/src/vb6_parser_statements_test.cpp index 7baadd0..790d06a 100644 --- a/src/vb6_parser_statements_test.cpp +++ b/src/vb6_parser_statements_test.cpp @@ -6,7 +6,7 @@ //#define BOOST_SPIRIT_X3_DEBUG -#include "test_grammar_helper.hpp" +#include "test_grammar_helper_gtest.hpp" #include "vb6_parser.hpp" #include @@ -178,7 +178,7 @@ GTEST_TEST(vb6_parser_statements, Call_explicit) EXPECT_EQ(st.sub_name, "Foo"); EXPECT_EQ(st.params.size(), 1); - test_grammar_false("Call Foo 13\r\n", vb6_grammar::statements::callexplicitStmt, st); + test_grammar("Call Foo 13\r\n", vb6_grammar::statements::callexplicitStmt, st, false); } GTEST_TEST(vb6_parser_statements, Call_implicit) diff --git a/src/vb6_parser_test.cpp b/src/vb6_parser_test.cpp index c8b78bc..9cd7659 100644 --- a/src/vb6_parser_test.cpp +++ b/src/vb6_parser_test.cpp @@ -6,7 +6,7 @@ //#define BOOST_SPIRIT_X3_DEBUG -#include "test_grammar_helper.hpp" +#include "test_grammar_helper_gtest.hpp" #include "vb6_parser.hpp" #include "vb6_ast_printer.hpp" diff --git a/src/vb6_parser_test_main.cpp b/src/vb6_parser_test_main.cpp index 5fda93e..6b3e92f 100644 --- a/src/vb6_parser_test_main.cpp +++ b/src/vb6_parser_test_main.cpp @@ -11,17 +11,22 @@ int main(int argc, char* argv[]) { testing::InitGoogleTest(&argc, argv); - - // --gtest_filter = Test_Cases1* - // --gtest_repeat = 1000 - // --gtest_break_on_failure - // --gtest_shuffle - // --gtest_random_seed=SEED - // --gtest_color=no - // --gtest_print_time=0 - // --gtest_print_utf8=0 - // --gtest_output=xml:path_to_output_file - // --gtest_output=json:path_to_output_file +/* + --gtest_list_tests + --gtest_filter=POSITIVE_PATTERNS[-NEGATIVE_PATTERNS] + --gtest_also_run_disabled_tests + --gtest_repeat=[COUNT] + --gtest_shuffle + --gtest_random_seed=[NUMBER] + --gtest_color=(yes|no|auto) + --gtest_brief=1 + --gtest_print_time=0 + --gtest_output=(json|xml)[:DIRECTORY_PATH/|:FILE_PATH] + --gtest_death_test_style=(fast|threadsafe) + --gtest_break_on_failure + --gtest_throw_on_failure + --gtest_catch_exceptions=0 +*/ //::test::GTEST_FLAG(list_tests) = true; //::testing::GTEST_FLAG(filter) = "*empty_lines*";