├── package.cmake ├── test ├── main_test.cpp ├── empty_test.cpp ├── test_include.hpp ├── any_test.cpp ├── always_test.hpp ├── many1_of_test.cpp ├── many_of_test.cpp ├── if_char_satisfy.cpp ├── none_of_test.cpp ├── many1_if_test.cpp ├── CMakeLists.txt ├── many_if_test.cpp ├── symbol_test.cpp ├── left_test.cpp ├── right_test.cpp ├── one_of_test.cpp ├── str_test.cpp ├── satisfy_test.cpp ├── combine_test.cpp ├── exactly_n_test.cpp ├── pipe_test.cpp ├── seperated_by_test.cpp ├── then_test.cpp ├── many1_test.cpp ├── many_test.cpp ├── or_with_test.cpp ├── sequence_test.cpp ├── transform_test.cpp └── unconsume_str_test.cpp ├── conanfile.txt ├── .gitignore ├── CMakeUserPresets.json ├── update_package ├── examples ├── CMakeLists.txt ├── 3_int_parsing.cpp ├── big_string_parsing.cpp └── cyclic_parsing.cpp ├── .cmake-format.yaml ├── CMakeLists.txt ├── LICENSE ├── cmake ├── StaticAnalyzers.cmake ├── StandardProjectSettings.cmake └── CompilerWarnings.cmake ├── .clang-format ├── include └── parser │ └── parser.hpp └── README.md /package.cmake: -------------------------------------------------------------------------------- 1 | find_package(DOCTEST REQUIRED) 2 | -------------------------------------------------------------------------------- /test/main_test.cpp: -------------------------------------------------------------------------------- 1 | #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN 2 | #include "doctest/doctest.h" 3 | -------------------------------------------------------------------------------- /conanfile.txt: -------------------------------------------------------------------------------- 1 | [requires] 2 | doctest/2.4.11 3 | 4 | [generators] 5 | CMakeDeps 6 | CMakeToolchain 7 | -------------------------------------------------------------------------------- /test/empty_test.cpp: -------------------------------------------------------------------------------- 1 | #include "test_include.hpp" 2 | 3 | TEST_CASE("empty") { 4 | static_assert(parser::empty("string") == std::nullopt); 5 | } 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | .cache 3 | .ccls-cache 4 | compile_commands.json 5 | bin/ 6 | CMakeCache.txt 7 | CMakeFiles/ 8 | Makefile 9 | cmake_install.cmake 10 | -------------------------------------------------------------------------------- /test/test_include.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "parser/parser.hpp" 3 | #include 4 | 5 | using namespace std::string_view_literals; 6 | using namespace std::string_literals; 7 | -------------------------------------------------------------------------------- /CMakeUserPresets.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 4, 3 | "vendor": { 4 | "conan": {} 5 | }, 6 | "include": [ 7 | "/home/rishabh/personal/libparse/bin/CMakePresets.json" 8 | ] 9 | } -------------------------------------------------------------------------------- /update_package: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | conan install . --output-folder=bin --build=missing 3 | cat conanfile.txt | sed -e '/\[generators\]/,$d' | grep "/" | sed -e 's/\/.*//g' | awk '{print toupper($0)}' | sed -r 's/(.*)/find_package(\1 REQUIRED)/' > package.cmake 4 | -------------------------------------------------------------------------------- /test/any_test.cpp: -------------------------------------------------------------------------------- 1 | #include "test_include.hpp" 2 | 3 | TEST_CASE("empty") { static_assert(parser::any("") == std::nullopt); }; 4 | 5 | TEST_CASE("elements") { 6 | static_assert( 7 | parser::any("test") == std::optional{ std::pair{ 't', "est"sv } }); 8 | }; 9 | -------------------------------------------------------------------------------- /test/always_test.hpp: -------------------------------------------------------------------------------- 1 | #include "test_include.hpp" 2 | 3 | TEST_CASE("empty") { 4 | static_assert(parser::always(2)("") == std::pair{ 2, ""sv }); 5 | } 6 | 7 | TEST_CASE("some string") { 8 | static_assert(parser::always(2)("test") == std::pair{ 2, "test"sv }); 9 | } 10 | -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | file(GLOB_RECURSE src_list "*.cpp") 2 | foreach(file ${src_list}) 3 | get_filename_component(filename ${file} NAME_WE) 4 | add_executable(${filename} ${file}) 5 | target_include_directories(${filename} PRIVATE ../include) 6 | target_link_libraries(${filename} PRIVATE project_warnings) 7 | target_link_libraries(${filename} PRIVATE project_options) 8 | endforeach() 9 | -------------------------------------------------------------------------------- /test/many1_of_test.cpp: -------------------------------------------------------------------------------- 1 | #include "test_include.hpp" 2 | 3 | constexpr auto many_c = parser::many1_of('c'); 4 | 5 | TEST_CASE("more than 1") { 6 | static_assert(many_c("cccaa") == std::pair{ "ccc"sv, "aa"sv }); 7 | } 8 | 9 | TEST_CASE("one") { static_assert(many_c("caa") == std::pair{ "c"sv, "aa"sv }); } 10 | 11 | TEST_CASE("zero") { static_assert(many_c("aa") == std::nullopt); } 12 | -------------------------------------------------------------------------------- /test/many_of_test.cpp: -------------------------------------------------------------------------------- 1 | #include "test_include.hpp" 2 | 3 | constexpr auto many_c = parser::many_of('c'); 4 | 5 | TEST_CASE("more than 1") { 6 | static_assert(many_c("cccaa") == std::pair{ "ccc"sv, "aa"sv }); 7 | } 8 | 9 | TEST_CASE("one") { static_assert(many_c("caa") == std::pair{ "c"sv, "aa"sv }); } 10 | 11 | TEST_CASE("zero") { static_assert(many_c("aa") == std::pair{ ""sv, "aa"sv }); } 12 | -------------------------------------------------------------------------------- /test/if_char_satisfy.cpp: -------------------------------------------------------------------------------- 1 | #include "test_include.hpp" 2 | 3 | constexpr auto is_l = [](auto c) { return c == 'l'; }; 4 | 5 | TEST_CASE("predicate satisfied") { 6 | static_assert( 7 | parser::if_char_satisfies(is_l)("lower") == std::pair{ 'l', "ower"sv }); 8 | } 9 | 10 | TEST_CASE("predicate not satisfied") { 11 | static_assert(parser::if_char_satisfies(is_l)("ower") == std::nullopt); 12 | } 13 | -------------------------------------------------------------------------------- /test/none_of_test.cpp: -------------------------------------------------------------------------------- 1 | #include "test_include.hpp" 2 | 3 | TEST_CASE("empty") { 4 | static_assert(parser::none_of("test")("") == std::nullopt); 5 | } 6 | 7 | TEST_CASE("matching elements") { 8 | static_assert(parser::none_of("test")("test") == std::nullopt); 9 | } 10 | 11 | TEST_CASE("non matching elements") { 12 | static_assert(parser::none_of("user")("test") == std::pair('t', "est"sv)); 13 | } 14 | -------------------------------------------------------------------------------- /test/many1_if_test.cpp: -------------------------------------------------------------------------------- 1 | #include "test_include.hpp" 2 | 3 | constexpr auto many_c = parser::many1_if([](auto c) { return c == 'c'; }); 4 | 5 | TEST_CASE("more than 1") { 6 | static_assert(many_c("cccaa") == std::pair{ "ccc"sv, "aa"sv }); 7 | } 8 | 9 | TEST_CASE("one") { static_assert(many_c("caa") == std::pair{ "c"sv, "aa"sv }); } 10 | 11 | TEST_CASE("zero") { static_assert(many_c("aa") == std::nullopt); } 12 | -------------------------------------------------------------------------------- /.cmake-format.yaml: -------------------------------------------------------------------------------- 1 | additional_commands: 2 | foo: 3 | flags: 4 | - BAR 5 | - BAZ 6 | kwargs: 7 | DEPENDS: '*' 8 | HEADERS: '*' 9 | SOURCES: '*' 10 | bullet_char: '*' 11 | dangle_parens: false 12 | enum_char: . 13 | line_ending: unix 14 | line_width: 120 15 | max_pargs_hwrap: 3 16 | separate_ctrl_name_with_space: false 17 | separate_fn_name_with_space: false 18 | tab_size: 2 19 | 20 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | file(GLOB_RECURSE test_list "*_test.cpp") 2 | add_executable(libparse_tests ${test_list}) 3 | target_include_directories(libparse_tests PRIVATE ../include) 4 | target_link_libraries(libparse_tests PRIVATE project_warnings) 5 | target_link_libraries(libparse_tests PUBLIC doctest::doctest) 6 | target_link_libraries(libparse_tests PRIVATE project_options) 7 | add_test(NAME libparse_tests COMMAND libparse_tests) 8 | -------------------------------------------------------------------------------- /test/many_if_test.cpp: -------------------------------------------------------------------------------- 1 | #include "test_include.hpp" 2 | 3 | constexpr auto many_c = parser::many_if([](auto c) { return c == 'c'; }); 4 | 5 | TEST_CASE("more than 1") { 6 | static_assert(many_c("cccaa") == std::pair{ "ccc"sv, "aa"sv }); 7 | } 8 | 9 | TEST_CASE("one") { static_assert(many_c("caa") == std::pair{ "c"sv, "aa"sv }); } 10 | 11 | TEST_CASE("zero") { static_assert(many_c("aa") == std::pair{ ""sv, "aa"sv }); } 12 | -------------------------------------------------------------------------------- /test/symbol_test.cpp: -------------------------------------------------------------------------------- 1 | #include "test_include.hpp" 2 | 3 | TEST_CASE("empty") { static_assert(parser::symbol('t')("") == std::nullopt); } 4 | 5 | TEST_CASE("wrong symbolment") { 6 | static_assert(parser::symbol('e')("test") == std::nullopt); 7 | static_assert(parser::symbol('s')("test") == std::nullopt); 8 | } 9 | 10 | TEST_CASE("correct symbolment") { 11 | static_assert(parser::symbol('t')("test") == std::pair('t', "est"sv)); 12 | static_assert(parser::symbol('u')("unit") == std::pair('u', "nit"sv)); 13 | } 14 | -------------------------------------------------------------------------------- /test/left_test.cpp: -------------------------------------------------------------------------------- 1 | #include "test_include.hpp" 2 | 3 | constexpr auto to_dig(char c) { return c - '0'; } 4 | 5 | TEST_CASE("left") { 6 | constexpr auto whitespace_parser = parser::symbol(' '); 7 | constexpr auto dig_parser = 8 | parser::transform(parser::one_of("0123456789"), to_dig); 9 | constexpr auto whitespace_after_digit = dig_parser// 10 | | parser::ignore(whitespace_parser); 11 | static_assert(whitespace_after_digit("1 34") == std::pair{ 1, "34"sv }); 12 | } 13 | -------------------------------------------------------------------------------- /test/right_test.cpp: -------------------------------------------------------------------------------- 1 | #include "test_include.hpp" 2 | 3 | constexpr auto to_dig(char c) { return c - '0'; } 4 | 5 | TEST_CASE("right") { 6 | constexpr auto whitespace_parser = parser::symbol(' '); 7 | constexpr auto dig_parser = 8 | parser::transform(parser::one_of("0123456789"), to_dig); 9 | constexpr auto whitespace_after_digit = whitespace_parser// 10 | | parser::ignore_previous(dig_parser); 11 | static_assert(whitespace_after_digit(" 134") == std::pair{ 1, "34"sv }); 12 | } 13 | -------------------------------------------------------------------------------- /test/one_of_test.cpp: -------------------------------------------------------------------------------- 1 | #include "test_include.hpp" 2 | 3 | TEST_CASE("empty") { 4 | static_assert(parser::one_of("test")("") == std::nullopt); 5 | } 6 | 7 | TEST_CASE("wrong elements") { 8 | static_assert(parser::one_of("test")("unit") == std::nullopt); 9 | static_assert(parser::one_of("abc")("test") == std::nullopt); 10 | } 11 | 12 | TEST_CASE("matching elements") { 13 | static_assert(parser::one_of("test")("sample") == std::pair('s', "ample"sv)); 14 | static_assert(parser::one_of("unit")("test") == std::pair('t', "est"sv)); 15 | } 16 | -------------------------------------------------------------------------------- /test/str_test.cpp: -------------------------------------------------------------------------------- 1 | #include "test_include.hpp" 2 | 3 | TEST_CASE("empty") { static_assert(parser::str("test")("") == std::nullopt); } 4 | 5 | TEST_CASE("matching") { 6 | static_assert(parser::str("test")("tester") == std::pair("test"sv, "er"sv)); 7 | static_assert( 8 | parser::str("unit")("unit test") == std::pair("unit"sv, " test"sv)); 9 | } 10 | 11 | TEST_CASE("non matching") { 12 | static_assert(parser::str("test")("tesla") == std::nullopt); 13 | static_assert(parser::str("test")("te") == std::nullopt); 14 | static_assert(parser::str("money")("test") == std::nullopt); 15 | } 16 | -------------------------------------------------------------------------------- /test/satisfy_test.cpp: -------------------------------------------------------------------------------- 1 | #include "test_include.hpp" 2 | #include 3 | 4 | constexpr auto to_dig(char c) { return c - '0'; } 5 | constexpr auto greater_than_5 = [](auto num) { return num > 5; }; 6 | constexpr auto dig_parser = 7 | parser::transform(parser::one_of("0123456789"), to_dig); 8 | constexpr auto greater_than_5_parser = dig_parser// 9 | | parser::if_satisfies(greater_than_5); 10 | 11 | TEST_CASE("predicate satisfies") { 12 | static_assert(greater_than_5_parser("678") == std::pair{ 6, "78"sv }); 13 | } 14 | 15 | TEST_CASE("explicit parser fail") { 16 | static_assert(greater_than_5_parser("567") == std::nullopt); 17 | } 18 | -------------------------------------------------------------------------------- /test/combine_test.cpp: -------------------------------------------------------------------------------- 1 | #include "test_include.hpp" 2 | 3 | constexpr auto is_same_char(char a, char b) -> bool { return a == b; } 4 | 5 | constexpr auto c_parser = parser::symbol('c'); 6 | constexpr auto d_parser = parser::symbol('d'); 7 | constexpr auto cc_parser = c_parser// 8 | | parser::combine_with(c_parser, is_same_char); 9 | constexpr auto cd_parser = c_parser// 10 | | parser::combine_with(d_parser, is_same_char); 11 | 12 | TEST_CASE("first fail") { static_assert(cc_parser("bcd") == std::nullopt); } 13 | 14 | TEST_CASE("second fail") { static_assert(cc_parser("cbd") == std::nullopt); } 15 | 16 | TEST_CASE("both passes") { 17 | static_assert(cd_parser("cde") == std::pair{ false, "e"sv }); 18 | } 19 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | project(libparse CXX) 3 | include(cmake/StandardProjectSettings.cmake) 4 | include(cmake/StaticAnalyzers.cmake) 5 | include(cmake/CompilerWarnings.cmake) 6 | include(package.cmake) 7 | 8 | add_library(project_options INTERFACE) 9 | add_library(project_warnings INTERFACE) 10 | target_compile_features(project_options INTERFACE cxx_std_20) 11 | set_project_warnings(project_warnings) 12 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 13 | 14 | 15 | option(ENABLE_TESTING "Enable Test Builds" ON) 16 | option(ENABLE_EXAMPLES "Enable Example Builds" ON) 17 | 18 | if(ENABLE_TESTING) 19 | enable_testing() 20 | add_subdirectory(test) 21 | endif() 22 | 23 | if(ENABLE_EXAMPLES) 24 | add_subdirectory(examples) 25 | endif() 26 | -------------------------------------------------------------------------------- /test/exactly_n_test.cpp: -------------------------------------------------------------------------------- 1 | #include "test_include.hpp" 2 | 3 | constexpr auto concat_digits(int a, char b) -> int { 4 | return a = a * 10 + (b - '0'); 5 | } 6 | 7 | constexpr auto digit_parser = parser::one_of("0123456789"); 8 | constexpr auto three_digit_parser = digit_parser// 9 | | parser::exactly_n(0, concat_digits, 3); 10 | 11 | 12 | TEST_CASE("exactly 3") { 13 | static_assert(three_digit_parser("123abc") == std::pair{ 123, "abc"sv }); 14 | } 15 | 16 | TEST_CASE("less than 3") { 17 | static_assert(three_digit_parser("12abc") == std::nullopt); 18 | } 19 | 20 | TEST_CASE("empty") { static_assert(three_digit_parser("") == std::nullopt); } 21 | 22 | TEST_CASE("more than 3") { 23 | static_assert(three_digit_parser("1234abc") == std::pair{ 123, "4abc"sv }); 24 | } 25 | -------------------------------------------------------------------------------- /test/pipe_test.cpp: -------------------------------------------------------------------------------- 1 | #include "test_include.hpp" 2 | 3 | constexpr auto add_(int a, int b) { return a + b; } 4 | constexpr auto add_1_(int a) { return a + 1; } 5 | 6 | constexpr auto add = parser::piped(add_); 7 | constexpr auto add_1 = parser::piped(add_1_); 8 | 9 | constexpr auto dummy(char /*unused*/) { return 'c'; } 10 | 11 | constexpr auto empty_char_parser(std::string_view /*unused*/) 12 | -> parser::parsed_t { 13 | return {}; 14 | } 15 | 16 | 17 | TEST_CASE("1 param missing") { static_assert((1 | add(2)) == 3); } 18 | TEST_CASE("all param there") { static_assert(add(1, 2) == 3); } 19 | TEST_CASE("0 param pipes") { static_assert((1 | add_1()) == 2); } 20 | TEST_CASE("1 argument function") { static_assert((add_1(1)) == 2); } 21 | TEST_CASE("function pointer pipe") { 22 | static_assert( 23 | (empty_char_parser | parser::transform(dummy))("hello") == std::nullopt); 24 | } 25 | -------------------------------------------------------------------------------- /test/seperated_by_test.cpp: -------------------------------------------------------------------------------- 1 | #include "test_include.hpp" 2 | 3 | constexpr auto sum(int a, int b) { return a + b; } 4 | 5 | constexpr auto to_digit(char c) { return c - '0'; } 6 | 7 | constexpr auto concat_digits(int a, char b) -> int { 8 | return a = a * 10 + (b - '0'); 9 | } 10 | 11 | constexpr auto digit_parser = parser::one_of("0123456789"); 12 | 13 | constexpr auto int_parser = digit_parser// 14 | | parser::many1(0, concat_digits); 15 | constexpr auto whitespace_parser = parser::many_of(' '); 16 | 17 | constexpr auto plus_token_parser = 18 | whitespace_parser// 19 | | parser::ignore_previous(parser::symbol('+'))// 20 | | parser::ignore(whitespace_parser); 21 | 22 | constexpr auto expr_sum_parser = 23 | parser::seperated_by(int_parser, plus_token_parser, 0, sum); 24 | 25 | 26 | TEST_CASE("more than 1 values") { 27 | static_assert(expr_sum_parser("2 + 3 + 5") == std::pair{ 10, ""sv }); 28 | } 29 | -------------------------------------------------------------------------------- /test/then_test.cpp: -------------------------------------------------------------------------------- 1 | #include "test_include.hpp" 2 | #include 3 | 4 | constexpr auto to_digit(char c) { return c - '0'; } 5 | constexpr auto digit_parser = parser::one_of("0123456789")// 6 | | parser::transform(to_digit); 7 | constexpr auto append_digit(int a, int b) { return a * 10 + b; } 8 | 9 | constexpr auto concat_digit(int num) { 10 | auto append_to_num = [=](int i) { return append_digit(num, i); }; 11 | return digit_parser// 12 | | parser::transform(append_to_num); 13 | } 14 | 15 | constexpr auto two_digit_parser = digit_parser// 16 | | parser::then(concat_digit); 17 | 18 | TEST_CASE("both parsers succeed") { 19 | static_assert(two_digit_parser("12a") == std::pair{ 12, "a"sv }); 20 | } 21 | 22 | TEST_CASE("first parser fails") { 23 | static_assert(two_digit_parser("a2a") == std::nullopt); 24 | } 25 | 26 | TEST_CASE("second parser fails") { 27 | static_assert(two_digit_parser("1aa") == std::nullopt); 28 | } 29 | -------------------------------------------------------------------------------- /test/many1_test.cpp: -------------------------------------------------------------------------------- 1 | #include "test_include.hpp" 2 | 3 | 4 | constexpr auto concat_digits(int a, char b) -> int { 5 | return a = a * 10 + (b - '0'); 6 | } 7 | 8 | auto concat_string(std::string res, char cur) -> std::string { 9 | res += cur; 10 | return res; 11 | } 12 | 13 | constexpr auto digit_parser = parser::one_of("0123456789"); 14 | constexpr auto int_parser = digit_parser// 15 | | parser::many1(0, concat_digits); 16 | 17 | TEST_CASE("more than one matches") { 18 | static_assert(int_parser("123abc") == std::pair{ 123, "abc"sv }); 19 | } 20 | 21 | TEST_CASE("only one match") { 22 | static_assert(int_parser("1abc") == std::pair{ 1, "abc"sv }); 23 | } 24 | 25 | TEST_CASE("zero match") { static_assert(int_parser("abc") == std::nullopt); } 26 | 27 | TEST_CASE("move required parser") { 28 | auto const string_parser = parser::any// 29 | | parser::many1(""s, concat_string); 30 | REQUIRE(string_parser("abcdef") == std::pair{ "abcdef"s, ""sv }); 31 | } 32 | -------------------------------------------------------------------------------- /test/many_test.cpp: -------------------------------------------------------------------------------- 1 | #include "test_include.hpp" 2 | 3 | constexpr auto concat_digits(int a, char b) -> int { 4 | return a = a * 10 + (b - '0'); 5 | } 6 | 7 | auto concat_string_custom(std::string res, char cur) -> std::string { 8 | res += cur; 9 | return res; 10 | } 11 | 12 | constexpr auto digit_parser = parser::one_of("0123456789"); 13 | constexpr auto int_parser = digit_parser// 14 | | parser::many(0, concat_digits); 15 | 16 | TEST_CASE("more than one matches") { 17 | static_assert(int_parser("123abc") == std::pair{ 123, "abc"sv }); 18 | } 19 | 20 | TEST_CASE("only one match") { 21 | static_assert(int_parser("1abc") == std::pair{ 1, "abc"sv }); 22 | } 23 | 24 | TEST_CASE("zero match") { 25 | static_assert(int_parser("abc") == std::pair{ 0, "abc"sv }); 26 | } 27 | 28 | TEST_CASE("move required parser") { 29 | auto const string_parser = parser::any// 30 | | parser::many(""s, concat_string_custom); 31 | REQUIRE(string_parser("abcdef") == std::pair{ "abcdef"s, ""sv }); 32 | } 33 | -------------------------------------------------------------------------------- /test/or_with_test.cpp: -------------------------------------------------------------------------------- 1 | #include "test_include.hpp" 2 | 3 | constexpr auto a_parser = parser::symbol('a'); 4 | constexpr auto b_parser = parser::symbol('b'); 5 | constexpr auto c_parser = parser::symbol('c'); 6 | constexpr auto aa_parser = a_parser// 7 | | parser::or_with(a_parser); 8 | constexpr auto ab_parser = a_parser// 9 | | parser::or_with(b_parser); 10 | constexpr auto abc_parsr = a_parser// 11 | | parser::or_with(b_parser)// 12 | | parser::or_with(c_parser); 13 | 14 | TEST_CASE("both fails") { static_assert(ab_parser("def") == std::nullopt); } 15 | 16 | TEST_CASE("first fails") { 17 | static_assert(ab_parser("bcd") == std::pair{ 'b', "cd"sv }); 18 | } 19 | 20 | TEST_CASE("second fails") { 21 | static_assert(ab_parser("abc") == std::pair{ 'a', "bc"sv }); 22 | } 23 | 24 | TEST_CASE("both passes") { 25 | static_assert(aa_parser("abc") == std::pair{ 'a', "bc"sv }); 26 | } 27 | 28 | TEST_CASE("three parsers") { 29 | static_assert(abc_parsr("cba") == std::pair{ 'c', "ba"sv }); 30 | } 31 | -------------------------------------------------------------------------------- /test/sequence_test.cpp: -------------------------------------------------------------------------------- 1 | #include "test_include.hpp" 2 | 3 | constexpr auto char_to_int(char c) { return c - '0'; } 4 | 5 | constexpr auto append_digits(int a, char b) { return a * 10 + char_to_int(b); } 6 | 7 | constexpr auto digit_parser = parser::one_of("0123456789"); 8 | 9 | constexpr auto whitespace_parser = parser::many_of(' '); 10 | 11 | constexpr auto int_parser = 12 | parser::one_of("123456789")// 13 | | parser::then([](char c) { 14 | return parser::many(digit_parser, char_to_int(c), append_digits); 15 | }); 16 | 17 | constexpr auto int_then_whitespace_parser = 18 | int_parser | parser::ignore(whitespace_parser); 19 | 20 | constexpr auto three_int_sum_parser = 21 | parser::sequence([](int a, int b, int c) { return a + b + c; }, 22 | int_then_whitespace_parser, 23 | int_then_whitespace_parser, 24 | int_then_whitespace_parser); 25 | 26 | TEST_CASE("when parser matches") { 27 | static_assert(three_int_sum_parser("12 10 11")->first == 33); 28 | } 29 | 30 | TEST_CASE("when parser matches") { 31 | static_assert(three_int_sum_parser("12 1011") == std::nullopt); 32 | } 33 | -------------------------------------------------------------------------------- /test/transform_test.cpp: -------------------------------------------------------------------------------- 1 | #include "test_include.hpp" 2 | 3 | constexpr auto to_dig(char c) -> int { return c - '0'; } 4 | 5 | TEST_CASE("failing parser mapping") { 6 | constexpr auto parser = parser::empty// 7 | | parser::transform(to_dig); 8 | static_assert(parser("123") == std::nullopt); 9 | }; 10 | 11 | TEST_CASE("working parser mapping") { 12 | constexpr parser::Parser auto dig_parser = parser::one_of("0123456789")// 13 | | parser::transform(to_dig); 14 | static_assert(dig_parser("123") == std::pair{ 1, "23"sv }); 15 | }; 16 | 17 | TEST_CASE("working parser mapping") { 18 | constexpr parser::Parser auto dig_parser = parser::one_of("0123456789")// 19 | | parser::transform(to_dig); 20 | static_assert(dig_parser("123") == std::pair{ 1, "23"sv }); 21 | }; 22 | 23 | TEST_CASE("pipe operator test") { 24 | constexpr parser::Parser auto dig_parser = 25 | parser::always(2)// 26 | | parser::transform([](auto) { return 3; }); 27 | static_assert(dig_parser("123") == std::pair{ 3, "123"sv }); 28 | }; 29 | -------------------------------------------------------------------------------- /test/unconsume_str_test.cpp: -------------------------------------------------------------------------------- 1 | #include "test_include.hpp" 2 | 3 | constexpr auto char_to_int(char c) { return c - '0'; } 4 | 5 | constexpr auto append_digits(int a, char b) { return a * 10 + char_to_int(b); } 6 | 7 | constexpr auto digit_parser = parser::one_of("0123456789"); 8 | 9 | constexpr auto int_parser = 10 | parser::one_of("123456789")// 11 | | parser::then([](char c) { 12 | return parser::many(digit_parser, char_to_int(c), append_digits); 13 | }); 14 | 15 | constexpr auto int_unconsume_parser = int_parser | parser::unconsume_str(); 16 | 17 | constexpr auto repeated_int_parser = 18 | parser::sequence([](auto a, auto b, auto c) { return a + b + c; }, 19 | int_unconsume_parser, 20 | int_unconsume_parser, 21 | int_unconsume_parser); 22 | 23 | 24 | TEST_CASE("when parser matches") { 25 | constexpr auto str = "12"; 26 | static_assert(repeated_int_parser(str)->first == 36); 27 | static_assert(repeated_int_parser(str)->second == "12"); 28 | } 29 | 30 | TEST_CASE("when parser fails to parse") { 31 | constexpr auto str = "a12"; 32 | static_assert(repeated_int_parser(str) == std::nullopt); 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /cmake/StaticAnalyzers.cmake: -------------------------------------------------------------------------------- 1 | option(ENABLE_CPPCHECK "Enable static analysis with cppcheck" OFF) 2 | option(ENABLE_CLANG_TIDY "Enable static analysis with clang-tidy" OFF) 3 | option(ENABLE_INCLUDE_WHAT_YOU_USE "Enable static analysis with include-what-you-use" OFF) 4 | 5 | if(ENABLE_CPPCHECK) 6 | find_program(CPPCHECK cppcheck) 7 | if(CPPCHECK) 8 | set(CMAKE_CXX_CPPCHECK 9 | ${CPPCHECK} 10 | --suppress=missingInclude 11 | --enable=all 12 | --inline-suppr 13 | --inconclusive 14 | -i 15 | ${CMAKE_SOURCE_DIR}/imgui/lib) 16 | else() 17 | message(SEND_ERROR "cppcheck requested but executable not found") 18 | endif() 19 | endif() 20 | 21 | if(ENABLE_CLANG_TIDY) 22 | find_program(CLANGTIDY clang-tidy) 23 | if(CLANGTIDY) 24 | set(CMAKE_CXX_CLANG_TIDY ${CLANGTIDY} -extra-arg=-Wno-unknown-warning-option) 25 | else() 26 | message(SEND_ERROR "clang-tidy requested but executable not found") 27 | endif() 28 | endif() 29 | 30 | if(ENABLE_INCLUDE_WHAT_YOU_USE) 31 | find_program(INCLUDE_WHAT_YOU_USE include-what-you-use) 32 | if(INCLUDE_WHAT_YOU_USE) 33 | set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE ${INCLUDE_WHAT_YOU_USE}) 34 | else() 35 | message(SEND_ERROR "include-what-you-use requested but executable not found") 36 | endif() 37 | endif() 38 | -------------------------------------------------------------------------------- /cmake/StandardProjectSettings.cmake: -------------------------------------------------------------------------------- 1 | # Set a default build type if none was specified 2 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 3 | message(STATUS "Setting build type to 'RelWithDebInfo' as none was specified.") 4 | set(CMAKE_BUILD_TYPE 5 | RelWithDebInfo 6 | CACHE STRING "Choose the type of build." FORCE) 7 | # Set the possible values of build type for cmake-gui, ccmake 8 | set_property( 9 | CACHE CMAKE_BUILD_TYPE 10 | PROPERTY STRINGS 11 | "Debug" 12 | "Release" 13 | "MinSizeRel" 14 | "RelWithDebInfo") 15 | endif() 16 | 17 | # Generate compile_commands.json to make it easier to work with clang based tools 18 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 19 | 20 | option(ENABLE_IPO "Enable Interprocedural Optimization, aka Link Time Optimization (LTO)" OFF) 21 | 22 | if(ENABLE_IPO) 23 | include(CheckIPOSupported) 24 | check_ipo_supported( 25 | RESULT 26 | result 27 | OUTPUT 28 | output) 29 | if(result) 30 | set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) 31 | else() 32 | message(SEND_ERROR "IPO is not supported: ${output}") 33 | endif() 34 | endif() 35 | if(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") 36 | add_compile_options(-fcolor-diagnostics) 37 | elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") 38 | add_compile_options(-fdiagnostics-color=always) 39 | else() 40 | message(STATUS "No colored compiler diagnostic set for '${CMAKE_CXX_COMPILER_ID}' compiler.") 41 | endif() 42 | 43 | -------------------------------------------------------------------------------- /examples/3_int_parsing.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace std::string_view_literals; 5 | 6 | constexpr auto to_digit(char c) { return c - '0'; } 7 | constexpr auto concat_digit(int a, int b) { return a * 10 + b; } 8 | constexpr auto add_int(int a, int b) { return a + b; } 9 | 10 | constexpr auto digit_parser = parser::one_of("01234567889")// 11 | | parser::transform(to_digit); 12 | constexpr auto whitespace_parser = parser::symbol(' '); 13 | constexpr auto plus_token_parser = parser::str(" + "); 14 | 15 | 16 | // parses string like <1 2 3> -> 123 17 | constexpr auto three_dig_parser = 18 | digit_parser// 19 | | parser::ignore(whitespace_parser) 20 | | parser::combine_with(digit_parser, concat_digit) 21 | | parser::ignore(whitespace_parser) 22 | | parser::combine_with(digit_parser, concat_digit); 23 | 24 | // parse string like < 1 + 2 + 3 > -> 6 25 | constexpr auto three_dig_sum = whitespace_parser 26 | | parser::ignore_previous(digit_parser)// 27 | | parser::ignore(plus_token_parser)// 28 | | parser::combine_with(digit_parser, add_int)// 29 | | parser::ignore(plus_token_parser)// 30 | | parser::combine_with(digit_parser, add_int)// 31 | | parser::ignore(whitespace_parser); 32 | 33 | auto main() -> int { 34 | static_assert(three_dig_parser("3 6 1") == std::pair{ 361, ""sv }); 35 | static_assert(three_dig_sum(" 3 + 6 + 1 ").value().first == 10); 36 | } 37 | -------------------------------------------------------------------------------- /examples/big_string_parsing.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | 8 | /* 9 | * Peformance example: 10 | * Parses string of length 15000000 with 11 | * 50000000 1's seperated by " ," in almost 12 | * 200ms. 13 | */ 14 | 15 | using namespace std::string_view_literals; 16 | 17 | constexpr auto char_to_int(char c) { return c - '0'; } 18 | 19 | constexpr auto append_digits(int a, char b) { return a * 10 + char_to_int(b); } 20 | 21 | constexpr auto whitespace_parser = parser::many_of(' '); 22 | 23 | constexpr auto token(char c) { 24 | return whitespace_parser// 25 | | parser::ignore_previous(parser::symbol(c))// 26 | | parser::ignore(whitespace_parser); 27 | } 28 | 29 | constexpr auto digit_parser = parser::one_of("0123456789"); 30 | 31 | constexpr auto int_parser = 32 | parser::one_of("123456789")// 33 | | parser::then([](char c) { 34 | return parser::many(digit_parser, char_to_int(c), append_digits); 35 | }); 36 | 37 | auto main() -> int { 38 | std::ostringstream str; 39 | int n = 50000000; 40 | for (int i = 1; i < n; ++i) { str << '1' << ',' << ' '; } 41 | str << '1'; 42 | std::cout << "creating string done...\n"; 43 | 44 | auto start = std::chrono::high_resolution_clock::now(); 45 | auto const val = 46 | parser::seperated_by(int_parser, token(','), 0, std::plus<>{})(str.str()) 47 | .value() 48 | .first; 49 | auto stop = std::chrono::high_resolution_clock::now(); 50 | std::cout << "Took: " 51 | << std::chrono::duration_cast( 52 | stop - start) 53 | .count() 54 | << "ms" << std::endl; 55 | 56 | std::cout << "Sum: " << val << std::endl; 57 | } 58 | -------------------------------------------------------------------------------- /examples/cyclic_parsing.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace std::string_view_literals; 5 | 6 | /* 7 | * Grammar: 8 | * 9 | * expr = term + expr | term 10 | * term = factors * term | factors 11 | * factors = (expr) | int 12 | */ 13 | 14 | /* 15 | * Intution: 16 | * 17 | * Cyclic parsing needs a dependency cycle between parsers. 18 | * Lambdas are ussually great but functions in C++ is the 19 | * one only that serves this feature. So, our parsers would be 20 | * functions. 21 | */ 22 | 23 | constexpr auto char_to_int(char c) { return c - '0'; } 24 | 25 | constexpr auto append_digits(int a, char b) { return a * 10 + char_to_int(b); } 26 | 27 | constexpr auto whitespace_parser = parser::many_of(' '); 28 | 29 | constexpr auto token(char c) { 30 | return whitespace_parser// 31 | | parser::ignore_previous(parser::symbol(c))// 32 | | parser::ignore(whitespace_parser); 33 | } 34 | 35 | constexpr auto digit_parser = parser::one_of("0123456789"); 36 | 37 | constexpr auto int_parser = 38 | parser::one_of("123456789")// 39 | | parser::then([](char c) { 40 | return parser::many(digit_parser, char_to_int(c), append_digits); 41 | }); 42 | 43 | template constexpr auto trim_whitespace(P &&p) { 44 | return whitespace_parser// 45 | | parser::ignore_previous(std::forward

