diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 62a9a87..92432c7 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -8,10 +8,11 @@ "workspaceFolder": "/workspaces/jsonlogic/cpp", "customizations": { "vscode": { - "extensions": [ + + "extensions": [ "llvm-vs-code-extensions.vscode-clangd" - // add other extensions as needed - ] + // add other extensions as needed + ] } } // "mounts": [ diff --git a/cpp/.vscode/launch.json b/cpp/.vscode/launch.json index cd11bed..82fcf3b 100644 --- a/cpp/.vscode/launch.json +++ b/cpp/.vscode/launch.json @@ -99,4 +99,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index fee26eb..6043dc8 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -74,7 +74,6 @@ if(JSONLOGIC_ENABLE_TESTS) set(CTEST_BUILD_FLAGS -j${N}) set(ctest_test_args ${ctest_test_args} PARALLEL_LEVEL ${N}) endif() - add_subdirectory(tests) endif() @@ -99,8 +98,7 @@ if(EXISTS "${COMPILE_COMMANDS_SOURCE}") "${COMPILE_COMMANDS_LINK}" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/build" RESULT_VARIABLE symlink_result - ) - +) if(symlink_result EQUAL 0) message(STATUS "Created symlink: ${COMPILE_COMMANDS_LINK} -> ${COMPILE_COMMANDS_SOURCE}") else() diff --git a/cpp/README.md b/cpp/README.md index 04094e8..ad4a198 100644 --- a/cpp/README.md +++ b/cpp/README.md @@ -1,5 +1,7 @@ # JsonLogic for C++ +[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/LLNL/jsonlogic) + This is an implementation for [JsonLogic](https://jsonlogic.com/) for C++. The API uses the Boost JSON implementation (e.g., [Boost 1.82](https://www.boost.org/doc/libs/1_82_0/libs/json/doc/html/index.html)). @@ -11,7 +13,7 @@ The library can be installed using cmake. From the top-level directory, ``` cmake --preset=default cd build/release -make + make ``` Benchmarks can be made with ``` @@ -26,6 +28,7 @@ make testeval && make test ## Use The simplest way is to create Json rule and data options and call jsonlogic::apply. + ```cpp #include diff --git a/cpp/bench/CMakeLists.txt b/cpp/bench/CMakeLists.txt index 243bdd7..4c974b6 100644 --- a/cpp/bench/CMakeLists.txt +++ b/cpp/bench/CMakeLists.txt @@ -4,7 +4,6 @@ # set(CMAKE_BUILD_TYPE Debug) include(FetchContent) - set(BUILD_TESTING OFF) # Disable Faker tests FetchContent_Declare(faker GIT_REPOSITORY https://github.com/cieslarmichal/faker-cxx.git @@ -12,7 +11,6 @@ FetchContent_Declare(faker ) FetchContent_MakeAvailable(faker) - FetchContent_Declare( cxxopts GIT_REPOSITORY https://github.com/jarro2783/cxxopts.git @@ -69,7 +67,6 @@ target_include_directories(jl-bench-membership SYSTEM PRIVATE target_link_libraries(jl-bench-membership PRIVATE jsonlogic faker-cxx) - add_executable(jl-bench-generic src/benchmark-generic.cpp) target_include_directories(jl-bench-generic SYSTEM PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../bench/include diff --git a/cpp/bench/src/benchmark-complex1.cpp b/cpp/bench/src/benchmark-complex1.cpp new file mode 100644 index 0000000..a03ab6c --- /dev/null +++ b/cpp/bench/src/benchmark-complex1.cpp @@ -0,0 +1,151 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +std::string read_file(const std::string &filename) { + std::ifstream file(filename); + if (!file) + throw std::runtime_error("Failed to open file"); + + return {std::string((std::istreambuf_iterator(file)), + std::istreambuf_iterator())}; +} + +const unsigned long SEED_ = 42; +static const size_t N_ = 1'000'000; +static const int N_RUNS_ = 3; +int main(int argc, const char **argv) try { + + // x: double + // y: int + // z: string + // (x / y > 5) or (x < 3.0 and y > 5 and z == "foo") or (y == 4 and x > 10.0 + // and "bar" in z) + std::string expr; + try { + expr = read_file("complex1.json"); + std::cout << "Successfully read complex1.json from current directory" + << std::endl; + } catch (const std::exception &) { + try { + expr = read_file("bench/src/complex1.json"); + std::cout << "Successfully read complex1.json from bench/src/" + << std::endl; + } catch (const std::exception &e) { + std::cerr << "Error: Could not find complex1.json: " << e.what() + << std::endl; + // Print current working directory + std::cerr << "Current directory: " << std::filesystem::current_path() + << std::endl; + + throw; + } + } + + std::span args(argv, argc); + + size_t N = N_; + if (argc > 1) { + N = std::stoul(args[1]); + } + size_t N_RUNS = N_RUNS_; + if (argc > 2) { + N_RUNS = std::stoul(args[2]); + } + + size_t SEED = SEED_; + if (argc > 3) { + SEED = std::stoul(args[3]); + } + + faker::getGenerator().seed(SEED); + std::vector xs; + xs.reserve(N); + std::vector ys; + ys.reserve(N); + + std::vector zs; + zs.reserve(N); + + std::vector strset{"foo", "bar", "baz", "quux", "foobar"}; + + // Create data + for (size_t i = 0; i < N; ++i) { + xs.push_back(faker::number::decimal(0, 50)); + ys.push_back(faker::number::integer(0, 255)); + auto ind = faker::number::integer(0, strset.size() - 1); + zs.push_back(strset[ind]); + } + + // JL 1 + + // Create jsonlogic benchmark + auto jv_xy = boost::json::parse(expr); + boost::json::object data_obj; + jsonlogic::logic_rule rule = jsonlogic::create_logic(jv_xy); + + size_t matches = 0; + auto jl_lambda = [&] { + matches = 0; + for (size_t i = 0; i < N; ++i) { + data_obj["x"] = xs[i]; + data_obj["y"] = ys[i]; + data_obj["z"] = zs[i]; + auto varaccess = jsonlogic::json_accessor(boost::json::value_from(data_obj)); + auto v_xy = rule.apply(varaccess); + + bool val = jsonlogic::truthy(v_xy); + + if (val) { + ++matches; + } + } + }; + + auto jl_bench = Benchmark("2ints-jl1", jl_lambda); + + // JL 2 + + auto jl2_lambda = [&] { + matches = 0; + jsonlogic::logic_rule rule = jsonlogic::create_logic(jv_xy); + for (size_t i = 0; i < N; ++i) { + auto v_xy = rule.apply({xs[i], ys[i], zs[i]}); + bool val = jsonlogic::truthy(v_xy); + + if (val) { + ++matches; + } + } + }; + + auto jl2_bench = Benchmark("2ints-jl2", jl2_lambda); + + auto jl_results = jl_bench.run(N_RUNS); + std::cout << "- jl1 matches: " << matches << std::endl; + auto jl2_results = jl2_bench.run(N_RUNS); + std::cout << "jl2 matches: " << matches << std::endl; + + jl_results.summarize(); + jl2_results.summarize(); + //~ jl_results.compare_to(cpp_results); + //~ cpp_results.compare_to(jl_results); + + jl2_results.compare_to(jl_results); + return 0; +} catch (const std::exception &e) { + std::cerr << "Fatal error: " << e.what() << '\n'; + return 1; +} catch (...) { + std::cerr << "Fatal unkown error\n"; + return 2; +} diff --git a/cpp/bench/src/benchmark-equality.cpp b/cpp/bench/src/benchmark-equality.cpp index 01ee640..de31981 100644 --- a/cpp/bench/src/benchmark-equality.cpp +++ b/cpp/bench/src/benchmark-equality.cpp @@ -162,11 +162,13 @@ int main(int argc, const char **argv) try { size_t matches = 0; auto jl_lambda = [&] { matches = 0; + auto rule = jsonlogic::create_logic(jv_xy); + for (size_t i = 0; i < N; ++i) { data_obj["x"] = xs[i]; data_obj["y"] = ys[i]; - auto data = boost::json::value_from(data_obj); - auto v_xy = jsonlogic::apply(jv_xy, data); + auto accessor = jsonlogic::json_accessor(boost::json::value_from(data_obj)); + auto v_xy = rule.apply(accessor); bool val = jsonlogic::truthy(v_xy); @@ -182,9 +184,9 @@ int main(int argc, const char **argv) try { auto jl2_lambda = [&] { matches = 0; - auto [rule, ignore1, igmore2] = jsonlogic::create_logic(jv_xy); + auto rule = jsonlogic::create_logic(jv_xy); for (size_t i = 0; i < N; ++i) { - auto v_xy = jsonlogic::apply(rule, {xs[i], ys[i]}); + auto v_xy = rule.apply({xs[i], ys[i]}); bool val = jsonlogic::truthy(v_xy); if (val) { diff --git a/cpp/bench/src/benchmark-generic.cpp b/cpp/bench/src/benchmark-generic.cpp index 8d9d66e..f8fd7b7 100644 --- a/cpp/bench/src/benchmark-generic.cpp +++ b/cpp/bench/src/benchmark-generic.cpp @@ -102,9 +102,11 @@ int main(int argc, const char **argv) try { data[i].push_back(fake_value(var_types[i])); } } + + size_t matches = 0; +#if UNSUPPORTED // JL1: Use boost::json::object for each row - size_t matches = 0; auto jl1_lambda = [&] { matches = 0; boost::json::object data_obj; @@ -127,11 +129,12 @@ int main(int argc, const char **argv) try { } }; auto jl1_bench = Benchmark("generic-jl1", jl1_lambda); +#endif /*UNSUPPORTED*/ // JL2: Use create_logic and pass values as tuple auto jl2_lambda = [&] { matches = 0; - auto [logic_rule, ignore1, ignore2] = jsonlogic::create_logic(rule); + auto jl2 = jsonlogic::create_logic(rule); for (size_t i = 0; i < N; ++i) { std::vector args; for (size_t v = 0; v < var_names.size(); ++v) { @@ -141,11 +144,11 @@ int main(int argc, const char **argv) try { else if (std::holds_alternative(val)) args.push_back(std::get(val)); else if (std::holds_alternative(val)) - args.push_back(std::get(val)); + args.push_back(jsonlogic::managed_string_view(std::get(val))); else if (std::holds_alternative(val)) args.push_back(std::get(val)); } - auto result = jsonlogic::apply(logic_rule, args); + auto result = jl2.apply(args); bool val = jsonlogic::truthy(result); if (val) ++matches; @@ -153,14 +156,21 @@ int main(int argc, const char **argv) try { }; auto jl2_bench = Benchmark("generic-jl2", jl2_lambda); +#if UNSUPPORTED // Run benchmarks auto jl1_results = jl1_bench.run(N_RUNS); std::cout << "JL1 matches: " << matches << std::endl; +#endif /*UNSUPPORTED*/ + auto jl2_results = jl2_bench.run(N_RUNS); std::cout << "JL2 matches: " << matches << std::endl; +#if UNSUPPORTED jl1_results.summarize(); +#endif /*UNSUPPORTED*/ jl2_results.summarize(); +#if UNSUPPORTED jl2_results.compare_to(jl1_results); +#endif /*UNSUPPORTED*/ return 0; } catch (const std::exception &e) { std::cerr << "Fatal error: " << e.what() << '\n'; diff --git a/cpp/bench/src/benchmark-membership.cpp b/cpp/bench/src/benchmark-membership.cpp index 69f4a4a..62a3b2e 100644 --- a/cpp/bench/src/benchmark-membership.cpp +++ b/cpp/bench/src/benchmark-membership.cpp @@ -85,16 +85,15 @@ int main(int argc, const char **argv) try { std::cout << "initialized data_obj\n"; // data_obj["haystack"] = haystack; size_t matches = 0; - jsonlogic::any_expr rule; - std::tie(rule, std::ignore, std::ignore) = jsonlogic::create_logic(jv_in); + jsonlogic::logic_rule rule = jsonlogic::create_logic(jv_in); auto jl_lambda = [&] { matches = 0; for (size_t i = 0; i < N; ++i) { data_obj["x"] = xs[i]; auto varaccess = - jsonlogic::data_accessor(boost::json::value_from(data_obj)); - auto v_in = jsonlogic::apply(rule, varaccess); + jsonlogic::json_accessor(boost::json::value_from(data_obj)); + auto v_in = rule.apply(varaccess); bool val = jsonlogic::truthy(v_in); diff --git a/cpp/bench/src/benchmark-simple-and.cpp b/cpp/bench/src/benchmark-simple-and.cpp new file mode 100644 index 0000000..e0706cb --- /dev/null +++ b/cpp/bench/src/benchmark-simple-and.cpp @@ -0,0 +1,141 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +std::string read_file(const std::string &filename) { + std::ifstream file(filename); + if (!file) + throw std::runtime_error("Failed to open file"); + + return {std::string((std::istreambuf_iterator(file)), + std::istreambuf_iterator())}; +} + +const unsigned long SEED_ = 42; +static const size_t N_ = 1'000'000; +static const int N_RUNS_ = 3; + +int main(int argc, const char **argv) try { + + // Test expression: x > 5 and y < 3 + std::string expr; + try { + expr = read_file("simple-and.json"); + std::cout << "Successfully read simple-and.json from current directory" + << std::endl; + } catch (const std::exception &) { + try { + expr = read_file("bench/src/simple-and.json"); + std::cout << "Successfully read simple-and.json from bench/src/" + << std::endl; + } catch (const std::exception &e) { + std::cerr << "Error: Could not find simple-and.json: " << e.what() + << std::endl; + // Print current working directory + std::cerr << "Current directory: " << std::filesystem::current_path() + << std::endl; + throw; + } + } + + std::span args(argv, argc); + + size_t N = N_; + if (argc > 1) { + N = std::stoul(args[1]); + } + size_t N_RUNS = N_RUNS_; + if (argc > 2) { + N_RUNS = std::stoul(args[2]); + } + + size_t SEED = SEED_; + if (argc > 3) { + SEED = std::stoul(args[3]); + } + + faker::getGenerator().seed(SEED); + std::vector xs; + xs.reserve(N); + std::vector ys; + ys.reserve(N); + + // Create test data + for (size_t i = 0; i < N; ++i) { + xs.push_back(faker::number::decimal( + 0, 10)); // 0-10 range to get some matches + ys.push_back( + faker::number::integer(0, 6)); // 0-6 range to get some matches + } + + // JL1 - Using boost::json::object approach + + auto jv_expr = boost::json::parse(expr); + boost::json::object data_obj; + jsonlogic::logic_rule rule = jsonlogic::create_logic(jv_expr); + + size_t matches = 0; + auto jl1_lambda = [&] { + matches = 0; + for (size_t i = 0; i < N; ++i) { + data_obj["x"] = xs[i]; + data_obj["y"] = ys[i]; + auto varaccess = jsonlogic::json_accessor(boost::json::value_from(data_obj)); + auto result = rule.apply(varaccess); + + bool val = jsonlogic::truthy(result); + + if (val) { + ++matches; + } + } + }; + + auto jl1_bench = Benchmark("simple-and-jl1", jl1_lambda); + + // JL2 - Using create_logic approach + + auto jl2_lambda = [&] { + matches = 0; + jsonlogic::logic_rule rule = jsonlogic::create_logic(jv_expr); + for (size_t i = 0; i < N; ++i) { + auto result = rule.apply({xs[i], ys[i]}); + bool val = jsonlogic::truthy(result); + + if (val) { + ++matches; + } + } + }; + + auto jl2_bench = Benchmark("simple-and-jl2", jl2_lambda); + + // Run benchmarks + auto jl1_results = jl1_bench.run(N_RUNS); + std::cout << "JL1 matches: " << matches << std::endl; + + auto jl2_results = jl2_bench.run(N_RUNS); + std::cout << "JL2 matches: " << matches << std::endl; + + // Display results + jl1_results.summarize(); + jl2_results.summarize(); + jl2_results.compare_to(jl1_results); + + return 0; +} catch (const std::exception &e) { + std::cerr << "Fatal error: " << e.what() << '\n'; + return 1; +} catch (...) { + std::cerr << "Fatal unknown error\n"; + return 2; +} diff --git a/cpp/bench/src/complex1.json b/cpp/bench/src/complex1.json index eab75ac..e7608c8 100644 --- a/cpp/bench/src/complex1.json +++ b/cpp/bench/src/complex1.json @@ -1,3 +1,4 @@ + { "rule": { "or": [ @@ -20,4 +21,4 @@ "y": "i", "z": "s" } -} \ No newline at end of file +} diff --git a/cpp/bench/src/simple-and.json b/cpp/bench/src/simple-and.json index d19579d..62e0e5a 100644 --- a/cpp/bench/src/simple-and.json +++ b/cpp/bench/src/simple-and.json @@ -1,3 +1,4 @@ + { "rule": { "and": [ diff --git a/cpp/include/jsonlogic/details/ast-core.hpp b/cpp/include/jsonlogic/details/ast-core.hpp deleted file mode 100644 index 82a7c48..0000000 --- a/cpp/include/jsonlogic/details/ast-core.hpp +++ /dev/null @@ -1,28 +0,0 @@ - -#pragma once - -#include - -namespace jsonlogic { - -struct visitor; - -// the root class -struct expr { - expr() = default; - virtual ~expr() = default; - - virtual void accept(visitor &) const = 0; - -private: - expr(expr &&) = delete; - expr(const expr &) = delete; - expr &operator=(expr &&) = delete; - expr &operator=(const expr &) = delete; -}; - -using any_expr = std::unique_ptr; - -std::ostream &operator<<(std::ostream &os, any_expr &n); - -} // namespace jsonlogic diff --git a/cpp/include/jsonlogic/details/ast-full.hpp b/cpp/include/jsonlogic/details/ast-full.hpp index a98105f..6f0aa88 100644 --- a/cpp/include/jsonlogic/details/ast-full.hpp +++ b/cpp/include/jsonlogic/details/ast-full.hpp @@ -1,17 +1,34 @@ #pragma once -#include +#include #include +#include #include +#include +#include +#include -#include "ast-core.hpp" #include "cxx-compat.hpp" -#if !defined(WITH_JSONLOGIC_EXTENSIONS) -#define WITH_JSONLOGIC_EXTENSIONS 1 -#endif /* !defined(WITH_JSONLOGIC_EXTENSIONS) */ namespace jsonlogic { + +struct visitor; + +// the root class +struct expr { + virtual ~expr() = default; + expr() = default; + expr(expr &&) = default; + expr(const expr &) = default; + expr &operator=(expr &&) = default; + expr &operator=(const expr &) = default; + + virtual void accept(visitor &) const = 0; +}; + +using any_expr = std::unique_ptr; + struct oper : expr, private std::vector { using container_type = std::vector; @@ -30,6 +47,8 @@ struct oper : expr, private std::vector { using container_type::reverse_iterator; using container_type::size; + oper() = default; + // convenience function so that the constructor does not need to be // implemented in every derived class. void set_operands(container_type &&opers) { this->swap(opers); } @@ -41,6 +60,12 @@ struct oper : expr, private std::vector { expr &operand(int n) const; virtual int num_evaluated_operands() const; + + private: + oper(oper &&) = delete; + oper(const oper &) = delete; + oper &operator=(oper &&) = delete; + oper &operator=(const oper &) = delete; }; // defines operators that have an upper bound on how many @@ -53,22 +78,22 @@ struct oper_n : oper { }; struct value_base : expr { - virtual boost::json::value to_json() const = 0; + virtual value_variant to_variant() const = 0; }; template struct value_generic : value_base { using value_type = T; - explicit value_generic(T t) : val(std::move(t)) {} + explicit value_generic(value_type t) : val(std::move(t)) {} // T &value() { return val; } - const T &value() const { return val; } + const value_type &value() const { return val; } - boost::json::value to_json() const final; + value_variant to_variant() const final; - private: - T val; +private: + value_type val; }; // @@ -167,8 +192,10 @@ struct modulo : oper_n<2> { // array -// arrays serve a dual purpose -// they can be considered collections, but also an aggregate value. +// arrays are considered collections of uninterpreted expressions. +// interpreted (evaluated) expressions are stored in a value_array. +// the distinction helps to avoid unnecessary copying and interpretation +// steps. // The class is final and it supports move ctor/assignment, so the data // can move efficiently. @@ -177,15 +204,8 @@ struct array final : oper // array is modeled as operator void accept(visitor &) const final; array() = default; - - array(array &&other) : oper() { - set_operands(std::move(other).move_operands()); - } - - array &operator=(array &&other) { - set_operands(std::move(other).move_operands()); - return *this; - } + array(array &&other); + array &operator=(array &&other); }; struct map : oper_n<2> { @@ -223,10 +243,10 @@ struct var : oper { void accept(visitor &) const final; void num(int val) { idx = val; } - int num() const { return idx; } + std::int16_t num() const { return idx; } - private: - int idx = computed; +private: + std::int16_t idx = computed; }; /// missing is modeled as operator with arbitrary number of arguments @@ -234,6 +254,7 @@ struct var : oper { /// in Calculator::visit(missing&) : /// if the first argument is an array, only the array will be considered /// otherwise all operands are treated as array. +/// \{ struct missing : oper { void accept(visitor &) const final; }; @@ -241,8 +262,10 @@ struct missing : oper { struct missing_some : oper_n<2> { void accept(visitor &) const final; }; +/// \} -// string operations +/// string operations +/// \{ struct cat : oper { void accept(visitor &) const final; }; @@ -250,13 +273,30 @@ struct cat : oper { struct substr : oper_n<3> { void accept(visitor &) const final; }; +/// \} -// string and array operation implementing "in" +/// string and array operation implementing "in" struct membership : oper { void accept(visitor &) const final; }; -// values +#if ENABLE_OPTIMIZATIONS +/// optimized membership test for arrays with constant values +struct opt_membership_array : oper_n<1> { + void accept(visitor &) const final; + + void set_elems(std::unordered_set els); + std::unordered_set const& elems() const; + private: + std::unordered_set elements; +}; +#endif /*ENABLE_OPTIMIZATIONS*/ + + +/// value classes +/// all but object_data are closely aligned with types listed value_variant. +/// \todo consider using a single value_variant class... +/// \{ struct null_value : value_base { null_value() = default; null_value(std::nullptr_t) {} @@ -265,7 +305,7 @@ struct null_value : value_base { std::nullptr_t value() const { return nullptr; } - boost::json::value to_json() const final; + value_variant to_variant() const final; }; struct bool_value : value_generic { @@ -296,15 +336,45 @@ struct real_value : value_generic { void accept(visitor &) const final; }; -struct string_value : value_generic { - using base = value_generic; +struct string_value : value_generic { + using base = value_generic; using base::base; void accept(visitor &) const final; }; -struct object_value : expr, private std::map { - using base = std::map; +struct array_value : value_base +{ + using container_type = std::vector; + + ~array_value() = default; + array_value(array_value&&) = default; + array_value& operator=(array_value&&) = default; + array_value(const array_value&) = default; + array_value& operator=(const array_value&) = default; + + explicit + array_value(container_type elems) + : vec(std::make_shared(std::move(elems))) + {} + + value_variant to_variant() const final; + container_type const& value() const; + const array_value* copy() const; + void accept(visitor &) const final; + + private: + const std::shared_ptr vec; + + array_value() = delete; +}; + + +// object types do not seem to have strong support by jsonlogic +using object_value_data = std::map; + +struct object_value : expr, private object_value_data { + using base = object_value_data; using base::base; ~object_value() = default; @@ -322,12 +392,14 @@ struct object_value : expr, private std::map { void accept(visitor &) const final; }; -// logger +/// \} + +/// logger struct log : oper_n<1> { void accept(visitor &) const final; }; -// error node +/// error node struct error : expr { void accept(visitor &) const final; }; @@ -391,6 +463,8 @@ struct visitor { virtual void visit(const unsigned_int_value &) = 0; virtual void visit(const real_value &) = 0; virtual void visit(const string_value &) = 0; + virtual void visit(const array_value &) = 0; + //~ virtual void visit(const array_view &) = 0; virtual void visit(const object_value &) = 0; virtual void visit(const error &) = 0; @@ -399,6 +473,11 @@ struct visitor { // extensions virtual void visit(const regex_match &) = 0; #endif /* WITH_JSON_LOGIC_CPP_EXTENSIONS */ + +#if ENABLE_OPTIMIZATIONS + // extensions + virtual void visit(const opt_membership_array &) = 0; +#endif /* ENABLE_OPTIMIZATIONS */ }; /// \private @@ -474,6 +553,8 @@ struct generic_dispatcher : visitor { void visit(const unsigned_int_value &n) final { res = apply(n, &n); } void visit(const real_value &n) final { res = apply(n, &n); } void visit(const string_value &n) final { res = apply(n, &n); } + void visit(const array_value &n) final { res = apply(n, &n); } + //~ void visit(const array_view &n) final { res = apply(n, &n); } void visit(const object_value &n) final { res = apply(n, &n); } void visit(const error &n) final { res = apply(n, &n); } @@ -483,6 +564,10 @@ struct generic_dispatcher : visitor { void visit(const regex_match &n) final { res = apply(n, &n); } #endif /* WITH_JSON_LOGIC_CPP_EXTENSIONS */ +#if ENABLE_OPTIMIZATIONS + void visit(const opt_membership_array &n) final { res = apply(n, &n); } +#endif /*ENABLE_OPTIMIZATIONS*/ + result_type result() && { return std::move(res); } private: @@ -512,4 +597,25 @@ auto generic_visit(ast_functor fn, ast_node *n, arguments... args) n->accept(disp); return std::move(disp).result(); } + +using logic_data_base = std::tuple, bool>; +struct logic_data : logic_data_base +{ + using base = logic_data_base; + using base::base; + + /// returns the logic expression + any_expr const &syntax_tree() const { return std::get<0>(*this); } + + /// returns static variable names (i.e., variable names that are not computed) + std::vector const &variable_names() const { return std::get<1>(*this); } + + /// returns if the expression contains computed names. + bool has_computed_variable_names() const { return std::get<2>(*this); } +}; + + + + + } // namespace jsonlogic diff --git a/cpp/include/jsonlogic/logic.hpp b/cpp/include/jsonlogic/logic.hpp index 4337fdc..560d3d9 100644 --- a/cpp/include/jsonlogic/logic.hpp +++ b/cpp/include/jsonlogic/logic.hpp @@ -1,16 +1,27 @@ - #pragma once +#include #include -#include "details/ast-core.hpp" +#include "managed_string_view.hpp" + +#if !defined(WITH_JSONLOGIC_EXTENSIONS) +// turn on to enable regex matching +#define WITH_JSONLOGIC_EXTENSIONS 1 +#endif /* !defined(WITH_JSONLOGIC_EXTENSIONS) */ + +#if !defined(ENABLE_OPTIMIZATIONS) +// enables experimental optimizations +#define ENABLE_OPTIMIZATIONS 1 +#endif /* !defined(ENABLE_OPTIMIZATIONS) */ + namespace jsonlogic { // // exception classes -/// thrown when no type conversion rules are able to satisy an operation's type requirements +/// thrown when no type conversion rules are able to satisfy an operation's type requirements struct type_error : std::runtime_error { using base = std::runtime_error; using base::base; @@ -24,29 +35,42 @@ struct variable_resolution_error : std::runtime_error { // -// API to create an expression +// API to evaluate/apply an expression -/// result type of create_logic -// \todo remove dependence on boost::json::string and use std::string_view instead. -using logic_rule_base = std::tuple, bool>; +struct array_value; -/// interprets the json object \ref n as a jsonlogic expression and -/// returns a jsonlogic representation together with some information -/// on variables inside the jsonlogic expression. -logic_rule_base create_logic(boost::json::value n); +/// a type representing views on value types in jsonlogic +/// \details +/// (1) the variant contains options for all primitive types + strings and arrays. +/// (2) some rules treat the absence of a value differently from a null value +/// \todo document lifetime requirements +using value_variant_base = std::variant< std::monostate, // not available + std::nullptr_t, // value is null + bool, + std::int64_t, + std::uint64_t, + double, + managed_string_view, + array_value const* + >; + +struct value_variant : value_variant_base { + using base = value_variant_base; + using base::base; -// -// API to evaluate/apply an expression + value_variant() + : base(std::monostate{}) + {} -/// a type representing views on value types in jsonlogic -// \todo consider adding std::unique_ptr as fallback type. -using value_variant = std::variant< std::monostate, // or std::nullptr_t ? - bool, - std::int64_t, - std::uint64_t, - double, - std::string_view - >; + value_variant(const value_variant&); + value_variant(value_variant&&); + value_variant& operator=(const value_variant&); + value_variant& operator=(value_variant&&); + + ~value_variant(); +}; + +bool operator==(const value_variant& lhs, const value_variant& rhs); @@ -67,7 +91,14 @@ using value_variant = std::variant< std::monostate, // or std::nullptr_t ? /// a string_view or std::variant .. /// * consider removing the limitation on any_expr being a value.. using variable_accessor = - std::function; + std::function; + + +#if DEPRECATED_FIX_STRINGS + +// since value_variant contains string_views whose underlying strings may +// be dynamically created during rule evaluation, the free standing API +// can no longer be supported. /// evaluates \ref exp and uses \ref vars to query variables from /// the context. @@ -82,10 +113,10 @@ using variable_accessor = /// throws a variable_resolution_error when a computed variable name /// is requested. /// \{ -any_expr apply(const expr &exp, const variable_accessor &var_accessor); -any_expr apply(const any_expr &exp, const variable_accessor &var_accessor); -any_expr apply(const any_expr &exp); -any_expr apply(const any_expr &exp, std::vector vars); +value_variant apply(const expr &exp, const variable_accessor &var_accessor); +value_variant apply(const any_expr &exp, const variable_accessor &var_accessor); +value_variant apply(const any_expr &exp); +value_variant apply(const any_expr &exp, std::vector vars); /// \} /// evaluates the rule \ref rule with the provided data \ref data. @@ -97,75 +128,54 @@ any_expr apply(const any_expr &exp, std::vector vars); /// converts rule to a jsonlogic expression and creates a variable_accessor /// to query variables from data, before calling apply() on jsonlogic /// expression. -any_expr apply(boost::json::value rule, boost::json::value data); +value_variant apply(boost::json::value rule, boost::json::value data); -/// creates a variable accessor to access data in \ref data. -variable_accessor data_accessor(boost::json::value data); +#endif /* DEPRECATED_FIX_STRINGS */ -// -// conversion functions - -/// creates a jsonlogic value representation for \ref val -/// \post any_expr != nullptr +/// creates a variable accessor to access data in \ref data. /// \{ -any_expr to_expr(std::nullptr_t val); -any_expr to_expr(bool val); -any_expr to_expr(std::int64_t val); -any_expr to_expr(std::uint64_t val); -any_expr to_expr(double val); -any_expr to_expr(boost::json::string val); -any_expr to_expr(const boost::json::array &val); +variable_accessor json_accessor(boost::json::value data); +variable_accessor variant_accessor(std::vector data); /// \} -/// creates a value representation for \p n in jsonlogic form. -/// \param n any boost::json type, except for boost::json::object -/// \return a value in jsonlogic form -/// \throw an std::logic_error if \p n contains a boost::json::object -/// \post any_expr != nullptr -any_expr to_expr(const boost::json::value &n); +// +// conversion functions -/// creates a value representation for \p val in jsonlogic form. -any_expr to_expr(value_variant val); /// creates a json representation from \p e -boost::json::value to_json(const any_expr &e); +//~ boost::json::value to_json(const any_expr &e); /// returns true if \p el is truthy /// \details /// truthy performs the appropriate type conversions /// but does not evaluate \p el, in case it is a /// jsonlogic expression. -bool truthy(const any_expr &el); +bool truthy(const value_variant &el); /// returns true if \p el is !truthy -bool falsy(const any_expr &el); +bool falsy(const value_variant &el); /// prints out \p n to \p os /// \pre n must be a value -std::ostream &operator<<(std::ostream &os, any_expr &n); - +std::ostream &operator<<(std::ostream &os, const value_variant &n); -/// convenience class providing named accessors to logic_rule_base -struct logic_rule : logic_rule_base { - using base = logic_rule_base; +/// result type of create_logic +//~ using logic_rule_base = std::tuple, bool>; - // no explicit - logic_rule(logic_rule_base&& logic_object) - : base(std::move(logic_object)) - {} +struct logic_data; - logic_rule(logic_rule&&) = default; - logic_rule& operator=(logic_rule&&) = default; - ~logic_rule() = default; +/// convenience class providing named accessors to logic_rule_base +struct logic_rule { + explicit logic_rule(std::unique_ptr&& rule_data); + logic_rule(logic_rule&&); + logic_rule& operator=(logic_rule&&); + ~logic_rule(); - /// the logic expression - /// \{ - any_expr const &syntax_tree() const; - // any_expr expr() && { return std::get<0>(std::move(*this)); } - /// \} + /// returns the logic expression + //~ any_expr const &syntax_tree() const; - /// returns variable names that are not computed. - std::vector const &variable_names() const; + /// returns static variable names (i.e., variable names that are not computed) + std::vector const &variable_names() const; /// returns if the expression contains computed names. bool has_computed_variable_names() const; @@ -173,24 +183,56 @@ struct logic_rule : logic_rule_base { /// evaluates the logic_rule. /// \return a jsonlogic value /// \throws variable_resolution_error when evaluation accesses a variable - any_expr apply() const; + value_variant apply() ; /// evaluates the logic_rule and uses \p var_accessor to query variables. /// \param var_accessor a variable accessor to retrieve variables from the context /// \return a jsonlogic value - any_expr apply(const variable_accessor &var_accessor) const; + value_variant apply(const variable_accessor &var_accessor) ; /// evaluates the logic_rule and uses \p vars to obtain values for non-computed variable names. /// \param vars a variable array with values for non-computed variable names. /// \return a jsonlogic value /// \throws variable_resolution_error when evaluation accesses a computed variable name - any_expr apply(std::vector vars) const; + value_variant apply(std::vector vars) ; + + /// returns the data held internally for internal use. + logic_data& internal_data(); private: logic_rule() = delete; logic_rule(const logic_rule&) = delete; logic_rule& operator=(const logic_rule&) = delete; + + std::unique_ptr data; }; +// +// API to create an expression + + +/// interprets the json object \ref n as a jsonlogic expression and +/// returns a jsonlogic representation together with some information +/// on variables inside the jsonlogic expression. +logic_rule create_logic(const boost::json::value& n); } // namespace jsonlogic + + +namespace std +{ + template <> + struct hash { + size_t operator()(const jsonlogic::value_variant_base& v) const; + }; + + template <> + struct hash { + size_t operator()(const jsonlogic::value_variant& v) const; + }; + + template <> + struct hash { + size_t operator()(const jsonlogic::array_value& v) const; + }; +}; diff --git a/cpp/include/jsonlogic/managed_string_view.hpp b/cpp/include/jsonlogic/managed_string_view.hpp new file mode 100644 index 0000000..6c7103a --- /dev/null +++ b/cpp/include/jsonlogic/managed_string_view.hpp @@ -0,0 +1,67 @@ +/// A Shared String class + +#pragma once + +#include +#include +#include + +namespace jsonlogic +{ + using shared_string_ptr = std::shared_ptr; + + struct managed_string_view : private shared_string_ptr, public std::string_view + { + static + std::string_view to_string_view(shared_string_ptr p) { return *p; } + + using holder = shared_string_ptr; + using base = std::string_view; + using size_type = std::string_view::size_type; + + ~managed_string_view() = default; + managed_string_view(const managed_string_view&) = default; + managed_string_view(managed_string_view&&) = default; + managed_string_view& operator=(const managed_string_view&) = default; + managed_string_view& operator=(managed_string_view&&) = default; + + explicit + managed_string_view(std::string_view view) + : holder(std::make_shared(view.begin(), view.end())), base(to_string_view(*this)) + {} + + // explicit + managed_string_view(std::string&& s) + : holder(std::make_shared(std::move(s))), base(to_string_view(*this)) + {} + + template + managed_string_view(ForwardIterator beg, std::size_t len) + : holder(std::make_shared(beg, len)), base(to_string_view(*this)) + {} + + + private: + + managed_string_view(holder string_ptr, std::string_view view) + : holder(std::move(string_ptr)), base(view) + {} + + public: + + managed_string_view substr(size_type ofs = 0, size_type cnt = base::npos) const + { + return { holder(*this), base::substr(ofs, cnt) }; + } + + std::string_view view() const { return *this; } + }; + + // \todo replace with space ship operator + inline bool operator==(const managed_string_view& lhs, const managed_string_view& rhs) { return lhs.view() == rhs.view(); } + inline bool operator!=(const managed_string_view& lhs, const managed_string_view& rhs) { return lhs.view() != rhs.view(); } + inline bool operator<(const managed_string_view& lhs, const managed_string_view& rhs) { return lhs.view() < rhs.view(); } + inline bool operator<=(const managed_string_view& lhs, const managed_string_view& rhs) { return lhs.view() <= rhs.view(); } + inline bool operator>(const managed_string_view& lhs, const managed_string_view& rhs) { return lhs.view() > rhs.view(); } + inline bool operator>=(const managed_string_view& lhs, const managed_string_view& rhs) { return lhs.view() >= rhs.view(); } +} diff --git a/cpp/src/logic.cc b/cpp/src/logic.cc index 032735d..ab3258b 100644 --- a/cpp/src/logic.cc +++ b/cpp/src/logic.cc @@ -5,10 +5,14 @@ // standard headers #include #include +#include #include #include #include #include +#include +#include +#include #if WITH_JSON_LOGIC_CPP_EXTENSIONS #include @@ -22,23 +26,33 @@ #include "jsonlogic/details/cxx-compat.hpp" namespace { -[[maybe_unused]] constexpr bool DEBUG_OUTPUT = false; +CXX_MAYBE_UNUSED constexpr bool DEBUG_OUTPUT = false; constexpr int COMPUTED_VARIABLE_NAME = -1; -} // namespace + + +} // namespace + + namespace jsonlogic { -namespace json = boost::json; + namespace { CXX_NORETURN +inline void unsupported() { throw std::logic_error("functionality not yet implemented"); } CXX_NORETURN +inline void throw_type_error() { throw type_error("typing error"); } +CXX_NORETURN +inline +void implementation_error() { throw std::logic_error("implementation error"); } + template T &deref(T *p, const char *msg = "assertion failed") { if (p == nullptr) { @@ -71,23 +85,30 @@ const T &deref(const std::unique_ptr &p, } //~ template T &up_cast(T &n) { return n; } -template const T &up_cast(const T &n) { return n; } +template +const T &up_cast(const T &n) { + return n; +} -template struct down_caster_internal { +template +struct down_caster_internal { T *operator()(const expr &) const { return nullptr; } T *operator()(const T &o) const { return &o; } }; -template const T *may_down_cast(const expr &e) { +template +const T *may_down_cast(const expr &e) { return generic_visit(down_caster_internal{}, &e); } -template T *may_down_cast(expr &e) { - return dynamic_cast(&e); // \todo replace dynamic_cast +template +T *may_down_cast(expr &e) { + return dynamic_cast(&e); // \todo replace dynamic_cast } -template const T &down_cast(const expr &e) { +template +const T &down_cast(const expr &e) { if (T *casted = may_down_cast(e)) { CXX_LIKELY; return *casted; @@ -96,7 +117,8 @@ template const T &down_cast(const expr &e) { throw_type_error(); } -template T &down_cast(expr &e) { +template +T &down_cast(expr &e) { if (T *casted = may_down_cast(e)) { CXX_LIKELY; return *casted; @@ -106,10 +128,470 @@ template T &down_cast(expr &e) { } } // namespace +namespace json = boost::json; + +using any_value = value_variant; + +enum /* anonymous */ { + mono_variant = 0, + null_variant = 1, + bool_variant = 2, + sint_variant = 3, + uint_variant = 4, + real_variant = 5, + strv_variant = 6, + sequ_variant = 7, + //~ json_variant = 8 +}; + +static_assert(std::variant_size_v == 8); +static_assert(std::is_same_v >); +static_assert(std::is_same_v >); +static_assert(std::is_same_v >); +static_assert(std::is_same_v >); +static_assert(std::is_same_v >); +static_assert(std::is_same_v >); +static_assert(std::is_same_v >); +static_assert(std::is_same_v >); + +bool null_equivalent(value_variant val) +{ + bool res = false; + + switch (val.index()) + { + case mono_variant: + case null_variant: + res = true; + break; + + default: + CXX_LIKELY; + } + + return res; +} + + +template +bool +stricteq(const value_variant& lhs, const value_variant& rhs) +try +{ + return std::get(lhs) == std::get(rhs); +} +catch (...) +{ + return false; +} + +// using variant_span = std::span; + +using variant_span_base = std::tuple::const_iterator, std::vector::const_iterator>; + +struct variant_span : variant_span_base +{ + using base = variant_span_base; + using base::base; + + std::vector::const_iterator begin() const { return std::get<0>(*this); } + std::vector::const_iterator end() const { return std::get<1>(*this); } + std::size_t size() const { return std::distance(begin(), end()); } +}; + + +managed_string_view +element_range(const value_variant& val, const std::string_view&) +{ + return std::get(val); +} + +variant_span +element_range(const std::vector& vec) +{ + return { vec.begin(), vec.end() }; +} + +variant_span +element_range(const array_value* arr) +{ + return element_range(deref(arr).value()); +} + +variant_span +element_range(const value_variant& val, const array_value*) +{ + return element_range(std::get(val)); +} + +template +bool +stricteq_range(const value_variant& lhs, const value_variant& rhs) +{ + if (rhs.index() != lhs.index()) + return false; + + auto ll = element_range(lhs, RangeT{}); + auto rr = element_range(rhs, RangeT{}); + + if (ll.size() != rr.size()) + return false; + + auto const lhslim = ll.end(); + + return std::mismatch(ll.begin(), lhslim, rr.begin()).first == lhslim; +} + +bool inteq(std::int64_t x, std::uint64_t y) +{ + bool const y_within_int_range = (y <= std::uint64_t(std::numeric_limits::max())); + + return y_within_int_range & (x == std::int64_t(y)); +} + +template +bool +inteq(const value_variant& lhs, const value_variant& rhs) +{ + if (rhs.index() == lhs.index()) + { + CXX_LIKELY; + return std::get(lhs) == std::get(rhs); + } + + if (rhs.index() == uint_variant) + return inteq(std::get(lhs), std::get(rhs)); + + if (rhs.index() == sint_variant) + return inteq(std::get(rhs), std::get(lhs)); + + return false; +} + + +bool operator==(const value_variant& lhs, const value_variant& rhs) +{ + bool res = false; + + switch (lhs.index()) + { + case mono_variant: + case null_variant: + res = lhs.index() == rhs.index(); + break; + + case bool_variant: + res = stricteq(lhs, rhs); + break; + + case sint_variant: + res = inteq(lhs, rhs); + break; + + case uint_variant: + res = inteq(lhs, rhs); + break; + + case real_variant: + res = stricteq(lhs, rhs); + break; + + case strv_variant: + res = stricteq_range(lhs, rhs); + break; + + case sequ_variant: + res = stricteq_range(lhs, rhs); + break; + + default: + throw_type_error(); + } + + return res; +} + + +std::size_t leftRotate(std::size_t n, std::size_t d) { //rotate n by d bits + return (n << d)|(n >> (sizeof(n)*CHAR_BIT - d)); +} + +} + + +// overload std::hash for jsonlogic types +namespace std +{ + size_t + hash::operator()(const jsonlogic::array_value& v) const { + auto values = v.value() | std::views::transform(std::hash{}); + + // C++23: return std::ranges::fold_left(values, size_t(0), std::plus{}); + return std::accumulate(values.begin(), values.end(), size_t(0), std::plus{}); + } + + size_t + hash::operator()(const jsonlogic::value_variant_base& v) const { + // Use the variant index as part of the hash to distinguish between + // different types that might hash to the same value + size_t const index_hash = hash{}(v.index()); + size_t value_hash = 0; + + switch (v.index()) { + case jsonlogic::mono_variant: + case jsonlogic::null_variant: + // identified through their index type + break; + + case jsonlogic::bool_variant: + value_hash = std::hash{}(std::get(v)); + break; + + case jsonlogic::sint_variant: + value_hash = std::hash{}(std::get(v)); + break; + + case jsonlogic::uint_variant: + value_hash = std::hash{}(std::get(v)); + break; + + case jsonlogic::real_variant: + value_hash = std::hash{}(std::get(v)); + break; + + case jsonlogic::strv_variant: + value_hash = std::hash{}(std::get(v)); + break; + + case jsonlogic::sequ_variant: + value_hash = std::hash{}(*std::get(v)); + break; + + default: + jsonlogic::implementation_error(); + } + + return index_hash + jsonlogic::leftRotate(value_hash, 1); + } + + size_t + hash::operator()(const jsonlogic::value_variant& v) const { + return std::hash{}(v); + } +} // namespace std + + +namespace jsonlogic +{ +namespace +{ + /// functor testing if a jsonlogic expression can be converted to a variant representation. + struct convertible_to_value_variant + { + auto operator()(const expr &) const -> bool { return false; } + + template + auto operator()(const T &) const -> decltype(std::declval().to_variant(), bool{}) { + return true; + } + }; + + /// returns true iff e can be converted to a value_variant + /// (without deep analysis or constant folding). + bool not_convertible_to_value_variant(const any_expr& e) + { + return !generic_visit(convertible_to_value_variant{}, e.get()); + } + + /// functor converting jsonlogic expression a variant representation. + /// \details + /// the functor throws an std::logic_error if the conversion cannot be performed. + struct value_variant_conversion + { + CXX_NORETURN + auto operator()(const expr &) const -> value_variant + { + implementation_error(); + } + + template + auto operator()(const T &o) const -> decltype(std::declval().to_variant(), value_variant{}) const { + return o.to_variant(); + } + }; + + /// returns the value_variant in \p e, or throws an exception if the + /// expression does not have value_variant representation. + value_variant as_value_variant(const any_expr& e) + { + return generic_visit(value_variant_conversion{}, e.get()); + } + + /// converts an array in \p e to an unordered_set + /// for more efficient lookup. Returns an empty set if the + /// conversion is not possible, either due to \p e not + /// representing an array (i.e., a string), or by the + /// array members not being constant. + /// \param e a json_logic expression + /// \result a set with value_variant if all elements in e's array + /// are convertible to value_variant. + std::unordered_set + try_static_set(const any_expr& e) + { + const array* arr = may_down_cast(deref(e.get())); + if (arr == nullptr) + return {}; + + const oper::container_type& elems = arr->operands(); + auto const beg = elems.begin(); + auto const lim = elems.end(); + + if (auto pos = std::find_if(beg, lim, not_convertible_to_value_variant); pos != lim) + return {}; + + auto values = elems | std::views::transform(as_value_variant); + + return std::unordered_set(values.begin(), values.end()); + } + + array_value& mk_array_value(std::vector elems = {}); + + void delete_array(value_variant_base& val) + { + if (val.index() == sequ_variant) + { + CXX_UNLIKELY; + delete std::get(val); + } + } + + void copy_array(value_variant_base& val) + { + if (val.index() == sequ_variant) + { + CXX_UNLIKELY; + val = std::get(val)->copy(); + } + } + + void invalidate_array(value_variant_base& val) + { + if (val.index() == sequ_variant) + { + CXX_UNLIKELY; + val = std::monostate{}; + } + } +} + + +// implement variants destructor to possibly free memory if needed. +value_variant::~value_variant() +{ + delete_array(*this); +} + +value_variant::value_variant(const value_variant& other) +: base(other) +{ + assert(this != &other); + copy_array(*this); +} + +value_variant::value_variant(value_variant&& other) +: base(std::move(other)) +{ + assert(this != &other); + invalidate_array(other); +} + +value_variant& value_variant::operator=(const value_variant& other) +{ + if (&other != this) + { + delete_array(*this); + static_cast(*this) = other; + copy_array(*this); + } + + return *this; +} + +value_variant& value_variant::operator=(value_variant&& other) +{ + if (&other != this) + { + delete_array(*this); + static_cast(*this) = std::move(other); + invalidate_array(other); + } + + return *this; +} + + + + // // foundation classes // \{ +array::array(array &&other) : oper() { + set_operands(std::move(other).move_operands()); +} + +array& +array::operator=(array &&other) { + set_operands(std::move(other).move_operands()); + return *this; +} + + +array_value::container_type const& +array_value::value() const +{ + assert(vec.get()); + + return *vec; +} + +const array_value* +array_value::copy() const +{ + return new array_value(*this); +} + +value_variant +array_value::to_variant() const +{ + return this; +} + +#if ENABLE_OPTIMIZATIONS +void +opt_membership_array::set_elems(std::unordered_set els) +{ + elements = std::move(els); +} + +std::unordered_set const& +opt_membership_array::elems() const +{ + return elements; +} +#endif /*ENABLE_OPTIMIZATIONS*/ + + + + // accept implementations void equal::accept(visitor &v) const { v.visit(*this); } void strict_equal::accept(visitor &v) const { v.visit(*this); } @@ -154,6 +636,8 @@ void unsigned_int_value::accept(visitor &v) const { v.visit(*this); } void real_value::accept(visitor &v) const { v.visit(*this); } void string_value::accept(visitor &v) const { v.visit(*this); } void object_value::accept(visitor &v) const { v.visit(*this); } +void array_value::accept(visitor &v) const { v.visit(*this); } +//~ void array_view::accept(visitor &v) const { v.visit(*this); } void error::accept(visitor &v) const { v.visit(*this); } @@ -161,12 +645,18 @@ void error::accept(visitor &v) const { v.visit(*this); } void regex_match::accept(visitor &v) const { v.visit(*this); } #endif /* WITH_JSON_LOGIC_CPP_EXTENSIONS */ -// to_json implementations -template json::value value_generic::to_json() const { +#if ENABLE_OPTIMIZATIONS +void opt_membership_array::accept(visitor &v) const { v.visit(*this); } +#endif /*ENABLE_OPTIMIZATIONS*/ + + +// to_variant conversion +template +value_variant value_generic::to_variant() const { return value(); } -json::value null_value::to_json() const { return value(); } +value_variant null_value::to_variant() const { return value(); } // num_evaluated_operands implementations int oper::num_evaluated_operands() const { return static_cast(size()); } @@ -213,6 +703,7 @@ struct forwarding_visitor : visitor { void visit(const missing &n) override { visit(up_cast(n)); } void visit(const missing_some &n) override { visit(up_cast(n)); } void visit(const log &n) override { visit(up_cast(n)); } + void visit(const array &n) override { visit(up_cast(n)); } void visit(const if_expr &n) override { visit(up_cast(n)); } @@ -225,59 +716,67 @@ struct forwarding_visitor : visitor { } void visit(const real_value &n) override { visit(up_cast(n)); } void visit(const string_value &n) override { visit(up_cast(n)); } + void visit(const array_value &n) override { visit(up_cast(n)); } - void visit(const array &n) override { visit(up_cast(n)); } void visit(const object_value &n) override { visit(up_cast(n)); } void visit(const error &n) override { visit(up_cast(n)); } #if WITH_JSON_LOGIC_CPP_EXTENSIONS // extensions - void visit(const regex_match &n) override { visit(up_cast(n)); } + void visit(const regex_match &n) override { visit(up_cast(n)); } #endif /* WITH_JSON_LOGIC_CPP_EXTENSIONS */ + +#if ENABLE_OPTIMIZATIONS + // optimizations + void visit(const opt_membership_array &n) override { visit(up_cast(n)); } +#endif /* ENABLE_OPTIMIZATIONS */ }; namespace { struct variable_map { void insert(var &el); - std::vector to_vector() const; + std::vector to_vector() const; /// accessors for withComputedNames /// \{ bool hasComputedVariables() const { return withComputedNames; } - void setComputedVariables(bool b) { withComputedNames = b; } + void set_computed_variables(bool b) { withComputedNames = b; } /// \} -private: - using container_type = std::map; + private: + using container_type = std::map; - container_type mapping; + container_type mapping = {}; bool withComputedNames = false; }; -void variable_map::insert(var &el) { +void variable_map::insert(var &var) { try { - any_expr &arg = el.back(); - auto &str = down_cast(*arg); + any_expr &arg = var.back(); + string_value &str = down_cast(*arg); const bool comp = (str.value().find('.') != json::string::npos && str.value().find('[') != json::string::npos); if (comp) { - setComputedVariables(true); + set_computed_variables(true); } else if (str.value() != "") { - // do nothing for free variables in "lambdas" auto [pos, success] = mapping.emplace(str.value(), mapping.size()); - el.num(pos->second); + var.num(pos->second); + } + else + { + // do nothing for free variables in "lambdas" } } catch (const type_error &) { - setComputedVariables(true); + set_computed_variables(true); } } -std::vector variable_map::to_vector() const { - std::vector res; +std::vector variable_map::to_vector() const { + std::vector res; res.resize(mapping.size()); @@ -289,64 +788,107 @@ std::vector variable_map::to_vector() const { /// translates all children /// \{ -oper::container_type translate_children(json::array &children, variable_map &); +oper::container_type translate_children(const json::array &children, variable_map &); -oper::container_type translate_children(json::value &n, variable_map &); +oper::container_type translate_children(const json::value &n, variable_map &); /// \} -template ExprT &mkOperator_(json::object &n, variable_map &m) { - assert(n.size() == 1); +array& mk_array() { return deref(new array); } + +array_value& mk_array_value(std::vector elems) +{ + return deref(new array_value{std::move(elems)}); +} + +//~ array_view& mk_array_view(const array_value& arr) +//~ { + //~ return deref(new array_view{arr}); +//~ } +template +ExprT &mk_operator_(oper::container_type args) { ExprT &res = deref(new ExprT); - res.set_operands(translate_children(n.begin()->value(), m)); + res.set_operands(std::move(args)); return res; } -template expr &mk_operator(json::object &n, variable_map &m) { - return mkOperator_(n, m); +template +ExprT &mk_operator_(const json::object &n, variable_map &m) { + assert(n.size() == 1); + return mk_operator_(translate_children(n.begin()->value(), m)); +} + +template +expr &mk_operator(const json::object &n, variable_map &m) { + return mk_operator_(n, m); +} + +/// creates and optimized membership tests for static arrays +/// and returns a "normal" membership test if the optimization +/// is not possible or disabled. +expr &mk_membership_opt(const json::object &n, variable_map &m) +{ + oper::container_type args = translate_children(n.begin()->value(), m); + +#if ENABLE_OPTIMIZATIONS + if (std::unordered_set elems = try_static_set(args.back()); elems.size() != 0) + { + args.pop_back(); + opt_membership_array &res = mk_operator_(std::move(args)); + + res.set_elems(std::move(elems)); + return res; + } +#endif /*ENABLE_OPTIMIZATIONS*/ + + return mk_operator_(std::move(args)); } -template expr &mk_missing(json::object &n, variable_map &m) { - // \todo * extract variables from array and only setComputedVariables when +template +expr &mk_missing(const json::object &n, variable_map &m) { + // \todo * extract variables from array and only set_computed_variables when // needed. // * check reference implementation when missing is null - m.setComputedVariables(true); - return mkOperator_(n, m); + m.set_computed_variables(true); + return mk_operator_(n, m); } -expr &mk_variable(json::object &n, variable_map &m) { - var &v = mkOperator_(n, m); +expr &mk_variable(const json::object &n, variable_map &m) { + var &v = mk_operator_(n, m); m.insert(v); return v; } -array &mk_array(json::array &children, variable_map &m) { - array &res = deref(new array); +array &mk_array(const json::array &children, variable_map &m) { + array &res = mk_array(); res.set_operands(translate_children(children, m)); return res; } -template value_t &mk_value(typename value_t::value_type n) { +template +value_t &mk_value(typename value_t::value_type n) { return deref(new value_t(std::move(n))); } null_value &mk_null_value() { return deref(new null_value); } using dispatch_table = - std::map; + std::map; dispatch_table::const_iterator lookup(const dispatch_table &m, const json::object &op) { - if (op.size() != 1) - return m.end(); + if (op.size() != 1) return m.end(); - return m.find(op.begin()->key()); + const json::string& key = op.begin()->key(); + std::string_view keyvw(&*key.begin(), key.size()); + + return m.find(keyvw); } -any_expr translate_internal(json::value n, variable_map &varmap) { +any_expr translate_internal(const json::value& n, variable_map &varmap) { static const dispatch_table dt = { {"==", &mk_operator}, {"===", &mk_operator}, @@ -375,7 +917,7 @@ any_expr translate_internal(json::value n, variable_map &varmap) { {"none", &mk_operator}, {"some", &mk_operator}, {"merge", &mk_operator}, - {"in", &mk_operator}, + {"in", &mk_membership_opt}, {"cat", &mk_operator}, {"log", &mk_operator}, {"var", &mk_variable}, @@ -390,78 +932,79 @@ any_expr translate_internal(json::value n, variable_map &varmap) { expr *res = nullptr; switch (n.kind()) { - case json::kind::object: { - json::object &obj = n.get_object(); - dispatch_table::const_iterator pos = lookup(dt, obj); + case json::kind::object: { + const json::object &obj = n.get_object(); + dispatch_table::const_iterator pos = lookup(dt, obj); + + if (pos != dt.end()) { + CXX_LIKELY; + res = &pos->second(obj, varmap); + } else { + // does jsonlogic support value objects? + unsupported(); + } - if (pos != dt.end()) { - CXX_LIKELY; - res = &pos->second(obj, varmap); - } else { - // does jsonlogic support value objects? - unsupported(); + break; } - break; - } - - case json::kind::array: { - // array is an operator that combines its subexpressions into an array - res = &mk_array(n.get_array(), varmap); - break; - } + case json::kind::array: { + // array is an operator that combines its subexpressions into an array + res = &mk_array(n.get_array(), varmap); + break; + } - case json::kind::string: { - res = &mk_value(std::move(n.get_string())); - break; - } + case json::kind::string: { + const json::string& str = n.get_string(); + res = &mk_value(managed_string_view(str.begin(), str.size())); + break; + } - case json::kind::int64: { - res = &mk_value(n.get_int64()); - break; - } + case json::kind::int64: { + res = &mk_value(n.get_int64()); + break; + } - case json::kind::uint64: { - res = &mk_value(n.get_uint64()); - break; - } + case json::kind::uint64: { + res = &mk_value(n.get_uint64()); + break; + } - case json::kind::double_: { - res = &mk_value(n.get_double()); - break; - } + case json::kind::double_: { + res = &mk_value(n.get_double()); + break; + } - case json::kind::bool_: { - res = &mk_value(n.get_bool()); - break; - } + case json::kind::bool_: { + res = &mk_value(n.get_bool()); + break; + } - case json::kind::null: { - res = &mk_null_value(); - break; - } + case json::kind::null: { + res = &mk_null_value(); + break; + } - default: - unsupported(); + default: + unsupported(); } return any_expr(res); } -oper::container_type translate_children(json::array &children, +oper::container_type translate_children(const json::array &children, variable_map &varmap) { oper::container_type res; res.reserve(children.size()); - for (json::value &elem : children) + for (const json::value &elem : children) res.emplace_back(translate_internal(elem, varmap)); return res; } -oper::container_type translate_children(json::value &n, variable_map &varmap) { - if (json::array *arr = n.if_array()) { +oper::container_type translate_children(const json::value &n, variable_map &varmap) { + if (const json::array *arr = n.if_array()) { CXX_LIKELY; return translate_children(*arr, varmap); } @@ -473,145 +1016,87 @@ oper::container_type translate_children(json::value &n, variable_map &varmap) { } } // namespace -logic_rule_base create_logic(json::value n) { + +logic_rule create_logic(const json::value& n) { variable_map varmap; - any_expr node = translate_internal(std::move(n), varmap); - bool hasComputedVariables = varmap.hasComputedVariables(); + any_expr node = translate_internal(n, varmap); + bool const hasComputedVariables = varmap.hasComputedVariables(); - return {std::move(node), varmap.to_vector(), hasComputedVariables}; + return logic_rule(std::make_unique(std::move(node), varmap.to_vector(), hasComputedVariables)); } + + // // to value_base conversion -any_expr to_expr(std::nullptr_t) { return any_expr(new null_value); } -any_expr to_expr(bool val) { return any_expr(new bool_value(val)); } -any_expr to_expr(std::int64_t val) { return any_expr(new int_value(val)); } -any_expr to_expr(std::uint64_t val) { - return any_expr(new unsigned_int_value(val)); -} -any_expr to_expr(double val) { return any_expr(new real_value(val)); } -any_expr to_expr(json::string val) { - return any_expr(new string_value(std::move(val))); -} +any_value to_value(std::nullptr_t) { return nullptr; } +any_value to_value(bool val) { return val; } +any_value to_value(std::int64_t val) { return val; } +any_value to_value(std::uint64_t val) { return val; } +any_value to_value(double val) { return val; } +any_value to_value(managed_string_view val) { return val; } -any_expr to_expr(const json::array &val) { - oper::container_type elems; +any_value to_value(const json::value &n); // \todo remove after moving to logic.hpp - std::transform(val.begin(), val.end(), std::back_inserter(elems), - [](const json::value &el) -> any_expr { return to_expr(el); }); - array &arr = deref(new array); +any_value to_value(const json::value &n) { + any_value res; - arr.set_operands(std::move(elems)); - return any_expr(&arr); -} + switch (n.kind()) { + case json::kind::string: { + const json::string& str = n.get_string(); // \todo this may be unsafe.. + res = to_value(managed_string_view(&*str.begin(), str.size())); + break; + } -any_expr to_expr(const json::value &n) { - any_expr res; + case json::kind::int64: { + res = to_value(n.get_int64()); + break; + } - switch (n.kind()) { - case json::kind::string: { - res = to_expr(n.get_string()); - break; - } + case json::kind::uint64: { + res = to_value(n.get_uint64()); + break; + } - case json::kind::int64: { - res = to_expr(n.get_int64()); - break; - } + case json::kind::double_: { + res = to_value(n.get_double()); + break; + } - case json::kind::uint64: { - res = to_expr(n.get_uint64()); - break; - } + case json::kind::bool_: { + res = to_value(n.get_bool()); + break; + } - case json::kind::double_: { - res = to_expr(n.get_double()); - break; - } + case json::kind::null: { + res = to_value(nullptr); + break; + } - case json::kind::bool_: { - res = to_expr(n.get_bool()); - break; - } + case json::kind::array: { + std::vector values; + const json::array& arr = n.get_array(); - case json::kind::null: { - res = to_expr(nullptr); - break; - } + for (const json::value& val : arr) + values.emplace_back(to_value(val)); - case json::kind::array: { - res = to_expr(n.get_array()); - break; - } + res = &mk_array_value(std::move(values)); + break; + } - default: - unsupported(); + default: + unsupported(); } - assert(res.get()); return res; } -any_expr to_expr(value_variant val) { - // guard against accidental variant modification - // value_variant = variant - static_assert(std::variant_size_v == 6); - static_assert(std::is_same_v>); - static_assert( - std::is_same_v>); - static_assert(std::is_same_v>); - static_assert(std::is_same_v>); - static_assert( - std::is_same_v>); - static_assert(std::is_same_v>); - - any_expr res; - - switch (val.index()) { - case 0: { - res = to_expr(nullptr); - break; - } - - case 1: { - res = to_expr(std::get(val)); - break; - } - - case 2: { - res = to_expr(std::get(val)); - break; - } - - case 3: { - res = to_expr(std::get(val)); - break; - } - - case 4: { - res = to_expr(std::get(val)); - break; - } - - case 5: { - res = to_expr(boost::json::string(std::get(val))); - break; - } - - default: - // did val hold a valid value? - unsupported(); - } - assert(res.get()); - return res; +any_value to_value(const any_expr& n) +{ + return down_cast(*n).to_variant(); } namespace { @@ -624,14 +1109,27 @@ struct not_int64_error : internal_coercion_error {}; struct not_uint64_error : internal_coercion_error {}; struct unpacked_array_req : internal_coercion_error {}; + +template +T from_string(std::string_view str, T el) { + auto [ptr, err] = std::from_chars(str.data(), str.data() + str.size(), el); + + if (err != std::errc{}) { + CXX_UNLIKELY; + throw std::runtime_error{"logic.cc: in to_concrete(string_view, int64_t)"}; + } + + return el; +} + /// conversion to int64 /// \{ -[[maybe_unused]] inline std::int64_t to_concrete(std::int64_t v, +CXX_MAYBE_UNUSED inline std::int64_t to_concrete(std::int64_t v, const std::int64_t &) { return v; } -inline std::int64_t to_concrete(const json::string &str, const std::int64_t &) { - return std::stoll(std::string{str.c_str()}); +inline std::int64_t to_concrete(const managed_string_view& str, const std::int64_t &el) { + return from_string(str, el); } inline std::int64_t to_concrete(double v, const std::int64_t &) { return static_cast(v); @@ -655,13 +1153,12 @@ inline std::int64_t to_concrete(std::uint64_t v, const std::int64_t &) { /// conversion to uint64 /// \{ -[[maybe_unused]] inline std::uint64_t to_concrete(std::uint64_t v, +CXX_MAYBE_UNUSED inline std::uint64_t to_concrete(std::uint64_t v, const std::uint64_t &) { return v; } -inline std::uint64_t to_concrete(const json::string &str, - const std::uint64_t &) { - return std::stoull(std::string{str.c_str()}); +inline std::uint64_t to_concrete(const managed_string_view& str, const std::uint64_t &el) { + return from_string(str, el); } inline std::uint64_t to_concrete(double v, const std::uint64_t &) { return static_cast(v); @@ -685,8 +1182,8 @@ inline std::uint64_t to_concrete(std::int64_t v, const std::uint64_t &) { /// conversion to double /// \{ -inline double to_concrete(const json::string &str, const double &) { - return std::stod(std::string{str.c_str()}); +inline double to_concrete(const managed_string_view& str, const double &el) { + return from_string(str, el); } inline double to_concrete(std::int64_t v, const double &) { return static_cast(v); @@ -694,34 +1191,37 @@ inline double to_concrete(std::int64_t v, const double &) { inline double to_concrete(std::uint64_t v, const double &) { return static_cast(v); } -[[maybe_unused]] inline double to_concrete(double v, const double &) { +CXX_MAYBE_UNUSED inline double to_concrete(double v, const double &) { return v; } inline double to_concrete(bool v, const double &) { return static_cast(v); } -[[maybe_unused]] inline double to_concrete(std::nullptr_t, const double &) { +CXX_MAYBE_UNUSED inline double to_concrete(std::nullptr_t, const double &) { return 0; } /// \} + + + /// conversion to string /// \{ template -inline json::string to_concrete(Val v, const json::string &) { - return json::string{std::to_string(v)}; +inline managed_string_view to_concrete(Val v, const std::string_view &) { + return managed_string_view(std::to_string(v)); } +inline managed_string_view to_concrete(bool v, const std::string_view &) { + static constexpr const char* bool_string[] = {"false", "true"}; -inline json::string to_concrete(bool v, const json::string &) { - return json::string{v ? "true" : "false"}; + return managed_string_view(std::string_view(bool_string[v])); } -[[maybe_unused]] inline json::string to_concrete(const json::string &s, - const json::string &) { +CXX_MAYBE_UNUSED inline managed_string_view to_concrete(const managed_string_view &s, const std::string_view &) { return s; } -inline json::string to_concrete(std::nullptr_t, const json::string &) { - return json::string{"null"}; +inline managed_string_view to_concrete(std::nullptr_t, const std::string_view &) { + return managed_string_view(std::string_view("null")); } /// \} @@ -736,18 +1236,43 @@ inline bool to_concrete(std::uint64_t v, const bool &) { return static_cast(v); } inline bool to_concrete(double v, const bool &) { return static_cast(v); } -inline bool to_concrete(const json::string &v, const bool &) { - return !v.empty(); +inline bool to_concrete(const managed_string_view &v, const bool &) { + return v.size() != 0; } inline bool to_concrete(std::nullptr_t, const bool &) { return false; } -// \todo logical_not sure if conversions from arrays to values should be +// \todo not sure if conversions from arrays to values should be // supported like this -inline bool to_concrete(const array &v, const bool &) { - return v.num_evaluated_operands() != 0; +inline bool to_concrete(array_value const* arr, const bool &) { + return arr->value().size(); } /// \} +} + + +/* +template +struct to_concrete_nostrings : std::false_type {}; + +template +struct to_concrete_nostrings(), std::declval())) >> + : std::true_type {}; + +//~ template< class T, class U> inline constexpr bool to_concrete_nostrings_v = + //~ to_concrete_nostrings::value; + +template +U to_concrete_(T&& v, const U& u, string_table& strings) +{ + if constexpr (to_concrete_nostrings::value) + return to_concrete(std::forward(v), u); + else + return to_concrete(std::forward(v), u, strings); +} +*/ + + struct comparison_operator_base { enum : bool { defined_for_string = true, @@ -763,8 +1288,8 @@ struct comparison_operator_base { template T to_calc_type(const T *val) { return *val; } -std::string_view to_calc_type(const json::string *val) { - return std::string_view{val->data(), val->size()}; +const managed_string_view& to_calc_type(const managed_string_view *val) { + return *val; } /* std::nullptr_t @@ -773,13 +1298,13 @@ to_calc_type(std::nullptr_t el) return el; } */ -const array *to_calc_type(const array *arr) { return arr; } +variant_span to_calc_type(array_value const *n) { return element_range(n); } /// \brief a strict equality operator operates on operands of the same /// type. The operation on two different types returns false. /// NO type coercion is performed. struct strict_equality_operator : comparison_operator_base { - std::tuple coerce(const array *, const array *) { + std::tuple coerce(const array *, const array *) const { // arrays are never equal // \todo arrays may be equal if they have the same reference // i.e., if they originate from the same variable @@ -790,68 +1315,68 @@ struct strict_equality_operator : comparison_operator_base { template std::tuple())), decltype(to_calc_type(std::declval()))> - coerce(const LhsT *lv, const RhsT *rv) { + coerce(const LhsT *lv, const RhsT *rv) const { return {to_calc_type(lv), to_calc_type(rv)}; } std::tuple coerce(std::nullptr_t, - std::nullptr_t) { - return {nullptr, nullptr}; // two null pointers are equal + std::nullptr_t) const { + return {nullptr, nullptr}; // two null pointers are equal } template std::tuple())), std::nullptr_t> - coerce(const LhsT *lv, std::nullptr_t) { + coerce(const LhsT *lv, std::nullptr_t) const { return {to_calc_type(lv), nullptr}; } template std::tuple()))> - coerce(std::nullptr_t, const RhsT *rv) { + coerce(std::nullptr_t, const RhsT *rv) const { return {nullptr, to_calc_type(rv)}; } }; struct numeric_binary_operator_base { - std::tuple coerce(const double *lv, const double *rv) { + std::tuple coerce(const double *lv, const double *rv) const { return {*lv, *rv}; } - std::tuple coerce(const double *lv, const std::int64_t *rv) { + std::tuple coerce(const double *lv, const std::int64_t *rv) const { return {*lv, to_concrete(*rv, *lv)}; } - std::tuple coerce(const double *lv, const std::uint64_t *rv) { + std::tuple coerce(const double *lv, const std::uint64_t *rv) const { return {*lv, to_concrete(*rv, *lv)}; } - std::tuple coerce(const std::int64_t *lv, const double *rv) { + std::tuple coerce(const std::int64_t *lv, const double *rv) const { return {to_concrete(*lv, *rv), *rv}; } std::tuple coerce(const std::int64_t *lv, - const std::int64_t *rv) { + const std::int64_t *rv) const { return {*lv, *rv}; } std::tuple coerce(const std::int64_t *lv, - const std::uint64_t *rv) { + const std::uint64_t *rv) const { return {*lv, to_concrete(*rv, *lv)}; } - std::tuple coerce(const std::uint64_t *lv, const double *rv) { + std::tuple coerce(const std::uint64_t *lv, const double *rv) const { return {to_concrete(*lv, *rv), *rv}; } std::tuple coerce(const std::uint64_t *lv, - const std::int64_t *rv) { + const std::int64_t *rv) const { return {to_concrete(*lv, *rv), *rv}; } std::tuple coerce(const std::uint64_t *lv, - const std::uint64_t *rv) { + const std::uint64_t *rv) const { return {*lv, *rv}; } }; @@ -862,78 +1387,78 @@ struct numeric_binary_operator_base { struct relational_operator_base : numeric_binary_operator_base { using numeric_binary_operator_base::coerce; - std::tuple coerce(const double *lv, const json::string *rv) { + std::tuple coerce(const double *lv, const managed_string_view* rv) const { return {*lv, to_concrete(*rv, *lv)}; } - std::tuple coerce(const double *lv, const bool *rv) { + std::tuple coerce(const double *lv, const bool *rv) const { return {*lv, to_concrete(*rv, *lv)}; } std::tuple coerce(const std::int64_t *lv, - const json::string *rv) { + const managed_string_view* rv) const { return {*lv, to_concrete(*rv, *lv)}; } std::tuple coerce(const std::int64_t *lv, - const bool *rv) { + const bool *rv) const { return {*lv, to_concrete(*rv, *lv)}; } std::tuple coerce(const std::uint64_t *lv, - const json::string *rv) { + const managed_string_view* rv) const { return {*lv, to_concrete(*rv, *lv)}; } std::tuple coerce(const std::uint64_t *lv, - const bool *rv) { + const bool *rv) const { return {*lv, to_concrete(*rv, *lv)}; } - std::tuple coerce(const json::string *lv, const double *rv) { + std::tuple coerce(const managed_string_view* lv, const double *rv) const { return {to_concrete(*lv, *rv), *rv}; } - std::tuple coerce(const bool *lv, const double *rv) { + std::tuple coerce(const bool *lv, const double *rv) const { return {to_concrete(*lv, *rv), *rv}; } - std::tuple coerce(const json::string *lv, - const std::int64_t *rv) { + std::tuple coerce(const managed_string_view* lv, + const std::int64_t *rv) const { return {to_concrete(*lv, *rv), *rv}; } std::tuple coerce(const bool *lv, - const std::int64_t *rv) { + const std::int64_t *rv) const { return {to_concrete(*lv, *rv), *rv}; } - std::tuple coerce(const json::string *lv, - const std::uint64_t *rv) { + std::tuple coerce(const managed_string_view* lv, + const std::uint64_t *rv) const { return {to_concrete(*lv, *rv), *rv}; } std::tuple coerce(const bool *lv, - const std::uint64_t *rv) { + const std::uint64_t *rv) const { return {to_concrete(*lv, *rv), *rv}; } - std::tuple coerce(const json::string *, const bool *) { + std::tuple coerce(const managed_string_view *, const bool *) const { // strings and boolean are never equal return {true, false}; } - std::tuple coerce(const bool *, const json::string *) { + std::tuple coerce(const bool *, const managed_string_view *) const { // strings and boolean are never equal return {true, false}; } - std::tuple - coerce(const json::string *lv, const json::string *rv) { - return {to_calc_type(lv), to_calc_type(rv)}; + std::tuple coerce( + const managed_string_view *lv, const managed_string_view *rv) const { + return {*lv, *rv}; } - std::tuple coerce(const bool *lv, const bool *rv) { + std::tuple coerce(const bool *lv, const bool *rv) const { return {*lv, *rv}; } }; @@ -944,59 +1469,57 @@ struct equality_operator : relational_operator_base, comparison_operator_base { // due to special conversion rules, the coercion function may just produce // the result instead of just unpacking and coercing values. - std::tuple coerce(const array *, const array *) { - return {true, false}; // arrays are never equal + std::tuple coerce(const array_value *, const array_value *) const { + return {true, false}; // arrays are never equal } template - std::tuple coerce(const T *lv, const array *rv) { + std::tuple coerce(const T *lv, const array_value *rv) const { // an array may be compared to a value_base // (1) *lv == arr[0], iff the array has exactly one element - if (rv->num_evaluated_operands() == 1) - throw unpacked_array_req{}; + if (rv->value().size() == 1) throw unpacked_array_req{}; // (2) or if [] and *lv converts to false - if (rv->num_evaluated_operands() > 1) - return {false, true}; + if (rv->value().size() > 1) return {false, true}; - const bool convToFalse = !to_concrete(*lv, false); + const bool convToFalse = to_concrete(*lv, false) == false; return {convToFalse, true /* zero elements */}; } template - std::tuple coerce(const array *lv, const T *rv) { + std::tuple coerce(const array_value *lv, const T *rv) const { // see comments in coerce(T*,array*) - if (lv->num_evaluated_operands() == 1) - throw unpacked_array_req{}; + if (lv->value().size() == 1) throw unpacked_array_req{}; - if (lv->num_evaluated_operands() > 1) - return {false, true}; + if (lv->value().size() > 1) return {false, true}; - const bool convToFalse = !to_concrete(*rv, false); + const bool convToFalse = to_concrete(*rv, false) == false; return {true /* zero elements */, convToFalse}; } std::tuple coerce(std::nullptr_t, - std::nullptr_t) { - return {nullptr, nullptr}; // two null pointers are equal + std::nullptr_t) const { + return {nullptr, nullptr}; // two null pointers are equal } - template std::tuple coerce(const T *, std::nullptr_t) { - return {false, true}; // null pointer is only equal to itself + template + std::tuple coerce(const T *, std::nullptr_t) const { + return {false, true}; // null pointer is only equal to itself } - template std::tuple coerce(std::nullptr_t, const T *) { - return {true, false}; // null pointer is only equal to itself + template + std::tuple coerce(std::nullptr_t, const T *) const { + return {true, false}; // null pointer is only equal to itself } - std::tuple coerce(const array *, std::nullptr_t) { - return {true, false}; // null pointer is only equal to itself + std::tuple coerce(const array_value *, std::nullptr_t) const { + return {true, false}; // null pointer is only equal to itself } - std::tuple coerce(std::nullptr_t, const array *) { - return {true, false}; // null pointer is only equal to itself + std::tuple coerce(std::nullptr_t, const array_value *) const { + return {true, false}; // null pointer is only equal to itself } }; @@ -1004,90 +1527,86 @@ struct relational_operator : relational_operator_base, comparison_operator_base { using relational_operator_base::coerce; - std::tuple coerce(const array *lv, - const array *rv) { - return {lv, rv}; + std::tuple + coerce(const array_value *lv, const array_value *rv) const { + return {element_range(lv), element_range(rv)}; } template - std::tuple coerce(const T *lv, const array *rv) { + std::tuple coerce(const T *lv, array_value const* rv) const { // an array may be equal to another value_base if // (1) *lv == arr[0], iff the array has exactly one element - if (rv->num_evaluated_operands() == 1) - throw unpacked_array_req{}; + if (rv->value().size() == 1) throw unpacked_array_req{}; // (2) or if [] and *lv converts to false - if (rv->num_evaluated_operands() > 1) - return {false, true}; + if (rv->value().size() > 1) return {false, true}; - const bool convToTrue = to_concrete(*lv, true); + const bool convToTrue = to_concrete(*lv, true) == true; return {convToTrue, false /* zero elements */}; } template - std::tuple coerce(const array *lv, const T *rv) { + std::tuple coerce(array_value const*lv, const T *rv) const { // see comments in coerce(T*,array*) - if (lv->num_evaluated_operands() == 1) - throw unpacked_array_req{}; + if (lv->value().size() == 1) throw unpacked_array_req{}; - if (lv->num_evaluated_operands() > 1) - return {false, true}; + if (lv->value().size() > 1) return {false, true}; - const bool convToTrue = to_concrete(*rv, true); + const bool convToTrue = to_concrete(*rv, true) == true; return {false /* zero elements */, convToTrue}; } std::tuple coerce(std::nullptr_t, - std::nullptr_t) { - return {nullptr, nullptr}; // two null pointers are equal + std::nullptr_t) const { + return {nullptr, nullptr}; // two null pointers are equal } - std::tuple coerce(const bool *lv, std::nullptr_t) { - return {*lv, false}; // null pointer -> false + std::tuple coerce(const bool *lv, std::nullptr_t) const { + return {*lv, false}; // null pointer -> false } std::tuple coerce(const std::int64_t *lv, - std::nullptr_t) { - return {*lv, 0}; // null pointer -> 0 + std::nullptr_t) const { + return {*lv, 0}; // null pointer -> 0 } std::tuple coerce(const std::uint64_t *lv, - std::nullptr_t) { - return {*lv, 0}; // null pointer -> 0 + std::nullptr_t) const { + return {*lv, 0}; // null pointer -> 0 } - std::tuple coerce(const double *lv, std::nullptr_t) { - return {*lv, 0}; // null pointer -> 0.0 + std::tuple coerce(const double *lv, std::nullptr_t) const { + return {*lv, 0}; // null pointer -> 0.0 } - std::tuple coerce(const json::string *lv, - std::nullptr_t) { - return {to_calc_type(lv), nullptr}; // requires special handling + std::tuple coerce(const managed_string_view *lv, + std::nullptr_t) const { + return {to_calc_type(lv), nullptr}; // requires special handling } - std::tuple coerce(std::nullptr_t, const bool *rv) { - return {false, *rv}; // null pointer -> false + std::tuple coerce(std::nullptr_t, const bool *rv) const { + return {false, *rv}; // null pointer -> false } std::tuple coerce(std::nullptr_t, - const std::int64_t *rv) { - return {0, *rv}; // null pointer -> 0 + const std::int64_t *rv) const { + return {0, *rv}; // null pointer -> 0 } std::tuple coerce(std::nullptr_t, - const std::uint64_t *rv) { - return {0, *rv}; // null pointer -> 0 + const std::uint64_t *rv) const { + return {0, *rv}; // null pointer -> 0 } - std::tuple coerce(std::nullptr_t, const double *rv) { - return {0, *rv}; // null pointer -> 0 + std::tuple coerce(std::nullptr_t, const double *rv) const { + return {0, *rv}; // null pointer -> 0 } - std::tuple coerce(std::nullptr_t, - const json::string *rv) { - return {nullptr, to_calc_type(rv)}; // requires special handling + std::tuple coerce(std::nullptr_t, + const managed_string_view *rv) const { + return {nullptr, to_calc_type(rv)}; // requires special handling } }; // @} @@ -1103,42 +1622,42 @@ struct arithmetic_operator : numeric_binary_operator_base { defined_for_array = false }; - using result_type = any_expr; + using result_type = any_value; using numeric_binary_operator_base::coerce; std::tuple coerce(const double *, - std::nullptr_t) { + std::nullptr_t) const { return {nullptr, nullptr}; } std::tuple coerce(const std::int64_t *, - std::nullptr_t) { + std::nullptr_t) const { return {nullptr, nullptr}; } std::tuple coerce(const std::uint64_t *, - std::nullptr_t) { + std::nullptr_t) const { return {nullptr, nullptr}; } std::tuple coerce(std::nullptr_t, - const double *) { + const double *) const { return {nullptr, nullptr}; } std::tuple coerce(std::nullptr_t, - const std::int64_t *) { + const std::int64_t *) const { return {nullptr, nullptr}; } std::tuple coerce(std::nullptr_t, - const std::uint64_t *) { + const std::uint64_t *) const { return {nullptr, nullptr}; } std::tuple coerce(std::nullptr_t, - std::nullptr_t) { + std::nullptr_t) const { return {nullptr, nullptr}; } }; @@ -1166,12 +1685,11 @@ struct string_operator_non_destructive { defined_for_array = false }; - using result_type = any_expr; + using result_type = any_value; - std::tuple - coerce(const json::string *lv, const json::string *rv) { - return {std::string_view{lv->data(), lv->size()}, - std::string_view{rv->data(), rv->size()}}; + std::tuple coerce( + const managed_string_view *lv, const managed_string_view *rv) const { + return {*lv, *rv}; } }; @@ -1185,604 +1703,448 @@ struct array_operator { defined_for_array = true }; - using result_type = any_expr; + using result_type = array_value const*; - std::tuple coerce(const array *lv, - const array *rv) { - return {lv, rv}; + std::tuple + coerce(const array_value *lv, const array_value *rv) const { + return {element_range(lv), element_range(rv)}; } }; -/* - any_expr convert(any_expr val, ...) - { - return val; - } -*/ - -any_expr convert(any_expr val, const arithmetic_operator &) { - struct arithmetic_converter : forwarding_visitor { - explicit arithmetic_converter(any_expr val) : res(std::move(val)) {} - - void visit(const expr &) final { throw_type_error(); } - // defined for the following types - void visit(const int_value &) final {} - void visit(const unsigned_int_value &) final {} - void visit(const real_value &) final {} - void visit(const null_value &) final {} - - // need to convert values - void visit(const string_value &el) final { - double dd = to_concrete(el.value(), double{}); - std::int64_t ii = to_concrete(el.value(), std::int64_t{}); - // uint? - - res = (dd != static_cast(ii)) ? to_expr(dd) : to_expr(ii); - } - - void visit(const bool_value &) final { - // \todo correct? - res = to_expr(nullptr); - } - - any_expr result() && { return std::move(res); } - - private: - any_expr res; - }; - - expr *node = val.get(); - arithmetic_converter conv{std::move(val)}; - - node->accept(conv); - return std::move(conv).result(); -} - -/* - any_expr convert(any_expr val, const integer_arithmetic_operator&) - { - struct integer_arithmetic_converter : forwarding_visitor - { - explicit - integer_arithmetic_converter(any_expr val) - : res(std::move(val)) - {} - - void visit(expr&) final { throw_type_error(); } - - // defined for the following types - void visit(int_value&) final {} - void visit(unsigned_int_value&) final {} - - // need to convert values - void visit(string_value& el) final - { - res = to_expr(to_concrete(el.value(), std::int64_t{})); - } - - void visit(bool_value& el) final - { - res = to_expr(to_concrete(el.value(), std::int64_t{})); - } - - void visit(real_value& el) final - { - res = to_expr(to_concrete(el.value(), std::int64_t{})); - } - - void visit(null_value&) final - { - res = to_expr(std::int64_t{0}); - } - - any_expr result() && { return std::move(res); } - - private: - any_expr res; - }; - - expr* node = val.get(); - integer_arithmetic_converter conv{std::move(val)}; - - node->accept(conv); - return std::move(conv).result(); - } -*/ - -any_expr convert(any_expr val, const string_operator_non_destructive &) { - struct string_converter : forwarding_visitor { - explicit string_converter(any_expr val) : res(std::move(val)) {} +struct arithmetic_converter { + template + CXX_NORETURN + any_value operator()(T) const { throw_type_error(); } - void visit(const expr &) final { throw_type_error(); } + any_value operator()(std::nullptr_t) const { return nullptr; } - // defined for the following types - void visit(const string_value &) final {} - - // need to convert values - void visit(const bool_value &el) final { - res = to_expr(to_concrete(el.value(), json::string{})); - } + // defined for the following types + any_value operator()(std::int64_t v) const { return v; } + any_value operator()(std::uint64_t v) const { return v; } + any_value operator()(double v) const { return v; } - void visit(const int_value &el) final { - res = to_expr(to_concrete(el.value(), json::string{})); - } + any_value operator()(bool) const { return nullptr; /* correct? */ } - void visit(const unsigned_int_value &el) final { - res = to_expr(to_concrete(el.value(), json::string{})); - } + // need to convert values + any_value operator()(const managed_string_view& v) const { + const double dblval = to_concrete(v, double{}); + const std::int64_t intval = dblval; + const bool intval_is_precise = dblval == intval; - void visit(const real_value &el) final { - res = to_expr(to_concrete(el.value(), json::string{})); - } + if (intval_is_precise) + return intval; - void visit(const null_value &el) final { - res = to_expr(to_concrete(el.value(), json::string{})); - } + const std::uint64_t uintval = dblval; + const bool uint_is_precise = uintval == dblval; - any_expr result() && { return std::move(res); } + if (uint_is_precise) + return uintval; - private: - any_expr res; - }; + return dblval; + } +}; - expr *node = val.get(); - string_converter conv{std::move(val)}; - node->accept(conv); - return std::move(conv).result(); +any_value convert(any_value val, const arithmetic_operator &) { + return std::visit(arithmetic_converter{}, val); } -any_expr convert(any_expr val, const array_operator &) { - struct array_converter : forwarding_visitor { - explicit array_converter(any_expr val) : val(std::move(val)) {} - void visit(const expr &) final { throw_type_error(); } +struct string_converter { + template + CXX_NORETURN + managed_string_view operator()(T) const { throw_type_error(); } - // moves res into - void to_array() { - array &arr = deref(new array); - oper::container_type operands; + // defined for the following types + managed_string_view operator()(managed_string_view val) const { return val; } + // need to convert values + managed_string_view operator()(bool val) const { return to_concrete(val, std::string_view{}); } + managed_string_view operator()(std::int64_t val) const { return to_concrete(val, std::string_view{}); } + managed_string_view operator()(std::uint64_t val) const { return to_concrete(val, std::string_view{}); } + managed_string_view operator()(double val) const { return to_concrete(val, std::string_view{}); } + managed_string_view operator()(std::nullptr_t) const { return to_concrete(nullptr, std::string_view{}); } +}; + + +managed_string_view convert(any_value val, const string_operator_non_destructive &) { + return std::visit(string_converter{}, val); +} - operands.emplace_back(&arr); + struct array_converter { + using result_type = array_value const*; - // swap the operand and result - std::swap(operands.back(), val); + // moves res into + result_type to_array(any_value val) const { + std::vector values; - // then set the operands - arr.set_operands(std::move(operands)); + values.emplace_back(std::move(val)); + return &mk_array_value(std::move(values)); } // defined for the following types - void visit(const array &) final {} - - // need to move value_base to new array - void visit(const string_value &) final { to_array(); } - void visit(const bool_value &) final { to_array(); } - void visit(const int_value &) final { to_array(); } - void visit(const unsigned_int_value &) final { to_array(); } - void visit(const real_value &) final { to_array(); } - void visit(const null_value &) final { to_array(); } + template + CXX_NORETURN + result_type operator()(T&&) const { throw_type_error(); } - any_expr result() && { return std::move(val); } + result_type operator()(array_value const* v) const + { + return v->copy(); + } - private: - any_expr val; + // need to move value_base to new array + result_type operator()(const managed_string_view& v) const { return to_array(to_value(v)); } + result_type operator()(bool v) const { return to_array(to_value(v)); } + result_type operator()(std::int64_t v) const { return to_array(to_value(v)); } + result_type operator()(std::uint64_t v) const { return to_array(to_value(v)); } + result_type operator()(double v) const { return to_array(to_value(v)); } + result_type operator()(std::nullptr_t) const { return to_array(to_value(nullptr)); } }; - expr *node = val.get(); - array_converter conv{std::move(val)}; - node->accept(conv); - return std::move(conv).result(); +any_value convert(any_value val, const array_operator &) { + return std::visit(array_converter{}, val); } +template +struct tag_type_traits +{ + using type = value_t; +}; -template struct unpacker : forwarding_visitor { - void assign(value_t &lhs, const value_t &val) { lhs = val; } - - template void assign(value_t &lhs, const U &val) { - lhs = to_concrete(val, lhs); - } - - CXX_NORETURN - void visit(const expr &) final { throw_type_error(); } +template <> +struct tag_type_traits +{ + using type = std::string_view; +}; - // defined for the following types - void visit(const string_value &el) final { assign(res, el.value()); } +template +struct unpacker { + using tag_type = tag_type_traits::type; - // need to convert values - void visit(const bool_value &el) final { assign(res, el.value()); } + value_t conv(const value_t& val, const tag_type&) { return val; } - void visit(const int_value &el) final { assign(res, el.value()); } + template + value_t conv(const U &val, const tag_type &tag) const { + return to_concrete(val, tag); + } - void visit(const unsigned_int_value &el) final { assign(res, el.value()); } + template + CXX_NORETURN + value_t operator()(T) const { throw_type_error(); } - void visit(const real_value &el) final { assign(res, el.value()); } + // defined for the following types + value_t operator()(const managed_string_view& val) const { return conv(val, tag_type{}); } - void visit(const null_value &el) final { assign(res, el.value()); } + // need to convert values + value_t operator()(bool val) const { return conv(val, tag_type{}); } + value_t operator()(std::int64_t val) const { return conv(val, tag_type{}); } + value_t operator()(std::uint64_t val) const { return conv(val, tag_type{}); } + value_t operator()(double val) const { return conv(val, tag_type{}); } + value_t operator()(std::nullptr_t) const { return conv(nullptr, tag_type{}); } + + value_t operator()(array_value const* val) const { + if constexpr (std::is_same::value) { + CXX_LIKELY; + return conv(val, tag_type{}); + } - void visit(const array &el) final { - if constexpr (std::is_same::value) { - CXX_LIKELY; - return assign(res, el); + throw_type_error(); } - throw_type_error(); - } - - value_t result() && { return std::move(res); } - -private: - value_t res; }; -template T unpack_value(expr &expr) { - unpacker unpack; +/* +template +T unpack_value(const expr &expr, string_table& strings) { + unpacker unpack{strings}; expr.accept(unpack); return std::move(unpack).result(); } +*/ -template T unpack_value(const any_expr &el) { - return unpack_value(*el); +template +T unpack_value(any_value el) { + return std::visit(unpacker{}, el); } -template T unpack_value(any_expr &&el) { - return unpack_value(*el); -} // // Json Logic - truthy/falsy -bool truthy(expr &el) { return unpack_value(el); } -bool truthy(any_expr &&el) { return unpack_value(std::move(el)); } -bool falsy(expr &el) { return !truthy(el); } -// bool falsy(any_expr&& el) { return !truthy(std::move(el)); } -} // namespace - -bool truthy(const any_expr &el) { return unpack_value(el); } -bool falsy(const any_expr &el) { return !truthy(el); } - -// -// cloning - -namespace { -any_expr clone_expr(const any_expr &expr); - -struct expr_cloner { - /// init functions that set up children - /// \{ - expr &init(const oper &, oper &) const; - expr &init(const object_value &, object_value &) const; - /// \} - - /// function family for type specific cloning - /// \param n the original node - /// \param unnamed a tag parameter to summarily handle groups of types - /// \return the cloned node - /// \{ - CXX_NORETURN - expr &clone(const expr &, const expr &) const { unsupported(); } - - expr &clone(const error &, const error &) const { return deref(new error); } - - template - expr &clone(const value_t &n, const value_base &) const { - return deref(new value_t(n.value())); - } - - template expr &clone(const oper_t &n, const oper &) const { - return init(n, deref(new oper_t)); - } - - expr &clone(const object_value &n, const object_value &) const { - return init(n, deref(new object_value)); - } - /// \} - - template expr *operator()(expr_t &n) { return &clone(n, n); } -}; - -expr &expr_cloner::init(const oper &src, oper &tgt) const { - oper::container_type children; - std::transform(src.operands().begin(), src.operands().end(), - std::back_inserter(children), - [](const any_expr &e) -> any_expr { return clone_expr(e); }); - tgt.set_operands(std::move(children)); - return tgt; +bool truthy(const any_value &el) +{ + return unpack_value(el); } -expr &expr_cloner::init(const object_value &src, object_value &tgt) const { - std::transform( - src.begin(), src.end(), std::inserter(tgt.elements(), tgt.end()), - [](const object_value::value_type &entry) -> object_value::value_type { - return {entry.first, clone_expr(entry.second)}; - }); +bool falsy(const any_value &el) { return !truthy(el); } - return tgt; -} -any_expr clone_expr(const any_expr &exp) { - return any_expr(generic_visit(expr_cloner{}, exp.get())); -} -template -auto with_type(expr *e, fn_t fn, alt_fn_t altfn) -> decltype(altfn()) { - if (e == nullptr) { - CXX_UNLIKELY; - return altfn(); - } +namespace { - expr_t *casted = may_down_cast(*e); - return casted ? fn(*casted) : altfn(); +template +auto with_type(any_value& v, fn_t fn, alt_fn_t altfn) -> decltype(altfn()) { + if (T* casted = std::get_if(&v)) + return fn(*casted); + + return altfn(); } // // binary operator - double dispatch pattern template -struct binary_operator_visitor_2 : forwarding_visitor { +struct binary_operator_visitor_2 { // : forwarding_visitor { using result_type = typename binary_op_t::result_type; binary_operator_visitor_2(lhs_value_t lval, binary_op_t oper) - : lv(lval), op(oper), res() {} + : lv(lval), op(oper) {} - template void calc(rhs_value_t rv) { + template + result_type calc(rhs_value_t rv) const { auto [ll, rr] = op.coerce(lv, rv); - - //~ std::cerr << lv << " -- " << rv << " " << typeid(ll).name() << - // std::endl; - res = op(std::move(ll), std::move(rr)); + //~ std::cerr << lv << " -- " << rv << " " << typeid(ll).name() + //~ << std::endl; + return op(std::move(ll), std::move(rr)); //~ std::cerr << lv << " == " << rv << std::endl; } - void visit(const expr &) final { throw_type_error(); } + template + CXX_NORETURN + result_type operator()(T) const { throw_type_error(); } - void visit(const string_value &n) final { - if constexpr (binary_op_t::defined_for_string) - return calc(&n.value()); + result_type operator()(const managed_string_view& n) const { + if constexpr (binary_op_t::defined_for_string) return calc(&n); throw_type_error(); } - void visit(const null_value &) final { - if constexpr (binary_op_t::defined_for_null) - return calc(nullptr); + result_type operator()(std::nullptr_t) const { + if constexpr (binary_op_t::defined_for_null) return calc(nullptr); throw_type_error(); } - void visit(const bool_value &n) final { - if constexpr (binary_op_t::defined_for_boolean) - return calc(&n.value()); + result_type operator()(bool n) const { + if constexpr (binary_op_t::defined_for_boolean) return calc(&n); throw_type_error(); } - void visit(const int_value &n) final { + result_type operator()(std::int64_t n) const { if constexpr (binary_op_t::defined_for_integer) { try { - return calc(&n.value()); + return calc(&n); } catch (const not_int64_error &ex) { - if (n.value() < 0) { + if (n < 0) { CXX_UNLIKELY; throw std::range_error{ "unable to consolidate uint>max(int) with int<0"}; } } - std::uint64_t alt = n.value(); + std::uint64_t alt = n; return calc(&alt); } throw_type_error(); } - void visit(const unsigned_int_value &n) final { + result_type operator()(std::uint64_t n) const { if constexpr (binary_op_t::defined_for_integer) { try { - return calc(&n.value()); + return calc(&n); } catch (const not_uint64_error &ex) { - if (n.value() > std::uint64_t(std::numeric_limits::max)) { + if (n > std::uint64_t(std::numeric_limits::max)) { CXX_UNLIKELY; throw std::range_error{ "unable to consolidate int<0 with uint>max(int)"}; } } - auto alt = static_cast(n.value()); + std::int64_t alt = n; return calc(&alt); } throw_type_error(); } - void visit(const real_value &n) final { - if constexpr (binary_op_t::defined_for_real) - return calc(&n.value()); + result_type operator()(double n) const { + if constexpr (binary_op_t::defined_for_real) return calc(&n); throw_type_error(); } - void visit(const array &n) final { + result_type operator()(array_value const* n) const { + result_type res; if constexpr (binary_op_t::defined_for_array) { try { - calc(&n); + res = calc(n); } catch (const unpacked_array_req &) { - assert(n.num_evaluated_operands() == 1); - n.operand(0).accept(*this); + assert(deref(n).value().size() == 1); + + res = std::visit(*this, n->value().front()); } - return; + return res; } throw_type_error(); } - result_type result() && { return std::move(res); } - -private: + private: lhs_value_t lv; - binary_op_t op; - result_type res; + mutable binary_op_t op; }; template -struct binary_operator_visitor : forwarding_visitor { +struct binary_operator_visitor { using result_type = typename binary_op_t::result_type; - binary_operator_visitor(binary_op_t oper, const any_expr &rhsarg) - : op(oper), rhs(rhsarg), res() {} + binary_operator_visitor(binary_op_t oper, const any_value &rhsarg) + : op(oper), rhs(rhsarg) {} - template void calc(LhsValue lv) { + template + result_type calc(LhsValue lv) const { using rhs_visitor = binary_operator_visitor_2; - rhs_visitor vis{lv, op}; - - rhs->accept(vis); - res = std::move(vis).result(); + return std::visit(rhs_visitor{lv, op}, rhs); } - void visit(const string_value &n) final { - if constexpr (binary_op_t::defined_for_string) - return calc(&n.value()); + template + CXX_NORETURN + result_type operator()(T) const { throw_type_error(); } + + result_type operator()(const managed_string_view& n) const { + if constexpr (binary_op_t::defined_for_string) return calc(&n); throw_type_error(); } - void visit(const null_value &) final { - if constexpr (binary_op_t::defined_for_null) - return calc(nullptr); + result_type operator()(std::nullptr_t) const { + if constexpr (binary_op_t::defined_for_null) return calc(nullptr); throw_type_error(); } - void visit(const bool_value &n) final { - if constexpr (binary_op_t::defined_for_boolean) - return calc(&n.value()); + result_type operator()(bool n) const { + if constexpr (binary_op_t::defined_for_boolean) return calc(&n); throw_type_error(); } - void visit(const int_value &n) final { + result_type operator()(std::int64_t n) const { if constexpr (binary_op_t::defined_for_integer) { try { - return calc(&n.value()); + return calc(&n); } catch (const not_int64_error &ex) { - if (n.value() < 0) { + if (n < 0) { CXX_UNLIKELY; throw std::range_error{ "unable to consolidate int<0 with uint>max(int)"}; } } - std::uint64_t alt = n.value(); - return calc(&alt); + std::uint64_t alt = n; + + return calc(&alt); // should we rethrow range_error } throw_type_error(); } - void visit(const unsigned_int_value &n) final { + result_type operator()(std::uint64_t n) const { if constexpr (binary_op_t::defined_for_integer) { try { - return calc(&n.value()); + return calc(&n); } catch (const not_uint64_error &ex) { - if (n.value() > std::uint64_t(std::numeric_limits::max)) { + if (n > std::uint64_t(std::numeric_limits::max)) { CXX_UNLIKELY; throw std::range_error{ "unable to consolidate uint>max(int) with int<0"}; } } - auto alt = static_cast(n.value()); - return calc(&alt); + std::int64_t alt = n; + return calc(&alt); // should we rethrow range_error } throw_type_error(); } - void visit(const real_value &n) final { - if constexpr (binary_op_t::defined_for_real) - return calc(&n.value()); + result_type operator()(double n) const { + if constexpr (binary_op_t::defined_for_real) return calc(&n); throw_type_error(); } - void visit(const array &n) final { + result_type operator()(array_value const* n) const { if constexpr (binary_op_t::defined_for_array) { try { - calc(&n); + return calc(n); } catch (const unpacked_array_req &) { - assert(n.num_evaluated_operands() == 1); - n.operand(0).accept(*this); + assert(deref(n).value().size() == 1); + + return std::visit(*this, n->value().front()); } - return; + return result_type{}; // \TODO was: return; } throw_type_error(); } - result_type result() && { return std::move(res); } -private: - binary_op_t op; - const any_expr &rhs; - result_type res; + private: + binary_op_t op; + const any_value& rhs; }; // // compute and sequence functions template -typename binary_op_t::result_type compute(const any_expr &lhs, - const any_expr &rhs, binary_op_t op) { +typename binary_op_t::result_type compute(const any_value &lhs, + const any_value &rhs, binary_op_t op) { using lhs_visitor = binary_operator_visitor; - assert(lhs.get() && rhs.get()); - - lhs_visitor vis{op, rhs}; - - lhs->accept(vis); - return std::move(vis).result(); + return std::visit(lhs_visitor{op, rhs}, lhs); } template -bool compare_sequence(const array &lv, const array &rv, - binary_predicate_t pred) { - const std::size_t lsz = lv.num_evaluated_operands(); - const std::size_t rsz = rv.num_evaluated_operands(); +bool compare_sequence(variant_span lv, variant_span rv, binary_predicate_t pred) { + const std::size_t lsz = lv.size(); + const std::size_t rsz = rv.size(); - if (lsz == 0) - return pred(false, rsz != 0); + if (lsz == 0) return pred(false, rsz != 0); - if (rsz == 0) - return pred(true, false); + if (rsz == 0) return pred(true, false); std::size_t const len = std::min(lsz, rsz); - std::size_t i = 0; - bool res = false; - bool found = false; - - while ((i < len) && !found) { - res = compute(lv.at(i), rv.at(i), pred); + auto const lbeg = lv.begin(); + bool res = false; + auto cmpElems = [&pred,&res](const any_value& lhs, const any_value& rhs) -> bool + { + res = compute(lhs, rhs, pred); + return res == compute(rhs, lhs, pred); + }; - // res is conclusive if the reverse test yields a different result - found = res != compute(rv.at(i), lv.at(i), pred); + auto const [lpos, rpos] = std::mismatch(lbeg, lbeg + len, rv.begin(), cmpElems); + bool const matching_elems = lbeg + len == lpos; - ++i; - } + if (matching_elems) + res = pred(lsz, rsz); - return found ? res : pred(lsz, rsz); + return res; } + template -bool compare_sequence(const array *lv, const array *rv, - binary_predicate_t pred) { - return compare_sequence(deref(lv), deref(rv), std::move(pred)); +bool compare_sequence(array_value const *lv, array_value const *rv, binary_predicate_t pred) { + return compare_sequence(element_range(lv), element_range(rv), std::move(pred)); } /// convenience template that only returns \p R @@ -1793,11 +2155,13 @@ bool compare_sequence(const array *lv, const array *rv, /// \tparam V second operator type /// \tparam R operator result type /// \{ -template struct mismatched_types { +template +struct mismatched_types { using type = R; }; -template struct mismatched_types { +template +struct mismatched_types { // missing "using type" triggers SFINAE exclusion for matching types }; /// \} @@ -1805,9 +2169,11 @@ template struct mismatched_types { // // the calc operator implementations -template struct operator_impl {}; +template +struct operator_impl {}; -template <> struct operator_impl : equality_operator { +template <> +struct operator_impl : equality_operator { using equality_operator::result_type; template @@ -1822,7 +2188,8 @@ template <> struct operator_impl : equality_operator { } }; -template <> struct operator_impl : equality_operator { +template <> +struct operator_impl : equality_operator { using equality_operator::result_type; template @@ -1837,7 +2204,8 @@ template <> struct operator_impl : equality_operator { } }; -template <> struct operator_impl : strict_equality_operator { +template <> +struct operator_impl : strict_equality_operator { using strict_equality_operator::result_type; template @@ -1853,7 +2221,8 @@ template <> struct operator_impl : strict_equality_operator { } }; -template <> struct operator_impl : strict_equality_operator { +template <> +struct operator_impl : strict_equality_operator { using strict_equality_operator::result_type; template @@ -1868,283 +2237,300 @@ template <> struct operator_impl : strict_equality_operator { } }; -template <> struct operator_impl : relational_operator { +template <> +struct operator_impl : relational_operator { using relational_operator::result_type; result_type operator()(const std::nullptr_t, std::nullptr_t) const { return false; } - result_type operator()(const array *lv, const array *rv) const { + result_type operator()(variant_span lv, variant_span rv) const { return compare_sequence(lv, rv, *this); } - result_type operator()(std::string_view, std::nullptr_t) const { + result_type operator()(const managed_string_view&, std::nullptr_t) const { return false; } - result_type operator()(std::nullptr_t, std::string_view) const { + result_type operator()(std::nullptr_t, const managed_string_view&) const { return false; } - template result_type operator()(const T &lhs, const T &rhs) const { + template + result_type operator()(const T &lhs, const T &rhs) const { return lhs < rhs; } }; -template <> struct operator_impl : relational_operator { +template <> +struct operator_impl : relational_operator { using relational_operator::result_type; result_type operator()(const std::nullptr_t, std::nullptr_t) const { return false; } - result_type operator()(const array *lv, const array *rv) const { + result_type operator()(variant_span lv, variant_span rv) const { return compare_sequence(lv, rv, *this); } - result_type operator()(std::string_view, std::nullptr_t) const { + result_type operator()(const managed_string_view&, std::nullptr_t) const { return false; } - result_type operator()(std::nullptr_t, std::string_view) const { + result_type operator()(std::nullptr_t, const managed_string_view&) const { return false; } - template result_type operator()(const T &lhs, const T &rhs) const { + template + result_type operator()(const T &lhs, const T &rhs) const { return rhs < lhs; } }; -template <> struct operator_impl : relational_operator { +template <> +struct operator_impl : relational_operator { using relational_operator::result_type; result_type operator()(const std::nullptr_t, std::nullptr_t) const { return true; } - result_type operator()(const array *lv, const array *rv) const { + result_type operator()(variant_span lv, variant_span rv) const { return compare_sequence(lv, rv, *this); } - result_type operator()(std::string_view lhs, std::nullptr_t) const { + result_type operator()(const managed_string_view& lhs, std::nullptr_t) const { return lhs.empty(); } - result_type operator()(std::nullptr_t, std::string_view rhs) const { + result_type operator()(std::nullptr_t, const managed_string_view& rhs) const { return rhs.empty(); } - template result_type operator()(const T &lhs, const T &rhs) const { + template + result_type operator()(const T &lhs, const T &rhs) const { return lhs <= rhs; } }; -template <> struct operator_impl : relational_operator { +template <> +struct operator_impl : relational_operator { using relational_operator::result_type; result_type operator()(const std::nullptr_t, std::nullptr_t) const { return true; } - result_type operator()(const array *lv, const array *rv) const { + result_type operator()(variant_span lv, variant_span rv) const { return compare_sequence(lv, rv, *this); } - result_type operator()(std::string_view lhs, std::nullptr_t) const { + result_type operator()(const managed_string_view& lhs, std::nullptr_t) const { return lhs.empty(); } - result_type operator()(std::nullptr_t, std::string_view rhs) const { + result_type operator()(std::nullptr_t, const managed_string_view& rhs) const { return rhs.empty(); } - template result_type operator()(const T &lhs, const T &rhs) const { + template + result_type operator()(const T &lhs, const T &rhs) const { return rhs <= lhs; } }; -template <> struct operator_impl : arithmetic_operator { +template <> +struct operator_impl : arithmetic_operator { using arithmetic_operator::result_type; result_type operator()(std::nullptr_t, std::nullptr_t) const { - return to_expr(nullptr); + return to_value(nullptr); } - template result_type operator()(const T &lhs, const T &rhs) const { - return to_expr(lhs + rhs); + template + result_type operator()(const T &lhs, const T &rhs) const { + return to_value(lhs + rhs); } }; -template <> struct operator_impl : arithmetic_operator { +template <> +struct operator_impl : arithmetic_operator { using arithmetic_operator::result_type; result_type operator()(std::nullptr_t, std::nullptr_t) const { - return to_expr(nullptr); + return to_value(nullptr); } - template result_type operator()(const T &lhs, const T &rhs) const { - return to_expr(lhs - rhs); + template + result_type operator()(const T &lhs, const T &rhs) const { + return to_value(lhs - rhs); } }; -template <> struct operator_impl : arithmetic_operator { +template <> +struct operator_impl : arithmetic_operator { using arithmetic_operator::result_type; result_type operator()(std::nullptr_t, std::nullptr_t) const { - return to_expr(nullptr); + return to_value(nullptr); } - template result_type operator()(const T &lhs, const T &rhs) const { - return to_expr(lhs * rhs); + template + result_type operator()(const T &lhs, const T &rhs) const { + return to_value(lhs * rhs); } }; -template <> struct operator_impl : arithmetic_operator { +template <> +struct operator_impl : arithmetic_operator { using arithmetic_operator::result_type; result_type operator()(std::nullptr_t, std::nullptr_t) const { - return to_expr(nullptr); + return to_value(nullptr); } result_type operator()(double lhs, double rhs) const { double res = lhs / rhs; // if (isInteger(res)) return toInt(res); - return to_expr(res); + return to_value(res); } - template result_type operator()(Int_t lhs, Int_t rhs) const { - if (lhs % rhs) - return (*this)(double(lhs), double(rhs)); + template + result_type operator()(Int_t lhs, Int_t rhs) const { + if (lhs % rhs) return (*this)(double(lhs), double(rhs)); - return to_expr(lhs / rhs); + return to_value(lhs / rhs); } }; -template <> struct operator_impl : integer_arithmetic_operator { +template <> +struct operator_impl : integer_arithmetic_operator { using integer_arithmetic_operator::result_type; std::nullptr_t operator()(std::nullptr_t, std::nullptr_t) const { return nullptr; } - template result_type operator()(const T &lhs, const T &rhs) const { - if (rhs == 0) - return to_expr(nullptr); + template + result_type operator()(const T &lhs, const T &rhs) const { + if (rhs == 0) return to_value(nullptr); - return to_expr(lhs % rhs); + return to_value(lhs % rhs); } }; -template <> struct operator_impl : arithmetic_operator { +template <> +struct operator_impl : arithmetic_operator { using arithmetic_operator::result_type; result_type operator()(const std::nullptr_t, std::nullptr_t) const { return nullptr; } - template result_type operator()(const T &lhs, const T &rhs) const { - return to_expr(std::min(lhs, rhs)); + template + result_type operator()(const T &lhs, const T &rhs) const { + return to_value(std::min(lhs, rhs)); } }; -template <> struct operator_impl : arithmetic_operator { +template <> +struct operator_impl : arithmetic_operator { using arithmetic_operator::result_type; result_type operator()(const std::nullptr_t, std::nullptr_t) const { return nullptr; } - template result_type operator()(const T &lhs, const T &rhs) const { - return to_expr(std::max(lhs, rhs)); + template + result_type operator()(const T &lhs, const T &rhs) const { + return to_value(std::max(lhs, rhs)); } }; -template <> struct operator_impl { +template <> +struct operator_impl { using result_type = bool; - result_type operator()(expr &val) const { return falsy(val); } + result_type operator()(any_value val) const { return falsy(val); } }; -template <> struct operator_impl { +template <> +struct operator_impl { using result_type = bool; - result_type operator()(expr &val) const { return truthy(val); } + result_type operator()(any_value val) const { return truthy(val); } }; -template <> struct operator_impl : string_operator_non_destructive { +template <> +struct operator_impl : string_operator_non_destructive { using string_operator_non_destructive::result_type; - result_type operator()(std::string_view lhs, std::string_view rhs) const { - json::string tmp; + result_type operator()(const managed_string_view& lhs, const managed_string_view& rhs) const { + std::string tmp; tmp.reserve(lhs.size() + rhs.size()); tmp.append(lhs.begin(), lhs.end()); tmp.append(rhs.begin(), rhs.end()); - return to_expr(std::move(tmp)); + return to_value(managed_string_view(std::move(tmp))); } + }; /// implements the string mode of the membership operator /// the mode testing element in an array is implemented /// in the evaluator::visit function. -template <> struct operator_impl : string_operator_non_destructive { +template <> +struct operator_impl : string_operator_non_destructive { using string_operator_non_destructive::result_type; - result_type operator()(std::string_view lhs, std::string_view rhs) const { + result_type operator()(const managed_string_view& lhs, const managed_string_view& rhs) const { const bool res = (rhs.find(lhs) != json::string::npos); - return to_expr(res); + return to_value(res); } }; + #if WITH_JSON_LOGIC_CPP_EXTENSIONS template <> struct operator_impl - : string_operator_non_destructive // \todo the conversion rules differ + : string_operator_non_destructive // \todo the conversion rules differ { using string_operator_non_destructive::result_type; - result_type operator()(std::string_view lhs, std::string_view rhs) const { + result_type operator()(const managed_string_view& lhs, const managed_string_view& rhs) const { std::regex rgx(lhs.c_str(), lhs.size()); - return to_expr(std::regex_search(rhs.begin(), rhs.end(), rgx)); + return to_value(std::regex_search(rhs.begin(), rhs.end(), rgx)); } }; #endif /* WITH_JSON_LOGIC_CPP_EXTENSIONS */ -template <> struct operator_impl : array_operator { +template <> +struct operator_impl : array_operator { using array_operator::result_type; - result_type operator()(const array *ll, const array *rr) const { - // note, to use the lhs directly, it would need to be released - // from its any_expr - auto *lhs = const_cast(ll); - auto *rhs = const_cast(rr); - array &res = deref(new array); - - { - oper::container_type &opers = res.operands(); - - opers.swap(lhs->operands()); + result_type operator()(const variant_span ll, const variant_span rr) const { + std::vector values; - oper::container_type &ropers = rhs->operands(); + values.reserve(ll.size() + rr.size()); - auto beg = std::make_move_iterator(ropers.begin()); - auto lim = std::make_move_iterator(ropers.end()); + std::copy(ll.begin(), ll.end(), std::back_inserter(values)); + std::copy(rr.begin(), rr.end(), std::back_inserter(values)); - opers.insert(opers.end(), beg, lim); - } - - return any_expr(&res); + return &mk_array_value(std::move(values)); } }; +using variant_logger = std::function; + struct evaluator : forwarding_visitor { - evaluator(variable_accessor varAccess, std::ostream &out) + evaluator(variable_accessor varAccess, variant_logger& out) : vars(std::move(varAccess)), logger(out), calcres(nullptr) {} void visit(const equal &) final; @@ -2184,25 +2570,29 @@ struct evaluator : forwarding_visitor { void visit(const if_expr &) final; - void visit(const null_value &n) final; - void visit(const bool_value &n) final; - void visit(const int_value &n) final; - void visit(const unsigned_int_value &n) final; - void visit(const real_value &n) final; - void visit(const string_value &n) final; + void visit(const null_value &) final; + void visit(const bool_value &) final; + void visit(const int_value &) final; + void visit(const unsigned_int_value &) final; + void visit(const real_value &) final; + void visit(const string_value &) final; + void visit(const array_value &) final; - void visit(const error &n) final; + void visit(const error &) final; #if WITH_JSON_LOGIC_CPP_EXTENSIONS - void visit(const regex_match &n) final; + void visit(const regex_match &) final; #endif /* WITH_JSON_LOGIC_CPP_EXTENSIONS */ +#if ENABLE_OPTIMIZATIONS + void visit(const opt_membership_array &) final; +#endif /* ENABLE_OPTIMIZATIONS */ - any_expr eval(const expr &n); + any_value eval(const expr &); -private: + private: variable_accessor vars; - std::ostream &logger; - any_expr calcres; + variant_logger& logger; + any_value calcres; evaluator(const evaluator &) = delete; evaluator(evaluator &&) = delete; @@ -2226,135 +2616,90 @@ struct evaluator : forwarding_visitor { void reduce_sequence(const oper &n, binary_op_t op); /// computes unary operation on n[0] - template void unary(const oper &n, UnaryOperator pred); + template + void unary(const oper &n, UnaryOperator calc); /// binary operation on all elements (invents an element if none is present) - template void binary(const oper &n, binary_op_t binop); + template + void binary(const oper &n, binary_op_t binop); /// evaluates and unpacks n[argpos] to a fundamental value_base - template - value_t unpack_optional_arg(const oper &n, int argpos, - const value_t &defaultVal); + std::int64_t unpack_optional_int_arg(const oper &n, int argpos, + std::int64_t defaultVal); /// auxiliary missing method - std::size_t missing_aux(array &elems); + std::tuple, std::size_t> + missing_aux(const oper& n, std::size_t arrpos); - template void _value(const ValueNode &val) { - calcres = to_expr(val.value()); + template + void _value(const ValueNode &val) { + calcres = to_value(val.value()); } }; struct sequence_function { - sequence_function(const expr &e, std::ostream &logstream) + sequence_function(const expr &e, variant_logger &logstream) : exp(e), logger(logstream) {} - any_expr operator()(any_expr &&elem) const { - any_expr *elptr = &elem; // workaround, b/c unique_ptr cannot be captured - - evaluator sub{[elptr](const json::value &keyval, int) -> any_expr { - if (const json::string *pkey = keyval.if_string()) { - const json::string &key = *pkey; - - if (key.size() == 0) - return clone_expr(*elptr); - - try { - auto &o = down_cast(**elptr); - - if (auto pos = o.find(key); pos != o.end()) - return clone_expr(pos->second); - } catch (const type_error &) { - } + any_value operator()(const any_value &elem) const { + evaluator sub{[&elem](value_variant keyval, int) -> any_value { + if (managed_string_view* pkey = std::get_if(&keyval)) { + if (pkey->size() == 0) + return elem; } - return to_expr(nullptr); + return nullptr; }, logger}; return sub.eval(exp); } -private: + private: const expr &exp; - std::ostream &logger; + variant_logger& logger; }; struct sequence_predicate : sequence_function { using sequence_function::sequence_function; - bool operator()(any_expr &&elem) const { - return truthy(sequence_function::operator()(std::move(elem))); - } -}; - -struct sequence_predicate_nondestructive : sequence_function { - using sequence_function::sequence_function; - - bool operator()(any_expr &&elem) const { - return truthy(sequence_function::operator()(clone_expr(elem))); + bool operator()(const any_value &elem) const { + return truthy(sequence_function::operator()(elem)); } }; -/* - template - any_expr - accumulate_move(InputIterator pos, InputIterator lim, any_expr accu, - BinaryOperation binop) - { - for ( ; pos != lim; ++pos) - accu = binop(std::move(accu), std::move(*first)); - - return accu; - } -*/ struct sequence_reduction { - sequence_reduction(expr &e, std::ostream &logstream) + sequence_reduction(expr &e, variant_logger& logstream) : exp(e), logger(logstream) {} - // for compatibility reasons, the first argument is passed in as - // templated && ref. - // g++ -std=c++17 passes in a & ref, while g++ -std=c++20 passes - // in a &&. - // the templated && together with reference collapsing make the code portable - // across standard versions. - // \note const ValueExprT& would also work, but currently the - // visitor calls in clone_expr require a non-const reference. - template - any_expr operator()(ValueExprT &&accu, any_expr elem) const { - any_expr *acptr = &accu; // workaround, b/c unique_ptr cannot be captured - any_expr *elptr = &elem; // workaround, b/c unique_ptr cannot be captured - - evaluator sub{[acptr, elptr](const json::value &keyval, int) -> any_expr { - if (const json::string *pkey = keyval.if_string()) { - if (*pkey == "current") - return clone_expr(*elptr); - - if (*pkey == "accumulator") - return clone_expr(*acptr); + any_value operator()(any_value accu, any_value elem) const { + evaluator sub{[&accu, &elem](value_variant keyval, int) -> any_value { + if (const managed_string_view *pkey = std::get_if(&keyval)) { + CXX_LIKELY; + if (*pkey == "current") return elem; + if (*pkey == "accumulator") return accu; } - - return to_expr(nullptr); + return to_value(nullptr); }, logger}; return sub.eval(exp); } -private: + private: expr &exp; - std::ostream &logger; + variant_logger& logger; }; -template -value_t evaluator::unpack_optional_arg(const oper &n, int argpos, - const value_t &defaultVal) { +std::int64_t evaluator::unpack_optional_int_arg(const oper &n, int argpos, + std::int64_t defaultVal) { if (std::size_t(argpos) >= n.size()) { CXX_UNLIKELY; return defaultVal; } - return unpack_value(*eval(n.operand(argpos))); + return unpack_value(eval(n.operand(argpos))); } template @@ -2363,9 +2708,9 @@ void evaluator::unary(const oper &n, unary_predicate_t pred) { const int num = n.num_evaluated_operands(); assert(num == 1); - const bool res = pred(*eval(n.operand(0))); + const bool res = pred(eval(n.operand(0))); - calcres = to_expr(res); + calcres = res; } template @@ -2374,16 +2719,16 @@ void evaluator::binary(const oper &n, binary_op_t binop) { assert(num == 1 || num == 2); int idx = -1; - any_expr lhs; + any_value lhs; if (num == 2) { CXX_LIKELY; lhs = eval(n.operand(++idx)); } else { - lhs = to_expr(std::int64_t(0)); + lhs = std::int64_t(0); } - any_expr rhs = eval(n.operand(++idx)); + any_value rhs = eval(n.operand(++idx)); calcres = compute(lhs, rhs, binop); } @@ -2394,15 +2739,16 @@ void evaluator::reduce_sequence(const oper &n, binary_op_t op) { assert(num >= 1); int idx = -1; - any_expr res = eval(n.operand(++idx)); - res = convert(std::move(res), op); + any_value tmp0 = eval(n.operand(++idx)); + any_value res = convert(std::move(tmp0), op); while (idx != (num - 1)) { - any_expr rhs = eval(n.operand(++idx)); + any_value tmp1 = eval(n.operand(++idx)); + any_value rhs = convert(std::move(tmp1), op); + any_value tmp2 = compute(res, rhs, op); - rhs = convert(std::move(rhs), op); - res = compute(res, rhs, op); + res = std::move(tmp2); } calcres = std::move(res); @@ -2416,20 +2762,16 @@ void evaluator::eval_pair_short_circuit(const oper &n, bool res = true; int idx = -1; - any_expr rhs = eval(n.operand(++idx)); - assert(rhs.get()); + any_value rhs = eval(n.operand(++idx)); while (res && (idx != (num - 1))) { - any_expr lhs = std::move(rhs); - assert(lhs.get()); + any_value lhs = std::move(rhs); rhs = eval(n.operand(++idx)); - assert(rhs.get()); - res = compute(lhs, rhs, pred); } - calcres = to_expr(res); + calcres = res; } void evaluator::eval_short_circuit(const oper &n, bool val) { @@ -2441,24 +2783,23 @@ void evaluator::eval_short_circuit(const oper &n, bool val) { } int idx = -1; - any_expr oper = eval(n.operand(++idx)); + any_value tmpval = eval(n.operand(++idx)); //~ std::cerr << idx << ") " << oper << std::endl; - bool found = (idx == num - 1) || (truthy(*oper) == val); + bool found = (idx == num - 1) || (truthy(tmpval) == val); // loop until *aa == val or when *aa is the last valid element while (!found) { - oper = eval(n.operand(++idx)); - //~ std::cerr << idx << ") " << oper << std::endl; + tmpval = eval(n.operand(++idx)); - found = (idx == (num - 1)) || (truthy(*oper) == val); + found = (idx == (num - 1)) || (truthy(tmpval) == val); } - calcres = std::move(oper); + calcres = std::move(tmpval); } -any_expr evaluator::eval(const expr &n) { - any_expr res; +any_value evaluator::eval(const expr &n) { + any_value res; n.accept(*this); res.swap(calcres); @@ -2548,40 +2889,42 @@ void evaluator::visit(const membership &n) { assert(n.num_evaluated_operands() >= 1); // lhs in rhs - rhs is a possibly large set. - any_expr lhs = eval(n.operand(0)); - any_expr rhs = eval(n.operand(1)); - - // std::cout << "in membership: lhs = " << lhs << ", rhs = " << rhs << - // std::endl; + any_value lhs = eval(n.operand(0)); + any_value rhs = eval(n.operand(1)); - auto array_op = [&lhs](array &arrop) -> any_expr { - auto beg = arrop.begin(); - auto lim = arrop.end(); - auto isEqual = [&lhs](any_expr &rhs) -> bool { - return compute(lhs, rhs, operator_impl{}); - }; + auto array_op = [&lhs](array_value const* v) -> any_value { + variant_span spn = element_range(v); + auto const lim = spn.end(); - return to_expr(std::find_if(beg, lim, isEqual) != lim); + return std::find(spn.begin(), lim, lhs) != lim; }; - auto string_op = [&lhs, &rhs]() -> any_expr { + auto string_op = [&lhs, &rhs]() -> any_value { try { return compute(lhs, rhs, operator_impl{}); } catch (const type_error &) { } - return to_expr(false); + return to_value(false); }; - calcres = with_type(rhs.get(), array_op, string_op); + calcres = with_type(rhs, array_op, string_op); +} + +#if ENABLE_OPTIMIZATIONS +void evaluator::visit(const opt_membership_array &n) { + any_value lhs = eval(n.operand(0)); + + calcres = n.elems().count(lhs) > 0; } +#endif /*ENABLE_OPTIMIZATIONS*/ void evaluator::visit(const substr &n) { assert(n.num_evaluated_operands() >= 1); - json::string str = unpack_value(*eval(n.operand(0))); - std::int64_t ofs = unpack_optional_arg(n, 1, 0); - std::int64_t cnt = unpack_optional_arg(n, 2, 0); + managed_string_view str = unpack_value(eval(n.operand(0))); + std::int64_t ofs = unpack_optional_int_arg(n, 1, 0); + std::int64_t cnt = unpack_optional_int_arg(n, 2, 0); if (ofs < 0) { CXX_UNLIKELY; @@ -2593,23 +2936,20 @@ void evaluator::visit(const substr &n) { cnt = std::max(std::int64_t(str.size()) - ofs + cnt, std::int64_t(0)); } - calcres = to_expr(json::string{str.subview(ofs, cnt)}); + calcres = to_value(str.substr(ofs, cnt)); } void evaluator::visit(const array &n) { - oper::container_type elems; - evaluator *self = this; + std::vector elems; - // \todo consider making arrays lazy - std::transform( - n.begin(), n.end(), std::back_inserter(elems), - [self](const any_expr &exp) -> any_expr { return self->eval(*exp); }); + elems.reserve(n.num_evaluated_operands()); - array &res = deref(new array); + evaluator *self = this; + auto eval_array_elems = [self](const any_expr &exp) -> any_value { return self->eval(*exp); }; - res.set_operands(std::move(elems)); + std::transform(n.begin(), n.end(), std::back_inserter(elems), eval_array_elems); - calcres = any_expr(&res); + calcres = &mk_array_value( std::move(elems) ); } void evaluator::visit(const merge &n) { @@ -2617,97 +2957,118 @@ void evaluator::visit(const merge &n) { } void evaluator::visit(const reduce &n) { - any_expr arr = eval(n.operand(0)); + any_value arr = eval(n.operand(0)); expr &expr = n.operand(1); - any_expr accu = eval(n.operand(2)); - any_expr *acptr = &accu; - - auto op = [&expr, acptr, - calclogger = &this->logger](array &arrop) -> any_expr { - // non destructive predicate is required for evaluating and copying - return std::accumulate(std::make_move_iterator(arrop.begin()), - std::make_move_iterator(arrop.end()), - std::move(*acptr), - sequence_reduction{expr, *calclogger}); + any_value accu = eval(n.operand(2)); + + auto op = [&expr, accu, calclogger = &this->logger] + (array_value const* v) -> any_value { + variant_span spn = element_range(v); + return std::accumulate( spn.begin(), spn.end(), + std::move(accu), + sequence_reduction{expr, *calclogger} + ); }; - auto mkInvalid = []() -> any_expr { return nullptr; }; + auto mkInvalid = []() -> any_value { return nullptr; }; - calcres = with_type(arr.get(), op, mkInvalid); + calcres = with_type(arr, op, mkInvalid); } +array_value const* +empty_array_value() { return &mk_array_value(); } + void evaluator::visit(const map &n) { - any_expr arr = eval(n.operand(0)); - auto mapper = [&n, &arr, - calclogger = &this->logger](array &arrop) -> any_expr { + any_value arr = eval(n.operand(0)); + auto mapper = [&n, &arr, calclogger = &this->logger] + (array_value const* v) -> array_value const* { + variant_span spn = element_range(v); expr &expr = n.operand(1); - oper::container_type mapped_elements; + std::vector mapped_elements; + + mapped_elements.reserve(spn.size()); - std::transform(std::make_move_iterator(arrop.begin()), - std::make_move_iterator(arrop.end()), + std::transform(spn.begin(), spn.end(), std::back_inserter(mapped_elements), sequence_function{expr, *calclogger}); - arrop.set_operands(std::move(mapped_elements)); - return std::move(arr); + return &mk_array_value(std::move(mapped_elements)); }; - calcres = with_type(arr.get(), mapper, - []() -> any_expr { return any_expr{new array}; }); + calcres = with_type(arr, mapper, &empty_array_value); } void evaluator::visit(const filter &n) { - any_expr arr = eval(n.operand(0)); - auto filter = [&n, &arr, - calclogger = &this->logger](array &arrop) -> any_expr { + any_value arr = eval(n.operand(0)); + auto filter = [&n, &arr, calclogger = &this->logger] + (array_value const* v) -> array_value const* { + variant_span spn = element_range(v); expr &expr = n.operand(1); - oper::container_type filtered_elements; + std::vector filtered_elements; // non destructive predicate is required for evaluating and copying - std::copy_if(std::make_move_iterator(arrop.begin()), - std::make_move_iterator(arrop.end()), + std::copy_if(spn.begin(), spn.end(), std::back_inserter(filtered_elements), - sequence_predicate_nondestructive{expr, *calclogger}); + sequence_predicate{expr, *calclogger}); - arrop.set_operands(std::move(filtered_elements)); - return std::move(arr); + return &mk_array_value(std::move(filtered_elements)); }; - calcres = with_type(arr.get(), filter, - []() -> any_expr { return any_expr{new array}; }); + calcres = with_type(arr, filter, &empty_array_value); } void evaluator::visit(const all &n) { - any_expr arr = eval(n.operand(0)); - array &elems = down_cast(*arr); // evaluated elements - expr &expr = n.operand(1); - const bool res = std::all_of(std::make_move_iterator(elems.begin()), - std::make_move_iterator(elems.end()), - sequence_predicate{expr, logger}); + any_value arr = eval(n.operand(0)); + + auto all_of = [&n, &arr, calclogger = &this->logger] + (array_value const* v) -> bool { + variant_span spn = element_range(v); + expr &expr = n.operand(1); + + return std::all_of( spn.begin(), spn.end(), + sequence_predicate{expr, *calclogger} + ); + }; + + auto mismatch_false = []()->bool { return false; }; - calcres = to_expr(res); + calcres = with_type(arr, all_of, mismatch_false); } void evaluator::visit(const none &n) { - any_expr arr = eval(n.operand(0)); - array &elems = down_cast(*arr); // evaluated elements - expr &expr = n.operand(1); - const bool res = std::none_of(std::make_move_iterator(elems.begin()), - std::make_move_iterator(elems.end()), - sequence_predicate{expr, logger}); + any_value arr = eval(n.operand(0)); + + auto none_of = [&n, &arr, calclogger = &this->logger] + (array_value const* v) -> bool { + variant_span spn = element_range(v); + expr &expr = n.operand(1); + + return std::none_of( spn.begin(), spn.end(), + sequence_predicate{expr, *calclogger} + ); + }; + + auto mismatch_true = []()->bool { return true; }; - calcres = to_expr(res); + calcres = with_type(arr, none_of, mismatch_true); } void evaluator::visit(const some &n) { - any_expr arr = eval(n.operand(0)); - array &elems = down_cast(*arr); // evaluated elements - expr &expr = n.operand(1); - const bool res = std::any_of(std::make_move_iterator(elems.begin()), - std::make_move_iterator(elems.end()), - sequence_predicate{expr, logger}); + any_value arr = eval(n.operand(0)); + + auto any_of = [&n, &arr, calclogger = &this->logger] + (array_value const* v) -> bool { + variant_span spn = element_range(v); + expr &expr = n.operand(1); + + return std::any_of( spn.begin(), spn.end(), + sequence_predicate{expr, *calclogger} + ); + }; + + auto mismatch_false = []()->bool { return false; }; - calcres = to_expr(res); + calcres = with_type(arr, any_of, mismatch_false); } void evaluator::visit(const error &) { unsupported(); } @@ -2715,80 +3076,81 @@ void evaluator::visit(const error &) { unsupported(); } void evaluator::visit(const var &n) { assert(n.num_evaluated_operands() >= 1); - any_expr elm = eval(n.operand(0)); - value_base &val = down_cast(*elm); + any_value elm = eval(n.operand(0)); try { - calcres = vars(val.to_json(), n.num()); + calcres = vars(elm, n.num()); } catch (const variable_resolution_error &) { calcres = (n.num_evaluated_operands() > 1) ? eval(n.operand(1)) - : to_expr(nullptr); + : to_value(nullptr); } } -std::size_t evaluator::missing_aux(array &elems) { - auto avail = [calc = this](const any_expr &v) -> bool { +std::tuple, std::size_t> +evaluator::missing_aux(const oper& n, std::size_t arrpos) { + any_value arr = eval(n.operand(arrpos)); + std::vector tmpval; + + auto non_array_alt = [&tmpval, &arr, &n, pos=arrpos+1, calc = this]() -> variant_span + { + std::transform( std::next(n.begin(), pos), n.end(), + std::back_inserter(tmpval), + [&calc](const any_expr &e) -> any_value { return calc->eval(*e); } + ); + + return element_range(tmpval); + }; + + auto unwrapped = [](array_value const* v) -> variant_span { return element_range(v); }; + + variant_span elems = with_type(arr, unwrapped, non_array_alt); + + auto notavail = [calc = this](const any_value &val) -> bool { try { - const value_base &val = down_cast(*v); - any_expr res = calc->vars(val.to_json(), COMPUTED_VARIABLE_NAME); + any_value res = calc->vars(val, COMPUTED_VARIABLE_NAME); - // value-missing := res == 0 || *res is a null_value - // return !value-missing - return res && (may_down_cast(*res) == nullptr); + return null_equivalent(res); } catch (const jsonlogic::variable_resolution_error &) { - return false; + return true; } }; - array::iterator beg = elems.begin(); - array::iterator lim = elems.end(); - array::iterator pos = std::remove_if(beg, lim, avail); - std::size_t res = std::distance(pos, lim); + std::vector res; - elems.operands().erase(pos, lim); - return res; + std::copy_if( std::make_move_iterator(elems.begin()), std::make_move_iterator(elems.end()), + std::back_inserter(res), + notavail + ); + + return { std::move(res), + elems.size() - res.size() + }; } void evaluator::visit(const missing &n) { - any_expr arg = eval(n.operand(0)); - auto non_array_alt = [&arg, &n, calc = this]() -> array & { - array &res = deref(new array); - - res.operands().emplace_back(std::move(arg)); - arg.reset(&res); - - std::transform( - std::next(n.begin()), n.end(), std::back_inserter(res.operands()), - [&calc](const any_expr &e) -> any_expr { return calc->eval(*e); }); - - return res; - }; + std::vector res; - array &elems = with_type( - arg.get(), [](array &arr) -> array & { return arr; }, // ignore other args - non_array_alt); + std::tie(res, std::ignore) = missing_aux(n, 0); - missing_aux(elems); - calcres = std::move(arg); + calcres = &mk_array_value(std::move(res)); } void evaluator::visit(const missing_some &n) { const std::uint64_t minreq = unpack_value(eval(n.operand(0))); - any_expr arr = eval(n.operand(1)); - array &elems = down_cast(*arr); // evaluated elements - std::size_t avail = missing_aux(elems); + + auto [res, avail] = missing_aux(n, 1); if (avail >= minreq) - elems.operands().clear(); + res.clear(); - calcres = std::move(arr); + calcres = &mk_array_value(std::move(res)); } void evaluator::visit(const if_expr &n) { const int num = n.num_evaluated_operands(); if (num == 0) { - calcres = to_expr(nullptr); + calcres = to_value(nullptr); return; } @@ -2804,7 +3166,7 @@ void evaluator::visit(const if_expr &n) { pos += 2; } - calcres = (pos < num) ? eval(n.operand(pos)) : to_expr(nullptr); + calcres = (pos < num) ? eval(n.operand(pos)) : to_value(nullptr); } void evaluator::visit(const log &n) { @@ -2812,9 +3174,11 @@ void evaluator::visit(const log &n) { calcres = eval(n.operand(0)); - logger << calcres << std::endl; + logger(calcres); } +void evaluator::visit(const array_value &n) { calcres = new array_value(n); } + void evaluator::visit(const null_value &n) { _value(n); } void evaluator::visit(const bool_value &n) { _value(n); } void evaluator::visit(const int_value &n) { _value(n); } @@ -2822,14 +3186,14 @@ void evaluator::visit(const unsigned_int_value &n) { _value(n); } void evaluator::visit(const real_value &n) { _value(n); } void evaluator::visit(const string_value &n) { _value(n); } -any_expr eval_path(const json::string &path, const json::object *obj) { - if (obj != nullptr) { - if (const auto *pos = obj->find(path); pos != obj->end()) - return jsonlogic::to_expr(pos->value()); +any_value eval_path(const managed_string_view& path, const json::object *obj) { + if (obj) { + if (auto pos = obj->find(path); pos != obj->end()) + return jsonlogic::to_value(pos->value()); if (std::size_t pos = path.find('.'); pos != json::string::npos) { - json::string selector = path.subview(0, pos); - json::string suffix = path.subview(pos + 1); + managed_string_view selector = path.substr(0, pos); + managed_string_view suffix = path.substr(pos + 1); return eval_path(suffix, obj->at(selector).if_object()); } @@ -2838,66 +3202,126 @@ any_expr eval_path(const json::string &path, const json::object *obj) { throw variable_resolution_error{"in logic.cc::eval_path."}; } -template any_expr eval_index(IntT idx, const json::array &arr) { - return jsonlogic::to_expr(arr[idx]); +template +any_value eval_index(IntT idx, const json::array &arr) { + return jsonlogic::to_value(arr[idx]); } -} // namespace -any_expr apply(expr &exp, const variable_accessor &vars) { - evaluator ev{vars, std::cerr}; +any_value apply(const expr &exp, const variable_accessor &vars) { + variant_logger logger = [](const value_variant& val) -> void { std::cerr << val << std::endl; }; + evaluator ev{vars, logger}; return ev.eval(exp); } -any_expr apply(const any_expr &exp, const variable_accessor &var_accessor) { - assert(exp.get()); - return apply(*exp, var_accessor); +any_value apply(logic_data& rule, const variable_accessor &vars) { + assert(rule.syntax_tree().get()); + return jsonlogic::apply(*rule.syntax_tree(), vars); +} + +any_value apply(logic_data& rule) { + auto no_variables = [](value_variant, int) -> any_value { throw std::logic_error{"variable accessor not available"}; }; + + return jsonlogic::apply(rule, no_variables); } -any_expr apply(const any_expr &exp) { - return jsonlogic::apply(exp, [](const json::value &, int) -> any_expr { - throw std::logic_error{"variable accessor not available"}; - }); +any_value apply(logic_data& rule, std::vector vars) { + return jsonlogic::apply(rule, variant_accessor(std::move(vars))); } -variable_accessor data_accessor(json::value data) { - return [data = std::move(data)](const json::value &keyval, int) -> any_expr { - if (const json::string *ppath = keyval.if_string()) { +} // namespace + + + +variable_accessor json_accessor(json::value data) { + return [data = std::move(data)](value_variant keyval, int) -> any_value { + if (const managed_string_view *ppath = std::get_if(&keyval)) { //~ std::cerr << *ppath << std::endl; return ppath->size() ? eval_path(*ppath, data.if_object()) - : to_expr(data); + : to_value(data); } - if (const std::int64_t *pidx = keyval.if_int64()) + if (const std::int64_t *pidx = std::get_if(&keyval)) return eval_index(*pidx, data.as_array()); - if (const std::uint64_t *pidx = keyval.if_uint64()) - return eval_index(*pidx, data.as_array()); + if (const std::uint64_t *puidx = std::get_if(&keyval)) + return eval_index(*puidx, data.as_array()); throw std::logic_error{"jsonlogic - unsupported var access"}; }; } -any_expr apply(json::value rule, json::value data) { - logic_rule logic(create_logic(std::move(rule))); - - return jsonlogic::apply(logic.syntax_tree(), data_accessor(std::move(data))); -} - -variable_accessor data_accessor(std::vector vars) { - return [vars = std::move(vars)](const json::value &, int idx) -> any_expr { +variable_accessor variant_accessor(std::vector vars) { + return [vars = std::move(vars)](value_variant, int idx) -> any_value { if ((idx >= 0) && (std::size_t(idx) < vars.size())) { CXX_LIKELY; - return to_expr(vars[idx]); + return vars[idx]; } throw std::logic_error{"unable to access (computed) variable"}; }; } -any_expr apply(const any_expr &exp, std::vector vars) { - return jsonlogic::apply(exp, data_accessor(std::move(vars))); + + +std::ostream& operator<<(std::ostream& os, const value_variant& val) +{ + switch (val.index()) { + case mono_variant: { + os << ""; + break; + } + + case null_variant: { + os << "null"; + break; + } + + case bool_variant: { + os << (std::get(val) ? "true" : "false"); + break; + } + + case sint_variant: { + os << std::get(val); + break; + } + + case uint_variant: { + os << std::get(val); + break; + } + + case real_variant: { + // print the double as json would print it so that strings + // are comparable. + os << json::value(std::get(val)); + break; + } + + case strv_variant: { + os << '"' << std::get(val).view() << '"'; + break; + } + + case sequ_variant: { + const char* sep = ""; + os << '['; + for (const value_variant& v : std::get(val)->value()) { + os << sep << v; + sep = ","; + } + os << ']'; + break; + } + + default: + // did val hold a valid value? + os << ""; + } + + return os; } namespace { @@ -2905,11 +3329,11 @@ namespace { struct value_printer : forwarding_visitor { explicit value_printer(std::ostream &stream) : os(stream) {} - void prn(const json::value &val) { os << val; } + void prn(value_variant val) { os << val; } void visit(const expr &) final { unsupported(); } - void visit(const value_base &n) final { prn(n.to_json()); } + void visit(const value_base &n) final { prn(n.to_variant()); } void visit(const array &n) final { bool first = true; @@ -2927,7 +3351,7 @@ struct value_printer : forwarding_visitor { os << "]"; } -private: + private: std::ostream &os; }; @@ -2943,181 +3367,35 @@ std::ostream &operator<<(std::ostream &os, any_expr &n) { expr &oper::operand(int n) const { return deref(this->at(n).get()); } // -// json_logic_rule - -any_expr const &logic_rule::syntax_tree() const { return std::get<0>(*this); } +// logic_rule -std::vector const &logic_rule::variable_names() const { - return std::get<1>(*this); -} - -bool logic_rule::has_computed_variable_names() const { - return std::get<2>(*this); -} +logic_rule::logic_rule(std::unique_ptr&& rule_data) +: data(std::move(rule_data)) +{} -any_expr logic_rule::apply() const { return jsonlogic::apply(syntax_tree()); } +logic_rule::logic_rule(logic_rule&&) = default; +logic_rule& logic_rule::operator=(logic_rule&&) = default; +logic_rule::~logic_rule() = default; -any_expr logic_rule::apply(const variable_accessor &var_accessor) const { - return jsonlogic::apply(syntax_tree(), var_accessor); -} +logic_data& logic_rule::internal_data() { return *data; } -any_expr logic_rule::apply(std::vector vars) const { - return jsonlogic::apply(syntax_tree(), std::move(vars)); +std::vector const &logic_rule::variable_names() const { + return data->variable_names(); } -} // namespace jsonlogic - -#if UNSUPPORTED_SUPPLEMENTAL - -/// traverses the children of a node; does logical_not traverse grandchildren -void traverseChildren(visitor &v, const oper &node); -void traverseAllChildren(visitor &v, const oper &node); -void traverseChildrenReverse(visitor &v, const oper &node); - -// only operators have children -void _traverseChildren(visitor &v, oper::const_iterator aa, - oper::const_iterator zz) { - std::for_each(aa, zz, [&v](const any_expr &e) -> void { e->accept(v); }); +bool logic_rule::has_computed_variable_names() const { + return data->has_computed_variable_names(); } -void traverseChildren(visitor &v, const oper &node) { - oper::const_iterator aa = node.begin(); +any_value logic_rule::apply() { return jsonlogic::apply(*data); } - _traverseChildren(v, aa, aa + node.num_evaluated_operands()); -} -void traverseAllChildren(visitor &v, const oper &node) { - _traverseChildren(v, node.begin(), node.end()); +any_value logic_rule::apply(const variable_accessor &var_accessor) { + return jsonlogic::apply(*data, var_accessor); } -void traverseChildrenReverse(visitor &v, const oper &node) { - oper::const_reverse_iterator zz = node.crend(); - oper::const_reverse_iterator aa = zz - node.num_evaluated_operands(); - - std::for_each(aa, zz, [&v](const any_expr &e) -> void { e->accept(v); }); +any_value logic_rule::apply(std::vector vars) { + return jsonlogic::apply(*data, std::move(vars)); } -namespace { -struct SAttributeTraversal : visitor { - explicit SAttributeTraversal(visitor &client) : sub(client) {} - - void visit(expr &) final; - void visit(oper &) final; - void visit(equal &) final; - void visit(strict_equal &) final; - void visit(not_equal &) final; - void visit(strict_not_equal &) final; - void visit(less &) final; - void visit(greater &) final; - void visit(less_or_equal &) final; - void visit(greater_or_equal &) final; - void visit(logical_and &) final; - void visit(logical_or &) final; - void visit(logical_not &) final; - void visit(logical_not_not &) final; - void visit(add &) final; - void visit(subtract &) final; - void visit(multiply &) final; - void visit(divide &) final; - void visit(modulo &) final; - void visit(min &) final; - void visit(max &) final; - void visit(map &) final; - void visit(reduce &) final; - void visit(filter &) final; - void visit(all &) final; - void visit(none &) final; - void visit(some &) final; - void visit(merge &) final; - void visit(cat &) final; - void visit(substr &) final; - void visit(membership &) final; - void visit(array &n) final; - void visit(var &) final; - void visit(log &) final; - - void visit(if_expr &) final; - - void visit(null_value &n) final; - void visit(bool_value &n) final; - void visit(int_value &n) final; - void visit(unsigned_int_value &n) final; - void visit(real_value &n) final; - void visit(string_value &n) final; - - void visit(error &n) final; - -private: - visitor ⊂ - - template inline void _visit(OperatorNode &n) { - traverseChildren(*this, n); - sub.visit(n); - } - - template inline void _value(ValueNode &n) { sub.visit(n); } -}; - -void SAttributeTraversal::visit(expr &) { throw_type_error(); } -void SAttributeTraversal::visit(oper &) { throw_type_error(); } -void SAttributeTraversal::visit(equal &n) { _visit(n); } -void SAttributeTraversal::visit(strict_equal &n) { _visit(n); } -void SAttributeTraversal::visit(not_equal &n) { _visit(n); } -void SAttributeTraversal::visit(strict_not_equal &n) { _visit(n); } -void SAttributeTraversal::visit(less &n) { _visit(n); } -void SAttributeTraversal::visit(greater &n) { _visit(n); } -void SAttributeTraversal::visit(less_or_equal &n) { _visit(n); } -void SAttributeTraversal::visit(greater_or_equal &n) { _visit(n); } -void SAttributeTraversal::visit(logical_and &n) { _visit(n); } -void SAttributeTraversal::visit(logical_or &n) { _visit(n); } -void SAttributeTraversal::visit(logical_not &n) { _visit(n); } -void SAttributeTraversal::visit(logical_not_not &n) { _visit(n); } -void SAttributeTraversal::visit(add &n) { _visit(n); } -void SAttributeTraversal::visit(subtract &n) { _visit(n); } -void SAttributeTraversal::visit(multiply &n) { _visit(n); } -void SAttributeTraversal::visit(divide &n) { _visit(n); } -void SAttributeTraversal::visit(modulo &n) { _visit(n); } -void SAttributeTraversal::visit(min &n) { _visit(n); } -void SAttributeTraversal::visit(max &n) { _visit(n); } -void SAttributeTraversal::visit(array &n) { _visit(n); } -void SAttributeTraversal::visit(map &n) { _visit(n); } -void SAttributeTraversal::visit(reduce &n) { _visit(n); } -void SAttributeTraversal::visit(filter &n) { _visit(n); } -void SAttributeTraversal::visit(all &n) { _visit(n); } -void SAttributeTraversal::visit(none &n) { _visit(n); } -void SAttributeTraversal::visit(some &n) { _visit(n); } -void SAttributeTraversal::visit(merge &n) { _visit(n); } -void SAttributeTraversal::visit(cat &n) { _visit(n); } -void SAttributeTraversal::visit(substr &n) { _visit(n); } -void SAttributeTraversal::visit(membership &n) { _visit(n); } -void SAttributeTraversal::visit(missing &n) { _visit(n); } -void SAttributeTraversal::visit(missing_some &n) { _visit(n); } -void SAttributeTraversal::visit(var &n) { _visit(n); } -void SAttributeTraversal::visit(log &n) { _visit(n); } - -void SAttributeTraversal::visit(if_expr &n) { _visit(n); } - -#if WITH_JSON_LOGIC_CPP_EXTENSIONS -void SAttributeTraversal::visit(regex_match &n) { _visit(n); } -#endif /* WITH_JSON_LOGIC_CPP_EXTENSIONS */ - -void SAttributeTraversal::visit(null_value &n) { _value(n); } -void SAttributeTraversal::visit(bool_value &n) { _value(n); } -void SAttributeTraversal::visit(int_value &n) { _value(n); } -void SAttributeTraversal::visit(unsigned_int_value &n) { _value(n); } -void SAttributeTraversal::visit(real_value &n) { _value(n); } -void SAttributeTraversal::visit(string_value &n) { _value(n); } - -void SAttributeTraversal::visit(error &n) { sub.visit(n); } -} // namespace - -/// AST traversal function that calls v's visit methods in post-fix -/// order -void traverseInSAttributeOrder(expr &e, visitor &vis); - -void traverseInSAttributeOrder(expr &e, visitor &vis) { - SAttributeTraversal trav{vis}; - - e.accept(trav); -} -#endif /* SUPPLEMENTAL */ +} // namespace jsonlogic diff --git a/cpp/tests/json/in-009.json b/cpp/tests/json/in-009.json new file mode 100644 index 0000000..3d9f8a7 --- /dev/null +++ b/cpp/tests/json/in-009.json @@ -0,0 +1 @@ +{"rule":{"in":[{"var":"x"},["alpha","beta","gamma"]]},"data":{"x": "omega"},"expected":false, "description": "in operator with a string needle not in a static haystack"} diff --git a/cpp/tests/json/in-010.json b/cpp/tests/json/in-010.json new file mode 100644 index 0000000..3e337d3 --- /dev/null +++ b/cpp/tests/json/in-010.json @@ -0,0 +1 @@ +{"rule":{"in":[{"var":"x"},[]]},"data":{"x": 2.4},"expected":false, "description": "in operator with a variable needle in an empty haystack"} diff --git a/cpp/tests/json/merge-001.json b/cpp/tests/json/merge-001.json new file mode 100644 index 0000000..62bab88 --- /dev/null +++ b/cpp/tests/json/merge-001.json @@ -0,0 +1 @@ +{"rule":{"merge":[[1,2],[3,4]]},"expected":[1,2,3,4]} diff --git a/cpp/tests/src/testeval.cpp b/cpp/tests/src/testeval.cpp index 6d6454c..af12f53 100644 --- a/cpp/tests/src/testeval.cpp +++ b/cpp/tests/src/testeval.cpp @@ -1,13 +1,13 @@ #include #include #include -#include #include #include #include #include #include #include +#include enum class ResultStatus : std::uint8_t { NoError = 0, // no error @@ -141,7 +141,7 @@ jsonlogic::value_variant to_value_variant(const bjsn::value &n) { switch (n.kind()) { case bjsn::kind::string: { const bjsn::string &str = n.get_string(); - res = std::string_view(str.data(), str.size()); + res = jsonlogic::managed_string_view(std::string_view(str.data(), str.size())); break; } @@ -165,28 +165,39 @@ jsonlogic::value_variant to_value_variant(const bjsn::value &n) { break; } - case bjsn::kind::null: { - res = std::monostate{}; - break; - } + case bjsn::kind::null: { + res = nullptr; + break; + } - default: - throw std::runtime_error{"cannot convert"}; + default: + throw std::runtime_error{"cannot convert"}; } assert(!res.valueless_by_exception()); return res; } -jsonlogic::any_expr call_apply(settings &config, const bjsn::value &rule, +std::string variant_to_string(const jsonlogic::value_variant& val) +{ + std::stringstream os; + + os << val; + return os.str(); +} + +std::string call_apply(settings &config, const bjsn::value &rule, const bjsn::value &data) { using value_vector = std::vector; - if (config.simple_apply) - return jsonlogic::apply(rule, data); - jsonlogic::logic_rule logic = jsonlogic::create_logic(rule); + if (config.simple_apply) + { + // simple_apply currently not supported; just call apply.. + return variant_to_string(logic.apply(jsonlogic::json_accessor(data))); + } + if (!logic.has_computed_variable_names()) { if (config.verbose) std::cerr << "execute with precomputed value array." << std::endl; @@ -199,10 +210,9 @@ jsonlogic::any_expr call_apply(settings &config, const bjsn::value &rule, // extract all variable values into vector auto const varvalues = - logic.variable_names() | boost::adaptors::transformed(value_maker); - - // NOTE: data's lifetime must extend beyond the call to apply. - return logic.apply(value_vector(varvalues.begin(), varvalues.end())); + logic.variable_names() | std::views::transform(value_maker); + + return variant_to_string(logic.apply(value_vector(varvalues.begin(), varvalues.end()))); } catch (...) { } } @@ -210,7 +220,7 @@ jsonlogic::any_expr call_apply(settings &config, const bjsn::value &rule, if (config.verbose) std::cerr << "falling back to normal apply" << std::endl; - return logic.apply(jsonlogic::data_accessor(data)); + return variant_to_string(logic.apply(jsonlogic::json_accessor(data))); } int main(int argc, const char **argv) { @@ -282,7 +292,7 @@ int main(int argc, const char **argv) { dat.emplace_object(); try { - jsonlogic::any_expr res = call_apply(config, rule, dat); + std::string res = call_apply(config, rule, dat); if (config.verbose) std::cerr << res << std::endl; @@ -299,7 +309,6 @@ int main(int argc, const char **argv) { result_matches_expected = expStream.str() == resStream.str(); resultStatus = ResultStatus::NoError; - } catch (const std::exception &ex) { resultStatus = ResultStatus::Error; resultError = ex.what();