From 6589ec52116dd2df14e1373911224605744bf032 Mon Sep 17 00:00:00 2001 From: Lars Melchior Date: Wed, 10 Apr 2019 18:48:27 +0200 Subject: [PATCH] v1.5 (#21) * rename files * add extension * add extension test * refactor peg grammar program * update parser extension test * update deps * update travis * update Glue * v1.5 --- .travis.yml | 2 +- CMakeLists.txt | 40 ++++++++-- cmake/CPM.cmake | 9 ++- cmake/CPMProject.CMakeLists.cmake.in | 2 +- cmake/LarsParserConfig.cmake.in | 5 ++ examples/calculator.cpp | 2 +- examples/calculator_sequental.cpp | 2 +- examples/type_checker.cpp | 2 +- include/lars/parser/extension.h | 9 +++ .../generator.h} | 18 +++-- include/lars/{ => parser}/grammar.h | 0 include/lars/{ => parser}/interpreter.h | 1 + include/lars/{ => parser}/parser.h | 0 include/lars/{ => parser}/peg.h | 5 +- source/extension.cpp | 74 +++++++++++++++++++ source/grammar.cpp | 4 +- source/interpreter.cpp | 2 +- source/parser.cpp | 2 +- source/peg.cpp | 58 +++++++-------- tests/CMakeLists.txt | 12 +-- tests/extension.cpp | 67 +++++++++++++++++ tests/parser.cpp | 57 +++++++------- 22 files changed, 286 insertions(+), 87 deletions(-) create mode 100644 include/lars/parser/extension.h rename include/lars/{parser_generator.h => parser/generator.h} (89%) rename include/lars/{ => parser}/grammar.h (100%) rename include/lars/{ => parser}/interpreter.h (99%) rename include/lars/{ => parser}/parser.h (100%) rename include/lars/{ => parser}/peg.h (70%) create mode 100644 source/extension.cpp create mode 100644 tests/extension.cpp diff --git a/.travis.yml b/.travis.yml index 1abf898..2033545 100644 --- a/.travis.yml +++ b/.travis.yml @@ -39,7 +39,7 @@ before_install: - sudo sh cmake.sh --skip-license --exclude-subdir --prefix=/usr/local install: - - cmake -H. -Bbuild + - cmake -H. -Bbuild -DBUILD_LARS_PARSER_GLUE_EXTENSION=On - cmake --build build - sudo cmake --build build --target install diff --git a/CMakeLists.txt b/CMakeLists.txt index d670e99..8b576c2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,7 @@ endif() # ---- Project ---- project(LarsParser - VERSION 1.4 + VERSION 1.5 LANGUAGES CXX ) @@ -19,6 +19,7 @@ endif() option(BUILD_LARS_PARSER_EXAMPLES "Enable examples" OFF) option(ENABLE_LARS_PARSER_TESTS "Enable tests" OFF) +option(BUILD_LARS_PARSER_GLUE_EXTENSION "Build LarsParser Glue extension" OFF) # ---- Dependencies ---- @@ -27,9 +28,17 @@ include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/CPM.cmake) CPMAddPackage( NAME LHC GIT_REPOSITORY https://github.com/TheLartians/LHC.git - VERSION 0.3 + VERSION 0.4 ) +if(BUILD_LARS_PARSER_GLUE_EXTENSION) + CPMAddPackage( + NAME Glue + GIT_REPOSITORY https://github.com/TheLartians/Glue.git + VERSION 0.3 + ) +endif() + # ---- Minimum CXX ---- if(NOT CMAKE_CXX_STANDARD GREATER 17) @@ -38,12 +47,34 @@ endif() # ---- Create library ---- -FILE(GLOB headers "${CMAKE_CURRENT_SOURCE_DIR}/include/lars/*.h") -FILE(GLOB sources "${CMAKE_CURRENT_SOURCE_DIR}/source/*.cpp") +SET(headers + "${CMAKE_CURRENT_SOURCE_DIR}/include/lars/parser/grammar.h" + "${CMAKE_CURRENT_SOURCE_DIR}/include/lars/parser/parser.h" + "${CMAKE_CURRENT_SOURCE_DIR}/include/lars/parser/interpreter.h" + "${CMAKE_CURRENT_SOURCE_DIR}/include/lars/parser/generator.h" + "${CMAKE_CURRENT_SOURCE_DIR}/include/lars/parser/peg.h" +) + +SET(sources + "${CMAKE_CURRENT_SOURCE_DIR}/source/grammar.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/source/parser.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/source/interpreter.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/source/peg.cpp" +) + +if(BUILD_LARS_PARSER_GLUE_EXTENSION) + LIST(APPEND headers "${CMAKE_CURRENT_SOURCE_DIR}/include/lars/parser/extension.h") + LIST(APPEND sources "${CMAKE_CURRENT_SOURCE_DIR}/source/extension.cpp") +endif() + add_library(LarsParser ${sources} ${headers}) target_link_libraries(LarsParser PRIVATE LHC) +if(BUILD_LARS_PARSER_GLUE_EXTENSION) + target_link_libraries(LarsParser PUBLIC Glue) +endif() + target_include_directories(LarsParser PUBLIC $ @@ -58,7 +89,6 @@ write_basic_package_version_file( COMPATIBILITY AnyNewerVersion ) - # ---- Install ---- install( diff --git a/cmake/CPM.cmake b/cmake/CPM.cmake index 38e2342..62cd2ef 100755 --- a/cmake/CPM.cmake +++ b/cmake/CPM.cmake @@ -2,10 +2,12 @@ set(_CPM_Dir "${CMAKE_CURRENT_LIST_DIR}") include(CMakeParseArguments) include(${_CPM_Dir}/DownloadProject.cmake) - -function(CPMHasPackage) -endfunction() +option(CPM_OFFLINE "CPM offline mode" OFF) + +if(NOT ${CPM_OFFLINE}) + set(CPM_PACKAGES "" CACHE INTERNAL "CPM Packages") +endif() function(CPMAddPackage) set(options QUIET) @@ -16,6 +18,7 @@ function(CPMAddPackage) VERSION GIT_TAG BINARY_DIR + UPDATE_DISCONNECTED ) set(multiValueArgs "") diff --git a/cmake/CPMProject.CMakeLists.cmake.in b/cmake/CPMProject.CMakeLists.cmake.in index 3bf475f..477e7dd 100755 --- a/cmake/CPMProject.CMakeLists.cmake.in +++ b/cmake/CPMProject.CMakeLists.cmake.in @@ -17,7 +17,7 @@ else() PROJ @CPM_ARGS_NAME@ GIT_REPOSITORY @CPM_ARGS_GIT_REPOSITORY@ GIT_TAG @CPM_ARGS_GIT_TAG@ - UPDATE_DISCONNECTED 1 + UPDATE_DISCONNECTED @CPM_OFFLINE@ GIT_SHALLOW 1 PREFIX @CPM_ARGS_BINARY_DIR@/dl QUIET diff --git a/cmake/LarsParserConfig.cmake.in b/cmake/LarsParserConfig.cmake.in index 5ad9c78..d692966 100644 --- a/cmake/LarsParserConfig.cmake.in +++ b/cmake/LarsParserConfig.cmake.in @@ -1,8 +1,13 @@ @PACKAGE_INIT@ include(CMakeFindDependencyMacro) + find_dependency(LHC) +if(@BUILD_LARS_PARSER_GLUE_EXTENSION@) + find_dependency(Glue) +endif() + include("${CMAKE_CURRENT_LIST_DIR}/LarsParserTargets.cmake") check_required_components("@PROJECT_NAME@") diff --git a/examples/calculator.cpp b/examples/calculator.cpp index 12ad534..c96b448 100644 --- a/examples/calculator.cpp +++ b/examples/calculator.cpp @@ -11,7 +11,7 @@ #include #include -#include +#include int main() { using namespace std; diff --git a/examples/calculator_sequental.cpp b/examples/calculator_sequental.cpp index ad35184..877cd78 100644 --- a/examples/calculator_sequental.cpp +++ b/examples/calculator_sequental.cpp @@ -7,7 +7,7 @@ #include #include -#include +#include int main() { using namespace std; diff --git a/examples/type_checker.cpp b/examples/type_checker.cpp index cb3b212..2ff1aff 100644 --- a/examples/type_checker.cpp +++ b/examples/type_checker.cpp @@ -13,7 +13,7 @@ #include #include -#include +#include int main() { using namespace std; diff --git a/include/lars/parser/extension.h b/include/lars/parser/extension.h new file mode 100644 index 0000000..5450000 --- /dev/null +++ b/include/lars/parser/extension.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +namespace lars{ + namespace extensions{ + std::shared_ptr parser(); + } +} diff --git a/include/lars/parser_generator.h b/include/lars/parser/generator.h similarity index 89% rename from include/lars/parser_generator.h rename to include/lars/parser/generator.h index d18d9f1..7317d28 100644 --- a/include/lars/parser_generator.h +++ b/include/lars/parser/generator.h @@ -6,16 +6,13 @@ namespace lars { template class ParserGenerator: public Program { private: - Program grammarProgram; + peg::GrammarProgram grammarProgram; std::unordered_map> rules; peg::GrammarNode::Shared separatorRule; - public: ParserGenerator(){ - grammarProgram = peg::createGrammarProgram([this](const std::string_view &name){ - return getRuleNode(std::string(name)); - }); + grammarProgram = peg::createGrammarProgram(); } std::shared_ptr getRule(const std::string &name) { @@ -45,8 +42,13 @@ namespace lars { return rule; } + peg::GrammarNode::Shared parseRule(const std::string_view &grammar){ + peg::RuleGetter rg = [this](const auto &name){ return getRuleNode(std::string(name)); }; + return grammarProgram.run(grammar, rg); + } + std::shared_ptr setRule(const std::string &name, const std::string_view &grammar, const typename Interpreter::Callback &callback = typename Interpreter::Callback()){ - return setRule(name, grammarProgram.run(grammar), callback); + return setRule(name, parseRule(grammar), callback); } template std::shared_ptr setProgramRule(const std::string &name, Program subprogram, std::function::Expression,Args...)> callback = [](auto e, Args...){ return e.evaluate(); }){ @@ -59,7 +61,7 @@ namespace lars { } std::shared_ptr setFilteredRule(const std::string &name, const std::string_view &grammar, const peg::GrammarNode::FilterCallback &filter, const typename Interpreter::Callback &callback = typename Interpreter::Callback()){ - return setRule(name, peg::GrammarNode::Sequence({grammarProgram.run(grammar), peg::GrammarNode::Filter(filter)}), callback); + return setRule(name, peg::GrammarNode::Sequence({parseRule(grammar), peg::GrammarNode::Filter(filter)}), callback); } void setSeparator(const std::shared_ptr &rule){ @@ -74,7 +76,7 @@ namespace lars { } std::shared_ptr setSeparatorRule(const std::string &name, const std::string_view &grammar){ - return setSeparatorRule(name, grammarProgram.run(grammar)); + return setSeparatorRule(name, parseRule(grammar)); } void setStart(const std::shared_ptr &rule){ diff --git a/include/lars/grammar.h b/include/lars/parser/grammar.h similarity index 100% rename from include/lars/grammar.h rename to include/lars/parser/grammar.h diff --git a/include/lars/interpreter.h b/include/lars/parser/interpreter.h similarity index 99% rename from include/lars/interpreter.h rename to include/lars/parser/interpreter.h index 186840e..d853697 100644 --- a/include/lars/interpreter.h +++ b/include/lars/parser/interpreter.h @@ -118,6 +118,7 @@ namespace lars { const char * what()const noexcept override; }; + template struct Program { using Expression = typename Interpreter::Expression; diff --git a/include/lars/parser.h b/include/lars/parser/parser.h similarity index 100% rename from include/lars/parser.h rename to include/lars/parser/parser.h diff --git a/include/lars/peg.h b/include/lars/parser/peg.h similarity index 70% rename from include/lars/peg.h rename to include/lars/parser/peg.h index 6c3aed7..231d8be 100644 --- a/include/lars/peg.h +++ b/include/lars/parser/peg.h @@ -12,7 +12,10 @@ namespace lars { std::function defaultEscapeCodeCallback(); Program createCharacterProgram(const std::function escapeCodeCallback = defaultEscapeCodeCallback()); Program createStringProgram(const std::string &open, const std::string &close); - Program createGrammarProgram(const std::function &getRule); + + using RuleGetter = const std::function &; + using GrammarProgram = Program; + GrammarProgram createGrammarProgram(); } } diff --git a/source/extension.cpp b/source/extension.cpp new file mode 100644 index 0000000..0c752d7 --- /dev/null +++ b/source/extension.cpp @@ -0,0 +1,74 @@ +#include +#include +#include +#include + +std::shared_ptr lars::extensions::parser(){ + using namespace lars; + + using ParserGenerator = lars::ParserGenerator; + using Expression = ParserGenerator::Expression; + + auto expressionExtension = std::make_shared(); + expressionExtension->set_class(); + + expressionExtension->add_function("evaluate", [](Expression &e,Any &d){ + return e.evaluate(d); + }); + + expressionExtension->add_function("size", [](Expression &e)->unsigned{ + return e.size(); + }); + + expressionExtension->add_function("get", [](Expression &e, unsigned i){ + if (i < e.size()) { + return e[i]; + } else { + throw std::runtime_error("invalid expression index"); + } + }); + + expressionExtension->add_function("string", [](Expression &e){ + return e.string(); + }); + + expressionExtension->add_function("position", [](Expression &e){ + return e.position(); + }); + + expressionExtension->add_function("length", [](Expression &e){ + return e.length(); + }); + + auto parserGeneratorExtension = std::make_shared(); + parserGeneratorExtension->set_class(); + parserGeneratorExtension->add_function("create", [](){ return ParserGenerator(); }); + + parserGeneratorExtension->add_function("run", [](ParserGenerator &g, const std::string &str, Any& arg){ + return g.run(str, arg); + }); + + parserGeneratorExtension->add_function("setRule",[](ParserGenerator &g, const std::string &name, const std::string &grammar){ + return g.setRule(name, grammar); + }); + + parserGeneratorExtension->add_function("setRuleWithCallback",[](ParserGenerator &g, const std::string &name, const std::string &grammar, AnyFunction callback){ + return g.setRule(name, grammar, [callback](auto e, Any v){ + return callback(e, v); + }); + }); + + parserGeneratorExtension->add_function("setStartRule", [](ParserGenerator &g, const std::string &name){ + g.setStart(g.getRule(name)); + }); + + parserGeneratorExtension->add_function("setSeparatorRule", [](ParserGenerator &g, const std::string &name){ + g.setSeparator(g.getRule(name)); + }); + + auto extension = std::make_shared(); + extension->add_extension("Program", parserGeneratorExtension); + extension->add_extension("Expression", expressionExtension); + + return extension; +} diff --git a/source/grammar.cpp b/source/grammar.cpp index 8b36b15..75655fb 100644 --- a/source/grammar.cpp +++ b/source/grammar.cpp @@ -1,6 +1,6 @@ -#include +#include +#include #include -#include using namespace lars::peg; diff --git a/source/interpreter.cpp b/source/interpreter.cpp index c96db5b..69ba96b 100644 --- a/source/interpreter.cpp +++ b/source/interpreter.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include #include diff --git a/source/parser.cpp b/source/parser.cpp index d5e702b..f5f31de 100644 --- a/source/parser.cpp +++ b/source/parser.cpp @@ -1,5 +1,5 @@ -#include +#include #include #include #include diff --git a/source/peg.cpp b/source/peg.cpp index c171e1a..70f1cca 100644 --- a/source/peg.cpp +++ b/source/peg.cpp @@ -1,4 +1,4 @@ -#include +#include #include using namespace lars; @@ -116,8 +116,8 @@ Program peg::createStringProgram(const std::string &open, const std return program; } -Program peg::createGrammarProgram(const std::function &getRuleCallback){ - Program program; +peg::GrammarProgram peg::createGrammarProgram(){ + peg::GrammarProgram program; auto whitespaceRule = makeRule("Whitespace", GN::ZeroOrMore(GN::Choice({GN::Word(" "),GN::Word("\t")}))); whitespaceRule->hidden = true; @@ -126,65 +126,65 @@ Program peg::createGrammarProgram(const std::function< auto stringProgram = createStringProgram("'", "'"); - auto expressionRule = program.interpreter.makeRule("Expression", GN::Empty(), [](auto e){ return e[0].evaluate(); }); + auto expressionRule = program.interpreter.makeRule("Expression", GN::Empty(), [](auto e,auto &g){ return e[0].evaluate(g); }); auto expression = GN::WeakRule(expressionRule); - auto atomicRule = program.interpreter.makeRule("Atomic", GN::Empty(), [](auto e){ return e[0].evaluate(); }); + auto atomicRule = program.interpreter.makeRule("Atomic", GN::Empty(), [](auto e,auto &g){ return e[0].evaluate(g); }); auto atomic = GN::WeakRule(atomicRule); - auto endOfFile = GN::Rule(program.interpreter.makeRule("EndOfFile", GN::Word(""), [](auto){ + auto endOfFile = GN::Rule(program.interpreter.makeRule("EndOfFile", GN::Word(""), [](auto,auto&){ return GN::EndOfFile(); })); - auto empty = GN::Rule(program.interpreter.makeRule("Empty", GN::Word("<>"), [](auto){ + auto empty = GN::Rule(program.interpreter.makeRule("Empty", GN::Word("<>"), [](auto,auto&){ return GN::Empty(); })); - auto any = GN::Rule(program.interpreter.makeRule("Any", GN::Word("."), [](auto){ + auto any = GN::Rule(program.interpreter.makeRule("Any", GN::Word("."), [](auto,auto&){ return GN::Any(); })); auto selectCharacterProgram = createCharacterProgram(); auto selectCharacter = GN::Sequence({GN::Not(GN::Choice({GN::Word("-"),GN::Word("]")})), GN::Rule(selectCharacterProgram.parser.grammar)}); - auto range = GN::Rule(program.interpreter.makeRule("Range", GN::Sequence({selectCharacter,GN::Word("-"),selectCharacter}), [interpreter=selectCharacterProgram.interpreter](auto e){ + auto range = GN::Rule(program.interpreter.makeRule("Range", GN::Sequence({selectCharacter,GN::Word("-"),selectCharacter}), [interpreter=selectCharacterProgram.interpreter](auto e,auto &g){ return GN::Range(e[0].evaluateBy(interpreter), e[1].evaluateBy(interpreter)); })); - auto singeCharacter = GN::Rule(program.interpreter.makeRule("Character", selectCharacter, [interpreter=selectCharacterProgram.interpreter](auto e){ + auto singeCharacter = GN::Rule(program.interpreter.makeRule("Character", selectCharacter, [interpreter=selectCharacterProgram.interpreter](auto e,auto &g){ return GN::Word(std::string(1,e[0].evaluateBy(interpreter))); })); auto selectSequence = GN::Sequence({GN::Word("["),GN::ZeroOrMore(GN::Choice({range, singeCharacter})),GN::Word("]")}); - auto select = GN::Rule(program.interpreter.makeRule("Select", selectSequence, [](auto e){ - if (e.size() == 1){ return e[0].evaluate(); } + auto select = GN::Rule(program.interpreter.makeRule("Select", selectSequence, [](auto e,auto &g){ + if (e.size() == 1){ return e[0].evaluate(g); } std::vector args; - for (auto c:e) { args.push_back(c.evaluate()); } + for (auto c:e) { args.push_back(c.evaluate(g)); } return GN::Choice(args); })); - auto word = GN::Rule(program.interpreter.makeRule("Word", stringProgram.parser.grammar, [interpreter=stringProgram.interpreter](auto e){ + auto word = GN::Rule(program.interpreter.makeRule("Word", stringProgram.parser.grammar, [interpreter=stringProgram.interpreter](auto e,auto &g){ return GN::Word(e[0].evaluateBy(interpreter)); })); auto ruleName = GN::Sequence({GN::Not(GN::Range('0', '9')),GN::OneOrMore(GN::Choice({GN::Range('a', 'z'),GN::Range('A', 'Z'),GN::Range('0', '9'),GN::Word("_")}))}); - auto rule = GN::Rule(program.interpreter.makeRule("Rule", ruleName, [getRuleCallback](auto e){ - return getRuleCallback(e.view()); + auto rule = GN::Rule(program.interpreter.makeRule("Rule", ruleName, [](auto e,auto &g){ + return g(e.view()); })); auto brackets = GN::Sequence({GN::Word("("), expression, GN::Word(")")}); - auto andPredicate = GN::Rule(program.interpreter.makeRule("AndPredicate", GN::Sequence({GN::Word("&"), atomic}), [](auto e){ - return GN::Also(e[0].evaluate()); + auto andPredicate = GN::Rule(program.interpreter.makeRule("AndPredicate", GN::Sequence({GN::Word("&"), atomic}), [](auto e,auto &g){ + return GN::Also(e[0].evaluate(g)); })); - auto notPredicate = GN::Rule(program.interpreter.makeRule("NotPredicate", GN::Sequence({GN::Word("!"), atomic}), [](auto e){ - return GN::Not(e[0].evaluate()); + auto notPredicate = GN::Rule(program.interpreter.makeRule("NotPredicate", GN::Sequence({GN::Word("!"), atomic}), [](auto e,auto &g){ + return GN::Not(e[0].evaluate(g)); })); atomicRule->node = withWhitespace(GN::Choice({andPredicate, notPredicate, word, brackets, endOfFile, any, empty, select, rule})); auto predicate = GN::Rule(makeRule("Predicate", GN::Choice({GN::Word("+"),GN::Word("*"),GN::Word("?")}))); - auto unary = withWhitespace(GN::Rule(program.interpreter.makeRule("Unary", GN::Sequence({GN::Rule(atomicRule), GN::Optional(predicate)}), [](auto e){ - auto inner = e[0].evaluate(); + auto unary = withWhitespace(GN::Rule(program.interpreter.makeRule("Unary", GN::Sequence({GN::Rule(atomicRule), GN::Optional(predicate)}), [](auto e,auto &g){ + auto inner = e[0].evaluate(g); if (e.size() == 1) { return inner; } auto op = e[1].view()[0]; if (op == '*') { return GN::ZeroOrMore(inner); } @@ -193,23 +193,23 @@ Program peg::createGrammarProgram(const std::function< throw std::runtime_error("unexpected unary operator"); }))); - auto sequence = GN::Rule(program.interpreter.makeRule("Sequence", GN::Sequence({unary, GN::ZeroOrMore(unary)}), [](auto e){ - if (e.size() == 1) { return e[0].evaluate(); } + auto sequence = GN::Rule(program.interpreter.makeRule("Sequence", GN::Sequence({unary, GN::ZeroOrMore(unary)}), [](auto e,auto &g){ + if (e.size() == 1) { return e[0].evaluate(g); } std::vector args; - for (auto c:e) { args.push_back(c.evaluate()); } + for (auto c:e) { args.push_back(c.evaluate(g)); } return GN::Sequence(args); })); - auto choice = GN::Rule(program.interpreter.makeRule("Choice", GN::Sequence({sequence, GN::ZeroOrMore(GN::Sequence({GN::Word("|"), sequence}))}), [](auto e){ - if (e.size() == 1) { return e[0].evaluate(); } + auto choice = GN::Rule(program.interpreter.makeRule("Choice", GN::Sequence({sequence, GN::ZeroOrMore(GN::Sequence({GN::Word("|"), sequence}))}), [](auto e,auto &g){ + if (e.size() == 1) { return e[0].evaluate(g); } std::vector args; - for (auto c:e) { args.push_back(c.evaluate()); } + for (auto c:e) { args.push_back(c.evaluate(g)); } return GN::Choice(args); })); expressionRule->node = withWhitespace(choice); - auto fullExpression = program.interpreter.makeRule("FullExpression", GN::Sequence({GN::Rule(expressionRule), GN::EndOfFile()}), [](auto e){ return e[0].evaluate(); }); + auto fullExpression = program.interpreter.makeRule("FullExpression", GN::Sequence({GN::Rule(expressionRule), GN::EndOfFile()}), [](auto e,auto &g){ return e[0].evaluate(g); }); program.parser.grammar = fullExpression; return program; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index b6d873f..22d76d5 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.5 FATAL_ERROR) # ---- Project ---- -project(Tests CXX) +project(LarsParserTests CXX) # ---- CXX Flags ---- @@ -23,13 +23,13 @@ include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/get_catch2.cmake) # ---- Create binary ---- file(GLOB tests_sources ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) -add_executable(tests ${tests_sources}) -add_dependencies(tests catch2-project) -target_link_libraries(tests catch2) -target_link_libraries(tests LarsParser) +add_executable(lars-parser-tests ${tests_sources}) +add_dependencies(lars-parser-tests catch2-project) +target_link_libraries(lars-parser-tests catch2) +target_link_libraries(lars-parser-tests LarsParser) # ---- Add tests ---- ENABLE_TESTING() -ADD_TEST(test tests) +ADD_TEST(lars-parser-tests lars-parser-tests) diff --git a/tests/extension.cpp b/tests/extension.cpp new file mode 100644 index 0000000..7413cfc --- /dev/null +++ b/tests/extension.cpp @@ -0,0 +1,67 @@ +#include +#include + +#include + +TEST_CASE("Extension"){ + using namespace lars; + auto extension = lars::extensions::parser(); + + auto programExtension = extension->get_extension("Program"); + auto expressionExtension = extension->get_extension("Expression"); + + auto createProgram = programExtension->get_function("create"); + auto setRule = programExtension->get_function("setRule"); + auto setSeparator = programExtension->get_function("setSeparatorRule"); + auto setStart = programExtension->get_function("setStartRule"); + auto setRuleWithCallback = programExtension->get_function("setRuleWithCallback"); + auto run = programExtension->get_function("run"); + auto evaluate = expressionExtension->get_function("evaluate"); + auto get = expressionExtension->get_function("get"); + auto string = expressionExtension->get_function("string"); + + auto program = createProgram(); + REQUIRE_NOTHROW(setRule(program, "Whitespace", "[\t ]")); + REQUIRE_NOTHROW(setSeparator(program, "Whitespace")); + REQUIRE_NOTHROW(setStart(program, "Sum")); + REQUIRE_NOTHROW(setRule(program, "Sum", "Add | Subtract | Product")); + REQUIRE_NOTHROW(setRule(program, "Product", "Multiply | Divide | Atomic")); + REQUIRE_NOTHROW(setRule(program, "Atomic", "Number | '(' Sum ')'")); + + REQUIRE_NOTHROW(setRuleWithCallback(program, "Add", "Sum '+' Product", lars::AnyFunction([=](lars::Any e,lars::Any &d){ + return evaluate(get(e,0),d).get() + evaluate(get(e,1),d).get(); + }))); + + REQUIRE_NOTHROW(setRuleWithCallback(program, "Subtract", "Sum '-' Product", lars::AnyFunction([=](lars::Any e,lars::Any &d){ + return evaluate(get(e,0),d).get() - evaluate(get(e,1),d).get(); + }))); + + REQUIRE_NOTHROW(setRuleWithCallback(program, "Multiply", "Product '*' Atomic", lars::AnyFunction([=](lars::Any e,lars::Any &d){ + return evaluate(get(e,0),d).get() * evaluate(get(e,1),d).get(); + }))); + + REQUIRE_NOTHROW(setRuleWithCallback(program, "Divide", "Product '/' Atomic", lars::AnyFunction([=](lars::Any e,lars::Any &d){ + return evaluate(get(e,0),d).get() / evaluate(get(e,1),d).get(); + }))); + + REQUIRE_NOTHROW(setRuleWithCallback(program, "Number", "'-'? [0-9]+ ('.' [0-9]+)?", lars::AnyFunction([=](lars::Any e,lars::Any &){ + return float(stof(string(e).get())); + }))); + + REQUIRE_NOTHROW(setRuleWithCallback(program, "Variable", "[a-zA-Z]+", lars::AnyFunction([=](lars::Any e,lars::Any &d){ + auto &vars = d.get>(); + return vars[string(e).get()]; + }))); + + std::unordered_map variables; + REQUIRE(run(program,"42",variables).get() == Approx(42)); + REQUIRE(run(program,"2+3",variables).get() == Approx(5)); + REQUIRE(run(program,"2*3",variables).get() == Approx(6)); + REQUIRE(run(program,"1+2+3",variables).get() == Approx(6)); + REQUIRE(run(program,"1+2*3",variables).get() == Approx(7)); + REQUIRE(run(program,"1+2-3",variables).get() == Approx(0)); + REQUIRE(run(program,"2*2/4*3",variables).get() == Approx(3)); + REQUIRE(run(program,"1 - 2*3/2 + 4",variables).get() == Approx(2)); + REQUIRE(run(program,"1 + 2 * (3+4)/ 2 - 3",variables).get() == Approx(5)); + +} diff --git a/tests/parser.cpp b/tests/parser.cpp index 55f4fad..5f87e71 100644 --- a/tests/parser.cpp +++ b/tests/parser.cpp @@ -4,7 +4,7 @@ #include #include -#include +#include template std::string stream_to_string(const T &obj){ std::stringstream stream; @@ -58,31 +58,33 @@ TEST_CASE("String Program") { } TEST_CASE("PEG Parser") { - auto parser = peg::createGrammarProgram([](std::string_view name){ + auto rc = [](std::string_view name){ return peg::GrammarNode::Rule(peg::makeRule(name, peg::GrammarNode::Empty())); - }); - REQUIRE(stream_to_string(*parser.run("rule")) == "rule"); - REQUIRE(stream_to_string(*parser.run("rule_2")) == "rule_2"); - REQUIRE(stream_to_string(*parser.run("!rule")) == "!rule"); - REQUIRE(stream_to_string(*parser.run("&rule")) == "&rule"); - REQUIRE(stream_to_string(*parser.run("rule+")) == "rule+"); - REQUIRE(stream_to_string(*parser.run("rule*")) == "rule*"); - REQUIRE(stream_to_string(*parser.run("rule?")) == "rule?"); - REQUIRE(stream_to_string(*parser.run("'word'")) == "'word'"); - REQUIRE(stream_to_string(*parser.run("[a-z]")) == "[a-z]"); - REQUIRE(stream_to_string(*parser.run("[abc]")) == "('a' | 'b' | 'c')"); - REQUIRE(stream_to_string(*parser.run("[abc-de]")) == "('a' | 'b' | [c-d] | 'e')"); - REQUIRE(stream_to_string(*parser.run("[abc\\-d]")) == "('a' | 'b' | 'c' | '-' | 'd')"); - REQUIRE(stream_to_string(*parser.run("")) == ""); - REQUIRE(stream_to_string(*parser.run("<>")) == "<>"); - REQUIRE(stream_to_string(*parser.run(".")) == "."); - REQUIRE(stream_to_string(*parser.run("a b c")) == "(a b c)"); - REQUIRE(stream_to_string(*parser.run("a | b |\tc")) == "(a | b | c)"); - REQUIRE(stream_to_string(*parser.run("'hello' | world '!'")) == "('hello' | (world '!'))"); - REQUIRE(stream_to_string(*parser.run("('a'+ (.? | b | <>)* [0-9] &)")) == "('a'+ (.? | b | <>)* [0-9] &)"); - REQUIRE_THROWS(parser.run("a | b | ")); - REQUIRE_THROWS(parser.run("a b @")); - REQUIRE_THROWS(parser.run("42")); + }; + + auto parser = peg::createGrammarProgram(); + REQUIRE(stream_to_string(*parser.run("rule",rc)) == "rule"); + REQUIRE(stream_to_string(*parser.run("rule_2",rc)) == "rule_2"); + REQUIRE(stream_to_string(*parser.run("!rule",rc)) == "!rule"); + REQUIRE(stream_to_string(*parser.run("&rule",rc)) == "&rule"); + REQUIRE(stream_to_string(*parser.run("rule+",rc)) == "rule+"); + REQUIRE(stream_to_string(*parser.run("rule*",rc)) == "rule*"); + REQUIRE(stream_to_string(*parser.run("rule?",rc)) == "rule?"); + REQUIRE(stream_to_string(*parser.run("'word'",rc)) == "'word'"); + REQUIRE(stream_to_string(*parser.run("[a-z]",rc)) == "[a-z]"); + REQUIRE(stream_to_string(*parser.run("[abc]",rc)) == "('a' | 'b' | 'c')"); + REQUIRE(stream_to_string(*parser.run("[abc-de]",rc)) == "('a' | 'b' | [c-d] | 'e')"); + REQUIRE(stream_to_string(*parser.run("[abc\\-d]",rc)) == "('a' | 'b' | 'c' | '-' | 'd')"); + REQUIRE(stream_to_string(*parser.run("",rc)) == ""); + REQUIRE(stream_to_string(*parser.run("<>",rc)) == "<>"); + REQUIRE(stream_to_string(*parser.run(".",rc)) == "."); + REQUIRE(stream_to_string(*parser.run("a b c",rc)) == "(a b c)"); + REQUIRE(stream_to_string(*parser.run("a | b |\tc",rc)) == "(a | b | c)"); + REQUIRE(stream_to_string(*parser.run("'hello' | world '!'",rc)) == "('hello' | (world '!'))"); + REQUIRE(stream_to_string(*parser.run("('a'+ (.? | b | <>)* [0-9] &)",rc)) == "('a'+ (.? | b | <>)* [0-9] &)"); + REQUIRE_THROWS(parser.run("a | b | ",rc)); + REQUIRE_THROWS(parser.run("a b @",rc)); + REQUIRE_THROWS(parser.run("42",rc)); } TEST_CASE("Program with return value"){ @@ -125,6 +127,7 @@ TEST_CASE("Evaluation"){ calculator.setRule("Sum", "Product ('+' Product)*", [](auto e){ float res = 0; for(auto t:e){ res += t.evaluate(); } return res; }); calculator.setRule("Product", "Number ('*' Number)*", [](auto e){ float res = 1; for(auto t:e){ res *= t.evaluate(); } return res; }); calculator.setProgramRule("Number", numberProgram); + REQUIRE(calculator.run("42") == 42); REQUIRE(calculator.run("1+2") == 3); REQUIRE(calculator.run("2 * 3") == 6); REQUIRE(calculator.run("1 + 2*3") == 7); @@ -141,6 +144,7 @@ TEST_CASE("Left recursion"){ calculator.setRule("Product", "Multiplication | Number"); calculator.setRule("Multiplication", "Product '*' Number", [](auto e){ return e[0].evaluate() * e[1].evaluate(); }); calculator.setProgramRule("Number", peg::createFloatProgram()); + REQUIRE(calculator.run("42") == 42); REQUIRE(calculator.run("1+2") == 3); REQUIRE(calculator.run("2 * 3") == 6); REQUIRE(calculator.run("1 + 2*3") == 7); @@ -202,14 +206,15 @@ TEST_CASE("Documentation Example"){ g.setSeparator(g["Whitespace"] << "[\t ]"); g["Sum" ] << "Add | Subtract | Product"; g["Product" ] << "Multiply | Divide | Atomic"; + g["Atomic" ] << "Number | '(' Sum ')'"; g["Add" ] << "Sum '+' Product" >> [](auto e){ return e[0].evaluate() + e[1].evaluate(); }; g["Subtract"] << "Sum '-' Product" >> [](auto e){ return e[0].evaluate() - e[1].evaluate(); }; g["Multiply"] << "Product '*' Atomic" >> [](auto e){ return e[0].evaluate() * e[1].evaluate(); }; g["Divide" ] << "Product '/' Atomic" >> [](auto e){ return e[0].evaluate() / e[1].evaluate(); }; - g["Atomic" ] << "Number | '(' Sum ')'"; g["Number" ] << "'-'? [0-9]+ ('.' [0-9]+)?" >> [](auto e){ return stof(e.string()); }; g.setStart(g["Sum"]); + REQUIRE(g.run("42") == Approx(42)); REQUIRE(g.run("2+3") == Approx(5)); REQUIRE(g.run("2*3") == Approx(6)); REQUIRE(g.run("1+2+3") == Approx(6));