(p))// 46 | | parser::ignore(whitespace_parser); 47 | } 48 | 49 | 50 | constexpr auto expr_(std::string_view str) -> parser::parsed_t; 51 | constexpr auto expr = trim_whitespace(expr_); 52 | constexpr auto terms(std::string_view str) -> parser::parsed_t; 53 | constexpr auto factors(std::string_view str) -> parser::parsed_t; 54 | 55 | constexpr auto expr_(std::string_view str) -> parser::parsed_t { 56 | return parser::seperated_by(terms, token('+'), 0, std::plus<>{})(str); 57 | } 58 | 59 | 60 | constexpr auto terms(std::string_view str) -> parser::parsed_t { 61 | return parser::seperated_by(factors, token('*'), 1, std::multiplies<>{})(str); 62 | } 63 | 64 | constexpr auto factors(std::string_view str) -> parser::parsed_t { 65 | constexpr auto prs = parser::symbol('(')// 66 | | parser::ignore_previous(expr)// 67 | | parser::ignore(parser::symbol(')'))// 68 | | parser::or_with(int_parser); 69 | return prs(str); 70 | } 71 | 72 | auto main() -> int { 73 | static_assert(int_parser("1").value().first == 1); 74 | static_assert(expr("1 + 2 + 3").value().first == 6); 75 | static_assert(expr("1 + 2 * 3").value().first == 7); 76 | static_assert(expr("1 * 2 * 3").value().first == 6); 77 | static_assert(expr("1 * (2 + 3 )").value().first == 5); 78 | static_assert(expr("2 * (2 + 3 )").value().first == 10); 79 | static_assert(expr("1 * 2").value().first == 2); 80 | static_assert(expr(" 1 * 2 * ( 2 + 3)").value().first == 10); 81 | static_assert( 82 | expr( 83 | " 1 *(2 + 3 * ( 2 * 5 * ( 3 + 5 ) * (5 + 2 ))) * (1 + (1))") 84 | .value() 85 | .first 86 | == 3364); 87 | } 88 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | AccessModifierOffset: -2 2 | AlignAfterOpenBracket: DontAlign 3 | AlignConsecutiveAssignments: false 4 | AlignConsecutiveDeclarations: false 5 | AlignEscapedNewlines: Left 6 | AlignOperands: true 7 | AlignTrailingComments: false 8 | AllowAllParametersOfDeclarationOnNextLine: false 9 | AllowShortBlocksOnASingleLine: true 10 | AllowShortCaseLabelsOnASingleLine: false 11 | AllowShortFunctionsOnASingleLine: All 12 | AllowShortIfStatementsOnASingleLine: true 13 | AllowShortLoopsOnASingleLine: true 14 | AlwaysBreakAfterDefinitionReturnType: None 15 | AlwaysBreakAfterReturnType: None 16 | AlwaysBreakBeforeMultilineStrings: true 17 | AlwaysBreakTemplateDeclarations: false 18 | BinPackArguments: false 19 | BinPackParameters: false 20 | BraceWrapping: 21 | AfterClass: false 22 | AfterControlStatement: false 23 | AfterEnum: false 24 | AfterFunction: false 25 | AfterNamespace: false 26 | AfterObjCDeclaration: false 27 | AfterStruct: false 28 | AfterUnion: false 29 | BeforeCatch: false 30 | BeforeElse: false 31 | IndentBraces: false 32 | SplitEmptyFunction: false 33 | SplitEmptyNamespace: true 34 | SplitEmptyRecord: true 35 | BreakAfterJavaFieldAnnotations: true 36 | BreakBeforeBinaryOperators: NonAssignment 37 | BreakBeforeBraces: Custom 38 | BreakBeforeInheritanceComma: true 39 | BreakBeforeTernaryOperators: true 40 | BreakConstructorInitializers: BeforeColon 41 | BreakConstructorInitializersBeforeComma: false 42 | BreakStringLiterals: true 43 | ColumnLimit: 80 44 | CommentPragmas: '^ IWYU pragma:' 45 | CompactNamespaces: false 46 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 47 | ConstructorInitializerIndentWidth: 2 48 | ContinuationIndentWidth: 2 49 | Cpp11BracedListStyle: false 50 | DerivePointerAlignment: false 51 | DisableFormat: false 52 | ExperimentalAutoDetectBinPacking: true 53 | FixNamespaceComments: true 54 | ForEachMacros: 55 | - foreach 56 | - Q_FOREACH 57 | - BOOST_FOREACH 58 | IncludeCategories: 59 | - Priority: 2 60 | Regex: ^"(llvm|llvm-c|clang|clang-c)/ 61 | - Priority: 3 62 | Regex: ^(<|"(gtest|gmock|isl|json)/) 63 | - Priority: 1 64 | Regex: .* 65 | IncludeIsMainRegex: (Test)?$ 66 | IndentCaseLabels: false 67 | IndentWidth: 2 68 | IndentWrappedFunctionNames: true 69 | JavaScriptQuotes: Leave 70 | JavaScriptWrapImports: true 71 | KeepEmptyLinesAtTheStartOfBlocks: true 72 | Language: Cpp 73 | MacroBlockBegin: '' 74 | MacroBlockEnd: '' 75 | MaxEmptyLinesToKeep: 2 76 | NamespaceIndentation: Inner 77 | ObjCBlockIndentWidth: 7 78 | ObjCSpaceAfterProperty: true 79 | ObjCSpaceBeforeProtocolList: false 80 | PointerAlignment: Right 81 | ReflowComments: true 82 | SortIncludes: false 83 | SortUsingDeclarations: false 84 | SpaceAfterCStyleCast: false 85 | SpaceAfterTemplateKeyword: false 86 | SpaceBeforeAssignmentOperators: true 87 | SpaceBeforeParens: ControlStatements 88 | SpaceInEmptyParentheses: false 89 | SpacesBeforeTrailingComments: 0 90 | SpacesInAngles: false 91 | SpacesInCStyleCastParentheses: false 92 | SpacesInContainerLiterals: true 93 | SpacesInParentheses: false 94 | SpacesInSquareBrackets: false 95 | Standard: Cpp11 96 | TabWidth: 8 97 | UseTab: Never 98 | # LambdaBodyIndentationKind: LBI_Signature 99 | # RequiresClausePositionStyle: RCPS_OwnLine 100 | # IndentRequiresClause: true 101 | -------------------------------------------------------------------------------- /cmake/CompilerWarnings.cmake: -------------------------------------------------------------------------------- 1 | # from here: 2 | # 3 | # https://github.com/lefticus/cppbestpractices/blob/master/02-Use_the_Tools_Available.md 4 | 5 | function(set_project_warnings project_name) 6 | option(WARNINGS_AS_ERRORS "Treat compiler warnings as errors" TRUE) 7 | 8 | set(MSVC_WARNINGS 9 | /W4 # Baseline reasonable warnings 10 | /w14242 # 'identifier': conversion from 'type1' to 'type1', possible loss of data 11 | /w14254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data 12 | /w14263 # 'function': member function does not override any base class virtual member function 13 | /w14265 # 'classname': class has virtual functions, but destructor is not virtual instances of this class may not 14 | # be destructed correctly 15 | /w14287 # 'operator': unsigned/negative constant mismatch 16 | /we4289 # nonstandard extension used: 'variable': loop control variable declared in the for-loop is used outside 17 | # the for-loop scope 18 | /w14296 # 'operator': expression is always 'boolean_value' 19 | /w14311 # 'variable': pointer truncation from 'type1' to 'type2' 20 | /w14545 # expression before comma evaluates to a function which is missing an argument list 21 | /w14546 # function call before comma missing argument list 22 | /w14547 # 'operator': operator before comma has no effect; expected operator with side-effect 23 | /w14549 # 'operator': operator before comma has no effect; did you intend 'operator'? 24 | /w14555 # expression has no effect; expected expression with side- effect 25 | /w14619 # pragma warning: there is no warning number 'number' 26 | /w14640 # Enable warning on thread un-safe static member initialization 27 | /w14826 # Conversion from 'type1' to 'type_2' is sign-extended. This may cause unexpected runtime behavior. 28 | /w14905 # wide string literal cast to 'LPSTR' 29 | /w14906 # string literal cast to 'LPWSTR' 30 | /w14928 # illegal copy-initialization; more than one user-defined conversion has been implicitly applied 31 | /permissive- # standards conformance mode for MSVC compiler. 32 | ) 33 | 34 | set(CLANG_WARNINGS 35 | -Wall 36 | -Wextra # reasonable and standard 37 | -Wshadow # warn the user if a variable declaration shadows one from a parent context 38 | -Wnon-virtual-dtor # warn the user if a class with virtual functions has a non-virtual destructor. This helps 39 | # catch hard to track down memory errors 40 | -Wold-style-cast # warn for c-style casts 41 | -Wcast-align # warn for potential performance problem casts 42 | -Wunused # warn on anything being unused 43 | -Woverloaded-virtual # warn if you overload (not override) a virtual function 44 | -Wpedantic # warn if non-standard C++ is used 45 | -Wconversion # warn on type conversions that may lose data 46 | -Wsign-conversion # warn on sign conversions 47 | -Wnull-dereference # warn if a null dereference is detected 48 | -Wdouble-promotion # warn if float is implicit promoted to double 49 | -Wformat=2 # warn on security issues around functions that format output (ie printf) 50 | ) 51 | 52 | if(WARNINGS_AS_ERRORS) 53 | set(CLANG_WARNINGS ${CLANG_WARNINGS} -Werror) 54 | set(MSVC_WARNINGS ${MSVC_WARNINGS} /WX) 55 | endif() 56 | 57 | set(GCC_WARNINGS 58 | ${CLANG_WARNINGS} 59 | -Wmisleading-indentation # warn if indentation implies blocks where blocks do not exist 60 | -Wduplicated-cond # warn if if / else chain has duplicated conditions 61 | -Wduplicated-branches # warn if if / else branches have duplicated code 62 | -Wlogical-op # warn about logical operations being used where bitwise were probably wanted 63 | -Wuseless-cast # warn if you perform a cast to the same type 64 | ) 65 | 66 | if(MSVC) 67 | set(PROJECT_WARNINGS ${MSVC_WARNINGS}) 68 | elseif(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") 69 | set(PROJECT_WARNINGS ${CLANG_WARNINGS}) 70 | elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") 71 | set(PROJECT_WARNINGS ${GCC_WARNINGS}) 72 | else() 73 | message(AUTHOR_WARNING "No compiler warnings set for '${CMAKE_CXX_COMPILER_ID}' compiler.") 74 | endif() 75 | 76 | target_compile_options(${project_name} INTERFACE ${PROJECT_WARNINGS}) 77 | 78 | endfunction() 79 | -------------------------------------------------------------------------------- /include/parser/parser.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace parser { 10 | template 11 | using parsed_t = std::optional>; 12 | 13 | template 14 | concept Parser = 15 | std::regular_invocable 16 | && std::same_as, 17 | parsed_t::value_type::first_type>>; 19 | 20 | template 21 | using parser_result_t = typename std::invoke_result_t; 22 | 23 | template 24 | using parser_value_t = typename parser_result_t

