diff --git a/.travis.yml b/.travis.yml index 78f97c5..80ce570 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,14 @@ language: cpp sudo: require +dist: xenial common_sources: &all_sources - ubuntu-toolchain-r-test - llvm-toolchain-trusty - - llvm-toolchain-trusty-6.0 + +python: + - 3.7 matrix: include: @@ -41,10 +44,5 @@ before_install: - cmake --version script: - - cmake -Hexamples/simple -Bbuild/simple - - cmake --build build/simple - - CTEST_OUTPUT_ON_FAILURE=1 cmake --build build/simple --target test - - cmake -Hexamples/complex -Bbuild/complex - - cmake --build build/complex - - CTEST_OUTPUT_ON_FAILURE=1 cmake --build build/complex --target test + - python3 examples/run_all.py \ No newline at end of file diff --git a/README.md b/README.md index 83fe9c3..a6f47c3 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ # CPM -CPM is a simple GIT dependency manager written in CMake built on top of CMake's built-in [FetchContent](https://cmake.org/cmake/help/latest/module/FetchContent.html) module. +CPM is a simple dependency manager written in CMake built on top of CMake's built-in [FetchContent](https://cmake.org/cmake/help/latest/module/FetchContent.html) module. ## Supported projects @@ -10,7 +10,26 @@ Any project that you can add via `add_subdirectory` should already work with CPM ## Usage -To add a new dependency to your project simply add the Projects target name, the git URL and the version. If the git tag for this version does not match the pattern `v$VERSION`, then the exact branch or tag can be specified with the `GIT_TAG` argument. CMake options can also be supplied with the package. If a package is not CMake compaitible it can still be downloaded with the `DOWNLOAD_ONLY` flag. See below for usage examples. +After `CPM.cmake` has been added to your project, you can call `CPMAddPackage` for every dependency of the project with the following named parameters. + +```cmake +CPMAddPackage( + NAME # The dependency name (usually chosen to match the target name) + VERSION # The minimum version of the dependency (optional, defaults to 0) + OPTIONS # Configuration options passed to the dependency (optional) + DOWNLOAD_ONLY # If set, the project is downloaded, but not configured (optional) + [...] # Source options, see below +) +``` + +The command downloads the project defined by the source options if a newer version hasn't been included before. +The source is usually a git repository, but svn and direct urls are als supported. +See the [FetchContent](https://cmake.org/cmake/help/latest/module/FetchContent.html) documentation for all available options. +If a `GIT_TAG` hasn't been explicitly specified it defaults to `v$VERSION` which is a common convention for github projects. + +After calling `CPMAddPackage`, the variables `(DEPENDENCY)_SOURCE_DIR` and `(DEPENDENCY)_BINARY_DIR` are set, where `(PACKAGE)` is the name of the dependency. + +## Example ```cmake cmake_minimum_required(VERSION 3.14 FATAL_ERROR) @@ -25,8 +44,7 @@ CPMAddPackage( NAME LarsParser VERSION 1.8 GIT_REPOSITORY https://github.com/TheLartians/Parser.git - GIT_TAG v1.8 # optional here, as indirectly defined by VERSION - OPTIONS # optional CMake arguments passed to the dependency + OPTIONS "LARS_PARSER_BUILD_GLUE_EXTENSION ON" ) @@ -36,6 +54,8 @@ set_target_properties(myProject PROPERTIES CXX_STANDARD 17) target_link_libraries(myProject LarsParser) ``` +See the [examples directory](https://github.com/TheLartians/CPM/tree/master/examples) for full examples with source. + ## Adding CPM To add CPM to your current project, simply add `cmake/CPM.cmake` to your project's `cmake` directory. The command below will perform this automatically. @@ -44,7 +64,13 @@ To add CPM to your current project, simply add `cmake/CPM.cmake` to your project wget -O cmake/CPM.cmake https://raw.githubusercontent.com/TheLartians/CPM/master/cmake/CPM.cmake ``` -## Examples +## Updating CPM + +To update CPM to the newest version, simply update the script in the project's cmake directory, for example by running the command above. Dependencies using CPM will automatically use the updated script of the outermost project. + +## Snipplets + +These are some small snipplets demonstrating how to include some projects used with CPM. ### Catch2 @@ -60,7 +86,7 @@ CPMAddPackage( ### google/benchmark -Has a CMakeLists.txt that supports `add_subdirectory`, but needs some configuring. +Has a CMakeLists.txt that supports `add_subdirectory`, but needs some configuring to work without external dependencies. ```cmake CPMAddPackage( @@ -69,7 +95,6 @@ CPMAddPackage( VERSION 1.4.1 OPTIONS "BENCHMARK_ENABLE_TESTING Off" - "BENCHMARK_USE_LIBCXX ON" ) # needed to compile with C++17 @@ -78,7 +103,7 @@ set_target_properties(benchmark PROPERTIES CXX_STANDARD 17) ### Lua -Has no CMakeLists.txt, target must be created manually. +Has no CMakeLists.txt, so a target must be created manually. ```cmake CPMAddPackage( @@ -89,7 +114,6 @@ CPMAddPackage( DOWNLOAD_ONLY YES ) -CPMGetProperties(lua) FILE(GLOB lua_sources ${lua_SOURCE_DIR}/*.c) add_library(lua STATIC ${lua_sources}) @@ -99,31 +123,10 @@ target_include_directories(lua ) ``` -### robin_hood::unordered_map +## Local packages -Has CMakeLists.txt, but it seems it is only used for testing. -Therefore we must create or own target. - -```cmake -CPMAddPackage( - NAME RobinHood - VERSION 3.2.7 - GIT_REPOSITORY https://github.com/martinus/robin-hood-hashing.git - DOWNLOAD_ONLY Yes -) - -CPMGetProperties(RobinHood) -add_library(RobinHood INTERFACE IMPORTED) -target_include_directories(RobinHood INTERFACE "${RobinHood_SOURCE_DIR}/src/include") -``` - -## Updating CPM - -To update CPM to the newest version, simply update the script in the project's cmake directory, for example by running the command above. Dependencies using CPM will automatically use the updated script of the outermost project. - -## Global Options - -Setting the CMake option `CPM_USE_LOCAL_PACKAGES` will activate finding packages via `find_package`. If the option `CPM_LOCAL_PACKAGES_ONLY` is set, CPM will only use local packages. +CPM can be configured to use `find_package` to search for locally installed dependencies first. +If `CPM_LOCAL_PACKAGES_ONLY` is set, CPM will error when dependency is not found locally. ## Advantages @@ -132,9 +135,12 @@ Setting the CMake option `CPM_USE_LOCAL_PACKAGES` will activate finding packages - **Reproducable builds** By using versioning via git tags it is ensured that a project will always be in the same state everywhere. - **No installation required** No need to install anything. Just add the script to your project and you're good to go. - **No Setup required** There is a good chance your existing projects already work as CPM dependencies. +- **Simple source distribution** CPM makes including projects with source files easy, reducing the need for monolithic header files. ## Limitations -- **First version used** In diamond-shaped dependency graphs (e.g. `A` depends on `C`(v1.1) and `A` depends on `B` depends on `C`(v1.2)) the first added dependency will be used (in this case `C`@1.1). If the current version is older than the version beeing added, or if provided options are incompatible, a CMake warning will be emitted. To resolve, add the new version of the common dependency to the outer project. -- **No auto-update** To update a dependency, version numbers or git tags in the cmake scripts must be adapted manually. -- **No pre-built binaries** Unless they are installed or included in the linked repository. +- **First version used** In diamond-shaped dependency graphs (e.g. `A` depends on `C`@1.1 and `B`, which itself depends on `C`@1.2 the first added dependency will be used (in this case `C`@1.1). In this case, B requires a newer version of `C` than `A`, so CPM will emit an error. This can be resolved by updating the outermost dependency version. +- **No auto-update** To update a dependency, version must be adapted manually and there is no way for CPM to figure out the most recent version. +- **No pre-built binaries** Unless they are installed or included in the linked repository. + +For projects with more complex needs and an extra setup step doesn't matter, it is worth to check out fully featured C++ package managers such as [conan](https://conan.io) or [hunter](https://github.com/ruslo/hunter) instead. diff --git a/cmake/CPM.cmake b/cmake/CPM.cmake index 083442d..5c91b64 100644 --- a/cmake/CPM.cmake +++ b/cmake/CPM.cmake @@ -28,7 +28,7 @@ cmake_minimum_required(VERSION 3.14 FATAL_ERROR) -set(CURRENT_CPM_VERSION 0.8.2) +set(CURRENT_CPM_VERSION 0.9) if(CPM_DIRECTORY) if(NOT ${CPM_DIRECTORY} MATCHES ${CMAKE_CURRENT_LIST_DIR}) @@ -136,6 +136,9 @@ function(CPMAddPackage) endforeach() endif() CPM_FETCH_PACKAGE(${CPM_ARGS_NAME} ${DOWNLOAD_ONLY}) + CPMGetProperties(${CPM_ARGS_NAME}) + SET(${CPM_ARGS_NAME}_SOURCE_DIR "${${CPM_ARGS_NAME}_SOURCE_DIR}" PARENT_SCOPE) + SET(${CPM_ARGS_NAME}_BINARY_DIR "${${CPM_ARGS_NAME}_BINARY_DIR}" PARENT_SCOPE) return() endif() @@ -150,6 +153,9 @@ function(CPMAddPackage) CPM_DECLARE_PACKAGE(${CPM_ARGS_NAME} ${CPM_ARGS_VERSION} ${CPM_ARGS_GIT_TAG} "${CPM_ARGS_UNPARSED_ARGUMENTS}") CPM_FETCH_PACKAGE(${CPM_ARGS_NAME} ${DOWNLOAD_ONLY}) + CPMGetProperties(${CPM_ARGS_NAME}) + SET(${CPM_ARGS_NAME}_SOURCE_DIR "${${CPM_ARGS_NAME}_SOURCE_DIR}" PARENT_SCOPE) + SET(${CPM_ARGS_NAME}_BINARY_DIR "${${CPM_ARGS_NAME}_BINARY_DIR}" PARENT_SCOPE) endfunction() function (CPM_DECLARE_PACKAGE PACKAGE VERSION GIT_TAG) diff --git a/examples/benchmark/CMakeLists.txt b/examples/benchmark/CMakeLists.txt new file mode 100644 index 0000000..48293f6 --- /dev/null +++ b/examples/benchmark/CMakeLists.txt @@ -0,0 +1,28 @@ +cmake_minimum_required(VERSION 3.14 FATAL_ERROR) + +# ---- Dependencies ---- + +include(../../cmake/CPM.cmake) + +CPMAddPackage( + NAME fibonacci + GIT_REPOSITORY https://github.com/TheLartians/Fibonacci.git + VERSION 1.0 +) + +CPMAddPackage( + NAME benchmark + GIT_REPOSITORY https://github.com/google/benchmark.git + VERSION 1.4.1 + OPTIONS + "BENCHMARK_ENABLE_TESTING Off" +) + +# patch google benchmark target +set_target_properties(benchmark PROPERTIES CXX_STANDARD 17) + +# ---- Executable ---- + +add_executable(CPMExampleBenchmark "main.cpp") +set_target_properties(CPMExampleBenchmark PROPERTIES CXX_STANDARD 17) +target_link_libraries(CPMExampleBenchmark fibonacci benchmark) diff --git a/examples/benchmark/main.cpp b/examples/benchmark/main.cpp new file mode 100644 index 0000000..630b436 --- /dev/null +++ b/examples/benchmark/main.cpp @@ -0,0 +1,36 @@ +#include +#include +#include +#include + +#include + + +std::vector createTestNumbers(){ + std::vector v; + for (int i=0;i<25;++i) v.emplace_back(i); + std::random_device rd; + std::mt19937 g(rd()); + std::shuffle(v.begin(), v.end(), g); + return v; +} + +void fibonnacci(benchmark::State& state) { + auto numbers = createTestNumbers(); + for (auto _ : state) { + for (auto v: numbers) benchmark::DoNotOptimize(fibonnacci(v)); + } +} + +BENCHMARK(fibonnacci); + +void fastFibonacci(benchmark::State& state) { + auto numbers = createTestNumbers(); + for (auto _ : state) { + for (auto v: numbers) benchmark::DoNotOptimize(fastFibonacci(v)); + } +} + +BENCHMARK(fastFibonacci); + +BENCHMARK_MAIN(); diff --git a/examples/catch2/CMakeLists.txt b/examples/catch2/CMakeLists.txt new file mode 100644 index 0000000..311fba5 --- /dev/null +++ b/examples/catch2/CMakeLists.txt @@ -0,0 +1,39 @@ +cmake_minimum_required(VERSION 3.14 FATAL_ERROR) + +# ---- Options ---- + +option(ENABLE_TEST_COVERAGE "Enable test coverage" OFF) + +# ---- Dependencies ---- + +include(../../cmake/CPM.cmake) + +CPMAddPackage( + NAME fibonacci + GIT_REPOSITORY https://github.com/TheLartians/Fibonacci.git + VERSION 1.0 +) + +CPMAddPackage( + NAME Catch2 + GIT_REPOSITORY https://github.com/catchorg/Catch2.git + VERSION 2.5.0 +) + +# ---- Create binary ---- + +add_executable(CPMExampleCatch2 main.cpp) +target_link_libraries(CPMExampleCatch2 fibonacci Catch2) +set_target_properties(CPMExampleCatch2 PROPERTIES CXX_STANDARD 17 COMPILE_FLAGS "-Wall -pedantic -Wextra -Werror") + +# ---- Enable testing ---- + +ENABLE_TESTING() +ADD_TEST(CPMExampleCatch2 CPMExampleCatch2) + +# ---- Add code coverage ---- + +if (${ENABLE_TEST_COVERAGE}) + set_target_properties(CPMExampleCatch2 PROPERTIES CXX_STANDARD 17 COMPILE_FLAGS "-O0 -g -fprofile-arcs -ftest-coverage --coverage") + target_link_options(CPMExampleCatch2 PUBLIC "--coverage") +endif() diff --git a/examples/catch2/main.cpp b/examples/catch2/main.cpp new file mode 100644 index 0000000..457e6dc --- /dev/null +++ b/examples/catch2/main.cpp @@ -0,0 +1,20 @@ +#define CATCH_CONFIG_MAIN + +#include +#include + +TEST_CASE("fibonnacci"){ + REQUIRE(fibonnacci(0) == 0); + REQUIRE(fibonnacci(1) == 1); + REQUIRE(fibonnacci(2) == 1); + REQUIRE(fibonnacci(3) == 2); + REQUIRE(fibonnacci(4) == 3); + REQUIRE(fibonnacci(5) == 5); + REQUIRE(fibonnacci(13) == 233); +} + +TEST_CASE("fastFibonnacci"){ + for (unsigned i=0; i<25; ++i){ + REQUIRE(fibonnacci(i) == fastFibonacci(i)); + } +} diff --git a/examples/complex/CMakeLists.txt b/examples/complex/CMakeLists.txt deleted file mode 100644 index 8f704bf..0000000 --- a/examples/complex/CMakeLists.txt +++ /dev/null @@ -1,55 +0,0 @@ -cmake_minimum_required(VERSION 3.5 FATAL_ERROR) - -project(CPMTest) - -include(../../cmake/CPM.cmake) - -# ignore locally installed projects for reproducable builds -set(CPM_REMOTE_PACKAGES_ONLY ON CACHE INTERNAL "") - -# util library -CPMAddPackage( - NAME LHC - GIT_REPOSITORY https://github.com/TheLartians/LHC.git - VERSION 0.8 -) - -# will be ignored as a newer version has already been added -# if a newer version is required, a warning will be emitted -CPMAddPackage( - NAME LHC - GIT_REPOSITORY https://github.com/TheLartians/LHC.git - VERSION 0.2 -) - -# language bindings -# uses git tag instead of version identifier -# depends on visitor library that depends on Event library and LHC -# CMake configuration arguments passed via OPTIONS -CPMAddPackage( - NAME Glue - GIT_REPOSITORY https://github.com/TheLartians/Glue.git - VERSION 0.8.1 - OPTIONS - "GLUE_ENABLE_LUA ON" - "GLUE_BUILD_LUA ON" -) - -# parser library -# depends on LHC and Glue -CPMAddPackage( - NAME LarsParser - GIT_REPOSITORY https://github.com/TheLartians/Parser.git - VERSION 1.9 - OPTIONS - "LARS_PARSER_BUILD_GLUE_EXTENSION ON" -) - -# add executable -add_executable(cpm-test-complex main.cpp) -set_target_properties(cpm-test-complex PROPERTIES CXX_STANDARD 17) -target_link_libraries(cpm-test-complex LHC LarsParser Glue) - -# tests -enable_testing() -add_test(cpm-test-complex cpm-test-complex) diff --git a/examples/complex/main.cpp b/examples/complex/main.cpp deleted file mode 100644 index 595d573..0000000 --- a/examples/complex/main.cpp +++ /dev/null @@ -1,41 +0,0 @@ -#include -#include -#include -#include - -int main() { - // create lua state - auto lua = glue::LuaState(); - lua.openStandardLibs(); - - lua["parser"] = lars::glue::parser(); - - // create a parser - lua.run(R"( - NumberMap = parser.Program.create() - NumberMap:setRule("Whitespace", "[ \t]") - NumberMap:setSeparatorRule("Whitespace") - NumberMap:setRuleWithCallback("Object", "'{' KeyValue (',' KeyValue)* '}'",function(e) - local N = e:size()-1 - local res = {} - for i=0,N do - local a = e:get(i) - res[a:get(0):evaluate()] = a:get(1):evaluate() - end - return res - end) - NumberMap:setRule("KeyValue", "Number ':' Number") - NumberMap:setRuleWithCallback("Number", "'-'? [0-9]+", function(e) return tonumber(e:string()); end) - NumberMap:setStartRule("Object") - )"); - - // parse a string - lua.run("m = NumberMap:run('{1:3, 2:-1, 3:42}')"); - - // check result - if (lua.get("m[1]") != 3) throw std::runtime_error("unexpected result"); - if (lua.get("m[2]") != -1) throw std::runtime_error("unexpected result"); - if (lua.get("m[3]") != 42) throw std::runtime_error("unexpected result"); - - return 0; -} diff --git a/examples/doctest/CMakeLists.txt b/examples/doctest/CMakeLists.txt new file mode 100644 index 0000000..17a3c41 --- /dev/null +++ b/examples/doctest/CMakeLists.txt @@ -0,0 +1,41 @@ +cmake_minimum_required(VERSION 3.14 FATAL_ERROR) + +# ---- Options ---- + +option(ENABLE_TEST_COVERAGE "Enable test coverage" OFF) + +# ---- Dependencies ---- + +include(../../cmake/CPM.cmake) + +CPMAddPackage( + NAME fibonacci + GIT_REPOSITORY https://github.com/TheLartians/Fibonacci.git + VERSION 1.0 +) + +CPMAddPackage( + NAME doctest + GIT_REPOSITORY https://github.com/onqtam/doctest.git + VERSION 2.3.2 + GIT_TAG 2.3.2 + GIT_SHALLOW True +) + +# ---- Create binary ---- + +add_executable(CPMExampleDoctest main.cpp) +target_link_libraries(CPMExampleDoctest fibonacci doctest) +set_target_properties(CPMExampleDoctest PROPERTIES CXX_STANDARD 17 COMPILE_FLAGS "-Wall -pedantic -Wextra -Werror") + +# ---- Enable testing ---- + +ENABLE_TESTING() +ADD_TEST(CPMExampleDoctest CPMExampleDoctest) + +# ---- Add code coverage ---- + +if (${ENABLE_TEST_COVERAGE}) + set_target_properties(CPMExampleDoctest PROPERTIES CXX_STANDARD 17 COMPILE_FLAGS "-O0 -g -fprofile-arcs -ftest-coverage --coverage") + target_link_options(CPMExampleDoctest PUBLIC "--coverage") +endif() diff --git a/examples/doctest/main.cpp b/examples/doctest/main.cpp new file mode 100644 index 0000000..2537165 --- /dev/null +++ b/examples/doctest/main.cpp @@ -0,0 +1,20 @@ +#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN + +#include +#include + +TEST_CASE("fibonnacci"){ + CHECK(fibonnacci(0) == 0); + CHECK(fibonnacci(1) == 1); + CHECK(fibonnacci(2) == 1); + CHECK(fibonnacci(3) == 2); + CHECK(fibonnacci(4) == 3); + CHECK(fibonnacci(5) == 5); + CHECK(fibonnacci(13) == 233); +} + +TEST_CASE("fastFibonnacci"){ + for (unsigned i=0; i<25; ++i){ + CHECK(fibonnacci(i) == fastFibonacci(i)); + } +} diff --git a/examples/parser-lua/CMakeLists.txt b/examples/parser-lua/CMakeLists.txt new file mode 100644 index 0000000..7c8f542 --- /dev/null +++ b/examples/parser-lua/CMakeLists.txt @@ -0,0 +1,28 @@ +cmake_minimum_required(VERSION 3.5 FATAL_ERROR) + +include(../../cmake/CPM.cmake) + +# ---- Dependencies ---- + +CPMAddPackage( + NAME Glue + GIT_REPOSITORY https://github.com/TheLartians/Glue.git + VERSION 0.8.1 + OPTIONS + "GLUE_ENABLE_LUA ON" + "GLUE_BUILD_LUA ON" +) + +CPMAddPackage( + NAME LarsParser + GIT_REPOSITORY https://github.com/TheLartians/Parser.git + VERSION 1.9 + OPTIONS + "LARS_PARSER_BUILD_GLUE_EXTENSION ON" +) + +# ---- Create binary ---- + +add_executable(parser-lua main.cpp) +set_target_properties(parser-lua PROPERTIES CXX_STANDARD 17) +target_link_libraries(parser-lua LHC LarsParser Glue) diff --git a/examples/parser-lua/main.cpp b/examples/parser-lua/main.cpp new file mode 100644 index 0000000..433e6db --- /dev/null +++ b/examples/parser-lua/main.cpp @@ -0,0 +1,46 @@ +#include +#include +#include +#include + +int main() { + auto lua = glue::LuaState(); + lua.openStandardLibs(); + + lua["parser"] = lars::glue::parser(); + + lua.run(R"( + wordParser = parser.Program.create() + + wordParser:setRule("Whitespace", "[ \t]") + wordParser:setSeparatorRule("Whitespace") + + wordParser:setRule("Word", "[a-zA-Z]+") + + wordParser:setRuleWithCallback("Words", "Word*", function(e) + local N = e:size() + local res = {} + for i=0,N-1 do res[#res+1] = e:get(i):string() end + return res + end) + + wordParser:setStartRule("Words") + )"); + + lua.run(R"( + while true do + print("please enter some words or 'quit' to exit"); + local input = io.read(); + if input == "quit" then os.exit() end + local result + ok, err = pcall(function() result = wordParser:run(input) end) + if ok then + print("you entered " .. #result .. " words!") + else + print("error: " .. tostring(err)) + end + end + )"); + + return 0; +} diff --git a/examples/parser/CMakeLists.txt b/examples/parser/CMakeLists.txt new file mode 100644 index 0000000..2fb4900 --- /dev/null +++ b/examples/parser/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.5 FATAL_ERROR) + +# add dependencies +include(../../cmake/CPM.cmake) + +CPMAddPackage( + NAME LarsParser + GIT_REPOSITORY https://github.com/TheLartians/Parser.git + VERSION 1.7 +) + +# add executable +add_executable(calculator main.cpp) +set_target_properties(calculator PROPERTIES CXX_STANDARD 17) +target_link_libraries(calculator LarsParser) diff --git a/examples/parser/main.cpp b/examples/parser/main.cpp new file mode 100644 index 0000000..583cfd8 --- /dev/null +++ b/examples/parser/main.cpp @@ -0,0 +1,57 @@ +#include +#include +#include + +#include + +int main() { + using namespace std; + using VariableMap = unordered_map; + + lars::ParserGenerator calculator; + + auto &g = calculator; + g.setSeparator(g["Whitespace"] << "[\t ]"); + + g["Expression"] << "Set | Sum"; + g["Set" ] << "Name '=' Sum" >> [](auto e, auto &v){ return v[e[0].string()] = e[1].evaluate(v); }; + g["Sum" ] << "Add | Subtract | Product"; + g["Product" ] << "Multiply | Divide | Exponent"; + g["Exponent" ] << "Power | Atomic"; + g["Atomic" ] << "Number | Brackets | Variable"; + g["Brackets" ] << "'(' Sum ')'"; + g["Add" ] << "Sum '+' Product" >> [](auto e, auto &v){ return e[0].evaluate(v) + e[1].evaluate(v); }; + g["Subtract" ] << "Sum '-' Product" >> [](auto e, auto &v){ return e[0].evaluate(v) - e[1].evaluate(v); }; + g["Multiply" ] << "Product '*' Exponent" >> [](auto e, auto &v){ return e[0].evaluate(v) * e[1].evaluate(v); }; + g["Divide" ] << "Product '/' Exponent" >> [](auto e, auto &v){ return e[0].evaluate(v) / e[1].evaluate(v); }; + g["Power" ] << "Atomic ('^' Exponent)" >> [](auto e, auto &v){ return pow(e[0].evaluate(v), e[1].evaluate(v)); }; + g["Variable" ] << "Name" >> [](auto e, auto &v){ return v[e[0].string()]; }; + g["Name" ] << "[a-zA-Z] [a-zA-Z0-9]*"; + g["Number" ] << "'-'? [0-9]+ ('.' [0-9]+)?" >> [](auto e, auto &){ return stod(e.string()); }; + + g.setStart(g["Expression"]); + + cout << "Enter an expression to be evaluated or 'quit' to exit.\n"; + + VariableMap variables; + + while (true) { + string str; + cout << "> "; + getline(cin,str); + if(str == "q" || str == "quit"){ break; } + try { + auto result = calculator.run(str, variables); + cout << str << " = " << result << endl; + } catch (lars::SyntaxError &error) { + auto syntax = error.syntax; + cout << " "; + cout << string(syntax->begin, ' '); + cout << string(syntax->length(), '~'); + cout << "^\n"; + cout << " " << "Syntax error while parsing " << syntax->rule->name << endl; + } + } + + return 0; +} diff --git a/examples/run_all.py b/examples/run_all.py new file mode 100644 index 0000000..03471a7 --- /dev/null +++ b/examples/run_all.py @@ -0,0 +1,29 @@ +#!/usr/bin/python3 + +from pathlib import Path +from subprocess import PIPE, run + +examples = [ + x for x in Path(__file__).parent.iterdir() if x.is_dir() and (x / 'CMakeLists.txt').exists() +] + +assert(len(examples) > 0) + +def runCommand(command): + print('- %s' % command) + result = run(command, stdout=PIPE, stderr=PIPE, universal_newlines=True, shell=True) + if result.returncode != 0: + print("error while running '%s':\n" % command, ' ' + str(result.stderr).replace('\n','\n ')) + exit(result.returncode) + return result.stdout + +print('') +for example in examples: + print("running example %s" % example.name) + print("================" + ('=' * len(example.name))) + project = Path(".") / 'build' / example.name + configure = runCommand('cmake -H%s -B%s -DCMAKE_BUILD_TYPE=RelWithDebInfo' % (example, project)) + print(' ' + '\n '.join([line for line in configure.split('\n') if 'CPM:' in line])) + build = runCommand('cmake --build %s -j4' % (project)) + print(' ' + '\n '.join([line for line in build.split('\n') if 'Built target' in line])) + print('') diff --git a/examples/simple/CMakeLists.txt b/examples/simple/CMakeLists.txt deleted file mode 100644 index 518b76d..0000000 --- a/examples/simple/CMakeLists.txt +++ /dev/null @@ -1,21 +0,0 @@ -cmake_minimum_required(VERSION 3.5 FATAL_ERROR) - -project(CPMTest) - -# add dependencies -include(../../cmake/CPM.cmake) - -CPMAddPackage( - NAME LarsParser - GIT_REPOSITORY https://github.com/TheLartians/Parser.git - VERSION 1.7 -) - -# add executable -add_executable(cpm-test-simple main.cpp) -set_target_properties(cpm-test-simple PROPERTIES CXX_STANDARD 17) -target_link_libraries(cpm-test-simple LarsParser) - -# tests -enable_testing() -add_test(cpm-test-simple cpm-test-simple) diff --git a/examples/simple/main.cpp b/examples/simple/main.cpp deleted file mode 100644 index a415c4d..0000000 --- a/examples/simple/main.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include - -int main() { - lars::ParserGenerator g; - - // Define grammar and evaluation rules - 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["Number" ] << "'-'? [0-9]+ ('.' [0-9]+)?" >> [](auto e){ return stof(e.string()); }; - g.setStart(g["Sum"]); - - // Execute a string - float result = g.run("1 + 2 * (3+4)/2 - 3"); - - // validate result - if (result == 5) { - return 0; - } else { - return 1; - } -}