::value_type::first_type; 25 | 26 | template 27 | concept ParserOf = 28 | Parser && std::same_as, T>; 29 | 30 | namespace detail { 31 | constexpr auto papply = [](auto &&func, auto &&...args) { 32 | if constexpr (std::invocable) { 33 | return std::invoke(std::forward(func), 34 | std::forward(args)...); 35 | } else { 36 | return std::bind_front(std::forward(func), 37 | std::forward(args)...); 38 | } 39 | }; 40 | 41 | namespace pipes { 42 | 43 | template struct pipe_adapter { 44 | F f; 45 | 46 | public: 47 | template constexpr auto operator()(Arg &&arg) const { 48 | return std::invoke(f, std::forward(arg)); 49 | } 50 | }; 51 | 52 | template 53 | constexpr auto operator|(Arg &&arg, pipe_adapter const &p) { 54 | return p(std::forward(arg)); 55 | } 56 | 57 | template constexpr auto make_pipe_adapter(F &&f) { 58 | return pipe_adapter{ std::forward(f) }; 59 | } 60 | 61 | template struct pipeable { 62 | private: 63 | F f; 64 | 65 | public: 66 | constexpr explicit pipeable(F &&f_p) noexcept : f(std::forward(f_p)) {} 67 | 68 | template 69 | requires std::invocable 70 | constexpr auto operator()(Xs &&...xs) const { 71 | return std::invoke(f, std::forward(xs)...); 72 | } 73 | 74 | template constexpr auto operator()(Xs &&...xs) const { 75 | return make_pipe_adapter( 76 | std::bind(f, std::placeholders::_1, std::forward(xs)...)); 77 | } 78 | }; 79 | }// namespace pipes 80 | 81 | template> F> 82 | auto constexpr transform(P &&p, F &&f) noexcept { 83 | using R = parsed_t>>; 84 | return [p = std::forward

(p), f = std::forward(f)]( 85 | std::string_view str) -> R { 86 | auto opt_i_res = std::invoke(p, str); 87 | if (opt_i_res == std::nullopt) return std::nullopt; 88 | return std::make_pair( 89 | std::invoke(f, std::move(opt_i_res->first)), opt_i_res->second); 90 | }; 91 | } 92 | 93 | template 94 | requires(std::same_as, parser_result_t>) 95 | constexpr auto or_with(P1 &&p1, P2 &&p2) noexcept { 96 | using R = parser_result_t; 97 | return [p1 = std::forward(p1), p2 = std::forward(p2)]( 98 | std::string_view str) -> R { 99 | auto opt_i_res = std::invoke(p1, str); 100 | if (opt_i_res) return *opt_i_res; 101 | return std::invoke(p2, str); 102 | }; 103 | } 104 | 105 | template, parser_value_t> F> 108 | constexpr auto combine(P1 &&p1, P2 &&p2, F &&f) noexcept { 109 | using R = 110 | parsed_t, parser_value_t>>; 111 | return [p1 = std::forward(p1), 112 | p2 = std::forward(p2), 113 | f = std::forward(f)](std::string_view str) -> R { 114 | auto opt_i_res = std::invoke(p1, str); 115 | if (!opt_i_res) return {}; 116 | auto opt_res = std::invoke(p2, opt_i_res->second); 117 | if (!opt_res) return {}; 118 | return std::make_pair( 119 | std::invoke(f, std::move(opt_i_res->first), std::move(opt_res->first)), 120 | opt_res->second); 121 | }; 122 | } 123 | 124 | template 125 | constexpr auto ignore_previous(P1 &&p1, P2 &&p2) noexcept { 126 | return combine(std::forward(p1), 127 | std::forward(p2), 128 | [](auto, auto r) { return r; }); 129 | } 130 | 131 | template 132 | constexpr auto ignore(P1 &&p1, P2 &&p2) noexcept { 133 | return combine(std::forward(p1), 134 | std::forward(p2), 135 | [](auto r, auto) { return r; }); 136 | } 137 | 138 | template> Predicate> 139 | constexpr auto if_satisfies(P &&p, Predicate &&pr) { 140 | return [p = std::forward

(p), pr = std::forward(pr)]( 141 | std::string_view str) -> parser_result_t

{ 142 | if (auto res = std::invoke(p, str)) { 143 | if (std::invoke(pr, res->first)) { 144 | return std::make_pair(std::move(res->first), res->second); 145 | } 146 | } 147 | return {}; 148 | }; 149 | } 150 | 151 | template> F> 152 | requires(Parser>>) 153 | constexpr auto then(P &&p, F &&f) { 154 | using R = parser_result_t>>; 155 | return [p = std::forward

(p), f = std::forward(f)]( 156 | std::string_view str) -> R { 157 | auto opt_i_res = std::invoke(p, str); 158 | if (!opt_i_res) return {}; 159 | return std::invoke( 160 | std::invoke(f, std::move(opt_i_res->first)), opt_i_res->second); 161 | }; 162 | } 163 | 164 | // many: Parser a -> b -> (b -> a -> b) -> Parser b 165 | template> F> 166 | requires(std::same_as>, B>) 167 | constexpr auto many(P &&p, B b, F &&f) { 168 | return [p = std::forward

(p), b = std::move(b), f = std::forward(f)]( 169 | std::string_view str) -> parsed_t { 170 | auto init = b; 171 | while (auto res = p(str)) { 172 | init = std::invoke(f, std::move(init), std::move(res->first)); 173 | str = res->second; 174 | } 175 | return std::make_pair(init, str); 176 | }; 177 | } 178 | 179 | // many1: Parser a -> b -> (b -> a -> b) -> Parser b 180 | template> F> 181 | requires(std::same_as>, B>) 182 | constexpr auto many1(P &&p, B b, F &&f) { 183 | return [p = std::forward

(p), b = std::move(b), f = std::forward(f)]( 184 | std::string_view str) -> parsed_t { 185 | auto res = std::invoke(p, str); 186 | if (!res) return {}; 187 | return many(p, std::invoke(f, b, std::move(res->first)), f)(res->second); 188 | }; 189 | } 190 | 191 | template> F> 192 | requires(std::same_as>, B>) 193 | constexpr auto exactly_n(P &&p, B b, F &&f, std::size_t n) { 194 | return 195 | [p = std::forward

(p), b = std::move(b), f = std::forward(f), n]( 196 | std::string_view str) -> parsed_t { 197 | auto init = b; 198 | for (size_t i{}; i < n; ++i) { 199 | if (auto res = p(str)) { 200 | init = std::invoke(f, std::move(init), std::move(res->first)); 201 | str = res->second; 202 | } else { 203 | return {}; 204 | } 205 | } 206 | return std::make_pair(init, str); 207 | }; 208 | } 209 | 210 | template 211 | requires(std::same_as>, B>) 212 | constexpr auto seperated_by(P1 &&p1, P2 &&p2, B b, F &&f) { 213 | return [p1 = std::forward(p1), 214 | p2 = std::forward(p2), 215 | b = std::move(b), 216 | f = std::forward(f)](std::string_view str) -> parsed_t { 217 | auto res = std::invoke(p1, str); 218 | if (!res) return {}; 219 | return many(ignore_previous(p2, p1), 220 | std::invoke(f, b, std::move(res->first)), 221 | f)(res->second); 222 | }; 223 | } 224 | 225 | 226 | template constexpr auto unconsume_str(P &&p) { 227 | return 228 | [p = std::forward

(p)](std::string_view str) -> parser_result_t

{ 229 | auto res = std::invoke(p, str); 230 | if (!res) return {}; 231 | return std::make_pair(res->first, str); 232 | }; 233 | } 234 | 235 | template constexpr auto operator^(P1 &&p1, P2 &&p2) { 236 | using result_t = std:: 237 | invoke_result_t, parser_value_t>; 238 | return [p1 = std::forward(p1), p2 = std::forward(p2)]( 239 | std::string_view str) -> parsed_t { 240 | if (auto p1_r = std::invoke(p1, str)) { 241 | if (auto p2_r = std::invoke(p2, p1_r->second)) { 242 | return { { std::invoke(papply, p1_r->first, p2_r->first), 243 | p2_r->second } }; 244 | } 245 | return {}; 246 | } 247 | return {}; 248 | }; 249 | } 250 | 251 | }// namespace detail 252 | 253 | template constexpr auto piped(F &&f) noexcept { 254 | return detail::pipes::pipeable{ std::forward(f) }; 255 | } 256 | 257 | constexpr auto any = [](std::string_view str) -> parsed_t { 258 | if (std::empty(str)) return {}; 259 | return std::make_pair(str[0], str.substr(1)); 260 | }; 261 | 262 | constexpr auto symbol(char c) noexcept { 263 | return [c](std::string_view str) -> parsed_t { 264 | if (std::empty(str) || str[0] != c) return {}; 265 | return std::make_pair(str[0], str.substr(1)); 266 | }; 267 | }; 268 | 269 | constexpr auto one_of(std::string_view sv) noexcept { 270 | return [sv](std::string_view str) -> parsed_t { 271 | if (std::empty(str)) return {}; 272 | const auto *const itr = std::find(cbegin(sv), cend(sv), str[0]); 273 | if (itr == cend(sv)) return {}; 274 | return std::make_pair(str[0], str.substr(1)); 275 | }; 276 | } 277 | 278 | constexpr auto none_of(std::string_view sv) noexcept { 279 | return [sv](std::string_view str) -> parsed_t { 280 | if (std::empty(str)) return {}; 281 | const auto *const itr = std::find(cbegin(sv), cend(sv), str[0]); 282 | if (itr != cend(sv)) return {}; 283 | return std::make_pair(str[0], str.substr(1)); 284 | }; 285 | } 286 | 287 | constexpr auto str(std::string_view sv) noexcept { 288 | return [sv](std::string_view str) -> parsed_t { 289 | auto [sv_itr, str_itr] = 290 | std::mismatch(cbegin(sv), cend(sv), cbegin(str), cend(str)); 291 | if (sv_itr != cend(sv)) { return {}; } 292 | return std::make_pair(sv, str.substr(sv.size())); 293 | }; 294 | } 295 | 296 | template 297 | constexpr auto empty = [](std::string_view) -> parsed_t { return {}; }; 298 | 299 | template constexpr auto always(T val) { 300 | return [val = std::move(val)](std::string_view str) -> parsed_t { 301 | return std::make_pair(val, str); 302 | }; 303 | } 304 | 305 | constexpr auto transform = piped([](P1 &&p1, F &&f) { 306 | return detail::transform(std::forward(p1), std::forward(f)); 307 | }); 308 | 309 | constexpr auto or_with = 310 | piped([](P1 &&p1, P2 &&p2) noexcept { 311 | return detail::or_with(std::forward(p1), std::forward(p2)); 312 | }); 313 | 314 | constexpr auto combine_with = 315 | piped([](P1 &&p1, P2 &&p2, F &&f) { 316 | return detail::combine( 317 | std::forward(p1), std::forward(p2), std::forward(f)); 318 | }); 319 | 320 | constexpr auto ignore_previous = 321 | piped([](P1 &&p1, P2 &&p2) noexcept { 322 | return detail::ignore_previous(std::forward(p1), std::forward(p2)); 323 | }); 324 | 325 | constexpr auto snd = ignore_previous; 326 | 327 | constexpr auto ignore = 328 | piped([](P1 &&p1, P2 &&p2) noexcept { 329 | return detail::ignore(std::forward(p1), std::forward(p2)); 330 | }); 331 | 332 | constexpr auto fst = ignore; 333 | 334 | constexpr auto if_satisfies = 335 | piped([](P1 &&p1, F &&f) { 336 | return detail::if_satisfies(std::forward(p1), std::forward(f)); 337 | }); 338 | 339 | template constexpr auto if_char_satisfies(F &&f) { 340 | return if_satisfies(any, std::forward(f)); 341 | } 342 | 343 | constexpr auto then = piped([](P1 &&p1, F &&f) { 344 | return detail::then(std::forward(p1), std::forward(f)); 345 | }); 346 | 347 | constexpr auto many = 348 | piped([](P &&p, B b, F &&f) { 349 | return detail::many(std::forward

(p), std::move(b), std::forward(f)); 350 | }); 351 | 352 | constexpr auto many1 = 353 | piped([](P &&p, B b, F &&f) { 354 | return detail::many1(std::forward

(p), std::move(b), std::forward(f)); 355 | }); 356 | 357 | constexpr auto exactly_n = 358 | piped([](P &&p, B b, F &&f, size_t n) { 359 | return detail::exactly_n( 360 | std::forward

(p), std::move(b), std::forward(f), n); 361 | }); 362 | 363 | constexpr auto seperated_by = 364 | piped([](P1 &&p1, 365 | P2 &&p2, 366 | B b, 367 | F &&f) { 368 | return detail::seperated_by(std::forward(p1), 369 | std::forward(p2), 370 | std::move(b), 371 | std::forward(f)); 372 | }); 373 | 374 | constexpr auto many1_of(char c) { 375 | return [c](std::string_view str) { 376 | using namespace std::string_view_literals; 377 | return detail::many1(symbol(c), ""sv, [str](auto prev, auto) { 378 | return str.substr(0, prev.size() + 1); 379 | })(str); 380 | }; 381 | } 382 | 383 | template Predicate> 384 | constexpr auto many1_if(Predicate &&p) { 385 | return [p = std::forward(p)](std::string_view str) { 386 | using namespace std::string_view_literals; 387 | return detail::many1(if_char_satisfies(p), ""sv, [str](auto prev, auto) { 388 | return str.substr(0, prev.size() + 1); 389 | })(str); 390 | }; 391 | } 392 | 393 | constexpr auto many_of(char c) { 394 | using namespace std::string_view_literals; 395 | return or_with(many1_of(c), always(""sv)); 396 | } 397 | 398 | template Predicate> constexpr auto many_if(Predicate &&p) { 399 | using namespace std::string_view_literals; 400 | return or_with(many1_if(std::forward(p)), always(""sv)); 401 | } 402 | 403 | 404 | // credit: Petter Holmberg 405 | constexpr auto sequence = [](auto &&f, auto &&...p) { 406 | using detail::operator^; 407 | return ( 408 | always(std::forward(f)) ^ ... ^ std::forward(p)); 409 | }; 410 | 411 | constexpr auto unconsume_str = 412 | piped([](P &&p) { return detail::unconsume_str(p); }); 413 | 414 | 415 | };// namespace parser 416 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # libparse 2 | 3 | A **single header** compile time functional string parsing libary in C++. 4 | It provides a set of builtin parsers and parser combinators. 5 | Parser combinators utilizes well known functional programming patterns. 6 | You can chain many parsers using parser combinators to define custom parsers you need. 7 | 8 | All you need is C++20! 9 | 10 | ## Usage 11 | 12 | ```cpp 13 | #include 14 | 15 | constexpr auto to_digit(char c) { return c - '0'; } 16 | 17 | constexpr auto digit_parser = parser::one_of("0123456789")// 18 | | parser::transform(to_digit); 19 | constexpr auto plus_token_parser = parser::str(" + "); 20 | 21 | 22 | constexpr auto three_dig_sum = digit_parser 23 | | parser::ignore(plus_token_parser) 24 | | parser::combine_with(digit_parser, std::plus<>{}) 25 | | parser::ignore(plus_token_parser) 26 | | parser::combine_with(digit_parser, std::plus<>{}); 27 | 28 | int main(){ 29 | static_assert(three_dig_sum("1 + 2 + 3")->first == 6); 30 | } 31 | ``` 32 | 33 | # Documentation 34 | 35 | A parser of thing is a function that takes string_view and optionally returns 36 | the thing and rest of string to parse. 37 | Parser parses 0 or more length prefix of string_view. 38 | 39 | ```cpp 40 | using parsed_t = std::optional>; 41 | ``` 42 | 43 | ```haskell 44 | ParserOf :: string_view -> parsed_t 45 | ``` 46 | 47 | If parser returns std::nullopt, that means parser was not able to parse the string. 48 | 49 | If a parser parses successfully, it consumes 0 or more characters from starting 50 | of string_view. Somehow, transforms that to T and returns T and the remaining 51 | characters of string_view to parse. 52 | 53 | A Parser combinator accepts one or more parsers and return a new parser. 54 | Every Parser combinator is both infix callable with pipes (pipeable) and 55 | callable with normal function call syntax with all arguments provided. 56 | 57 | Prefix: F(a, b) 58 | Infix: a | F(b) 59 | 60 | This makes a parser to look like a pipeline of parsers. 61 | 62 | **NOTE**: For more examples please look into project's test and examples directory. 63 | There is a test file for every parser and parser combinator. That test 64 | file well demostrates the functionality of that parser in code. 65 | 66 | ## parser::any 67 | 68 | ```cpp 69 | any :: string_view -> parsed_t 70 | ``` 71 | 72 | This parser parses first character of string_view and null if empty. 73 | 74 | ```cpp 75 | static_assert(parser::any("test") == std::pair{ 't', "est"sv }); 76 | ``` 77 | 78 | ## parser::symbol 79 | 80 | ```cpp 81 | symbol = (char c) -> (string_view -> parsed_t) 82 | ``` 83 | 84 | This parser parses the first character of string_view if first character is c. 85 | 86 | ```cpp 87 | static_assert(parser::symbol('t')("test") == std::pair{ 't', "est"sv }); 88 | ``` 89 | 90 | ## parser::one_of 91 | 92 | ```cpp 93 | one_of = (string_view s) -> (string_view -> parsed_t) 94 | ``` 95 | 96 | This parser parses the first character of string_view is contained in s. 97 | 98 | ```cpp 99 | static_assert(parser::one_of("tuv")("test") == std::pair{ 't', "est"sv }); 100 | ``` 101 | 102 | ## parser::none_of 103 | 104 | ```cpp 105 | none_of = (string_view s) -> ParserOf 106 | ``` 107 | 108 | This parser parses the first character of string_view is not contained in s. 109 | 110 | ```cpp 111 | static_assert(parser::one_of("uv")("test") == std::pair{ 't', "est"sv }); 112 | ``` 113 | 114 | ## parser::str 115 | 116 | ```cpp 117 | str = (string_view s) -> ParserOf 118 | ``` 119 | 120 | This parser parses if the string starts with s 121 | 122 | ```cpp 123 | static_assert(parser::one_of("te")("test") == std::pair{ "te"sv, "st"sv }); 124 | ``` 125 | 126 | ## parser::empty 127 | 128 | ```cpp 129 | empty = (string_view s) -> parsed_t 130 | ``` 131 | 132 | This parsers just returns null, and consumes nothing. 133 | 134 | ```cpp 135 | static_assert(parser::empty("str") == std::nulopt); 136 | ``` 137 | 138 | ## parser::always 139 | 140 | ```cpp 141 | always = (T t) -> (string_view -> parsed_t) 142 | ``` 143 | 144 | This parser always returns t and having the input string unconsumed 145 | 146 | ```cpp 147 | static_assert(parser::always('c')("str") == std::pair{'c', "str"sv}); 148 | ``` 149 | 150 | ## parser::transform 151 | 152 | ```cpp 153 | transform = (ParserOf p, F :: (T1 -> T2)) -> ParserOf 154 | ``` 155 | 156 | This parser combinator takes a parser of type T1 and a function from (T1 to T2) and 157 | returns a parser of type T2. This is fmap equivalent of functional programming. 158 | When parser succeds it returns result applying F otherwise returns null. 159 | 160 | ```cpp 161 | constexpr auto to_digit(char c){ 162 | return c - '0'; 163 | } 164 | 165 | constexpr auto dig_9 = parser::symbol('9') 166 | | parser::transform(to_dig); 167 | 168 | static_assert(dig_9("9str") == std::pair{9, "str"sv}); 169 | ``` 170 | 171 | ## parser::or_with 172 | 173 | ```cpp 174 | or_with = (ParserOf p1, ParserOf p2) -> ParserOf 175 | ``` 176 | 177 | This parser combinator takes 2 parsers of types T and returns a parser of Type T. 178 | It first tries to parse string with p1 if that succeds return result otherwise tries to parse with p2 179 | if that succeeds returns result otherwise null. 180 | 181 | ```cpp 182 | constexpr auto any_char = parser::empty 183 | | parser::or_with(parser::any); 184 | 185 | static_assert(any_char("9str") == std::pair{'9', "str"sv}); 186 | ``` 187 | 188 | ## parser::combine_with 189 | 190 | ```cpp 191 | or_with = (ParserOf p1, ParserOf p2, F f) -> ParserOf 192 | ``` 193 | 194 | This parser combinator first parses string with p1 and then rest of string with p2 and 195 | then returns the answer by applying f on it. 196 | If at any step any parser fails, it return null. 197 | 198 | ```cpp 199 | constexpr auto append_dig(int a, int b){ 200 | return a * 10 + b; 201 | } 202 | 203 | constexpr auto dig = parser::one_of("0123456789"); 204 | constexpr auto two_dig = dig 205 | | parser::combine_with(dig, append_dig); 206 | 207 | static_assert(two_dig("92str") == std::pair{92, "str"sv}); 208 | ``` 209 | 210 | ## parser::ignore_previous / parser::snd 211 | 212 | ```cpp 213 | ignore_previous = (ParserOf p1, ParserOf p2) -> ParserOf 214 | ``` 215 | 216 | This parser combinator first parses string with p1 and then rest of string with 217 | p2 and then return the result of p2 ignoring result of p1. 218 | 219 | ```cpp 220 | constexpr auto to_digit(char c){ 221 | return c - '0'; 222 | } 223 | 224 | constexpr auto whitespace = parser::many_of(' '); 225 | constexpr auto dig_parser = parser::one_of("0123456789") 226 | 227 | constexpr auto dig_after_whitespace = whitespace 228 | | parser::ignore_previous(dig_parser) 229 | 230 | static_assert(dig_after_whitespace(" 9a" == std::pair{9, "a"sv}); 231 | static_assert(parser::snd(whitespace, dig_parser)(" 9a" == std::pair{9, "a"sv}); 232 | ``` 233 | 234 | ## parser::ignore / parser::fst 235 | 236 | ```cpp 237 | ignore = (ParserOf p1, ParserOf p2) -> ParserOf 238 | ``` 239 | 240 | This parser combinator first parses string with p1 and then rest of string with 241 | p2 and then return the result of p1 ignoring result of p2. 242 | 243 | ```cpp 244 | constexpr auto to_digit(char c){ 245 | return c - '0'; 246 | } 247 | 248 | constexpr auto whitespace = parser::many_of(' '); 249 | constexpr auto dig_parser = parser::one_of("0123456789") 250 | 251 | constexpr auto whitspace_after_dig = dig_parser 252 | | parser::ignore(dig_parser) 253 | 254 | static_assert(whitspace_after_dig("9 a" == std::pair{9, "a"sv}); 255 | static_assert(parser::fst(dig_parser, whitespace)(" 9a" == std::pair{9, "a"sv}); 256 | ``` 257 | 258 | ## parser::if_satisfies 259 | 260 | ```cpp 261 | if_satisfies = (ParserOf p1, Predicate p) -> ParserOf 262 | ``` 263 | 264 | This parser combinator first parses string with p1. If p1 fails then returns 265 | null. Otherwise pass result to p. If result satisfies p then return result 266 | otherwise return null. 267 | 268 | ```cpp 269 | constexpr auto is_c_or_d(char c){ 270 | return c == 'c' or c == 'd'; 271 | } 272 | 273 | constexpr auto c_or_d = parser::any 274 | | parser::if_satisfies(is_c_or_d); 275 | 276 | static_assert(c_or_d("cat" == std::pair{'c', "at"sv}); 277 | ``` 278 | 279 | ## parser::if_char_satisfies 280 | 281 | ```cpp 282 | if_char_satisfies = (Predicate p) -> ParserOf 283 | ``` 284 | 285 | This parser parses a character from string_view. If that satisfies p return character otherwise null. 286 | 287 | ```cpp 288 | constexpr auto is_c_or_d(char c){ 289 | return c == 'c' or c == 'd'; 290 | } 291 | 292 | constexpr auto c_or_d = parser::if_char_satisfies(is_c_or_d); 293 | 294 | static_assert(c_or_d("cat" == std::pair{'c', "at"sv}); 295 | ``` 296 | 297 | ## parser::then 298 | 299 | ```cpp 300 | then = (ParserOf p, F f) -> ParserOf 301 | F = T -> ParserOf 302 | ``` 303 | 304 | This parser combinator takes a parser and a function. 305 | It parses the string with p, if that fails returns null. 306 | Otherwise pass the result to f, and parses the rest of string 307 | with parser returned by invoking f. 308 | 309 | f is required to return a parser. 310 | 311 | This is a monadic bind equivalent in functional programming. 312 | 313 | ```cpp 314 | constexpr auto char_to_int(char c) { return c - '0'; } 315 | 316 | constexpr auto int_parser = 317 | parser::one_of("123456789") 318 | | parser::then([](char c) { 319 | return parser::many(parser::one_of("0123456789"), char_to_int(c), append_digits); 320 | }); 321 | 322 | static_assert(int_parser("123abc") == std::pair{123, "abc"sv}); 323 | ``` 324 | 325 | ## parser::many 326 | 327 | ```cpp 328 | many = (ParserOf p1, T2 init, F f) -> Parser 329 | F = (T1, T2) -> T1 330 | ``` 331 | 332 | This accumulating parser combinator parses the string 0 or more times with p1. 333 | It accumulates the result with accumulation function f with the initial value 334 | init. After the first failure it returns the accumulated value and remaining string. 335 | 336 | ```cpp 337 | constexpr auto concat_digits(int a, char b) -> int { 338 | return a = a * 10 + (b - '0'); 339 | } 340 | 341 | constexpr auto digit_parser = parser::one_of("0123456789"); 342 | constexpr auto int_parser = digit_parser 343 | | parser::many(0, concat_digits); 344 | 345 | static_assert(int_parser("123abc") == std::pair{ 123, "abc"sv }); 346 | static_assert(int_parser("abc") == std::pair{ 0, "abc"sv }); 347 | ``` 348 | 349 | ## parser::many1 350 | 351 | ```cpp 352 | many1 = (ParserOf p1, T2 init, F f) -> Parser 353 | F = (T1, T2) -> T1 354 | ``` 355 | 356 | This accumulating parser combinator parses the string 1 or more times with p1. 357 | It accumulates the result with accumulation function f with the initial value 358 | init. After the first failure it returns the accumulated value and remaining string. 359 | If parser fails for the first time parsing with p1 itself, then it returns a null. 360 | 361 | ```cpp 362 | constexpr auto concat_digits(int a, char b) -> int { 363 | return a = a * 10 + (b - '0'); 364 | } 365 | 366 | constexpr auto digit_parser = parser::one_of("0123456789"); 367 | constexpr auto int_parser = digit_parser 368 | | parser::many(0, concat_digits); 369 | 370 | static_assert(int_parser("123abc") == std::pair{ 123, "abc"sv }); 371 | static_assert(int_parser("abc") == std::nullopt); 372 | ``` 373 | 374 | ## parser::exactly_n 375 | 376 | ```cpp 377 | exactly_n = (ParserOf p1, T2 init, F f, size_t n) -> Parser 378 | F = (T1, T2) -> T1 379 | ``` 380 | 381 | This accumulating parser combinator parses the string n times with p1. 382 | It accumulates the result with accumulation function f with the initial value 383 | init. If it can't parse the string n times with p1, then returns null otherwise the 384 | accumulated value. 385 | 386 | ```cpp 387 | constexpr auto concat_digits(int a, char b) -> int { 388 | return a = a * 10 + (b - '0'); 389 | } 390 | 391 | constexpr auto digit_parser = parser::one_of("0123456789"); 392 | constexpr auto three_dig_int = digit_parser 393 | | parser::exactly_n(0, concat_digits, 3); 394 | 395 | static_assert(int_parser("1234abc") == std::pair{ 123, "4abc"sv }); 396 | static_assert(int_parser("12abc") == std::nullopt); 397 | ``` 398 | 399 | ## parser::seperated_by 400 | 401 | ```cpp 402 | seperated_by = (ParserOf p1, ParserOf p2, T2 init, F f) -> Parser 403 | F = (T1, T2) -> T1 404 | ``` 405 | 406 | This accumulating parser combinator parses the string in which, 1 or more 407 | values satisfying p1 is seperated by values that satisfy p2. It accumulates all 408 | the values with accumulation function f. 409 | 410 | ```cpp 411 | constexpr auto to_digit(char c){ 412 | return c - '0'; 413 | } 414 | 415 | constexpr auto digit_parser = parser::one_of("0123456789") 416 | | parse::transform(to_digit); 417 | 418 | constexpr auto sum_dig = digit_parser 419 | | parser::seperated_by(parser::symbol('+'), 0, std::plus<>{}); 420 | 421 | static_assert(sum_dig("1+2+3a") == std::pair{ 6, "a"sv }); 422 | ``` 423 | 424 | ## parser::many_of 425 | 426 | ```cpp 427 | many_of = (char c) -> Parser 428 | ``` 429 | 430 | This accumulating parser extracts 0 or more adjacent c in starting of given string_view. 431 | If the given string_view doesn't start with c, it returns empty string_view as result 432 | and not consuming any of given string. 433 | 434 | ```cpp 435 | static_assert(many_of('c')("ccabc") == std::pair{ "cc"sv, "abc"sv }); 436 | static_assert(many_of('c')("abc") == std::pair{ ""sv, "abc"sv }); 437 | ``` 438 | 439 | ## parser::many_if 440 | 441 | ```cpp 442 | many_of = (Predicate p) -> Parser 443 | ``` 444 | 445 | This accumulating parser extracts 0 or more adjacent characters in starting of 446 | given string_view that satisfies p. If the given string_view doesn't start with 447 | any such characters, it returns empty string_view as result and not consuming any of given 448 | string. 449 | 450 | ```cpp 451 | constexpr auto is_c(char c){ 452 | return c == 'c'; 453 | } 454 | 455 | static_assert(many_if(is_c)("ccabc") == std::pair{ "cc"sv, "abc"sv }); 456 | static_assert(many_if(is_c)("abc") == std::pair{ ""sv, "abc"sv }); 457 | ``` 458 | 459 | ## parser::many1_of 460 | 461 | ```cpp 462 | many1_of = (char c) -> Parser 463 | ``` 464 | 465 | This accumulating parser extracts 1 or more adjacent c in starting of given string_view. 466 | If the given string_view doesn't start with c, it return null. 467 | 468 | ```cpp 469 | static_assert(many1_of('c')("ccabc") == std::pair{ "cc"sv, "abc"sv }); 470 | static_assert(many1_of('c')("abc") == std::nullopt); 471 | ``` 472 | 473 | ## parser::many1_if 474 | 475 | ```cpp 476 | many1_if = (Predicate p) -> Parser 477 | ``` 478 | 479 | This accumulating parser extracts 1 or more adjacent characters in starting of 480 | given string_view that satisfies p. If the given string_view doesn't start with 481 | any such characters, it returns null. 482 | 483 | ```cpp 484 | constexpr auto is_c(char c){ 485 | return c == 'c'; 486 | } 487 | 488 | static_assert(many1_if(is_c)("ccabc") == std::pair{ "cc"sv, "abc"sv }); 489 | static_assert(many1_if(is_c)("abc") == std::nullopt); 490 | ``` 491 | 492 | ## parser::many1_if 493 | 494 | ```cpp 495 | sequence = (F f, Parser... p) -> Parser 496 | ``` 497 | 498 | This is an applicative parser. That takes a function that combines the result 499 | and many parsers that would be executed sequentially. The resut of those 500 | parsers would be passed to the passed function. 501 | 502 | ```cpp 503 | constexpr auto combine_digits(int a, int b){ 504 | return a * 10 + b; 505 | } 506 | 507 | static_assert(sequence(combine_digits, digit_parser, digit_parser)("12")->first == nullopt); 508 | ``` 509 | 510 | ## What's next 511 | 512 | checkout [cycling_parsing.cpp](examples/cyclic_parsing.cpp) to understand 513 | how to parse a grammar with libparse and potentially if that grammer contains 514 | cyclic dependencies. 515 | 516 | ## TODO 517 | 518 | - Improve documentation 519 | - Package for conan 520 | - Package for vcpkg 521 | 522 | ## Credits 523 | 524 | - David Sankel's talk: [Monoids, Monads and Applicative Functors](https://youtu.be/giWCdQ7fnQU) 525 | - Ben Deane's talk: [constexpr all the things](https://youtu.be/PJwd4JLYJJY) 526 | - Peter Holmberg's talk: [Functional Parsing in C++20](https://youtu.be/5iXKLwoqbyw) 527 | - Prabhdeep Singh (@prabhdepSP) : [big_string_parsing.cpp](examples/big_string_parsing.cpp) 528 | --------------------------------------------------------------------------------