diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..5df108f --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,22 @@ +# FROM mcr.microsoft.com/devcontainers/cpp:1-ubuntu-24.04 +FROM mcr.microsoft.com/devcontainers/cpp:dev-ubuntu-24.04 + +ARG REINSTALL_CMAKE_VERSION_FROM_SOURCE="none" + +# Optionally install the cmake for vcpkg +COPY ./reinstall-cmake.sh /tmp/ + +RUN if [ "${REINSTALL_CMAKE_VERSION_FROM_SOURCE}" != "none" ]; then \ + chmod +x /tmp/reinstall-cmake.sh && /tmp/reinstall-cmake.sh ${REINSTALL_CMAKE_VERSION_FROM_SOURCE}; \ + fi \ + && rm -f /tmp/reinstall-cmake.sh + +# [Optional] Uncomment this section to install additional vcpkg ports. +# RUN su vscode -c "${VCPKG_ROOT}/vcpkg install clangd" + +# [Optional] Uncomment this section to install additional packages. +# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ +# && apt-get -y install --no-install-recommends +RUN apt update && export DEBIAN_FRONTEND=noninteractive && apt -y install clangd-19 clang-tidy-19 python3-pip +RUN update-alternatives --install /usr/bin/clangd clangd /usr/bin/clangd-19 100 +RUN update-alternatives --install /usr/bin/clang-tidy clang-tidy /usr/bin/clang-tidy-19 100 diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..68e3eee --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,21 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/cpp +{ + "name": "C++", + "build": { + "dockerfile": "Dockerfile" + }, + "containerEnv": { + "CLIPPY_BACKEND_PATH": "${containerWorkspaceFolder}/build/test" + }, + "customizations": { + "vscode": { + "extensions": [ + "llvm-vs-code-extensions.vscode-clangd", + "ms-python.python" + // add other extensions as needed + ] + } + }, + "postCreateCommand": "pip install --break-system-packages -r ${containerWorkspaceFolder}/test/requirements.txt" +} diff --git a/.devcontainer/reinstall-cmake.sh b/.devcontainer/reinstall-cmake.sh new file mode 100644 index 0000000..408b81d --- /dev/null +++ b/.devcontainer/reinstall-cmake.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash +#------------------------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information. +#------------------------------------------------------------------------------------------------------------- +# +set -e + +CMAKE_VERSION=${1:-"none"} + +if [ "${CMAKE_VERSION}" = "none" ]; then + echo "No CMake version specified, skipping CMake reinstallation" + exit 0 +fi + +# Cleanup temporary directory and associated files when exiting the script. +cleanup() { + EXIT_CODE=$? + set +e + if [[ -n "${TMP_DIR}" ]]; then + echo "Executing cleanup of tmp files" + rm -Rf "${TMP_DIR}" + fi + exit $EXIT_CODE +} +trap cleanup EXIT + + +echo "Installing CMake..." +apt-get -y purge --auto-remove cmake +mkdir -p /opt/cmake + +architecture=$(dpkg --print-architecture) +case "${architecture}" in + arm64) + ARCH=aarch64 ;; + amd64) + ARCH=x86_64 ;; + *) + echo "Unsupported architecture ${architecture}." + exit 1 + ;; +esac + +CMAKE_BINARY_NAME="cmake-${CMAKE_VERSION}-linux-${ARCH}.sh" +CMAKE_CHECKSUM_NAME="cmake-${CMAKE_VERSION}-SHA-256.txt" +TMP_DIR=$(mktemp -d -t cmake-XXXXXXXXXX) + +echo "${TMP_DIR}" +cd "${TMP_DIR}" + +curl -sSL "https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/${CMAKE_BINARY_NAME}" -O +curl -sSL "https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/${CMAKE_CHECKSUM_NAME}" -O + +sha256sum -c --ignore-missing "${CMAKE_CHECKSUM_NAME}" +sh "${TMP_DIR}/${CMAKE_BINARY_NAME}" --prefix=/opt/cmake --skip-license + +ln -s /opt/cmake/bin/cmake /usr/local/bin/cmake +ln -s /opt/cmake/bin/ctest /usr/local/bin/ctest diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 7abeace..1564894 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -11,7 +11,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: - python-version: '3.9' + python-version: '3.10' - name: Install dependencies run: | @@ -20,13 +20,13 @@ jobs: - name: Install Boost - uses: MarkusJx/install-boost@v2.4.5 + uses: MarkusJx/install-boost@v2 id: install-boost with: # REQUIRED: Specify the required boost version # A list of supported versions can be found here: # https://github.com/MarkusJx/prebuilt-boost/blob/main/versions-manifest.json - boost_version: 1.83.0 + boost_version: 1.87.0 # OPTIONAL: Specify a platform version # platform_version: 18.04 # OPTIONAL: Specify a custom install location diff --git a/.gitignore b/.gitignore index fb113a5..a886cab 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.DS_Store /.cache /**/build /.idea/ @@ -6,11 +7,10 @@ CMakeDoxygenDefaults.cmake DartConfiguration.tcl __pycache__ /.vscode -/.devcontainer *.dSYM testbracket testmvmap testdf test/TestGraph/testgraph test/include/wheredev -a.out \ No newline at end of file +a.out diff --git a/CMakeLists.txt b/CMakeLists.txt index 09a7c61..c7121fe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,11 +3,16 @@ # # SPDX-License-Identifier: MIT -# Works with 3.11 and tested through 3.15 (not tested yet) -cmake_minimum_required(VERSION 3.14) + +cmake_minimum_required(VERSION 3.26) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") set(ALLOW_DUPLICATE_CUSTOM_TARGETS TRUE) +# Avoid warning about DOWNLOAD_EXTRACT_TIMESTAMP in CMake 3.24: +if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.24.0") +cmake_policy(SET CMP0135 NEW) +endif() + project(CLIPPy VERSION 0.2 DESCRIPTION "Command Line Interface Plus Python" @@ -32,7 +37,7 @@ if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) # Note this needs to be done in the main CMakeLists # since it calls enable_testing, which must be in the # main CMakeLists. - include(CTest) + # include(CTest) # Docs only available if this is the main app find_package(Doxygen) @@ -72,26 +77,41 @@ endif() # # Boost -set(BOOST_URL - "https://github.com/boostorg/boost/releases/download/boost-1.87.0/boost-1.87.0-cmake.tar.gz" +# Download and build Boost::json +set(BOOST_URL + "https://github.com/boostorg/boost/releases/download/boost-1.87.0/boost-1.87.0-cmake.tar.gz" CACHE STRING "URL to fetch Boost tarball") -# -# Boost + +set(BOOST_INCLUDE_LIBRARIES json lexical_cast range) +set(BUILD_SHARED_LIBS ON) FetchContent_Declare( - Boost - URL ${BOOST_URL} -) -set(BOOST_INCLUDE_LIBRARIES json) + Boost + URL ${BOOST_URL}) FetchContent_MakeAvailable(Boost) + # # JSONLogic -FetchContent_Declare(jsonlogic - GIT_REPOSITORY https://github.com/LLNL/jsonlogic.git - GIT_TAG master -) -FetchContent_MakeAvailable(jsonlogic) +# find_package(jsonlogic QUIET) +# if (NOT jsonlogic_FOUND) +# message(STATUS "jsonlogic not found, doing stuff") + set(Boost_INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR}/boost-install) # needed for jsonlogic + + FetchContent_Declare(jsonlogic + GIT_REPOSITORY https://github.com/LLNL/jsonlogic.git + GIT_TAG master + SOURCE_SUBDIR cpp + ) + # set(jsonlogic_INCLUDE_DIR ${jsonlogic_SOURCE_DIR}/cpp/include/jsonlogic) + FetchContent_MakeAvailable(jsonlogic) + message(STATUS "jsonlogic source dir: ${jsonlogic_SOURCE_DIR}") + + +# else() +# message(STATUS "jsonlogic found, weird") + +# endif() ### Require out-of-source builds file(TO_CMAKE_PATH "${PROJECT_BINARY_DIR}/CMakeLists.txt" LOC_PATH) @@ -106,9 +126,12 @@ option(TEST_WITH_SLURM "Run tests with Slurm" OFF) # Header-only library, so likely not have src dir # add_subdirectory(src) +message(STATUS "CMAKE_PROJECT_NAME: ${CMAKE_PROJECT_NAME}") +message(STATUS "PROJECT_NAME: ${PROJECT_NAME}") # Testing & examples are only available if this is the main app # Emergency override MODERN_CMAKE_BUILD_TESTING provided as well if((CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME OR MODERN_CMAKE_BUILD_TESTING) AND BUILD_TESTING) + message(STATUS "adding test subdir") add_subdirectory(test) # Example codes are here. #add_subdirectory(examples) diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 0000000..2cf1031 --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,14 @@ +{ + "version": 2, + "configurePresets": [ + { + "name": "default", + "cacheVariables": { + "CMAKE_EXPORT_COMPILE_COMMANDS": "ON", + "BUILD_TESTING": "ON" + }, + "generator": "Unix Makefiles", + "binaryDir": "${sourceDir}/build" + } + ] +} \ No newline at end of file diff --git a/examples/logic/testeval.cpp b/examples/logic/testeval.cpp index 1d8ca8b..d24cce3 100644 --- a/examples/logic/testeval.cpp +++ b/examples/logic/testeval.cpp @@ -1,25 +1,19 @@ -#include -#include - -#include - -#include "clippy/clippy-eval.hpp" - #include +#include +#include +#include +#include namespace bjsn = boost::json; -bjsn::value -parseStream(std::istream& inps) -{ +bjsn::value parseStream(std::istream& inps) { bjsn::stream_parser p; - std::string line; + std::string line; // \todo skips ws in strings - while (inps >> line) - { + while (inps >> line) { bjsn::error_code ec; p.write(line.c_str(), line.size(), ec); @@ -34,18 +28,15 @@ parseStream(std::istream& inps) return p.release(); } -bjsn::value -parseFile(const std::string& filename) -{ +bjsn::value parseFile(const std::string& filename) { std::ifstream is{filename}; return parseStream(is); } - template -bool matchOpt1(const std::vector& args, N& pos, std::string opt, T& fld) -{ +bool matchOpt1(const std::vector& args, N& pos, std::string opt, + T& fld) { std::string arg(args.at(pos)); if (arg.find(opt) != 0) return false; @@ -57,8 +48,8 @@ bool matchOpt1(const std::vector& args, N& pos, std::string opt, T& } template -bool matchOpt1(const std::vector& args, N& pos, std::string opt, Fn fn) -{ +bool matchOpt1(const std::vector& args, N& pos, std::string opt, + Fn fn) { std::string arg(args.at(pos)); if (arg.find(opt) != 0) return false; @@ -70,8 +61,8 @@ bool matchOpt1(const std::vector& args, N& pos, std::string opt, Fn } template -bool matchOpt0(const std::vector& args, N& pos, std::string opt, Fn fn) -{ +bool matchOpt0(const std::vector& args, N& pos, std::string opt, + Fn fn) { std::string arg(args.at(pos)); if (arg.find(opt) != 0) return false; @@ -82,40 +73,34 @@ bool matchOpt0(const std::vector& args, N& pos, std::string opt, Fn } template -bool noSwitch0(const std::vector& args, N& pos, Fn /*fn*/) -{ +bool noSwitch0(const std::vector& args, N& pos, Fn /*fn*/) { //~ if (fn(args[pos])) - //~ return true; + //~ return true; std::cerr << "unrecognized argument: " << args[pos] << std::endl; ++pos; return false; } -bool endsWith(const std::string& str, const std::string& suffix) -{ - return ( str.size() >= suffix.size() - && std::equal(suffix.rbegin(), suffix.rend(), str.rbegin()) - ); +bool endsWith(const std::string& str, const std::string& suffix) { + return (str.size() >= suffix.size() && + std::equal(suffix.rbegin(), suffix.rend(), str.rbegin())); } - -int main(int argc, const char** argv) -{ +int main(int argc, const char** argv) { constexpr bool MATCH = false; - bool verbose = false; + bool verbose = false; bool genExpected = false; - int errorCode = 0; - std::vector arguments(argv, argv+argc); - std::string filename; - size_t argn = 1; + int errorCode = 0; + std::vector arguments(argv, argv + argc); + std::string filename; + size_t argn = 1; - auto setVerbose = [&verbose]() -> void { verbose = true; }; - auto setResult = [&genExpected]() -> void { genExpected = true; }; - auto setFile = [&filename](const std::string& name) -> bool - { + auto setVerbose = [&verbose]() -> void { verbose = true; }; + auto setResult = [&genExpected]() -> void { genExpected = true; }; + auto setFile = [&filename](const std::string& name) -> bool { const bool jsonFile = endsWith(name, ".json"); if (jsonFile) filename = name; @@ -123,39 +108,34 @@ int main(int argc, const char** argv) return jsonFile; }; - while (argn < arguments.size()) - { + while (argn < arguments.size()) { MATCH - || matchOpt0(arguments, argn, "-v", setVerbose) - || matchOpt0(arguments, argn, "--verbose", setVerbose) - || matchOpt0(arguments, argn, "-r", setResult) - || matchOpt0(arguments, argn, "--result", setResult) - || noSwitch0(arguments, argn, setFile) - ; + || matchOpt0(arguments, argn, "-v", setVerbose) || + matchOpt0(arguments, argn, "--verbose", setVerbose) || + matchOpt0(arguments, argn, "-r", setResult) || + matchOpt0(arguments, argn, "--result", setResult) || + noSwitch0(arguments, argn, setFile); } - bjsn::value all = parseStream(std::cin); - bjsn::object& allobj = all.as_object(); + bjsn::value all = parseStream(std::cin); + bjsn::object& allobj = all.as_object(); - bjsn::value rule = allobj["rule"]; - const bool hasData = allobj.contains("data"); - bjsn::value dat; - const bool hasExpected = allobj.contains("expected"); + bjsn::value rule = allobj["rule"]; + const bool hasData = allobj.contains("data"); + bjsn::value dat; + const bool hasExpected = allobj.contains("expected"); if (hasData) dat = allobj["data"]; else dat.emplace_object(); - try - { - json_logic::ValueExpr res = json_logic::apply(rule, dat); + try { + jsonlogic::any_expr res = jsonlogic::apply(rule, dat); - if (verbose) - std::cerr << res << std::endl; + if (verbose) std::cerr << res << std::endl; - if (genExpected) - { + if (genExpected) { std::stringstream resStream; resStream << res; @@ -163,9 +143,7 @@ int main(int argc, const char** argv) allobj["expected"] = parseStream(resStream); if (verbose) std::cerr << allobj["expected"] << std::endl; - } - else if (hasExpected) - { + } else if (hasExpected) { std::stringstream expStream; std::stringstream resStream; @@ -176,37 +154,27 @@ int main(int argc, const char** argv) if (verbose && errorCode) std::cerr << "test failed: " << "\n exp: " << expStream.str() - << "\n got: " << resStream.str() - << std::endl; - } - else - { + << "\n got: " << resStream.str() << std::endl; + } else { errorCode = 1; if (verbose) std::cerr << "unexpected completion, result: " << res << std::endl; } - } - catch (const std::exception& ex) - { - if (verbose) - std::cerr << "caught error: " << ex.what() << std::endl; + } catch (const std::exception& ex) { + if (verbose) std::cerr << "caught error: " << ex.what() << std::endl; if (genExpected) allobj.erase("expected"); else if (hasExpected) errorCode = 1; - } - catch (...) - { - if (verbose) - std::cerr << "caught unknown error" << std::endl; + } catch (...) { + if (verbose) std::cerr << "caught unknown error" << std::endl; errorCode = 1; } - if (genExpected && (errorCode == 0)) - std::cout << allobj << std::endl; + if (genExpected && (errorCode == 0)) std::cout << allobj << std::endl; if (verbose && errorCode) std::cerr << "errorCode: " << errorCode << std::endl; diff --git a/examples/oo-howdy/howdy-eval.cpp b/examples/oo-howdy/howdy-eval.cpp index b4f9906..10179a5 100644 --- a/examples/oo-howdy/howdy-eval.cpp +++ b/examples/oo-howdy/howdy-eval.cpp @@ -1,92 +1,80 @@ -#include +#include #include +#include +#include -#include #include "clippy/clippy.hpp" -#include "clippy/clippy-eval.hpp" namespace boostjsn = boost::json; -namespace jl = json_logic; +namespace jl = json_logic; static const std::string className = "Greeter"; static const std::string methodName = "eval"; static const std::string expr = "expressions"; -static const std::string selLetters = "letters"; -static const std::string selFieldAll = "all"; -static const std::string selFieldVowels = "vowels"; -static const std::string selFieldConsonants = "consonants"; +static const std::string selLetters = "letters"; +static const std::string selFieldAll = "all"; +static const std::string selFieldVowels = "vowels"; +static const std::string selFieldConsonants = "consonants"; static const std::string stGreeting = "greeting"; -static const std::string stGreeted = "greeted"; +static const std::string stGreeted = "greeted"; static const std::string stSelected = "selected"; - -int main(int argc, char** argv) -{ - int error_code = 0; +int main(int argc, char** argv) { + int error_code = 0; clippy::clippy clip{methodName, "Eval example"}; clip.member_of(className, "Customizable Greeting Generator"); - if (clip.parse(argc, argv)) { return 0; } + if (clip.parse(argc, argv)) { + return 0; + } - try - { + try { using JsonExpression = std::vector; - std::string greeting = clip.get_state(stGreeting); - std::string greeted = clip.get_state(stGreeted); + std::string greeting = clip.get_state(stGreeting); + std::string greeted = clip.get_state(stGreeted); JsonExpression jsonExpression = clip.get_state(stSelected); - std::string chars = greeting + " " + greeted; + std::string chars = greeting + " " + greeted; - for (boost::json::object& jexp : jsonExpression) - { - auto [ast, vars, hasComputed] = json_logic::translateNode(jexp["rule"]); + for (boost::json::object& jexp : jsonExpression) { + auto [ast, vars, hasComputed] = jsonlogic::translateNode(jexp["rule"]); std::string tmp; tmp.swap(chars); - for (char ch : tmp) - { + for (char ch : tmp) { boostjsn::string elem(1, ch); - jl::ValueExpr val = jl::calculate( ast, - [&elem](const boostjsn::value&, int) -> jl::ValueExpr - { - return jl::toValueExpr(elem); - } - ); - if (jl::unpackValue(val)) - chars += ch; + jl::any_expr val = jl::calculate( + ast, [&elem](const boostjsn::value&, int) -> jl::any_expr { + return jl::toany_expr(elem); + }); + if (jl::unpack_value(val)) chars += ch; } } - clippy::object res; res.set_val("result", chars); -/* - auto [ast, vars, hasComputed] = json_logic::translateNode(val); + /* + auto [ast, vars, hasComputed] = jsonlogic::translateNode(val); - std::cerr << "Ast has " << vars.size() << " free variables" - << (hasComputed ? ", and variables with computed names" : "") - << "." - << std::endl; + std::cerr << "Ast has " << vars.size() << " free variables" + << (hasComputed ? ", and variables with computed names" : "") + << "." + << std::endl; - json_logic::ValueExpr res = json_logic::calculate(*ast.get()); -*/ + jsonlogic::any_expr res = jsonlogic::calculate(*ast.get()); + */ clip.to_return(chars); - } - catch (const std::exception& err) - { + } catch (const std::exception& err) { clip.to_return(err.what()); error_code = 1; } return error_code; } - - - diff --git a/include/clippy/clippy-ast.hpp b/include/clippy/clippy-ast.hpp deleted file mode 100644 index cdd2ee4..0000000 --- a/include/clippy/clippy-ast.hpp +++ /dev/null @@ -1,2300 +0,0 @@ - -#pragma once - -#include -#include -#include -#include -#include - -#include - -#include - -namespace json_logic -{ - constexpr bool DEBUG_OUTPUT = true; - - namespace json = boost::json; - - using JsonExpr = json::value; - - template - T& as(T& n) { return n; } - - template - T& deref(T* p) - { - assert(p); - return *p; - } - - struct Visitor; - - // the root class - struct Expr - { - Expr() = default; - virtual ~Expr() = default; - - virtual void accept(Visitor&) = 0; - - private: - Expr(Expr&&) = delete; - Expr(const Expr&) = delete; - Expr& operator=(Expr&&) = delete; - Expr& operator=(const Expr&) = delete; - }; - - //~ Expr::~Expr() {} - - // - // foundation classes - // \{ - - using AnyExpr = std::unique_ptr; - - struct Operator : Expr, private std::vector - { - using container_type = std::vector; - - using container_type::iterator; - using container_type::const_iterator; - using container_type::const_reverse_iterator; - using container_type::reverse_iterator; - using container_type::begin; - using container_type::end; - using container_type::rbegin; - using container_type::rend; - using container_type::crbegin; - using container_type::crend; - using container_type::back; - using container_type::push_back; - - void set_operands(container_type&& opers) - { - this->swap(opers); - } - - Expr& operand(int n) const - { - return deref(this->at(n).get()); - } - - virtual int num_evaluated_operands() const; - }; - - // defines operators that have an upper bound on how many - // arguments are evaluated. - template - struct OperatorN : Operator - { - enum { MAX_OPERANDS = MaxArity }; - - int num_evaluated_operands() const final; - }; - - template - struct Value : Expr - { - using value_type = T; - - explicit - Value(T t) - : val(std::move(t)) - {} - - T& value() { return val; } - const T& value() const { return val; } - - private: - T val; - }; - - // \} - - // - // expression hierarchy - // comparison - - // binary - struct Eq : OperatorN<2> - { - void accept(Visitor&) final; - }; - - struct StrictEq : OperatorN<2> - { - void accept(Visitor&) final; - }; - - struct Neq : OperatorN<2> - { - void accept(Visitor&) final; - }; - - struct StrictNeq : OperatorN<2> - { - void accept(Visitor&) final; - }; - - // binary or ternary - struct Less : OperatorN<3> - { - void accept(Visitor&) final; - }; - - struct Greater : OperatorN<3> - { - void accept(Visitor&) final; - }; - - struct Leq : OperatorN<3> - { - void accept(Visitor&) final; - }; - - struct Geq : OperatorN<3> - { - void accept(Visitor&) final; - }; - - // logical operators - - // unary - struct Not : OperatorN<1> - { - void accept(Visitor&) final; - }; - - struct NotNot : OperatorN<1> - { - void accept(Visitor&) final; - }; - - // n-ary - struct And : Operator - { - void accept(Visitor&) final; - }; - - struct Or : Operator - { - void accept(Visitor&) final; - }; - - - // control structure - struct If : Operator - { - void accept(Visitor&) final; - }; - - // arithmetics - // n-ary - struct Add : Operator - { - void accept(Visitor&) final; - }; - - struct Mul : Operator - { - void accept(Visitor&) final; - }; - - struct Min : Operator - { - void accept(Visitor&) final; - }; - - struct Max : Operator - { - void accept(Visitor&) final; - }; - - - // binary - struct Sub : OperatorN<2> - { - void accept(Visitor&) final; - }; - - struct Div : OperatorN<2> - { - void accept(Visitor&) final; - }; - - struct Mod : OperatorN<2> - { - void accept(Visitor&) final; - }; - - - // array - struct Array : Operator // array is modeled as operator - { - void accept(Visitor&) final; - }; - - struct Map : OperatorN<2> - { - void accept(Visitor&) final; - }; - - struct Reduce : OperatorN<3> - { - void accept(Visitor&) final; - }; - - struct Filter : OperatorN<2> - { - void accept(Visitor&) final; - }; - - struct All : OperatorN<2> - { - void accept(Visitor&) final; - }; - - struct None : OperatorN<2> - { - void accept(Visitor&) final; - }; - - struct Some : OperatorN<2> - { - void accept(Visitor&) final; - }; - - struct Merge : Operator - { - void accept(Visitor&) final; - }; - - - // string operations - struct Var : OperatorN<1> - { - enum { computed = -1 }; - - void accept(Visitor&) final; - - void num(int val) { idx = val; } - int num() const { return idx; } - - private: - int idx = computed; - }; - - struct Cat : Operator - { - void accept(Visitor&) final; - }; - - struct Substr : OperatorN<3> - { - void accept(Visitor&) final; - }; - - - // string and array operation - struct In : Operator - { - void accept(Visitor&) final; - }; - - - // values - struct NullVal : Expr - { - void accept(Visitor&) final; - - std::nullptr_t value() const { return nullptr; } - }; - - struct BoolVal : Value - { - using base = Value; - using base::base; - - void accept(Visitor&) final; - }; - - struct IntVal : Value - { - using base = Value; - using base::base; - - void accept(Visitor&) final; - }; - - struct UintVal : Value - { - using base = Value; - using base::base; - - void accept(Visitor&) final; - }; - - struct DoubleVal : Value - { - using base = Value; - using base::base; - - void accept(Visitor&) final; - }; - - struct StringVal : Value - { - using base = Value; - using base::base; - - void accept(Visitor&) final; - }; - - // logger - struct Log : OperatorN<1> - { - void accept(Visitor&) final; - }; - - // error node - struct Error : Expr - { - void accept(Visitor&) final; - }; - - - // Visitor - struct Visitor - { - virtual void visit(Expr&) = 0; // error - virtual void visit(Operator& n) = 0; - virtual void visit(Eq&) = 0; - virtual void visit(StrictEq&) = 0; - virtual void visit(Neq&) = 0; - virtual void visit(StrictNeq&) = 0; - virtual void visit(Less&) = 0; - virtual void visit(Greater&) = 0; - virtual void visit(Leq&) = 0; - virtual void visit(Geq&) = 0; - virtual void visit(And&) = 0; - virtual void visit(Or&) = 0; - virtual void visit(Not&) = 0; - virtual void visit(NotNot&) = 0; - virtual void visit(Add&) = 0; - virtual void visit(Sub&) = 0; - virtual void visit(Mul&) = 0; - virtual void visit(Div&) = 0; - virtual void visit(Mod&) = 0; - virtual void visit(Min&) = 0; - virtual void visit(Max&) = 0; - virtual void visit(Map&) = 0; - virtual void visit(Reduce&) = 0; - virtual void visit(Filter&) = 0; - virtual void visit(All&) = 0; - virtual void visit(None&) = 0; - virtual void visit(Some&) = 0; - virtual void visit(Array&) = 0; - virtual void visit(Merge&) = 0; - virtual void visit(Cat&) = 0; - virtual void visit(Substr&) = 0; - virtual void visit(In&) = 0; - virtual void visit(Var&) = 0; - virtual void visit(Log&) = 0; - - // control structure - virtual void visit(If&) = 0; - - // values - virtual void visit(NullVal&) = 0; - virtual void visit(BoolVal&) = 0; - virtual void visit(IntVal&) = 0; - virtual void visit(UintVal&) = 0; - virtual void visit(DoubleVal&) = 0; - virtual void visit(StringVal&) = 0; - //~ virtual void visit(ObjectVal&) = 0; - - virtual void visit(Error&) = 0; - }; - - - struct FwdVisitor : Visitor - { - void visit(Expr&) override; - void visit(Operator& n) override { visit(as(n)); } - void visit(Eq& n) override { visit(as(n)); } - void visit(StrictEq& n) override { visit(as(n)); } - void visit(Neq& n) override { visit(as(n)); } - void visit(StrictNeq& n) override { visit(as(n)); } - void visit(Less& n) override { visit(as(n)); } - void visit(Greater& n) override { visit(as(n)); } - void visit(Leq& n) override { visit(as(n)); } - void visit(Geq& n) override { visit(as(n)); } - void visit(And& n) override { visit(as(n)); } - void visit(Or& n) override { visit(as(n)); } - void visit(Not& n) override { visit(as(n)); } - void visit(NotNot& n) override { visit(as(n)); } - void visit(Add& n) override { visit(as(n)); } - void visit(Sub& n) override { visit(as(n)); } - void visit(Mul& n) override { visit(as(n)); } - void visit(Div& n) override { visit(as(n)); } - void visit(Mod& n) override { visit(as(n)); } - void visit(Min& n) override { visit(as(n)); } - void visit(Max& n) override { visit(as(n)); } - void visit(Map& n) override { visit(as(n)); } - void visit(Reduce& n) override { visit(as(n)); } - void visit(Filter& n) override { visit(as(n)); } - void visit(All& n) override { visit(as(n)); } - void visit(None& n) override { visit(as(n)); } - void visit(Some& n) override { visit(as(n)); } - void visit(Array& n) override { visit(as(n)); } - void visit(Merge& n) override { visit(as(n)); } - void visit(Cat& n) override { visit(as(n)); } - void visit(Substr& n) override { visit(as(n)); } - void visit(In& n) override { visit(as(n)); } - void visit(Var& n) override { visit(as(n)); } - void visit(Log& n) override { visit(as(n)); } - - void visit(If& n) override { visit(as(n)); } - - void visit(NullVal& n) override { visit(as(n)); } - void visit(BoolVal& n) override { visit(as(n)); } - void visit(IntVal& n) override { visit(as(n)); } - void visit(UintVal& n) override { visit(as(n)); } - void visit(DoubleVal& n) override { visit(as(n)); } - void visit(StringVal& n) override { visit(as(n)); } - - void visit(Error& n) override { visit(as(n)); } - }; - - /// AST traversal function that calls v's visit methods in post-fix order - void traverseInSAttributeOrder(Expr& e, Visitor& vis); - - /// traverses the children of a node; does not traverse grandchildren - void traverseChildren(Visitor& v, const Operator& node); - void traverseAllChildren(Visitor& v, const Operator& node); - void traverseChildrenReverse(Visitor& v, const Operator& node); - - namespace - { - CXX_NORETURN - void unsupported() - { - throw std::logic_error("not yet implemented"); - } - - CXX_NORETURN - void typeError() - { - throw std::runtime_error("typing error"); - } - - struct VarMap - { - void insert(Var& el); - std::vector toVector() const; - bool hasComputedVariables() const { return hasComputed; } - - private: - using container_type = std::map; - - container_type mapping = {}; - bool hasComputed = false; - }; - - void VarMap::insert(Var& var) - { - AnyExpr& arg = var.back(); - StringVal* str = dynamic_cast(arg.get()); - - if (str == nullptr) - { - CXX_UNLIKELY; - hasComputed = true; - return; - } - - // do nothing for free variables in "lambdas" - if (str->value() == "") - return; - - auto [pos, success] = mapping.emplace(str->value(), mapping.size()); - - var.num(pos->second); - } - - std::vector - VarMap::toVector() const - { - std::vector res; - - res.resize(mapping.size()); - - for (const container_type::value_type& el : mapping) - res.at(el.second) = el.first; - - return res; - } - - - /// translates all children - /// \{ - Operator::container_type - translateChildren(json::array& children, VarMap&); - - Operator::container_type - translateChildren(JsonExpr& n, VarMap&); - /// \} - - - template - Expr& mkOperator(json::object& n, VarMap& m) - { - assert(n.size() == 1); - - ExprT& res = deref(new ExprT); - - res.set_operands(translateChildren(n.begin()->value(), m)); - return res; - } - - Array& mkArrayOperator(json::array& children, VarMap& m) - { - Array& res = deref(new Array); - - res.set_operands(translateChildren(children, m)); - return res; - } - - template - ValueT& mkValue(typename ValueT::value_type n) - { - return deref(new ValueT(std::move(n))); - } - - NullVal& mkNullValue() - { - return deref(new NullVal); - } - - using DispatchTable = std::map; - - DispatchTable::const_iterator - lookup(const DispatchTable& m, const json::object& op) - { - if (op.size() != 1) return m.end(); - - return m.find(op.begin()->key()); - } - - - AnyExpr - translateNode_internal(JsonExpr& n, VarMap& varmap) - { - static const DispatchTable dt = { { "==", &mkOperator }, - { "===", &mkOperator }, - { "!=", &mkOperator }, - { "!==", &mkOperator }, - { "if", &mkOperator }, - { "!", &mkOperator }, - { "!!", &mkOperator }, - { "or", &mkOperator }, - { "and", &mkOperator }, - { ">", &mkOperator }, - { ">=", &mkOperator }, - { "<", &mkOperator }, - { "<=", &mkOperator }, - { "max", &mkOperator }, - { "min", &mkOperator }, - { "+", &mkOperator }, - { "-", &mkOperator }, - { "*", &mkOperator }, - { "/", &mkOperator
}, - { "%", &mkOperator }, - { "map", &mkOperator }, - { "reduce", &mkOperator }, - { "filter", &mkOperator }, - { "all", &mkOperator }, - { "none", &mkOperator }, - { "some", &mkOperator }, - { "merge", &mkOperator }, - { "in", &mkOperator }, - { "cat", &mkOperator }, - { "log", &mkOperator }, - { "var", &mkOperator } - }; - - Expr* res = nullptr; - - switch (n.kind()) - { - case json::kind::object: - { - json::object& obj = n.get_object(); - DispatchTable::const_iterator pos = lookup(dt, obj); - - if (pos != dt.end()) - { - CXX_LIKELY; - res = &pos->second(obj, varmap); - - if (pos->second == mkOperator) - varmap.insert(dynamic_cast(*res)); - } - else - { - // does json_logic support value objects? - unsupported(); - } - - break; - } - - case json::kind::array: - { - // array is an operator that combines its subexpressions into an array - res = &mkArrayOperator(n.get_array(), varmap); - break; - } - - case json::kind::string: - { - res = &mkValue(std::move(n.get_string())); - break; - } - - case json::kind::int64: - { - res = &mkValue(n.get_int64()); - break; - } - - case json::kind::uint64: - { - res = &mkValue(n.get_uint64()); - break; - } - - case json::kind::double_: - { - res = &mkValue(n.get_double()); - break; - } - - case json::kind::bool_: - { - res = &mkValue(n.get_bool()); - break; - } - - case json::kind::null: - { - res = &mkNullValue(); - break; - } - - default: - unsupported(); - } - - return std::unique_ptr(res); - } - - - std::tuple, bool> - translateNode(JsonExpr& n) - { - VarMap varmap; - AnyExpr node = translateNode_internal(n, varmap); - bool hasComputedVariables = varmap.hasComputedVariables(); - - return std::make_tuple(std::move(node), varmap.toVector(), hasComputedVariables); - } - - Operator::container_type - translateChildren(json::array& children, VarMap& varmap) - { - Operator::container_type res; - - res.reserve(children.size()); - - for (JsonExpr& elem : children) - res.emplace_back(translateNode_internal(elem, varmap)); - - return res; - } - - Operator::container_type - translateChildren(JsonExpr& n, VarMap& varmap) - { - if (json::array* arr = n.if_array()) - { - CXX_LIKELY; - return translateChildren(*arr, varmap); - } - - Operator::container_type res; - - res.emplace_back(translateNode_internal(n, varmap)); - return res; - } - } - - // only operators have children - void _traverseChildren(Visitor& v, Operator::const_iterator aa, Operator::const_iterator zz) - { - std::for_each( aa, zz, - [&v](const AnyExpr& e) -> void - { - e->accept(v); - } - ); - } - - void traverseChildren(Visitor& v, const Operator& node) - { - Operator::const_iterator aa = node.begin(); - - _traverseChildren(v, aa, aa + node.num_evaluated_operands()); - } - - void traverseAllChildren(Visitor& v, const Operator& node) - { - _traverseChildren(v, node.begin(), node.end()); - } - - void traverseChildrenReverse(Visitor& v, const Operator& node) - { - Operator::const_reverse_iterator zz = node.crend(); - Operator::const_reverse_iterator aa = zz - node.num_evaluated_operands(); - - std::for_each( aa, zz, - [&v](const AnyExpr& e) -> void - { - e->accept(v); - } - ); - } - - - namespace - { - struct SAttributeTraversal : Visitor - { - explicit - SAttributeTraversal(Visitor& client) - : sub(client) - {} - - void visit(Expr&) final; - void visit(Operator&) final; - void visit(Eq&) final; - void visit(StrictEq&) final; - void visit(Neq&) final; - void visit(StrictNeq&) final; - void visit(Less&) final; - void visit(Greater&) final; - void visit(Leq&) final; - void visit(Geq&) final; - void visit(And&) final; - void visit(Or&) final; - void visit(Not&) final; - void visit(NotNot&) final; - void visit(Add&) final; - void visit(Sub&) final; - void visit(Mul&) final; - void visit(Div&) final; - void visit(Mod&) 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(In&) final; - void visit(Array& n) final; - void visit(Var&) final; - void visit(Log&) final; - - void visit(If&) final; - - void visit(NullVal& n) final; - void visit(BoolVal& n) final; - void visit(IntVal& n) final; - void visit(UintVal& n) final; - void visit(DoubleVal& n) final; - void visit(StringVal& n) final; - - void visit(Error& n) final; - - private: - Visitor& sub; - - 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&) { typeError(); } - void SAttributeTraversal::visit(Operator&) { typeError(); } - void SAttributeTraversal::visit(Eq& n) { _visit(n); } - void SAttributeTraversal::visit(StrictEq& n) { _visit(n); } - void SAttributeTraversal::visit(Neq& n) { _visit(n); } - void SAttributeTraversal::visit(StrictNeq& n) { _visit(n); } - void SAttributeTraversal::visit(Less& n) { _visit(n); } - void SAttributeTraversal::visit(Greater& n) { _visit(n); } - void SAttributeTraversal::visit(Leq& n) { _visit(n); } - void SAttributeTraversal::visit(Geq& n) { _visit(n); } - void SAttributeTraversal::visit(And& n) { _visit(n); } - void SAttributeTraversal::visit(Or& n) { _visit(n); } - void SAttributeTraversal::visit(Not& n) { _visit(n); } - void SAttributeTraversal::visit(NotNot& n) { _visit(n); } - void SAttributeTraversal::visit(Add& n) { _visit(n); } - void SAttributeTraversal::visit(Sub& n) { _visit(n); } - void SAttributeTraversal::visit(Mul& n) { _visit(n); } - void SAttributeTraversal::visit(Div& n) { _visit(n); } - void SAttributeTraversal::visit(Mod& 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(In& n) { _visit(n); } - void SAttributeTraversal::visit(Var& n) { _visit(n); } - void SAttributeTraversal::visit(Log& n) { _visit(n); } - - void SAttributeTraversal::visit(If& n) { _visit(n); } - - void SAttributeTraversal::visit(NullVal& n) { _value(n); } - void SAttributeTraversal::visit(BoolVal& n) { _value(n); } - void SAttributeTraversal::visit(IntVal& n) { _value(n); } - void SAttributeTraversal::visit(UintVal& n) { _value(n); } - void SAttributeTraversal::visit(DoubleVal& n) { _value(n); } - void SAttributeTraversal::visit(StringVal& n) { _value(n); } - - void SAttributeTraversal::visit(Error& n) { sub.visit(n); } - } - - using ValueExpr = std::unique_ptr; // could be value if we have a type for that - - std::ostream& operator<<(std::ostream& os, ValueExpr& n); - - ValueExpr toValueExpr(std::nullptr_t) { return ValueExpr(new NullVal); } - ValueExpr toValueExpr(bool val) { return ValueExpr(new BoolVal(val)); } - ValueExpr toValueExpr(std::int64_t val) { return ValueExpr(new IntVal(val)); } - ValueExpr toValueExpr(std::uint64_t val) { return ValueExpr(new UintVal(val)); } - ValueExpr toValueExpr(double val) { return ValueExpr(new DoubleVal(val)); } - ValueExpr toValueExpr(json::string val) { return ValueExpr(new StringVal(std::move(val))); } - - // - // coercion functions - - std::int64_t toInt(const json::string& str) - { - return std::stoi(std::string{str.c_str()}); - } - - std::int64_t toInt(double d) - { - return d; - } - - std::int64_t toInt(bool b) - { - return b; - } - - std::int64_t toInt(std::uint64_t v) - { - return v; - } - - std::uint64_t toUint(const json::string& str) - { - return std::stoull(std::string{str.c_str()}); - } - - std::uint64_t toUint(double d) - { - return d; - } - - std::uint64_t toUint(std::int64_t v) - { - return v; - } - - std::uint64_t toUint(bool b) - { - return b; - } - - double toDouble(const json::string& str) - { - return std::stod(std::string{str.c_str()}); - } - - double toDouble(std::int64_t val) - { - return val; - } - - double toDouble(std::uint64_t val) - { - return val; - } - - - template - json::string toString(Val v) - { - return json::string{std::to_string(v)}; - } - - json::string toString(bool b) - { - return json::string{b ? "true" : "false"}; - } - - json::string toString(std::nullptr_t) - { - return json::string{"null"}; - } - - /// conversion to boolean - /// \{ - - bool toBool(std::int64_t v) { return v; } - bool toBool(std::uint64_t v) { return v; } - bool toBool(double v) { return v; } - bool toBool(const json::string& v) { return v.size() != 0; } - bool toBool(Array& v) { return v.num_evaluated_operands(); } - - bool toBool(Expr& e) - { - struct BoolConverter : FwdVisitor - { - void visit(Expr&) final { typeError(); } - - void visit(NullVal&) final { res = false; } - void visit(BoolVal& n) final { res = n.value(); } - void visit(IntVal& n) final { res = toBool(n.value()); } - void visit(UintVal& n) final { res = toBool(n.value()); } - void visit(DoubleVal& n) final { res = toBool(n.value()); } - void visit(StringVal& n) final { res = toBool(n.value()); } - void visit(Array& n) final { res = toBool(n); } - - bool res; - }; - - BoolConverter conv; - - e.accept(conv); - return conv.res; - } - - bool toBool(ValueExpr& el) - { - return toBool(*el); - } - - /// \} - - - struct LogicalOperatorBase - { - enum - { - definedForString = true, - definedForDouble = true, - definedForInteger = true, - definedForBool = false, - definedForNull = false - }; - - using result_type = bool; - }; - - struct NoCoercion {}; - - /// \brief a strict binary operator operates on operands of the same - /// type. The operation on two different types returns false. - /// NO type coercion is performed. - struct StrictLogicalBinaryOperator : LogicalOperatorBase - { - template - std::tuple - coerce(LhsT* lv, RhsT* rv) - { - return std::make_tuple(std::move(*lv), std::move(*rv)); - } - }; - - struct NumericBinaryOperatorBase - { - std::tuple - coerce(double* lv, double* rv) - { - return std::make_tuple(*lv, *rv); - } - - std::tuple - coerce(double* lv, std::int64_t* rv) - { - return std::make_tuple(*lv, toDouble(*rv)); - } - - std::tuple - coerce(double* lv, std::uint64_t* rv) - { - return std::make_tuple(*lv, toDouble(*rv)); - } - - std::tuple - coerce(std::int64_t* lv, double* rv) - { - return std::make_tuple(toDouble(*lv), *rv); - } - - std::tuple - coerce(std::int64_t* lv, std::int64_t* rv) - { - return std::make_tuple(*lv, *rv); - } - - std::tuple - coerce(std::int64_t* lv, std::uint64_t* rv) - { - return std::make_tuple(*lv, toInt(*rv)); - } - - std::tuple - coerce(std::uint64_t* lv, double* rv) - { - return std::make_tuple(toDouble(*lv), *rv); - } - - std::tuple - coerce(std::uint64_t* lv, std::int64_t* rv) - { - return std::make_tuple(toInt(*lv), *rv); - } - - std::tuple - coerce(std::uint64_t* lv, std::uint64_t* rv) - { - return std::make_tuple(*lv, *rv); - } - }; - - - /// \brief a logical binary operator compares two values. If the - /// values have a different type, type coercion is performed - /// on one of the operands. - struct LogicalBinaryOperator : NumericBinaryOperatorBase, LogicalOperatorBase - { - using NumericBinaryOperatorBase::coerce; - - std::tuple - coerce(double* lv, json::string* rv) - { - return std::make_tuple(*lv, toDouble(*rv)); - } - - std::tuple - coerce(std::int64_t* lv, json::string* rv) - { - return std::make_tuple(*lv, toInt(*rv)); - } - - std::tuple - coerce(std::uint64_t* lv, json::string* rv) - { - return std::make_tuple(*lv, toUint(*rv)); - } - - std::tuple - coerce(json::string* lv, double* rv) - { - return std::make_tuple(toDouble(*lv), *rv); - } - - std::tuple - coerce(json::string* lv, std::int64_t* rv) - { - return std::make_tuple(toInt(*lv), *rv); - } - - std::tuple - coerce(json::string* lv, std::uint64_t* rv) - { - return std::make_tuple(toInt(*lv), *rv); - } - - std::tuple - coerce(json::string* lv, json::string* rv) - { - return std::make_tuple(std::move(*lv), std::move(*rv)); - } - }; - // @} - - // Arith - struct ArithmeticOperator : NumericBinaryOperatorBase - { - enum - { - definedForString = false, - definedForDouble = true, - definedForInteger = true, - definedForBool = false, - definedForNull = true - }; - - using result_type = ValueExpr; - - using NumericBinaryOperatorBase::coerce; - - std::tuple - coerce(double*, std::nullptr_t) - { - return std::make_tuple(nullptr, nullptr); - } - - std::tuple - coerce(std::int64_t*, std::nullptr_t) - { - return std::make_tuple(nullptr, nullptr); - } - - std::tuple - coerce(std::uint64_t*, std::nullptr_t) - { - return std::make_tuple(nullptr, nullptr); - } - - std::tuple - coerce(std::nullptr_t, double*) - { - return std::make_tuple(nullptr, nullptr); - } - - std::tuple - coerce(std::nullptr_t, std::int64_t*) - { - return std::make_tuple(nullptr, nullptr); - } - - std::tuple - coerce(std::nullptr_t, std::uint64_t*) - { - return std::make_tuple(nullptr, nullptr); - } - - std::tuple - coerce(std::nullptr_t, std::nullptr_t) - { - return std::make_tuple(nullptr, nullptr); - } - }; - - struct IntegerArithmeticOperator : ArithmeticOperator - { - enum - { - definedForString = false, - definedForDouble = false, - definedForInteger = true, - definedForBool = false, - definedForNull = false - }; - - using ArithmeticOperator::coerce; - }; - - struct StringOperator - { - enum - { - definedForString = true, - definedForDouble = false, - definedForInteger = false, - definedForBool = false, - definedForNull = false - }; - - using result_type = ValueExpr; - - std::tuple - coerce(json::string* lv, json::string* rv) - { - return std::make_tuple(std::move(*lv), std::move(*rv)); - } - }; - - - AnyExpr convert(AnyExpr val, ...) - { - return val; - } - - - AnyExpr convert(AnyExpr val, const ArithmeticOperator&) - { - struct ArithmeticConverter : FwdVisitor - { - explicit - ArithmeticConverter(AnyExpr val) - : res(std::move(val)) - {} - - void visit(Expr&) final { typeError(); } - - // defined for the following types - void visit(IntVal&) final {} - void visit(UintVal&) final {} - void visit(DoubleVal&) final {} - void visit(NullVal&) final {} - - // need to convert values - void visit(StringVal& el) final - { - double dd = toDouble(el.value()); - int64_t ii = toInt(el.value()); - // uint? - - res = (dd != ii) ? toValueExpr(dd) : toValueExpr(ii); - } - - void visit(BoolVal&) final - { - // \todo correct? - res = toValueExpr(nullptr); - } - - AnyExpr result() && { return std::move(res); } - private: - AnyExpr res; - }; - - Expr* node = val.get(); - ArithmeticConverter conv{std::move(val)}; - - node->accept(conv); - return std::move(conv).result(); - } - - AnyExpr convert(AnyExpr val, const IntegerArithmeticOperator&) - { - struct IntegerArithmeticConverter : FwdVisitor - { - explicit - IntegerArithmeticConverter(AnyExpr val) - : res(std::move(val)) - {} - - void visit(Expr&) final { typeError(); } - - // defined for the following types - void visit(IntVal&) final {} - void visit(UintVal&) final {} - - // need to convert values - void visit(StringVal& el) final - { - res = toValueExpr(toInt(el.value())); - } - - void visit(BoolVal& el) final - { - res = toValueExpr(toInt(el.value())); - } - - void visit(DoubleVal& el) final - { - res = toValueExpr(toInt(el.value())); - } - - void visit(NullVal&) final - { - res = toValueExpr(std::int64_t(0)); - } - - AnyExpr result() && { return std::move(res); } - - private: - AnyExpr res; - }; - - Expr* node = val.get(); - IntegerArithmeticConverter conv{std::move(val)}; - - node->accept(conv); - return std::move(conv).result(); - } - - AnyExpr convert(AnyExpr val, const StringOperator&) - { - struct StringConverter : FwdVisitor - { - explicit - StringConverter(AnyExpr val) - : res(std::move(val)) - {} - - void visit(Expr&) final { typeError(); } - - // defined for the following types - void visit(StringVal&) final {} - - - // need to convert values - void visit(BoolVal& el) final - { - res = toValueExpr(toString(el.value())); - } - - void visit(IntVal& el) final - { - res = toValueExpr(toString(el.value())); - } - - void visit(UintVal& el) final - { - res = toValueExpr(toString(el.value())); - } - - void visit(DoubleVal& el) final - { - res = toValueExpr(toString(el.value())); - } - - void visit(NullVal& el) final - { - res = toValueExpr(toString(el.value())); - } - - AnyExpr result() && { return std::move(res); } - - private: - AnyExpr res; - }; - - Expr* node = val.get(); - StringConverter conv{std::move(val)}; - - node->accept(conv); - return std::move(conv).result(); - } - - template - struct BinaryOperatorVisitor_ : FwdVisitor - { - using result_type = typename BinaryOperator::result_type; - - BinaryOperatorVisitor_(LhsValue lval, BinaryOperator oper) - : lv(lval), op(oper), res() - {} - - template - void calc(RhsValue rv) - { - auto [ll, rr] = op.coerce(lv, rv); - - res = op(ll, rr); - } - - void visit(Expr&) final { typeError(); } - - void visit(StringVal& n) final - { - if constexpr (BinaryOperator::definedForString) - return calc(&n.value()); - - typeError(); - } - - void visit(NullVal&) final - { - if constexpr (BinaryOperator::definedForNull) - return calc(nullptr); - - typeError(); - } - - void visit(BoolVal& n) final - { - if constexpr (BinaryOperator::definedForBool) - return calc(&n.value()); - - typeError(); - } - - void visit(IntVal& n) final - { - if constexpr (BinaryOperator::definedForInteger) - return calc(&n.value()); - - typeError(); - } - - void visit(UintVal& n) final - { - if constexpr (BinaryOperator::definedForInteger) - return calc(&n.value()); - - typeError(); - } - - void visit(DoubleVal& n) final - { - if constexpr (BinaryOperator::definedForDouble) - return calc(&n.value()); - - typeError(); - } - - void visit(Array&) final - { - unsupported(); // for now - } - - result_type result() && { return std::move(res); } - - private: - LhsValue lv; - BinaryOperator op; - result_type res; - }; - - template - struct BinaryOperatorVisitor : FwdVisitor - { - using result_type = typename BinaryOperator::result_type; - - BinaryOperatorVisitor(BinaryOperator oper, ValueExpr& rhsarg) - : op(oper), rhs(rhsarg), res() - {} - - template - void calc(LhsValue lv) - { - using RhsVisitor = BinaryOperatorVisitor_; - - RhsVisitor vis{lv, op}; - - rhs->accept(vis); - res = std::move(vis).result(); - } - - void visit(StringVal& n) final - { - if constexpr (BinaryOperator::definedForString) - return calc(&n.value()); - - typeError(); - } - - void visit(NullVal&) final - { - if constexpr (BinaryOperator::definedForNull) - return calc(nullptr); - - typeError(); - } - - void visit(BoolVal& n) final - { - if constexpr (BinaryOperator::definedForBool) - return calc(&n.value()); - - typeError(); - } - - void visit(IntVal& n) final - { - if constexpr (BinaryOperator::definedForInteger) - return calc(&n.value()); - - typeError(); - } - - void visit(UintVal& n) final - { - if constexpr (BinaryOperator::definedForInteger) - return calc(&n.value()); - - typeError(); - } - - void visit(DoubleVal& n) final - { - if constexpr (BinaryOperator::definedForDouble) - return calc(&n.value()); - - typeError(); - } - - void visit(Array&) final - { - typeError(); // for now - } - - result_type result() && { return std::move(res); } - - private: - BinaryOperator op; - ValueExpr& rhs; - result_type res; - }; - - template - typename BinaryOperator::result_type - compute(ValueExpr& lhs, ValueExpr& rhs, BinaryOperator op) - { - using LhsVisitor = BinaryOperatorVisitor; - - LhsVisitor vis{op, rhs}; - - assert(lhs.get()); - lhs->accept(vis); - return std::move(vis).result(); - } - - - - template - struct Calc {}; - - - template <> - struct Calc : LogicalBinaryOperator - { - using LogicalBinaryOperator::result_type; - - template - result_type - operator()(const T& lhs, const T& rhs) const - { - return lhs == rhs; - } - }; - - template <> - struct Calc : LogicalBinaryOperator - { - using LogicalBinaryOperator::result_type; - - template - result_type - operator()(const T& lhs, const T& rhs) const - { - return lhs != rhs; - } - }; - - template <> - struct Calc : StrictLogicalBinaryOperator - { - using StrictLogicalBinaryOperator::result_type; - - result_type operator()(...) const { return false; } // type mismatch - - template - result_type - operator()(const T& lhs, const T& rhs) const - { - return lhs == rhs; - } - }; - - template <> - struct Calc : StrictLogicalBinaryOperator - { - using StrictLogicalBinaryOperator::result_type; - - result_type operator()(...) const { return false; } // type mismatch - - template - result_type - operator()(const T& lhs, const T& rhs) const - { - return lhs == rhs; - } - }; - - template <> - struct Calc : LogicalBinaryOperator - { - using LogicalBinaryOperator::result_type; - - template - result_type - operator()(const T& lhs, const T& rhs) const - { - return lhs < rhs; - } - }; - - template <> - struct Calc : LogicalBinaryOperator - { - using LogicalBinaryOperator::result_type; - - template - result_type - operator()(const T& lhs, const T& rhs) const - { - return rhs < lhs; - } - }; - - template <> - struct Calc : LogicalBinaryOperator - { - using LogicalBinaryOperator::result_type; - - template - result_type - operator()(const T& lhs, const T& rhs) const - { - return lhs <= rhs; - } - }; - - template <> - struct Calc : LogicalBinaryOperator - { - using LogicalBinaryOperator::result_type; - - template - result_type - operator()(const T& lhs, const T& rhs) const - { - return rhs <= lhs; - } - }; - - template <> - struct Calc : ArithmeticOperator - { - using ArithmeticOperator::result_type; - - result_type - operator()(std::nullptr_t, std::nullptr_t) const { return toValueExpr(nullptr); } - - template - result_type - operator()(const T& lhs, const T& rhs) const - { - return toValueExpr(lhs + rhs); - } - }; - - template <> - struct Calc : ArithmeticOperator - { - using ArithmeticOperator::result_type; - - result_type - operator()(std::nullptr_t, std::nullptr_t) const { return toValueExpr(nullptr); } - - template - result_type - operator()(const T& lhs, const T& rhs) const - { - return toValueExpr(lhs - rhs); - } - }; - - template <> - struct Calc : ArithmeticOperator - { - using ArithmeticOperator::result_type; - - result_type - operator()(std::nullptr_t, std::nullptr_t) const { return toValueExpr(nullptr); } - - template - result_type - operator()(const T& lhs, const T& rhs) const - { - return toValueExpr(lhs * rhs); - } - }; - - template <> - struct Calc
: ArithmeticOperator - { - using ArithmeticOperator::result_type; - - result_type - operator()(std::nullptr_t, std::nullptr_t) const { return toValueExpr(nullptr); } - - result_type - operator()(double lhs, double rhs) const - { - double res = lhs / rhs; - - // if (isInteger(res)) return toInt(res); - return toValueExpr(res); - } - - template - result_type - operator()(Int_t lhs, Int_t rhs) const - { - if (lhs % rhs) return (*this)(double(lhs), double(rhs)); - - return toValueExpr(lhs / rhs); - } - }; - - template <> - struct Calc : IntegerArithmeticOperator - { - using IntegerArithmeticOperator::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 toValueExpr(nullptr); - - return toValueExpr(lhs % rhs); - } - }; - - template <> - struct Calc : ArithmeticOperator - { - using ArithmeticOperator::result_type; - - template - result_type - operator()(const T& lhs, const T& rhs) const - { - return toValueExpr(std::min(lhs, rhs)); - } - }; - - template <> - struct Calc : ArithmeticOperator - { - using ArithmeticOperator::result_type; - - template - result_type - operator()(const T& lhs, const T& rhs) const - { - return toValueExpr(std::max(lhs, rhs)); - } - }; - - template <> - struct Calc - { - using result_type = bool; - - result_type - operator()(Expr& val) const - { - return !toBool(val); - } - }; - - template <> - struct Calc - { - using result_type = bool; - - result_type - operator()(Expr& val) const - { - return toBool(val); - } - }; - - template <> - struct Calc : StringOperator - { - using StringOperator::result_type; - - result_type - operator()(const json::string& lhs, const json::string& rhs) const - { - json::string tmp; - - tmp.reserve(lhs.size()+rhs.size()); - - tmp.append(lhs.begin(), lhs.end()); - tmp.append(rhs.begin(), rhs.end()); - - return toValueExpr(std::move(tmp)); - } - }; - - struct Calculator : FwdVisitor - { - using VarAccess = std::function; - - Calculator(const VarAccess& varAccess, std::ostream& out) - : vars(varAccess), logger(out), calcres(nullptr) - {} - - void visit(Eq&) final; - void visit(StrictEq&) final; - void visit(Neq&) final; - void visit(StrictNeq&) final; - void visit(Less&) final; - void visit(Greater&) final; - void visit(Leq&) final; - void visit(Geq&) final; - void visit(And&) final; - void visit(Or&) final; - void visit(Not&) final; - void visit(NotNot&) final; - void visit(Add&) final; - void visit(Sub&) final; - void visit(Mul&) final; - void visit(Div&) final; - void visit(Mod&) final; - void visit(Min&) final; - void visit(Max&) final; - void visit(Array&) 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(In&) final; - void visit(Var&) final; - void visit(Log&) final; - - void visit(NullVal& n) final; - void visit(BoolVal& n) final; - void visit(IntVal& n) final; - void visit(UintVal& n) final; - void visit(DoubleVal& n) final; - void visit(StringVal& n) final; - - void visit(Error& n) final; - - ValueExpr eval(Expr& n); - - private: - const VarAccess& vars; - std::ostream& logger; - ValueExpr calcres; - - Calculator(const Calculator&) = delete; - Calculator(Calculator&&) = delete; - Calculator& operator=(const Calculator&) = delete; - Calculator& operator=(Calculator&&) = delete; - - // - // opers - - /// implements relop : [1, 2, 3, whatever] - template - void evalPairShortCircuit(Operator& n, BinaryPredicate calc) - { - const int num = n.num_evaluated_operands(); - assert(num >= 2); - - bool res = true; - int idx = -1; - ValueExpr rhs = eval(n.operand(++idx)); - assert(rhs.get()); - - while (res && (idx != (num-1))) - { - ValueExpr lhs = std::move(rhs); - assert(lhs.get()); - - rhs = eval(n.operand(++idx)); - assert(rhs.get()); - - res = compute(lhs, rhs, calc); - } - - calcres = toValueExpr(res); - } - - template - void reduce(Operator& n, BinaryOperator op) - { - const int num = n.num_evaluated_operands(); - assert(num >= 1); - - int idx = -1; - ValueExpr res = eval(n.operand(++idx)); - - res = convert(std::move(res), op); - - while (idx != (num-1)) - { - ValueExpr rhs = eval(n.operand(++idx)); - - rhs = convert(std::move(rhs), op); - res = compute(res, rhs, op); - } - - calcres = std::move(res); - } - - template - void binary(Operator& n, BinaryOperator calc) - { - const int num = n.num_evaluated_operands(); - assert(num == 1 || num == 2); - - int idx = -1; - ValueExpr lhs; - - if (num == 2) - { - CXX_LIKELY; - lhs = eval(n.operand(++idx)); - } - else - { - lhs = toValueExpr(std::int64_t(0)); - } - - ValueExpr rhs = eval(n.operand(++idx)); - - calcres = compute(lhs, rhs, calc); - } - - template - void unary(Operator& n, UnaryOperator calc) - { - const int num = n.num_evaluated_operands(); - assert(num == 1); - - const bool res = calc(*eval(n.operand(0))); - - calcres = toValueExpr(res); - } - - void evalShortCircuit(Operator& n, bool val) - { - const int num = n.num_evaluated_operands(); - assert(num >= 1); - - int idx = -1; - ValueExpr oper = eval(n.operand(++idx)); - bool found = (idx == num-1) || (toBool(*oper) == val); - - // loop until *aa == val or when *aa is the last valid element - while (!found) - { - oper = eval(n.operand(++idx)); - - found = (idx == (num-1)) || (toBool(*oper) == val); - } - - calcres = std::move(oper); - } - - template - void _value(const ValueNode& val) - { - calcres = toValueExpr(val.value()); - } - }; - - ValueExpr Calculator::eval(Expr& n) - { - ValueExpr res; - - n.accept(*this); - res.swap(calcres); - - return res; - } - - void Calculator::visit(Eq& n) - { - evalPairShortCircuit(n, Calc{}); - } - - void Calculator::visit(StrictEq& n) - { - evalPairShortCircuit(n, Calc{}); - } - - void Calculator::visit(Neq& n) - { - evalPairShortCircuit(n, Calc{}); - } - - void Calculator::visit(StrictNeq& n) - { - evalPairShortCircuit(n, Calc{}); - } - - void Calculator::visit(Less& n) - { - evalPairShortCircuit(n, Calc{}); - } - - void Calculator::visit(Greater& n) - { - evalPairShortCircuit(n, Calc{}); - } - - void Calculator::visit(Leq& n) - { - evalPairShortCircuit(n, Calc{}); - } - - void Calculator::visit(Geq& n) - { - evalPairShortCircuit(n, Calc{}); - } - - void Calculator::visit(And& n) - { - evalShortCircuit(n, false); - } - - void Calculator::visit(Or& n) - { - evalShortCircuit(n, true); - } - - void Calculator::visit(Not& n) - { - unary(n, Calc{}); - } - - void Calculator::visit(NotNot& n) - { - unary(n, Calc{}); - } - - void Calculator::visit(Add& n) - { - reduce(n, Calc{}); - } - - void Calculator::visit(Sub& n) - { - binary(n, Calc{}); - } - - void Calculator::visit(Mul& n) - { - reduce(n, Calc{}); - } - - void Calculator::visit(Div& n) - { - binary(n, Calc
{}); - } - - void Calculator::visit(Mod& n) - { - binary(n, Calc{}); - } - - void Calculator::visit(Min& n) - { - reduce(n, Calc{}); - } - - void Calculator::visit(Max& n) - { - reduce(n, Calc{}); - } - - void Calculator::visit(Cat& n) - { - reduce(n, Calc{}); - } - - void Calculator::visit(Substr&) { unsupported(); } - void Calculator::visit(Array&) { unsupported(); } - void Calculator::visit(Map&) { unsupported(); } - void Calculator::visit(Reduce&) { unsupported(); } - void Calculator::visit(Filter&) { unsupported(); } - void Calculator::visit(All&) { unsupported(); } - void Calculator::visit(None&) { unsupported(); } - void Calculator::visit(Some&) { unsupported(); } - void Calculator::visit(Merge&) { unsupported(); } - void Calculator::visit(In&) { unsupported(); } - - void Calculator::visit(Error&) { unsupported(); } - - void Calculator::visit(Var& n) - { - assert(n.num_evaluated_operands() == 1); - - AnyExpr elm = convert(eval(n.operand(0)), StringOperator{}); - - if (StringVal* str = dynamic_cast(elm.get())) - { - CXX_LIKELY; - calcres = vars(str->value(), n.num()); - return; - } - - typeError(); - } - - - void Calculator::visit(Log&) - { - const int num = n.num_evaluated_operands(); - assert(num == 1); - - calcres = calc(*eval(n.operand(0))); - - std::cerr << calcres << std::endl; - } - - void Calculator::visit(NullVal& n) { _value(n); } - void Calculator::visit(BoolVal& n) { _value(n); } - void Calculator::visit(IntVal& n) { _value(n); } - void Calculator::visit(UintVal& n) { _value(n); } - void Calculator::visit(DoubleVal& n) { _value(n); } - void Calculator::visit(StringVal& n) { _value(n); } - - void traverseInSAttributeOrder(Expr& e, Visitor& vis) - { - SAttributeTraversal trav{vis}; - - e.accept(trav); - } - - ValueExpr calculate(ValueExpr& exp, const Calculator::VarAccess& vars) - { - Calculator calc{vars, std::cerr}; - - assert(exp.get()); - return calc.eval(*exp); - } - - ValueExpr calculate(ValueExpr& exp) - { - return calculate(exp, [](const json::string&, int) -> ValueExpr { unsupported(); }); - } - - std::ostream& operator<<(std::ostream& os, ValueExpr& n) - { - struct ValuePrinter : FwdVisitor - { - explicit - ValuePrinter(std::ostream& stream) - : os(stream) - {} - - void visit(NullVal&) final { os << toString(nullptr); } - void visit(BoolVal& n) final { os << toString(n.value()); } - void visit(IntVal& n) final { os << n.value(); } - void visit(UintVal& n) final { os << n.value(); } - void visit(DoubleVal& n) final { os << n.value(); } - void visit(StringVal& n) final { os << n.value(); } - void visit(Array&) final { unsupported(); } - - std::ostream& os; - }; - - ValuePrinter prn{os}; - - n->accept(prn); - return os; - } - - // accept implementations - void Eq::accept(Visitor& v) { v.visit(*this); } - void StrictEq::accept(Visitor& v) { v.visit(*this); } - void Neq::accept(Visitor& v) { v.visit(*this); } - void StrictNeq::accept(Visitor& v) { v.visit(*this); } - void Less::accept(Visitor& v) { v.visit(*this); } - void Greater::accept(Visitor& v) { v.visit(*this); } - void Leq::accept(Visitor& v) { v.visit(*this); } - void Geq::accept(Visitor& v) { v.visit(*this); } - void And::accept(Visitor& v) { v.visit(*this); } - void Or::accept(Visitor& v) { v.visit(*this); } - void Not::accept(Visitor& v) { v.visit(*this); } - void NotNot::accept(Visitor& v) { v.visit(*this); } - void Add::accept(Visitor& v) { v.visit(*this); } - void Sub::accept(Visitor& v) { v.visit(*this); } - void Mul::accept(Visitor& v) { v.visit(*this); } - void Div::accept(Visitor& v) { v.visit(*this); } - void Mod::accept(Visitor& v) { v.visit(*this); } - void Min::accept(Visitor& v) { v.visit(*this); } - void Max::accept(Visitor& v) { v.visit(*this); } - void Map::accept(Visitor& v) { v.visit(*this); } - void Reduce::accept(Visitor& v) { v.visit(*this); } - void Filter::accept(Visitor& v) { v.visit(*this); } - void All::accept(Visitor& v) { v.visit(*this); } - void None::accept(Visitor& v) { v.visit(*this); } - void Some::accept(Visitor& v) { v.visit(*this); } - void Array::accept(Visitor& v) { v.visit(*this); } - void Merge::accept(Visitor& v) { v.visit(*this); } - void Cat::accept(Visitor& v) { v.visit(*this); } - void Substr::accept(Visitor& v) { v.visit(*this); } - void In::accept(Visitor& v) { v.visit(*this); } - void Var::accept(Visitor& v) { v.visit(*this); } - void Log::accept(Visitor& v) { v.visit(*this); } - void If::accept(Visitor& v) { v.visit(*this); } - - void NullVal::accept(Visitor& v) { v.visit(*this); } - void BoolVal::accept(Visitor& v) { v.visit(*this); } - void IntVal::accept(Visitor& v) { v.visit(*this); } - void UintVal::accept(Visitor& v) { v.visit(*this); } - void DoubleVal::accept(Visitor& v) { v.visit(*this); } - void StringVal::accept(Visitor& v) { v.visit(*this); } - - void Error::accept(Visitor& v) { v.visit(*this); } - - void FwdVisitor::visit(Expr&) { /* error ? */ } - - // num_evaluated_operands implementations - int Operator::num_evaluated_operands() const - { - return size(); - } - - template - int OperatorN::num_evaluated_operands() const - { - return std::min(MaxArity, Operator::num_evaluated_operands()); - } -} - diff --git a/include/clippy/clippy-eval.hpp b/include/clippy/clippy-eval.hpp deleted file mode 100644 index ad29610..0000000 --- a/include/clippy/clippy-eval.hpp +++ /dev/null @@ -1,3312 +0,0 @@ - -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace json_logic { -constexpr bool DEBUG_OUTPUT = false; - -namespace json = boost::json; - -using JsonExpr = json::value; - -struct Expr; - -template -T& up_cast(T& n) { - return n; -} - -template -T& down_cast(Expr& e); - -struct cast_error : std::runtime_error { - using base = std::runtime_error; - using base::base; -}; - -struct type_error : std::runtime_error { - using base = std::runtime_error; - using base::base; -}; - -template -T& deref(T* p, const char* msg = "assertion failed") { - if (p == nullptr) { - CXX_UNLIKELY; - throw Error{msg}; - } - - return *p; -} - -template -T& deref(std::unique_ptr& p, const char* msg = "assertion failed") { - if (p.get() == nullptr) { - CXX_UNLIKELY; - throw Error{msg}; - } - - return *p; -} - -struct Visitor; - -// the root class -struct Expr { - Expr() = default; - virtual ~Expr() = default; - - virtual void accept(Visitor&) = 0; - - private: - Expr(Expr&&) = delete; - Expr(const Expr&) = delete; - Expr& operator=(Expr&&) = delete; - Expr& operator=(const Expr&) = delete; -}; - -//~ Expr::~Expr() {} - -// -// foundation classes -// \{ - -using AnyExpr = std::unique_ptr; -using ValueExpr = - std::unique_ptr; // \todo consider std::unique_ptr - -struct Operator : Expr, private std::vector { - using container_type = std::vector; - - using container_type::at; - using container_type::back; - using container_type::begin; - using container_type::const_iterator; - using container_type::const_reverse_iterator; - using container_type::crbegin; - using container_type::crend; - using container_type::end; - using container_type::iterator; - using container_type::push_back; - using container_type::rbegin; - using container_type::rend; - using container_type::reverse_iterator; - using container_type::size; - - // 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); } - - container_type& operands() { return *this; } - container_type&& move_operands() && { return std::move(*this); } - - Expr& operand(int n) const { return deref(this->at(n).get()); } - - virtual int num_evaluated_operands() const; -}; - -// defines operators that have an upper bound on how many -// arguments are evaluated. -template -struct OperatorN : Operator { - enum { MAX_OPERANDS = MaxArity }; - - int num_evaluated_operands() const final; -}; - -struct Value : Expr { - virtual JsonExpr toJson() const = 0; -}; - -template -struct ValueT : Value { - using value_type = T; - - explicit ValueT(T t) : val(std::move(t)) {} - - T& value() { return val; } - const T& value() const { return val; } - - JsonExpr toJson() const final; - - private: - T val; -}; - -// \} - -// -// expression hierarchy -// comparison - -// binary -struct Eq : OperatorN<2> { - void accept(Visitor&) final; -}; - -struct StrictEq : OperatorN<2> { - void accept(Visitor&) final; -}; - -struct Neq : OperatorN<2> { - void accept(Visitor&) final; -}; - -struct StrictNeq : OperatorN<2> { - void accept(Visitor&) final; -}; - -// binary or ternary -struct Less : OperatorN<3> { - void accept(Visitor&) final; -}; - -struct Greater : OperatorN<3> { - void accept(Visitor&) final; -}; - -struct Leq : OperatorN<3> { - void accept(Visitor&) final; -}; - -struct Geq : OperatorN<3> { - void accept(Visitor&) final; -}; - -// logical operators - -// unary -struct Not : OperatorN<1> { - void accept(Visitor&) final; -}; - -struct NotNot : OperatorN<1> { - void accept(Visitor&) final; -}; - -// n-ary -struct And : Operator { - void accept(Visitor&) final; -}; - -struct Or : Operator { - void accept(Visitor&) final; -}; - -// control structure -struct If : Operator { - void accept(Visitor&) final; -}; - -// arithmetics -// n-ary -struct Add : Operator { - void accept(Visitor&) final; -}; - -struct Mul : Operator { - void accept(Visitor&) final; -}; - -struct Min : Operator { - void accept(Visitor&) final; -}; - -struct Max : Operator { - void accept(Visitor&) final; -}; - -// binary -struct Sub : OperatorN<2> { - void accept(Visitor&) final; -}; - -struct Div : OperatorN<2> { - void accept(Visitor&) final; -}; - -struct Mod : OperatorN<2> { - void accept(Visitor&) final; -}; - -// array - -// arrays serve a dual purpose -// they can be considered collections, but also an aggregate value -// The class is final and it supports move ctor/assignment, so the data -// can move efficiently. -struct Array final : Operator // array is modeled as operator -{ - void accept(Visitor&) final; - - // define - Array() = default; - Array(Array&&); - Array& operator=(Array&&); -}; - -Array::Array(Array&& other) : Operator() { - set_operands(std::move(other).move_operands()); -} - -Array& Array::operator=(Array&& other) { - set_operands(std::move(other).move_operands()); - return *this; -} - -struct Map : OperatorN<2> { - void accept(Visitor&) final; -}; - -struct Reduce : OperatorN<3> { - void accept(Visitor&) final; -}; - -struct Filter : OperatorN<2> { - void accept(Visitor&) final; -}; - -struct All : OperatorN<2> { - void accept(Visitor&) final; -}; - -struct None : OperatorN<2> { - void accept(Visitor&) final; -}; - -struct Some : OperatorN<2> { - void accept(Visitor&) final; -}; - -struct Merge : Operator { - void accept(Visitor&) final; -}; - -// data access -struct Var : Operator { - enum { computed = -1 }; - - void accept(Visitor&) final; - - void num(int val) { idx = val; } - int num() const { return idx; } - - private: - int idx = computed; -}; - -/// Missing is modeled as operator with arbitrary number of arguments -/// \details -/// 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 : Operator { - void accept(Visitor&) final; -}; - -struct MissingSome : OperatorN<2> { - void accept(Visitor&) final; -}; - -// string operations -struct Cat : Operator { - void accept(Visitor&) final; -}; - -struct Substr : OperatorN<3> { - void accept(Visitor&) final; -}; - -// string and array operation -struct In : Operator { - void accept(Visitor&) final; -}; - -// values -struct NullVal : Value { - NullVal() = default; - NullVal(std::nullptr_t) {} - - void accept(Visitor&) final; - - std::nullptr_t value() const { return nullptr; } - - JsonExpr toJson() const final; -}; - -struct BoolVal : ValueT { - using base = ValueT; - using base::base; - - void accept(Visitor&) final; -}; - -struct IntVal : ValueT { - using base = ValueT; - using base::base; - - void accept(Visitor&) final; -}; - -struct UintVal : ValueT { - using base = ValueT; - using base::base; - - void accept(Visitor&) final; -}; - -struct DoubleVal : ValueT { - using base = ValueT; - using base::base; - - void accept(Visitor&) final; -}; - -struct StringVal : ValueT { - using base = ValueT; - using base::base; - - void accept(Visitor&) final; -}; - -struct ObjectVal : Expr, private std::map { - using base = std::map; - using base::base; - - using base::begin; - using base::const_iterator; - using base::end; - using base::find; - using base::insert; - using base::iterator; - using base::value_type; - - base& elements() { return *this; } - - void accept(Visitor&) final; -}; - -// logger -struct Log : OperatorN<1> { - void accept(Visitor&) final; -}; - -// error node -struct Error : Expr { - void accept(Visitor&) final; -}; - -// -// jsonlogic extensions -// - -struct RegexMatch : OperatorN<2> { - void accept(Visitor&) final; -}; - -// Visitor -struct Visitor { - virtual void visit(Expr&) = 0; // error - virtual void visit(Operator& n) = 0; - virtual void visit(Eq&) = 0; - virtual void visit(StrictEq&) = 0; - virtual void visit(Neq&) = 0; - virtual void visit(StrictNeq&) = 0; - virtual void visit(Less&) = 0; - virtual void visit(Greater&) = 0; - virtual void visit(Leq&) = 0; - virtual void visit(Geq&) = 0; - virtual void visit(And&) = 0; - virtual void visit(Or&) = 0; - virtual void visit(Not&) = 0; - virtual void visit(NotNot&) = 0; - virtual void visit(Add&) = 0; - virtual void visit(Sub&) = 0; - virtual void visit(Mul&) = 0; - virtual void visit(Div&) = 0; - virtual void visit(Mod&) = 0; - virtual void visit(Min&) = 0; - virtual void visit(Max&) = 0; - virtual void visit(Map&) = 0; - virtual void visit(Reduce&) = 0; - virtual void visit(Filter&) = 0; - virtual void visit(All&) = 0; - virtual void visit(None&) = 0; - virtual void visit(Some&) = 0; - virtual void visit(Array&) = 0; - virtual void visit(Merge&) = 0; - virtual void visit(Cat&) = 0; - virtual void visit(Substr&) = 0; - virtual void visit(In&) = 0; - virtual void visit(Var&) = 0; - virtual void visit(Missing&) = 0; - virtual void visit(MissingSome&) = 0; - virtual void visit(Log&) = 0; - - // control structure - virtual void visit(If&) = 0; - - // values - virtual void visit(Value&) = 0; - virtual void visit(NullVal&) = 0; - virtual void visit(BoolVal&) = 0; - virtual void visit(IntVal&) = 0; - virtual void visit(UintVal&) = 0; - virtual void visit(DoubleVal&) = 0; - virtual void visit(StringVal&) = 0; - //~ virtual void visit(ObjectVal&) = 0; - - virtual void visit(Error&) = 0; - - // extensions - virtual void visit(RegexMatch&) = 0; -}; - -// accept implementations -void Eq::accept(Visitor& v) { v.visit(*this); } -void StrictEq::accept(Visitor& v) { v.visit(*this); } -void Neq::accept(Visitor& v) { v.visit(*this); } -void StrictNeq::accept(Visitor& v) { v.visit(*this); } -void Less::accept(Visitor& v) { v.visit(*this); } -void Greater::accept(Visitor& v) { v.visit(*this); } -void Leq::accept(Visitor& v) { v.visit(*this); } -void Geq::accept(Visitor& v) { v.visit(*this); } -void And::accept(Visitor& v) { v.visit(*this); } -void Or::accept(Visitor& v) { v.visit(*this); } -void Not::accept(Visitor& v) { v.visit(*this); } -void NotNot::accept(Visitor& v) { v.visit(*this); } -void Add::accept(Visitor& v) { v.visit(*this); } -void Sub::accept(Visitor& v) { v.visit(*this); } -void Mul::accept(Visitor& v) { v.visit(*this); } -void Div::accept(Visitor& v) { v.visit(*this); } -void Mod::accept(Visitor& v) { v.visit(*this); } -void Min::accept(Visitor& v) { v.visit(*this); } -void Max::accept(Visitor& v) { v.visit(*this); } -void Map::accept(Visitor& v) { v.visit(*this); } -void Reduce::accept(Visitor& v) { v.visit(*this); } -void Filter::accept(Visitor& v) { v.visit(*this); } -void All::accept(Visitor& v) { v.visit(*this); } -void None::accept(Visitor& v) { v.visit(*this); } -void Some::accept(Visitor& v) { v.visit(*this); } -void Array::accept(Visitor& v) { v.visit(*this); } -void Merge::accept(Visitor& v) { v.visit(*this); } -void Cat::accept(Visitor& v) { v.visit(*this); } -void Substr::accept(Visitor& v) { v.visit(*this); } -void In::accept(Visitor& v) { v.visit(*this); } -void Var::accept(Visitor& v) { v.visit(*this); } -void Missing::accept(Visitor& v) { v.visit(*this); } -void MissingSome::accept(Visitor& v) { v.visit(*this); } -void Log::accept(Visitor& v) { v.visit(*this); } -void If::accept(Visitor& v) { v.visit(*this); } - -void NullVal::accept(Visitor& v) { v.visit(*this); } -void BoolVal::accept(Visitor& v) { v.visit(*this); } -void IntVal::accept(Visitor& v) { v.visit(*this); } -void UintVal::accept(Visitor& v) { v.visit(*this); } -void DoubleVal::accept(Visitor& v) { v.visit(*this); } -void StringVal::accept(Visitor& v) { v.visit(*this); } - -void Error::accept(Visitor& v) { v.visit(*this); } -void RegexMatch::accept(Visitor& v) { v.visit(*this); } - -// toJson implementations -template -JsonExpr ValueT::toJson() const { - return value(); -} - -JsonExpr NullVal::toJson() const { return value(); } - -// num_evaluated_operands implementations -int Operator::num_evaluated_operands() const { return size(); } - -template -int OperatorN::num_evaluated_operands() const { - return std::min(MaxArity, Operator::num_evaluated_operands()); -} - -struct FwdVisitor : Visitor { - void visit(Expr&) override {} // error - void visit(Operator& n) override { visit(up_cast(n)); } - void visit(Eq& n) override { visit(up_cast(n)); } - void visit(StrictEq& n) override { visit(up_cast(n)); } - void visit(Neq& n) override { visit(up_cast(n)); } - void visit(StrictNeq& n) override { visit(up_cast(n)); } - void visit(Less& n) override { visit(up_cast(n)); } - void visit(Greater& n) override { visit(up_cast(n)); } - void visit(Leq& n) override { visit(up_cast(n)); } - void visit(Geq& n) override { visit(up_cast(n)); } - void visit(And& n) override { visit(up_cast(n)); } - void visit(Or& n) override { visit(up_cast(n)); } - void visit(Not& n) override { visit(up_cast(n)); } - void visit(NotNot& n) override { visit(up_cast(n)); } - void visit(Add& n) override { visit(up_cast(n)); } - void visit(Sub& n) override { visit(up_cast(n)); } - void visit(Mul& n) override { visit(up_cast(n)); } - void visit(Div& n) override { visit(up_cast(n)); } - void visit(Mod& n) override { visit(up_cast(n)); } - void visit(Min& n) override { visit(up_cast(n)); } - void visit(Max& n) override { visit(up_cast(n)); } - void visit(Map& n) override { visit(up_cast(n)); } - void visit(Reduce& n) override { visit(up_cast(n)); } - void visit(Filter& n) override { visit(up_cast(n)); } - void visit(All& n) override { visit(up_cast(n)); } - void visit(None& n) override { visit(up_cast(n)); } - void visit(Some& n) override { visit(up_cast(n)); } - void visit(Array& n) override { visit(up_cast(n)); } - void visit(Merge& n) override { visit(up_cast(n)); } - void visit(Cat& n) override { visit(up_cast(n)); } - void visit(Substr& n) override { visit(up_cast(n)); } - void visit(In& n) override { visit(up_cast(n)); } - void visit(Var& n) override { visit(up_cast(n)); } - void visit(Missing& n) override { visit(up_cast(n)); } - void visit(MissingSome& n) override { visit(up_cast(n)); } - void visit(Log& n) override { visit(up_cast(n)); } - - void visit(If& n) override { visit(up_cast(n)); } - - void visit(Value& n) override { visit(up_cast(n)); } - void visit(NullVal& n) override { visit(up_cast(n)); } - void visit(BoolVal& n) override { visit(up_cast(n)); } - void visit(IntVal& n) override { visit(up_cast(n)); } - void visit(UintVal& n) override { visit(up_cast(n)); } - void visit(DoubleVal& n) override { visit(up_cast(n)); } - void visit(StringVal& n) override { visit(up_cast(n)); } - - void visit(Error& n) override { visit(up_cast(n)); } - - // extensions - void visit(RegexMatch& n) override { visit(up_cast(n)); } -}; - -template -struct GVisitor : FwdVisitor { - explicit GVisitor(UnderVisitorT& selfref) : self(selfref) {} - - // list of all concrete types - void visit(Eq& n) final { self.gvisit(n); } - void visit(StrictEq& n) final { self.gvisit(n); } - void visit(Neq& n) final { self.gvisit(n); } - void visit(StrictNeq& n) final { self.gvisit(n); } - void visit(Less& n) final { self.gvisit(n); } - void visit(Greater& n) final { self.gvisit(n); } - void visit(Leq& n) final { self.gvisit(n); } - void visit(Geq& n) final { self.gvisit(n); } - void visit(And& n) final { self.gvisit(n); } - void visit(Or& n) final { self.gvisit(n); } - void visit(Not& n) final { self.gvisit(n); } - void visit(NotNot& n) final { self.gvisit(n); } - void visit(Add& n) final { self.gvisit(n); } - void visit(Sub& n) final { self.gvisit(n); } - void visit(Mul& n) final { self.gvisit(n); } - void visit(Div& n) final { self.gvisit(n); } - void visit(Mod& n) final { self.gvisit(n); } - void visit(Min& n) final { self.gvisit(n); } - void visit(Max& n) final { self.gvisit(n); } - void visit(Map& n) final { self.gvisit(n); } - void visit(Reduce& n) final { self.gvisit(n); } - void visit(Filter& n) final { self.gvisit(n); } - void visit(All& n) final { self.gvisit(n); } - void visit(None& n) final { self.gvisit(n); } - void visit(Some& n) final { self.gvisit(n); } - void visit(Array& n) final { self.gvisit(n); } - void visit(Merge& n) final { self.gvisit(n); } - void visit(Cat& n) final { self.gvisit(n); } - void visit(Substr& n) final { self.gvisit(n); } - void visit(In& n) final { self.gvisit(n); } - void visit(Var& n) final { self.gvisit(n); } - void visit(Missing& n) final { self.gvisit(n); } - void visit(MissingSome& n) final { self.gvisit(n); } - void visit(Log& n) final { self.gvisit(n); } - - void visit(If& n) final { self.gvisit(n); } - - // void visit(Value& n) final { self.gvisit(n); } - void visit(NullVal& n) final { self.gvisit(n); } - void visit(BoolVal& n) final { self.gvisit(n); } - void visit(IntVal& n) final { self.gvisit(n); } - void visit(UintVal& n) final { self.gvisit(n); } - void visit(DoubleVal& n) final { self.gvisit(n); } - void visit(StringVal& n) final { self.gvisit(n); } - - void visit(Error& n) final { self.gvisit(n); } - void visit(RegexMatch& n) final { self.gvisit(n); } - - private: - UnderVisitorT& self; -}; - -namespace { -CXX_NORETURN -void unsupported() { throw std::logic_error("not yet implemented"); } - -CXX_NORETURN -void typeError() { throw type_error("typing error"); } - -CXX_NORETURN -void requiresArgumentError() { - throw std::runtime_error("insufficient arguments"); -} - -struct VarMap { - void insert(Var& el); - std::vector toVector() const; - bool hasComputedVariables() const { return hasComputed; } - - private: - using container_type = std::map; - - container_type mapping = {}; - bool hasComputed = false; -}; - -void VarMap::insert(Var& var) { - try { - AnyExpr& arg = var.back(); - StringVal& str = down_cast(*arg); - const bool comp = (str.value().find('.') != json::string::npos && - str.value().find('[') != json::string::npos); - - if (comp) { - hasComputed = true; - } else if (str.value() != "") // do nothing for free variables in "lambdas" - { - auto [pos, success] = mapping.emplace(str.value(), mapping.size()); - - var.num(pos->second); - } - } catch (const cast_error&) { - hasComputed = true; - } -} - -std::vector VarMap::toVector() const { - std::vector res; - - res.resize(mapping.size()); - - for (const container_type::value_type& el : mapping) - res.at(el.second) = el.first; - - return res; -} - -/// translates all children -/// \{ -Operator::container_type translateChildren(json::array& children, VarMap&); - -Operator::container_type translateChildren(JsonExpr& n, VarMap&); -/// \} - -template -ExprT& mkOperator_(json::object& n, VarMap& m) { - assert(n.size() == 1); - - ExprT& res = deref(new ExprT); - - res.set_operands(translateChildren(n.begin()->value(), m)); - return res; -} - -template -Expr& mkOperator(json::object& n, VarMap& m) { - return mkOperator_(n, m); -} - -Expr& mkVar(json::object& n, VarMap& m) { - Var& v = mkOperator_(n, m); - - m.insert(v); - return v; -} - -Array& mkArrayOperator(json::array& children, VarMap& m) { - Array& res = deref(new Array); - - res.set_operands(translateChildren(children, m)); - return res; -} - -template -ValueT& mkValue(typename ValueT::value_type n) { - return deref(new ValueT(std::move(n))); -} - -NullVal& mkNullValue() { return deref(new NullVal); } - -using DispatchTable = std::map; - -DispatchTable::const_iterator lookup(const DispatchTable& m, - const json::object& op) { - if (op.size() != 1) return m.end(); - - return m.find(op.begin()->key()); -} - -AnyExpr translateNode_internal(JsonExpr& n, VarMap& varmap) { - static const DispatchTable dt = { - {"==", &mkOperator}, - {"===", &mkOperator}, - {"!=", &mkOperator}, - {"!==", &mkOperator}, - {"if", &mkOperator}, - {"!", &mkOperator}, - {"!!", &mkOperator}, - {"or", &mkOperator}, - {"and", &mkOperator}, - {">", &mkOperator}, - {">=", &mkOperator}, - {"<", &mkOperator}, - {"<=", &mkOperator}, - {"max", &mkOperator}, - {"min", &mkOperator}, - {"+", &mkOperator}, - {"-", &mkOperator}, - {"*", &mkOperator}, - {"/", &mkOperator
}, - {"%", &mkOperator}, - {"map", &mkOperator}, - {"reduce", &mkOperator}, - {"filter", &mkOperator}, - {"all", &mkOperator}, - {"none", &mkOperator}, - {"some", &mkOperator}, - {"merge", &mkOperator}, - {"in", &mkOperator}, - {"cat", &mkOperator}, - {"log", &mkOperator}, - {"var", &mkVar}, - {"missing", &mkOperator}, - {"missing_some", &mkOperator}, - /// extensions - {"regex", &mkOperator}, - }; - - Expr* res = nullptr; - - switch (n.kind()) { - case json::kind::object: { - json::object& obj = n.get_object(); - DispatchTable::const_iterator pos = lookup(dt, obj); - - if (pos != dt.end()) { - CXX_LIKELY; - res = &pos->second(obj, varmap); - } else { - // does json_logic support value objects? - - unsupported(); - } - - break; - } - - case json::kind::array: { - // array is an operator that combines its subexpressions into an array - res = &mkArrayOperator(n.get_array(), varmap); - break; - } - - case json::kind::string: { - res = &mkValue(std::move(n.get_string())); - break; - } - - case json::kind::int64: { - res = &mkValue(n.get_int64()); - break; - } - - case json::kind::uint64: { - res = &mkValue(n.get_uint64()); - break; - } - - case json::kind::double_: { - res = &mkValue(n.get_double()); - break; - } - - case json::kind::bool_: { - res = &mkValue(n.get_bool()); - break; - } - - case json::kind::null: { - res = &mkNullValue(); - break; - } - - default: - unsupported(); - } - - return AnyExpr(res); -} - -inline std::tuple, bool> translateNode( - JsonExpr& n) { - VarMap varmap; - AnyExpr node = translateNode_internal(n, varmap); - bool hasComputedVariables = varmap.hasComputedVariables(); - - return {std::move(node), varmap.toVector(), hasComputedVariables}; -} - -Operator::container_type translateChildren(json::array& children, - VarMap& varmap) { - Operator::container_type res; - - res.reserve(children.size()); - - for (JsonExpr& elem : children) - res.emplace_back(translateNode_internal(elem, varmap)); - - return res; -} - -Operator::container_type translateChildren(JsonExpr& n, VarMap& varmap) { - if (json::array* arr = n.if_array()) { - CXX_LIKELY; - return translateChildren(*arr, varmap); - } - - Operator::container_type res; - - res.emplace_back(translateNode_internal(n, varmap)); - return res; -} -} // namespace - -std::ostream& operator<<(std::ostream& os, ValueExpr& n); - -// -// to value conversion - -ValueExpr toValueExpr(const json::value& n); - -ValueExpr toValueExpr(std::nullptr_t) { return ValueExpr(new NullVal); } -ValueExpr toValueExpr(bool val) { return ValueExpr(new BoolVal(val)); } -ValueExpr toValueExpr(std::int64_t val) { return ValueExpr(new IntVal(val)); } -ValueExpr toValueExpr(std::uint64_t val) { return ValueExpr(new UintVal(val)); } -ValueExpr toValueExpr(double val) { return ValueExpr(new DoubleVal(val)); } -ValueExpr toValueExpr(json::string val) { - return ValueExpr(new StringVal(std::move(val))); -} - -ValueExpr toValueExpr(const json::array& val) { - Operator::container_type elems; - - std::transform( - val.begin(), val.end(), std::back_inserter(elems), - [](const json::value& el) -> ValueExpr { return toValueExpr(el); }); - - Array& arr = deref(new Array); - - arr.set_operands(std::move(elems)); - return ValueExpr(&arr); -} - -CXX_MAYBE_UNUSED -ValueExpr toValueExpr(const json::value& n) { - ValueExpr res; - - switch (n.kind()) { - case json::kind::string: { - res = toValueExpr(n.get_string()); - break; - } - - case json::kind::int64: { - res = toValueExpr(n.get_int64()); - break; - } - - case json::kind::uint64: { - res = toValueExpr(n.get_uint64()); - break; - } - - case json::kind::double_: { - res = toValueExpr(n.get_double()); - break; - } - - case json::kind::bool_: { - res = toValueExpr(n.get_bool()); - break; - } - - case json::kind::null: { - res = toValueExpr(nullptr); - break; - } - - case json::kind::array: { - res = toValueExpr(n.get_array()); - break; - } - - default: - unsupported(); - } - - assert(res.get()); - return res; -} - -// -// coercion functions - -struct CoercionError {}; -struct OutsideOfInt64Range : CoercionError {}; -struct OutsideOfUint64Range : CoercionError {}; -struct UnpackedArrayRequired : CoercionError {}; - -/// conversion to int64 -/// \{ -inline std::int64_t toConcreteValue(std::int64_t v, const std::int64_t&) { - return v; -} -inline std::int64_t toConcreteValue(const json::string& str, - const std::int64_t&) { - return std::stoll(std::string{str.c_str()}); -} -inline std::int64_t toConcreteValue(double v, const std::int64_t&) { return v; } -inline std::int64_t toConcreteValue(bool v, const std::int64_t&) { return v; } -inline std::int64_t toConcreteValue(std::nullptr_t, const std::int64_t&) { - return 0; -} - -inline std::int64_t toConcreteValue(std::uint64_t v, const std::int64_t&) { - if (v > std::uint64_t(std::numeric_limits::max())) { - CXX_UNLIKELY; - throw OutsideOfInt64Range{}; - } - - return v; -} -/// \} - -/// conversion to uint64 -/// \{ -inline std::uint64_t toConcreteValue(std::uint64_t v, const std::uint64_t&) { - return v; -} -inline std::uint64_t toConcreteValue(const json::string& str, - const std::uint64_t&) { - return std::stoull(std::string{str.c_str()}); -} -inline std::uint64_t toConcreteValue(double v, const std::uint64_t&) { - return v; -} -inline std::uint64_t toConcreteValue(bool v, const std::uint64_t&) { return v; } -inline std::uint64_t toConcreteValue(std::nullptr_t, const std::uint64_t&) { - return 0; -} - -inline std::uint64_t toConcreteValue(std::int64_t v, const std::uint64_t&) { - if (v < 0) { - CXX_UNLIKELY; - throw OutsideOfUint64Range{}; - } - - return v; -} -/// \} - -/// conversion to double -/// \{ -inline double toConcreteValue(const json::string& str, const double&) { - return std::stod(std::string{str.c_str()}); -} -inline double toConcreteValue(std::int64_t v, const double&) { return v; } -inline double toConcreteValue(std::uint64_t v, const double&) { return v; } -inline double toConcreteValue(double v, const double&) { return v; } -inline double toConcreteValue(bool v, const double&) { return v; } -inline double toConcreteValue(std::nullptr_t, const double&) { return 0; } -/// \} - -/// conversion to string -/// \{ -template -inline json::string toConcreteValue(Val v, const json::string&) { - return json::string{std::to_string(v)}; -} - -inline json::string toConcreteValue(bool v, const json::string&) { - return json::string{v ? "true" : "false"}; -} -inline json::string toConcreteValue(const json::string& s, - const json::string&) { - return s; -} -inline json::string toConcreteValue(std::nullptr_t, const json::string&) { - return json::string{"null"}; -} -/// \} - -/// conversion to boolean -/// implements truthy, falsy as described by https://jsonlogic.com/truthy.html -/// \{ -inline bool toConcreteValue(bool v, const bool&) { return v; } -inline bool toConcreteValue(std::int64_t v, const bool&) { return v; } -inline bool toConcreteValue(std::uint64_t v, const bool&) { return v; } -inline bool toConcreteValue(double v, const bool&) { return v; } -inline bool toConcreteValue(const json::string& v, const bool&) { - return v.size() != 0; -} -inline bool toConcreteValue(std::nullptr_t, const bool&) { return false; } - -// \todo not sure if conversions from arrays to values should be supported like -// this -inline bool toConcreteValue(const Array& v, const bool&) { - return v.num_evaluated_operands(); -} -/// \} - -struct ComparisonOperatorBase { - enum { - definedForString = true, - definedForDouble = true, - definedForInteger = true, - definedForBool = true, - definedForNull = true, - definedForArray = true - }; - - using result_type = bool; -}; - -/// \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 StrictEqualityOperator : ComparisonOperatorBase { - std::tuple coerce(Array*, Array*) { - return {true, false}; // arrays are never equal - } - - template - std::tuple coerce(LhsT* lv, RhsT* rv) { - return {std::move(*lv), std::move(*rv)}; - } - - std::tuple coerce(std::nullptr_t, - std::nullptr_t) { - return {nullptr, nullptr}; // two null pointers are equal - } - - template - std::tuple coerce(LhsT* lv, std::nullptr_t) { - return {std::move(*lv), nullptr}; - } - - template - std::tuple coerce(std::nullptr_t, RhsT* rv) { - return {nullptr, std::move(*rv)}; - } -}; - -struct NumericBinaryOperatorBase { - std::tuple coerce(double* lv, double* rv) { - return {*lv, *rv}; - } - - std::tuple coerce(double* lv, std::int64_t* rv) { - return {*lv, toConcreteValue(*rv, *lv)}; - } - - std::tuple coerce(double* lv, std::uint64_t* rv) { - return {*lv, toConcreteValue(*rv, *lv)}; - } - - std::tuple coerce(std::int64_t* lv, double* rv) { - return {toConcreteValue(*lv, *rv), *rv}; - } - - std::tuple coerce(std::int64_t* lv, - std::int64_t* rv) { - return {*lv, *rv}; - } - - std::tuple coerce(std::int64_t* lv, - std::uint64_t* rv) { - return {*lv, toConcreteValue(*rv, *lv)}; - } - - std::tuple coerce(std::uint64_t* lv, double* rv) { - return {toConcreteValue(*lv, *rv), *rv}; - } - - std::tuple coerce(std::uint64_t* lv, - std::int64_t* rv) { - return {toConcreteValue(*lv, *rv), *rv}; - } - - std::tuple coerce(std::uint64_t* lv, - std::uint64_t* rv) { - return {*lv, *rv}; - } -}; - -/// \brief an equality operator compares two values. If the -/// values have a different type, type coercion is performed -/// on one of the operands. -struct RelationalOperatorBase : NumericBinaryOperatorBase { - using NumericBinaryOperatorBase::coerce; - - std::tuple coerce(double* lv, json::string* rv) { - return {*lv, toConcreteValue(*rv, *lv)}; - } - - std::tuple coerce(double* lv, bool* rv) { - return {*lv, toConcreteValue(*rv, *lv)}; - } - - std::tuple coerce(std::int64_t* lv, - json::string* rv) { - return {*lv, toConcreteValue(*rv, *lv)}; - } - - std::tuple coerce(std::int64_t* lv, bool* rv) { - return {*lv, toConcreteValue(*rv, *lv)}; - } - - std::tuple coerce(std::uint64_t* lv, - json::string* rv) { - return {*lv, toConcreteValue(*rv, *lv)}; - } - - std::tuple coerce(std::uint64_t* lv, bool* rv) { - return {*lv, toConcreteValue(*rv, *lv)}; - } - - std::tuple coerce(json::string* lv, double* rv) { - return {toConcreteValue(*lv, *rv), *rv}; - } - - std::tuple coerce(bool* lv, double* rv) { - return {toConcreteValue(*lv, *rv), *rv}; - } - - std::tuple coerce(json::string* lv, - std::int64_t* rv) { - return {toConcreteValue(*lv, *rv), *rv}; - } - - std::tuple coerce(bool* lv, std::int64_t* rv) { - return {toConcreteValue(*lv, *rv), *rv}; - } - - std::tuple coerce(json::string* lv, - std::uint64_t* rv) { - return {toConcreteValue(*lv, *rv), *rv}; - } - - std::tuple coerce(bool* lv, std::uint64_t* rv) { - return {toConcreteValue(*lv, *rv), *rv}; - } - - std::tuple coerce(json::string*, bool* rv) { - // strings and boolean are never equal - return {!*rv, *rv}; - } - - std::tuple coerce(bool* lv, json::string*) { - // strings and boolean are never equal - return {*lv, !*lv}; - } - - std::tuple coerce(json::string* lv, - json::string* rv) { - return {std::move(*lv), std::move(*rv)}; - } - - std::tuple coerce(bool* lv, bool* rv) { return {*lv, *rv}; } -}; - -struct EqualityOperator : RelationalOperatorBase, ComparisonOperatorBase { - using RelationalOperatorBase::coerce; - - // due to special conversion rules, the coercion function may just produce - // the result instead of just unpacking and coercing values. - - std::tuple coerce(Array*, Array*) { - return {true, false}; // arrays are never equal - } - - template - std::tuple coerce(T* lv, Array* rv) { - // an array may be compared to a value - // (1) *lv == arr[0], iff the array has exactly one element - if (rv->num_evaluated_operands() == 1) throw UnpackedArrayRequired{}; - - // (2) or if [] and *lv converts to false - if (rv->num_evaluated_operands() > 1) return {false, true}; - - const bool convToFalse = toConcreteValue(*lv, false) == false; - - return {convToFalse, true /* zero elements */}; - } - - template - std::tuple coerce(Array* lv, T* rv) { - // see comments in coerce(T*,Array*) - if (lv->num_evaluated_operands() == 1) throw UnpackedArrayRequired{}; - - if (lv->num_evaluated_operands() > 1) return {false, true}; - - const bool convToFalse = toConcreteValue(*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 - } - - template - std::tuple coerce(T*, std::nullptr_t) { - return {false, true}; // null pointer is only equal to itself - } - - template - std::tuple coerce(std::nullptr_t, T*) { - return {true, false}; // null pointer is only equal to itself - } - - std::tuple coerce(Array*, std::nullptr_t) { - return {true, false}; // null pointer is only equal to itself - } - - std::tuple coerce(std::nullptr_t, Array*) { - return {true, false}; // null pointer is only equal to itself - } -}; - -struct RelationalOperator : RelationalOperatorBase, ComparisonOperatorBase { - using RelationalOperatorBase::coerce; - - std::tuple coerce(Array* lv, Array* rv) { return {lv, rv}; } - - template - std::tuple coerce(T* lv, Array* rv) { - // an array may be equal to another value if - // (1) *lv == arr[0], iff the array has exactly one element - if (rv->num_evaluated_operands() == 1) throw UnpackedArrayRequired{}; - - // (2) or if [] and *lv converts to false - if (rv->num_evaluated_operands() > 1) return {false, true}; - - const bool convToTrue = toConcreteValue(*lv, true) == true; - - return {convToTrue, false /* zero elements */}; - } - - template - std::tuple coerce(Array* lv, T* rv) { - // see comments in coerce(T*,Array*) - if (lv->num_evaluated_operands() == 1) throw UnpackedArrayRequired{}; - - if (lv->num_evaluated_operands() > 1) return {false, true}; - - const bool convToTrue = toConcreteValue(*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::tuple coerce(bool* lv, std::nullptr_t) { - return {*lv, false}; // null pointer -> false - } - - std::tuple coerce(std::int64_t* lv, - std::nullptr_t) { - return {*lv, 0}; // null pointer -> 0 - } - - std::tuple coerce(std::uint64_t* lv, - std::nullptr_t) { - return {*lv, 0}; // null pointer -> 0 - } - - std::tuple coerce(double* lv, std::nullptr_t) { - return {*lv, 0}; // null pointer -> 0.0 - } - - std::tuple coerce(json::string* lv, - std::nullptr_t) { - return {std::move(*lv), nullptr}; // requires special handling - } - - std::tuple coerce(std::nullptr_t, bool* rv) { - return {false, *rv}; // null pointer -> false - } - - std::tuple coerce(std::nullptr_t, - std::int64_t* rv) { - return {0, *rv}; // null pointer -> 0 - } - - std::tuple coerce(std::nullptr_t, - std::uint64_t* rv) { - return {0, *rv}; // null pointer -> 0 - } - - std::tuple coerce(std::nullptr_t, double* rv) { - return {0, *rv}; // null pointer -> 0 - } - - std::tuple coerce(std::nullptr_t, - json::string* rv) { - return {nullptr, std::move(*rv)}; // requires special handling - } -}; -// @} - -// Arith -struct ArithmeticOperator : NumericBinaryOperatorBase { - enum { - definedForString = false, - definedForDouble = true, - definedForInteger = true, - definedForBool = false, - definedForNull = true, - definedForArray = false - }; - - using result_type = ValueExpr; - - using NumericBinaryOperatorBase::coerce; - - std::tuple coerce(double*, std::nullptr_t) { - return {nullptr, nullptr}; - } - - std::tuple coerce(std::int64_t*, - std::nullptr_t) { - return {nullptr, nullptr}; - } - - std::tuple coerce(std::uint64_t*, - std::nullptr_t) { - return {nullptr, nullptr}; - } - - std::tuple coerce(std::nullptr_t, double*) { - return {nullptr, nullptr}; - } - - std::tuple coerce(std::nullptr_t, - std::int64_t*) { - return {nullptr, nullptr}; - } - - std::tuple coerce(std::nullptr_t, - std::uint64_t*) { - return {nullptr, nullptr}; - } - - std::tuple coerce(std::nullptr_t, - std::nullptr_t) { - return {nullptr, nullptr}; - } -}; - -struct IntegerArithmeticOperator : ArithmeticOperator { - enum { - definedForString = false, - definedForDouble = false, - definedForInteger = true, - definedForBool = false, - definedForNull = false, - definedForArray = false - }; - - using ArithmeticOperator::coerce; -}; - -struct StringOperator { - enum { - definedForString = true, - definedForDouble = false, - definedForInteger = false, - definedForBool = false, - definedForNull = false, - definedForArray = false - }; - - using result_type = ValueExpr; - - std::tuple coerce(json::string* lv, - json::string* rv) { - return {std::move(*lv), std::move(*rv)}; - } -}; - -struct ArrayOperator { - enum { - definedForString = false, - definedForDouble = false, - definedForInteger = false, - definedForBool = false, - definedForNull = false, - definedForArray = true - }; - - using result_type = ValueExpr; - - std::tuple coerce(Array* lv, Array* rv) { return {lv, rv}; } -}; - -AnyExpr convert(AnyExpr val, ...) { return val; } - -AnyExpr convert(AnyExpr val, const ArithmeticOperator&) { - struct ArithmeticConverter : FwdVisitor { - explicit ArithmeticConverter(AnyExpr val) : res(std::move(val)) {} - - void visit(Expr&) final { typeError(); } - - // defined for the following types - void visit(IntVal&) final {} - void visit(UintVal&) final {} - void visit(DoubleVal&) final {} - void visit(NullVal&) final {} - - // need to convert values - void visit(StringVal& el) final { - double dd = toConcreteValue(el.value(), double{}); - std::int64_t ii = toConcreteValue(el.value(), std::int64_t{}); - // uint? - - res = (dd != ii) ? toValueExpr(dd) : toValueExpr(ii); - } - - void visit(BoolVal&) final { - // \todo correct? - res = toValueExpr(nullptr); - } - - AnyExpr result() && { return std::move(res); } - - private: - AnyExpr res; - }; - - Expr* node = val.get(); - ArithmeticConverter conv{std::move(val)}; - - node->accept(conv); - return std::move(conv).result(); -} - -AnyExpr convert(AnyExpr val, const IntegerArithmeticOperator&) { - struct IntegerArithmeticConverter : FwdVisitor { - explicit IntegerArithmeticConverter(AnyExpr val) : res(std::move(val)) {} - - void visit(Expr&) final { typeError(); } - - // defined for the following types - void visit(IntVal&) final {} - void visit(UintVal&) final {} - - // need to convert values - void visit(StringVal& el) final { - res = toValueExpr(toConcreteValue(el.value(), std::int64_t{})); - } - - void visit(BoolVal& el) final { - res = toValueExpr(toConcreteValue(el.value(), std::int64_t{})); - } - - void visit(DoubleVal& el) final { - res = toValueExpr(toConcreteValue(el.value(), std::int64_t{})); - } - - void visit(NullVal&) final { res = toValueExpr(std::int64_t{0}); } - - AnyExpr result() && { return std::move(res); } - - private: - AnyExpr res; - }; - - Expr* node = val.get(); - IntegerArithmeticConverter conv{std::move(val)}; - - node->accept(conv); - return std::move(conv).result(); -} - -AnyExpr convert(AnyExpr val, const StringOperator&) { - struct StringConverter : FwdVisitor { - explicit StringConverter(AnyExpr val) : res(std::move(val)) {} - - void visit(Expr&) final { typeError(); } - - // defined for the following types - void visit(StringVal&) final {} - - // need to convert values - void visit(BoolVal& el) final { - res = toValueExpr(toConcreteValue(el.value(), json::string{})); - } - - void visit(IntVal& el) final { - res = toValueExpr(toConcreteValue(el.value(), json::string{})); - } - - void visit(UintVal& el) final { - res = toValueExpr(toConcreteValue(el.value(), json::string{})); - } - - void visit(DoubleVal& el) final { - res = toValueExpr(toConcreteValue(el.value(), json::string{})); - } - - void visit(NullVal& el) final { - res = toValueExpr(toConcreteValue(el.value(), json::string{})); - } - - AnyExpr result() && { return std::move(res); } - - private: - AnyExpr res; - }; - - Expr* node = val.get(); - StringConverter conv{std::move(val)}; - - node->accept(conv); - return std::move(conv).result(); -} - -AnyExpr convert(AnyExpr val, const ArrayOperator&) { - struct ArrayConverter : FwdVisitor { - explicit ArrayConverter(AnyExpr val) : val(std::move(val)) {} - - void visit(Expr&) final { typeError(); } - - // moves res into - void toArray() { - Array& arr = deref(new Array); - Operator::container_type operands; - - operands.emplace_back(&arr); - - // swap the operand and result - std::swap(operands.back(), val); - - // then set the operands - arr.set_operands(std::move(operands)); - } - - // defined for the following types - void visit(Array&) final {} - - // need to move value to new array - void visit(StringVal&) final { toArray(); } - void visit(BoolVal&) final { toArray(); } - void visit(IntVal&) final { toArray(); } - void visit(UintVal&) final { toArray(); } - void visit(DoubleVal&) final { toArray(); } - void visit(NullVal&) final { toArray(); } - - AnyExpr result() && { return std::move(val); } - - private: - AnyExpr val; - }; - - Expr* node = val.get(); - ArrayConverter conv{std::move(val)}; - - node->accept(conv); - return std::move(conv).result(); -} - -template -struct UnpackVisitor : FwdVisitor { - UnpackVisitor() : res() {} - - void assign(ValueT& lhs, const ValueT& val) { lhs = val; } - - template - void assign(ValueT& lhs, const U& val) { - lhs = toConcreteValue(val, lhs); - } - - void visit(Expr&) final { typeError(); } - - // defined for the following types - void visit(StringVal& el) final { assign(res, el.value()); } - - // need to convert values - void visit(BoolVal& el) final { assign(res, el.value()); } - - void visit(IntVal& el) final { assign(res, el.value()); } - - void visit(UintVal& el) final { assign(res, el.value()); } - - void visit(DoubleVal& el) final { assign(res, el.value()); } - - void visit(NullVal& el) final { assign(res, el.value()); } - - void visit(Array& el) final { - if constexpr (std::is_same::value) return assign(res, el); - - typeError(); - } - - ValueT result() && { return std::move(res); } - - private: - ValueT res; -}; - -template -T unpackValue(Expr& expr) { - UnpackVisitor unpack; - - expr.accept(unpack); - return std::move(unpack).result(); -} - -template -T unpackValue(ValueExpr& el) { - return unpackValue(*el); -} - -template -T unpackValue(ValueExpr&& el) { - return unpackValue(*el); -} - -// -// Json Logic - truthy/falsy -bool truthy(Expr& el) { return unpackValue(el); } -bool truthy(ValueExpr& el) { return unpackValue(el); } -bool truthy(ValueExpr&& el) { return unpackValue(std::move(el)); } -bool falsy(Expr& el) { return !truthy(el); } -bool falsy(ValueExpr& el) { return !truthy(el); } -bool falsy(ValueExpr&& el) { return !truthy(std::move(el)); } - -// -// cloning - -AnyExpr cloneExpr(AnyExpr& expr); - -struct ExprCloner : GVisitor { - using base = GVisitor; - - ExprCloner() : base(*this), res(nullptr) {} - - /// init functions that set up children - /// \{ - Expr& init(Operator&, Operator&); - Expr& init(ObjectVal&, ObjectVal&); - /// \} - - /// 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(Expr&, const Expr&) { unsupported(); } - - Expr& clone(Error&, const Error&) { return deref(new Error); } - - template - Expr& clone(TValue& n, const Value&) { - return deref(new TValue(n.value())); - } - - template - Expr& clone(TOperator& n, const Operator&) { - return init(n, deref(new TOperator)); - } - - Expr& clone(ObjectVal& n, const ObjectVal&) { - return init(n, deref(new ObjectVal)); - } - /// \} - - template - void gvisit(TExpr& n) { - res = &clone(n, n); - } - - Expr* result() { return res; } - - private: - Expr* res; -}; - -Expr& ExprCloner::init(Operator& src, Operator& tgt) { - Operator::container_type children; - - std::transform(src.operands().begin(), src.operands().end(), - std::back_inserter(children), - [](AnyExpr& e) -> AnyExpr { return cloneExpr(e); }); - - tgt.set_operands(std::move(children)); - return tgt; -} - -Expr& ExprCloner::init(ObjectVal& src, ObjectVal& tgt) { - std::transform(src.begin(), src.end(), - std::inserter(tgt.elements(), tgt.end()), - [](ObjectVal::value_type& entry) -> ObjectVal::value_type { - return {entry.first, cloneExpr(entry.second)}; - }); - - return tgt; -} - -AnyExpr cloneExpr(AnyExpr& expr) { - ExprCloner cloner; - - expr->accept(cloner); - return ValueExpr{cloner.result()}; -} - -// -// internal down cast - -template -struct DownCastVisitor : GVisitor > { - using base = GVisitor >; - - DownCastVisitor() : base(*this), res(nullptr) {} - - void gvisit(Expr&) { /* not found */ } - - void gvisit(ExprT& n) { res = &n; } - - ExprT* result() { return res; } - - private: - ExprT* res; -}; - -template -ExprT& down_cast(Expr& expr) { - DownCastVisitor vis; - - expr.accept(vis); - return deref(vis.result()); -} - -template -auto with_type(Expr* e, Fn fn, AltFn altfn) -> decltype(altfn()) { - if (e == nullptr) { - CXX_UNLIKELY; - return altfn(); - } - - DownCastVisitor vis; - e->accept(vis); - - return vis.result() ? fn(*vis.result()) : altfn(); -} - -// -// binary operator - double dispatch pattern - -template -struct BinaryOperatorVisitor_ : FwdVisitor { - using result_type = typename BinaryOperator::result_type; - - BinaryOperatorVisitor_(LhsValue lval, BinaryOperator oper) - : lv(lval), op(oper), res() {} - - template - void calc(RhsValue rv) { - auto [ll, rr] = op.coerce(lv, rv); - - res = op(std::move(ll), std::move(rr)); - } - - void visit(Expr&) final { typeError(); } - - void visit(StringVal& n) final { - if constexpr (BinaryOperator::definedForString) return calc(&n.value()); - - typeError(); - } - - void visit(NullVal&) final { - if constexpr (BinaryOperator::definedForNull) return calc(nullptr); - - typeError(); - } - - void visit(BoolVal& n) final { - if constexpr (BinaryOperator::definedForBool) return calc(&n.value()); - - typeError(); - } - - void visit(IntVal& n) final { - if constexpr (BinaryOperator::definedForInteger) { - try { - return calc(&n.value()); - } catch (const OutsideOfInt64Range& ex) { - if (n.value() < 0) { - CXX_UNLIKELY; - throw std::range_error{ - "unable to consolidate uint>max(int) with int<0"}; - } - } - - std::uint64_t alt = n.value(); - return calc(&alt); - } - - typeError(); - } - - void visit(UintVal& n) final { - if constexpr (BinaryOperator::definedForInteger) { - try { - return calc(&n.value()); - } catch (const OutsideOfUint64Range& ex) { - if (n.value() > std::uint64_t(std::numeric_limits::max)) { - CXX_UNLIKELY; - throw std::range_error{ - "unable to consolidate int<0 with uint>max(int)"}; - } - } - - std::int64_t alt = n.value(); - return calc(&alt); - } - - typeError(); - } - - void visit(DoubleVal& n) final { - if constexpr (BinaryOperator::definedForDouble) return calc(&n.value()); - - typeError(); - } - - void visit(Array& n) final { - if constexpr (BinaryOperator::definedForArray) { - try { - calc(&n); - } catch (const UnpackedArrayRequired&) { - assert(n.num_evaluated_operands() == 1); - n.operand(0).accept(*this); - } - - return; - } - - typeError(); - } - - result_type result() && { return std::move(res); } - - private: - LhsValue lv; - BinaryOperator op; - result_type res; -}; - -template -struct BinaryOperatorVisitor : FwdVisitor { - using result_type = typename BinaryOperator::result_type; - - BinaryOperatorVisitor(BinaryOperator oper, ValueExpr& rhsarg) - : op(oper), rhs(rhsarg), res() {} - - template - void calc(LhsValue lv) { - using RhsVisitor = BinaryOperatorVisitor_; - - RhsVisitor vis{lv, op}; - - rhs->accept(vis); - res = std::move(vis).result(); - } - - void visit(StringVal& n) final { - if constexpr (BinaryOperator::definedForString) return calc(&n.value()); - - typeError(); - } - - void visit(NullVal&) final { - if constexpr (BinaryOperator::definedForNull) return calc(nullptr); - - typeError(); - } - - void visit(BoolVal& n) final { - if constexpr (BinaryOperator::definedForBool) return calc(&n.value()); - - typeError(); - } - - void visit(IntVal& n) final { - if constexpr (BinaryOperator::definedForInteger) { - try { - return calc(&n.value()); - } catch (const OutsideOfInt64Range& ex) { - if (n.value() < 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); - } - - typeError(); - } - - void visit(UintVal& n) final { - if constexpr (BinaryOperator::definedForInteger) { - try { - return calc(&n.value()); - } catch (const OutsideOfUint64Range& ex) { - if (n.value() > std::uint64_t(std::numeric_limits::max)) { - CXX_UNLIKELY; - throw std::range_error{ - "unable to consolidate uint>max(int) with int<0"}; - } - } - - std::int64_t alt = n.value(); - return calc(&alt); - } - - typeError(); - } - - void visit(DoubleVal& n) final { - if constexpr (BinaryOperator::definedForDouble) return calc(&n.value()); - - typeError(); - } - - void visit(Array& n) final { - if constexpr (BinaryOperator::definedForArray) { - try { - calc(&n); - } catch (const UnpackedArrayRequired&) { - assert(n.num_evaluated_operands() == 1); - n.operand(0).accept(*this); - } - - return; - } - - typeError(); - } - - result_type result() && { return std::move(res); } - - private: - BinaryOperator op; - ValueExpr& rhs; - result_type res; -}; - -// -// compute and sequence functions - -template -typename BinaryOperator::result_type compute(ValueExpr& lhs, ValueExpr& rhs, - BinaryOperator op) { - using LhsVisitor = BinaryOperatorVisitor; - - assert(lhs.get() && rhs.get()); - - LhsVisitor vis{op, rhs}; - - lhs->accept(vis); - return std::move(vis).result(); -} - -template -bool compareSeq(Array& lv, Array& rv, BinaryPredicate pred) { - const std::size_t lsz = lv.num_evaluated_operands(); - const std::size_t rsz = rv.num_evaluated_operands(); - - if (lsz == 0) return pred(false, rsz != 0); - - 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); - - // res is conclusive if the reverse test yields a different result - found = res != compute(rv.at(i), lv.at(i), pred); - - ++i; - } - - return found ? res : pred(lsz, rsz); -} - -template -bool compareSeq(Array* lv, Array* rv, BinaryPredicate pred) { - return compareSeq(deref(lv), deref(rv), std::move(pred)); -} - -// convenience template that only returns a type when the types mismatch -template -struct MismatchedTypes { - using type = ResultType; -}; - -template -struct MismatchedTypes { - // using type // triggers SFINAE exclusion -}; - -// -// the calc operator implementations - -template -struct Calc {}; - -template <> -struct Calc : EqualityOperator { - using EqualityOperator::result_type; - - template - auto operator()(const U&, const V&) const -> - typename MismatchedTypes::type // type mismatch - { - return false; - } - - template - auto operator()(const T& lhs, const T& rhs) const -> result_type { - return lhs == rhs; - } -}; - -template <> -struct Calc : EqualityOperator { - using EqualityOperator::result_type; - - template - auto operator()(const U&, const V&) const -> - typename MismatchedTypes::type // type mismatch - { - return true; - } - - template - result_type operator()(const T& lhs, const T& rhs) const { - return lhs != rhs; - } -}; - -template <> -struct Calc : StrictEqualityOperator { - using StrictEqualityOperator::result_type; - - template - auto operator()(const U&, const V&) const -> - typename MismatchedTypes::type // type mismatch - { - return false; - } - - template - result_type operator()(const T& lhs, const T& rhs) const { - return lhs == rhs; - } -}; - -template <> -struct Calc : StrictEqualityOperator { - using StrictEqualityOperator::result_type; - - template - auto operator()(const U&, const V&) const -> - typename MismatchedTypes::type // type mismatch - { - return true; - } - - template - result_type operator()(const T& lhs, const T& rhs) const { - return lhs != rhs; - } -}; - -template <> -struct Calc : RelationalOperator { - using RelationalOperator::result_type; - - result_type operator()(const std::nullptr_t, std::nullptr_t) const { - return false; - } - - result_type operator()(Array* lv, Array* rv) const { - return compareSeq(lv, rv, *this); - } - - result_type operator()(const json::string&, std::nullptr_t) const { - return false; - } - - result_type operator()(std::nullptr_t, const json::string&) const { - return false; - } - - template - result_type operator()(const T& lhs, const T& rhs) const { - return lhs < rhs; - } -}; - -template <> -struct Calc : RelationalOperator { - using RelationalOperator::result_type; - - result_type operator()(const std::nullptr_t, std::nullptr_t) const { - return false; - } - - result_type operator()(Array* lv, Array* rv) const { - return compareSeq(lv, rv, *this); - } - - result_type operator()(const json::string&, std::nullptr_t) const { - return false; - } - - result_type operator()(std::nullptr_t, const json::string&) const { - return false; - } - - template - result_type operator()(const T& lhs, const T& rhs) const { - return rhs < lhs; - } -}; - -template <> -struct Calc : RelationalOperator { - using RelationalOperator::result_type; - - result_type operator()(const std::nullptr_t, std::nullptr_t) const { - return true; - } - - result_type operator()(Array* lv, Array* rv) const { - return compareSeq(lv, rv, *this); - } - - result_type operator()(const json::string& lhs, std::nullptr_t) const { - return lhs.empty(); - } - - result_type operator()(std::nullptr_t, const json::string& rhs) const { - return rhs.empty(); - } - - template - result_type operator()(const T& lhs, const T& rhs) const { - return lhs <= rhs; - } -}; - -template <> -struct Calc : RelationalOperator { - using RelationalOperator::result_type; - - result_type operator()(const std::nullptr_t, std::nullptr_t) const { - return true; - } - - result_type operator()(Array* lv, Array* rv) const { - return compareSeq(lv, rv, *this); - } - - result_type operator()(const json::string& lhs, std::nullptr_t) const { - return lhs.empty(); - } - - result_type operator()(std::nullptr_t, const json::string& rhs) const { - return rhs.empty(); - } - - template - result_type operator()(const T& lhs, const T& rhs) const { - return rhs <= lhs; - } -}; - -template <> -struct Calc : ArithmeticOperator { - using ArithmeticOperator::result_type; - - result_type operator()(std::nullptr_t, std::nullptr_t) const { - return toValueExpr(nullptr); - } - - template - result_type operator()(const T& lhs, const T& rhs) const { - return toValueExpr(lhs + rhs); - } -}; - -template <> -struct Calc : ArithmeticOperator { - using ArithmeticOperator::result_type; - - result_type operator()(std::nullptr_t, std::nullptr_t) const { - return toValueExpr(nullptr); - } - - template - result_type operator()(const T& lhs, const T& rhs) const { - return toValueExpr(lhs - rhs); - } -}; - -template <> -struct Calc : ArithmeticOperator { - using ArithmeticOperator::result_type; - - result_type operator()(std::nullptr_t, std::nullptr_t) const { - return toValueExpr(nullptr); - } - - template - result_type operator()(const T& lhs, const T& rhs) const { - return toValueExpr(lhs * rhs); - } -}; - -template <> -struct Calc
: ArithmeticOperator { - using ArithmeticOperator::result_type; - - result_type operator()(std::nullptr_t, std::nullptr_t) const { - return toValueExpr(nullptr); - } - - result_type operator()(double lhs, double rhs) const { - double res = lhs / rhs; - - // if (isInteger(res)) return toInt(res); - return toValueExpr(res); - } - - template - result_type operator()(Int_t lhs, Int_t rhs) const { - if (lhs % rhs) return (*this)(double(lhs), double(rhs)); - - return toValueExpr(lhs / rhs); - } -}; - -template <> -struct Calc : IntegerArithmeticOperator { - using IntegerArithmeticOperator::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 toValueExpr(nullptr); - - return toValueExpr(lhs % rhs); - } -}; - -template <> -struct Calc : ArithmeticOperator { - using ArithmeticOperator::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 toValueExpr(std::min(lhs, rhs)); - } -}; - -template <> -struct Calc : ArithmeticOperator { - using ArithmeticOperator::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 toValueExpr(std::max(lhs, rhs)); - } -}; - -template <> -struct Calc { - using result_type = bool; - - result_type operator()(Expr& val) const { return falsy(val); } -}; - -template <> -struct Calc { - using result_type = bool; - - result_type operator()(Expr& val) const { return truthy(val); } -}; - -template <> -struct Calc : StringOperator { - using StringOperator::result_type; - - result_type operator()(const json::string& lhs, - const json::string& rhs) const { - json::string tmp; - - tmp.reserve(lhs.size() + rhs.size()); - - tmp.append(lhs.begin(), lhs.end()); - tmp.append(rhs.begin(), rhs.end()); - - return toValueExpr(std::move(tmp)); - } -}; - -template <> -struct Calc : StringOperator // \todo the conversion rules differ -{ - using StringOperator::result_type; - - result_type operator()(const json::string& lhs, - const json::string& rhs) const { - const bool res = (rhs.find(lhs) != json::string::npos); - - return toValueExpr(res); - } -}; - -template <> -struct Calc : StringOperator // \todo the conversion rules differ -{ - using StringOperator::result_type; - - result_type operator()(const json::string& lhs, - const json::string& rhs) const { - std::regex rgx(lhs.c_str(), lhs.size()); - - return toValueExpr(std::regex_search(rhs.begin(), rhs.end(), rgx)); - } -}; - -template <> -struct Calc : ArrayOperator { - using ArrayOperator::result_type; - - result_type operator()(Array* lhs, Array* rhs) const { - // note, to use the lhs entirely, it would need to be released - // from its ValueExpr - Array& res = deref(new Array); - - { - Operator::container_type& opers = res.operands(); - - opers.swap(lhs->operands()); - - Operator::container_type& ropers = rhs->operands(); - - opers.insert(opers.end(), std::make_move_iterator(ropers.begin()), - std::make_move_iterator(ropers.end())); - } - - return ValueExpr(&res); - } -}; - -struct Calculator : FwdVisitor { - using VarAccess = std::function; - - Calculator(VarAccess varAccess, std::ostream& out) - : vars(std::move(varAccess)), logger(out), calcres(nullptr) {} - - void visit(Eq&) final; - void visit(StrictEq&) final; - void visit(Neq&) final; - void visit(StrictNeq&) final; - void visit(Less&) final; - void visit(Greater&) final; - void visit(Leq&) final; - void visit(Geq&) final; - void visit(And&) final; - void visit(Or&) final; - void visit(Not&) final; - void visit(NotNot&) final; - void visit(Add&) final; - void visit(Sub&) final; - void visit(Mul&) final; - void visit(Div&) final; - void visit(Mod&) final; - void visit(Min&) final; - void visit(Max&) final; - void visit(Array&) 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(In&) final; - void visit(Var&) final; - void visit(Missing&) final; - void visit(MissingSome&) final; - void visit(Log&) final; - - void visit(If&) final; - - void visit(NullVal& n) final; - void visit(BoolVal& n) final; - void visit(IntVal& n) final; - void visit(UintVal& n) final; - void visit(DoubleVal& n) final; - void visit(StringVal& n) final; - - void visit(Error& n) final; - void visit(RegexMatch& n) final; - - ValueExpr eval(Expr& n); - - private: - VarAccess vars; - std::ostream& logger; - ValueExpr calcres; - - Calculator(const Calculator&) = delete; - Calculator(Calculator&&) = delete; - Calculator& operator=(const Calculator&) = delete; - Calculator& operator=(Calculator&&) = delete; - - // - // opers - - /// implements relop : [1, 2, 3, whatever] as 1 relop 2 relop 3 - template - void evalPairShortCircuit(Operator& n, BinaryPredicate pred); - - /// returns the first expression in [ e1, e2, e3 ] that evaluates to val, or - /// the last expression otherwise - void evalShortCircuit(Operator& n, bool val); - - /// reduction operation on all elements - template - void reduce(Operator& n, BinaryOperator op); - - /// computes unary operation on n[0] - template - void unary(Operator& n, UnaryOperator calc); - - /// binary operation on all elements (invents an element if none is present) - template - void binary(Operator& n, BinaryOperator binop); - - /// evaluates and unpacks n[argpos] to a fundamental value - template - ValueT unpackOptionalArg(Operator& n, int argpos, const ValueT& defaultVal); - - template - void _value(const ValueNode& val) { - calcres = toValueExpr(val.value()); - } - - /// auxiliary missing method - std::size_t missing_aux(Array& elems); -}; - -struct SequenceFunction { - SequenceFunction(Expr& e, std::ostream& logstream) - : expr(e), logger(logstream) {} - - ValueExpr operator()(ValueExpr&& elem) const { - ValueExpr* elptr = &elem; // workaround, b/c unique_ptr cannot be captured - - Calculator sub{[elptr](const json::value& keyval, int) -> ValueExpr { - if (const json::string* pkey = keyval.if_string()) { - const json::string& key = *pkey; - - if (key.size() == 0) return cloneExpr(*elptr); - - try { - ObjectVal& o = down_cast(**elptr); - - if (auto pos = o.find(key); pos != o.end()) - return cloneExpr(pos->second); - } catch (const cast_error&) { - } - } - - return toValueExpr(nullptr); - }, - logger}; - - return sub.eval(expr); - } - - private: - Expr& expr; - std::ostream& logger; -}; - -struct SequencePredicate : SequenceFunction { - using SequenceFunction::SequenceFunction; - - bool operator()(ValueExpr&& elem) const { - return truthy(SequenceFunction::operator()(std::move(elem))); - } -}; - -struct SequencePredicateNondestructive : SequenceFunction { - using SequenceFunction::SequenceFunction; - - bool operator()(ValueExpr&& elem) const { - return truthy(SequenceFunction::operator()(cloneExpr(elem))); - } -}; - -/* - template - ValueExpr - accumulate_move(InputIterator pos, InputIterator lim, ValueExpr accu, - BinaryOperation binop) - { - for ( ; pos != lim; ++pos) - accu = binop(std::move(accu), std::move(*first)); - - return accu; - } -*/ - -struct SequenceReduction { - SequenceReduction(Expr& e, std::ostream& logstream) - : expr(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. - template - ValueExpr operator()(ValueExprT&& accu, ValueExpr elem) const { - ValueExpr* acptr = &accu; // workaround, b/c unique_ptr cannot be captured - ValueExpr* elptr = &elem; // workaround, b/c unique_ptr cannot be captured - - Calculator sub{[acptr, elptr](const json::value& keyval, int) -> ValueExpr { - if (const json::string* pkey = keyval.if_string()) { - if (*pkey == "current") return cloneExpr(*elptr); - - if (*pkey == "accumulator") return cloneExpr(*acptr); - } - - return toValueExpr(nullptr); - }, - logger}; - - return sub.eval(expr); - } - - private: - Expr& expr; - std::ostream& logger; -}; - -template -ValueT Calculator::unpackOptionalArg(Operator& n, int argpos, - const ValueT& defaultVal) { - if (std::size_t(argpos) >= n.size()) { - CXX_UNLIKELY; - return defaultVal; - } - - return unpackValue(*eval(n.operand(argpos))); -} - -template -void Calculator::unary(Operator& n, UnaryPredicate pred) { - const int num = n.num_evaluated_operands(); - assert(num == 1); - - const bool res = pred(*eval(n.operand(0))); - - calcres = toValueExpr(res); -} - -template -void Calculator::binary(Operator& n, BinaryOperator binop) { - const int num = n.num_evaluated_operands(); - assert(num == 1 || num == 2); - - int idx = -1; - ValueExpr lhs; - - if (num == 2) { - CXX_LIKELY; - lhs = eval(n.operand(++idx)); - } else { - lhs = toValueExpr(std::int64_t(0)); - } - - ValueExpr rhs = eval(n.operand(++idx)); - - calcres = compute(lhs, rhs, binop); -} - -template -void Calculator::reduce(Operator& n, BinaryOperator op) { - const int num = n.num_evaluated_operands(); - assert(num >= 1); - - int idx = -1; - ValueExpr res = eval(n.operand(++idx)); - - res = convert(std::move(res), op); - - while (idx != (num - 1)) { - ValueExpr rhs = eval(n.operand(++idx)); - - rhs = convert(std::move(rhs), op); - res = compute(res, rhs, op); - } - - calcres = std::move(res); -} - -template -void Calculator::evalPairShortCircuit(Operator& n, BinaryPredicate pred) { - const int num = n.num_evaluated_operands(); - assert(num >= 2); - - bool res = true; - int idx = -1; - ValueExpr rhs = eval(n.operand(++idx)); - assert(rhs.get()); - - while (res && (idx != (num - 1))) { - ValueExpr lhs = std::move(rhs); - assert(lhs.get()); - - rhs = eval(n.operand(++idx)); - assert(rhs.get()); - - res = compute(lhs, rhs, pred); - } - - calcres = toValueExpr(res); -} - -void Calculator::evalShortCircuit(Operator& n, bool val) { - const int num = n.num_evaluated_operands(); - - if (num == 0) { - CXX_UNLIKELY; - requiresArgumentError(); - } - - int idx = -1; - ValueExpr oper = eval(n.operand(++idx)); - //~ std::cerr << idx << ") " << oper << std::endl; - - bool found = (idx == num - 1) || (truthy(*oper) == 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; - - found = (idx == (num - 1)) || (truthy(*oper) == val); - } - - calcres = std::move(oper); -} - -ValueExpr Calculator::eval(Expr& n) { - ValueExpr res; - - n.accept(*this); - res.swap(calcres); - - return res; -} - -void Calculator::visit(Eq& n) { evalPairShortCircuit(n, Calc{}); } - -void Calculator::visit(StrictEq& n) { - evalPairShortCircuit(n, Calc{}); -} - -void Calculator::visit(Neq& n) { evalPairShortCircuit(n, Calc{}); } - -void Calculator::visit(StrictNeq& n) { - evalPairShortCircuit(n, Calc{}); -} - -void Calculator::visit(Less& n) { evalPairShortCircuit(n, Calc{}); } - -void Calculator::visit(Greater& n) { evalPairShortCircuit(n, Calc{}); } - -void Calculator::visit(Leq& n) { evalPairShortCircuit(n, Calc{}); } - -void Calculator::visit(Geq& n) { evalPairShortCircuit(n, Calc{}); } - -void Calculator::visit(And& n) { evalShortCircuit(n, false); } - -void Calculator::visit(Or& n) { evalShortCircuit(n, true); } - -void Calculator::visit(Not& n) { unary(n, Calc{}); } - -void Calculator::visit(NotNot& n) { unary(n, Calc{}); } - -void Calculator::visit(Add& n) { reduce(n, Calc{}); } - -void Calculator::visit(Sub& n) { binary(n, Calc{}); } - -void Calculator::visit(Mul& n) { reduce(n, Calc{}); } - -void Calculator::visit(Div& n) { binary(n, Calc
{}); } - -void Calculator::visit(Mod& n) { binary(n, Calc{}); } - -void Calculator::visit(Min& n) { reduce(n, Calc{}); } - -void Calculator::visit(Max& n) { reduce(n, Calc{}); } - -void Calculator::visit(Cat& n) { reduce(n, Calc{}); } - -void Calculator::visit(In& n) { binary(n, Calc{}); } - -void Calculator::visit(RegexMatch& n) { binary(n, Calc{}); } - -void Calculator::visit(Substr& n) { - assert(n.num_evaluated_operands() >= 1); - - json::string str = unpackValue(*eval(n.operand(0))); - std::int64_t ofs = unpackOptionalArg(n, 1, 0); - std::int64_t cnt = unpackOptionalArg(n, 2, 0); - - if (ofs < 0) { - CXX_UNLIKELY; - ofs = std::max(std::int64_t(str.size()) + ofs, std::int64_t(0)); - } - - if (cnt < 0) { - CXX_UNLIKELY; - cnt = std::max(std::int64_t(str.size()) - ofs + cnt, std::int64_t(0)); - } - - calcres = toValueExpr(json::string{str.subview(ofs, cnt)}); -} - -void Calculator::visit(Array& n) { - Operator::container_type elems; - Calculator* self = this; - - // \todo consider making arrays lazy - std::transform( - std::make_move_iterator(n.begin()), std::make_move_iterator(n.end()), - std::back_inserter(elems), - [self](AnyExpr&& exp) -> ValueExpr { return self->eval(*exp); }); - - Array& res = deref(new Array); - - res.set_operands(std::move(elems)); - - calcres = ValueExpr(&res); -} - -void Calculator::visit(Merge& n) { reduce(n, Calc{}); } - -void Calculator::visit(Reduce& n) { - ValueExpr arr = eval(n.operand(0)); - Expr& expr = n.operand(1); - ValueExpr accu = eval(n.operand(2)); - ValueExpr* acptr = &accu; - - auto op = [&expr, acptr, - calclogger = &this->logger](Array& arrop) -> ValueExpr { - // 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), - SequenceReduction{expr, *calclogger}); - }; - - calcres = - with_type(arr.get(), op, []() -> ValueExpr { return nullptr; }); -} - -void Calculator::visit(Map& n) { - ValueExpr arr = eval(n.operand(0)); - auto mapper = [&n, &arr, - calclogger = &this->logger](Array& arrop) -> ValueExpr { - Expr& expr = n.operand(1); - Operator::container_type mapped_elements; - - std::transform(std::make_move_iterator(arrop.begin()), - std::make_move_iterator(arrop.end()), - std::back_inserter(mapped_elements), - SequenceFunction{expr, *calclogger}); - - arrop.set_operands(std::move(mapped_elements)); - return std::move(arr); - }; - - calcres = with_type( - arr.get(), mapper, []() -> ValueExpr { return ValueExpr{new Array}; }); -} - -void Calculator::visit(Filter& n) { - ValueExpr arr = eval(n.operand(0)); - auto filter = [&n, &arr, - calclogger = &this->logger](Array& arrop) -> ValueExpr { - Expr& expr = n.operand(1); - Operator::container_type 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::back_inserter(filtered_elements), - SequencePredicateNondestructive{expr, *calclogger}); - - arrop.set_operands(std::move(filtered_elements)); - return std::move(arr); - }; - - calcres = with_type( - arr.get(), filter, []() -> ValueExpr { return ValueExpr{new Array}; }); -} - -void Calculator::visit(All& n) { - ValueExpr 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()), - SequencePredicate{expr, logger}); - - calcres = toValueExpr(res); -} - -void Calculator::visit(None& n) { - ValueExpr 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()), - SequencePredicate{expr, logger}); - - calcres = toValueExpr(res); -} - -void Calculator::visit(Some& n) { - ValueExpr 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()), - SequencePredicate{expr, logger}); - - calcres = toValueExpr(res); -} - -void Calculator::visit(Error&) { unsupported(); } - -void Calculator::visit(Var& n) { - assert(n.num_evaluated_operands() >= 1); - - AnyExpr elm = eval(n.operand(0)); - Value& val = down_cast(*elm); - - try { - calcres = vars(val.toJson(), n.num()); - } catch (...) { - calcres = (n.num_evaluated_operands() > 1) ? eval(n.operand(1)) - : toValueExpr(nullptr); - } -} - -std::size_t Calculator::missing_aux(Array& elems) { - auto avail = [calc = this](ValueExpr& v) -> bool { - try { - Value& val = down_cast(*v); - - calc->vars(val.toJson(), -1 /* not in varmap */); - } catch (...) { - 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); - - elems.operands().erase(pos, lim); - return res; -} - -void Calculator::visit(Missing& n) { - ValueExpr arg = eval(n.operand(0)); - auto nonArrayHandler = [&arg, &n, calc = this]() -> Array& { - Array& res = deref(new Array); - - res.set_operands(std::move(n).move_operands()); - res.operands().front().swap(arg); - arg.reset(&res); - calc->visit(res); - - return res; - }; - - Array& elems = with_type( - arg.get(), - [](Array& array) -> Array& { return array; }, // ignore other args - nonArrayHandler); - - missing_aux(elems); - calcres = std::move(arg); -} - -void Calculator::visit(MissingSome& n) { - const std::uint64_t minreq = unpackValue(eval(n.operand(0))); - ValueExpr arr = eval(n.operand(1)); - Array& elems = down_cast(*arr); // evaluated elements - std::size_t avail = missing_aux(elems); - - if (avail >= minreq) elems.operands().clear(); - - calcres = std::move(arr); -} - -void Calculator::visit(If& n) { - const int num = n.num_evaluated_operands(); - - if (num == 0) { - calcres = toValueExpr(nullptr); - return; - } - - const int lim = num - 1; - int pos = 0; - - while (pos < lim) { - if (truthy(eval(n.operand(pos)))) { - calcres = eval(n.operand(pos + 1)); - return; - } - - pos += 2; - } - - calcres = (pos < num) ? eval(n.operand(pos)) : toValueExpr(nullptr); -} - -void Calculator::visit(Log& n) { - assert(n.num_evaluated_operands() == 1); - - calcres = eval(n.operand(0)); - - logger << calcres << std::endl; -} - -void Calculator::visit(NullVal& n) { _value(n); } -void Calculator::visit(BoolVal& n) { _value(n); } -void Calculator::visit(IntVal& n) { _value(n); } -void Calculator::visit(UintVal& n) { _value(n); } -void Calculator::visit(DoubleVal& n) { _value(n); } -void Calculator::visit(StringVal& n) { _value(n); } - -CXX_MAYBE_UNUSED -ValueExpr calculate(Expr& exp, const Calculator::VarAccess& vars) { - Calculator calc{vars, std::cerr}; - - return calc.eval(exp); -} - -CXX_MAYBE_UNUSED -ValueExpr calculate(AnyExpr& exp, const Calculator::VarAccess& vars) { - assert(exp.get()); - return calculate(*exp, vars); -} - -CXX_MAYBE_UNUSED -ValueExpr calculate(AnyExpr& exp) { - return calculate(exp, - [](const json::value&, int) -> ValueExpr { unsupported(); }); -} - -ValueExpr evalPath(const json::string& path, const json::object& obj) { - if (auto pos = obj.find(path); pos != obj.end()) - return json_logic::toValueExpr(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); - - return evalPath(suffix, obj.at(selector).as_object()); - } - - throw std::out_of_range("json_logic - unable to locate path"); -} - -template -ValueExpr evalIdx(IntT idx, const json::array& arr) { - return json_logic::toValueExpr(arr[idx]); -} - -CXX_MAYBE_UNUSED -ValueExpr apply(json::value rule, json::value data) { - auto [ast, vars, hasComputed] = translateNode(rule); - Calculator::VarAccess varlookup = [data](const json::value& keyval, - int) -> ValueExpr { - if (const json::string* ppath = keyval.if_string()) { - //~ std::cerr << *ppath << std::endl; - return ppath->size() ? evalPath(*ppath, data.as_object()) - : json_logic::toValueExpr(data); - } - - if (const std::int64_t* pidx = keyval.if_int64()) - return evalIdx(*pidx, data.as_array()); - - if (const std::uint64_t* pidx = keyval.if_uint64()) - return evalIdx(*pidx, data.as_array()); - - throw std::logic_error{"json_logic - unsupported var access"}; - }; - - return calculate(ast, varlookup); -} - -struct ValuePrinter : FwdVisitor { - explicit ValuePrinter(std::ostream& stream) : os(stream) {} - - void prn(json::value val) { os << val; } - - void visit(Value& n) final { prn(n.toJson()); } - - void visit(Array& n) final { - bool first = true; - - os << "["; - for (AnyExpr& el : n) { - if (first) - first = false; - else - os << ","; - - deref(el).accept(*this); - } - - os << "]"; - } - - private: - std::ostream& os; -}; - -std::ostream& operator<<(std::ostream& os, ValueExpr& n) { - ValuePrinter prn{os}; - - deref(n).accept(prn); - return os; -} -} // namespace json_logic - -#if SUPPLEMENTAL - -/// traverses the children of a node; does not traverse grandchildren -void traverseChildren(Visitor& v, const Operator& node); -void traverseAllChildren(Visitor& v, const Operator& node); -void traverseChildrenReverse(Visitor& v, const Operator& node); - -// only operators have children -void _traverseChildren(Visitor& v, Operator::const_iterator aa, - Operator::const_iterator zz) { - std::for_each(aa, zz, [&v](const AnyExpr& e) -> void { e->accept(v); }); -} - -void traverseChildren(Visitor& v, const Operator& node) { - Operator::const_iterator aa = node.begin(); - - _traverseChildren(v, aa, aa + node.num_evaluated_operands()); -} - -void traverseAllChildren(Visitor& v, const Operator& node) { - _traverseChildren(v, node.begin(), node.end()); -} - -void traverseChildrenReverse(Visitor& v, const Operator& node) { - Operator::const_reverse_iterator zz = node.crend(); - Operator::const_reverse_iterator aa = zz - node.num_evaluated_operands(); - - std::for_each(aa, zz, [&v](const AnyExpr& e) -> void { e->accept(v); }); -} - -namespace { -struct SAttributeTraversal : Visitor { - explicit SAttributeTraversal(Visitor& client) : sub(client) {} - - void visit(Expr&) final; - void visit(Operator&) final; - void visit(Eq&) final; - void visit(StrictEq&) final; - void visit(Neq&) final; - void visit(StrictNeq&) final; - void visit(Less&) final; - void visit(Greater&) final; - void visit(Leq&) final; - void visit(Geq&) final; - void visit(And&) final; - void visit(Or&) final; - void visit(Not&) final; - void visit(NotNot&) final; - void visit(Add&) final; - void visit(Sub&) final; - void visit(Mul&) final; - void visit(Div&) final; - void visit(Mod&) 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(In&) final; - void visit(Array& n) final; - void visit(Var&) final; - void visit(Log&) final; - - void visit(If&) final; - - void visit(NullVal& n) final; - void visit(BoolVal& n) final; - void visit(IntVal& n) final; - void visit(UintVal& n) final; - void visit(DoubleVal& n) final; - void visit(StringVal& n) final; - - void visit(Error& n) final; - - private: - Visitor& sub; - - 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&) { typeError(); } -void SAttributeTraversal::visit(Operator&) { typeError(); } -void SAttributeTraversal::visit(Eq& n) { _visit(n); } -void SAttributeTraversal::visit(StrictEq& n) { _visit(n); } -void SAttributeTraversal::visit(Neq& n) { _visit(n); } -void SAttributeTraversal::visit(StrictNeq& n) { _visit(n); } -void SAttributeTraversal::visit(Less& n) { _visit(n); } -void SAttributeTraversal::visit(Greater& n) { _visit(n); } -void SAttributeTraversal::visit(Leq& n) { _visit(n); } -void SAttributeTraversal::visit(Geq& n) { _visit(n); } -void SAttributeTraversal::visit(And& n) { _visit(n); } -void SAttributeTraversal::visit(Or& n) { _visit(n); } -void SAttributeTraversal::visit(Not& n) { _visit(n); } -void SAttributeTraversal::visit(NotNot& n) { _visit(n); } -void SAttributeTraversal::visit(Add& n) { _visit(n); } -void SAttributeTraversal::visit(Sub& n) { _visit(n); } -void SAttributeTraversal::visit(Mul& n) { _visit(n); } -void SAttributeTraversal::visit(Div& n) { _visit(n); } -void SAttributeTraversal::visit(Mod& 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(In& n) { _visit(n); } -void SAttributeTraversal::visit(Var& n) { _visit(n); } -void SAttributeTraversal::visit(Log& n) { _visit(n); } - -void SAttributeTraversal::visit(If& n) { _visit(n); } - -void SAttributeTraversal::visit(NullVal& n) { _value(n); } -void SAttributeTraversal::visit(BoolVal& n) { _value(n); } -void SAttributeTraversal::visit(IntVal& n) { _value(n); } -void SAttributeTraversal::visit(UintVal& n) { _value(n); } -void SAttributeTraversal::visit(DoubleVal& n) { _value(n); } -void SAttributeTraversal::visit(StringVal& 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 */ diff --git a/include/experimental/json-io.hpp b/include/experimental/json-io.hpp index d93bace..4a4321c 100644 --- a/include/experimental/json-io.hpp +++ b/include/experimental/json-io.hpp @@ -2,130 +2,113 @@ #pragma once +#include #include #include -#include - #include "cxx-compat.hpp" #include "dataframe.hpp" // use clippy-eval's conversion functions -#include - +#include -namespace experimental -{ - std::string OTHER_COLUMN = "__other_column%%"; +namespace experimental { +std::string OTHER_COLUMN = "__other_column%%"; -namespace -{ - using ColumnID = std::optional; +namespace { +using ColumnID = std::optional; - ColumnID - findColumn(const std::vector& colnames, boost::string_view colname) - { - using iterator = std::vector::const_iterator; +ColumnID findColumn(const std::vector& colnames, + boost::string_view colname) { + using iterator = std::vector::const_iterator; - iterator beg = colnames.begin(); - iterator lim = colnames.end(); - iterator pos = std::find(beg, lim, colname); + iterator beg = colnames.begin(); + iterator lim = colnames.end(); + iterator pos = std::find(beg, lim, colname); - return pos != lim ? ColumnID{std::distance(beg, pos)} : ColumnID{}; - } + return pos != lim ? ColumnID{std::distance(beg, pos)} : ColumnID{}; +} - template - T extractValue(const boost::json::value& val, T&& tag) - { - if (const boost::json::string* s = val.if_string()) - return json_logic::toConcreteValue(*s, tag); +template +T extractValue(const boost::json::value& val, T&& tag) { + if (const boost::json::string* s = val.if_string()) + return jsonlogic::toConcreteValue(*s, tag); - if (const std::int64_t* i = val.if_int64()) - return json_logic::toConcreteValue(*i, tag); + if (const std::int64_t* i = val.if_int64()) + return jsonlogic::toConcreteValue(*i, tag); - if (const std::uint64_t* u = val.if_uint64()) - return json_logic::toConcreteValue(*u, tag); + if (const std::uint64_t* u = val.if_uint64()) + return jsonlogic::toConcreteValue(*u, tag); - if (const double* d = val.if_double()) - return json_logic::toConcreteValue(*d, tag); + if (const double* d = val.if_double()) + return jsonlogic::toConcreteValue(*d, tag); - if (val.is_null()) - return json_logic::toConcreteValue(std::nullptr_t{}, tag); + if (val.is_null()) return jsonlogic::toConcreteValue(std::nullptr_t{}, tag); - throw std::logic_error("unsupported json value conversion."); - } + throw std::logic_error("unsupported json value conversion."); +} - template - T extractValue(json_logic::ValueExpr&& val, T&& tag) - { - return json_logic::unpackValue(std::move(val)); - } +template +T extractValue(jsonlogic::any_expr&& val, T&& tag) { + return jsonlogic::unpack_value(std::move(val)); +} +void storeAtColumn(DataFrame& frame, const ColumnVariant& coldesc, + dataframe_variant_t& cell, const boost::json::value& val) { + const std::string& celltype = coldesc.type_name(); + + if (celltype == string_type_str) + cell = + frame.persistent_string_std(extractValue(val, boost::json::string{})); + else if (celltype == int_type_str) + cell = extractValue(val, std::int64_t{}); + else if (celltype == uint_type_str) + cell = extractValue(val, std::uint64_t{}); + else if (celltype == real_type_str) + cell = extractValue(val, double{}); + else + throw std::logic_error("unknown column type"); +} - void storeAtColumn( DataFrame& frame, - const ColumnVariant& coldesc, - dataframe_variant_t& cell, - const boost::json::value& val - ) - { - const std::string& celltype = coldesc.type_name(); - - if (celltype == string_type_str) - cell = frame.persistent_string_std(extractValue(val, boost::json::string{})); - else if (celltype == int_type_str) - cell = extractValue(val, std::int64_t{}); - else if (celltype == uint_type_str) - cell = extractValue(val, std::uint64_t{}); - else if (celltype == real_type_str) - cell = extractValue(val, double{}); - else throw std::logic_error("unknown column type"); - } +std::vector allColumns(const DataFrame& frame) { + std::vector res(frame.columns()); - std::vector allColumns(const DataFrame& frame) - { - std::vector res(frame.columns()); + std::iota(res.begin(), res.end(), 0); + return res; +} - std::iota(res.begin(), res.end(), 0); - return res; - } +auto toJson(const dataframe_variant_t& el) -> boost::json::value { + if (const string_t* s = std::get_if(&el)) + return boost::json::value{boost::string_view(&*s->begin(), s->size())}; - auto toJson(const dataframe_variant_t& el) -> boost::json::value - { - if (const string_t* s = std::get_if(&el)) - return boost::json::value{ boost::string_view(&*s->begin(), s->size()) }; + if (const int_t* i = std::get_if(&el)) return boost::json::value{*i}; - if (const int_t* i = std::get_if(&el)) - return boost::json::value{*i}; + if (const real_t* r = std::get_if(&el)) return boost::json::value{*r}; - if (const real_t* r = std::get_if(&el)) - return boost::json::value{*r}; + if (const uint_t* u = std::get_if(&el)) return boost::json::value{*u}; - if (const uint_t* u = std::get_if(&el)) - return boost::json::value{*u}; + CXX_UNLIKELY; + return boost::json::value{}; +} +void injectValue(boost::json::object& o, const std::string& colname, + const dataframe_variant_t& el) { + if (std::get_if(&el)) { CXX_UNLIKELY; - return boost::json::value{}; + return; } - void injectValue(boost::json::object& o, const std::string& colname, const dataframe_variant_t& el) - { - if (std::get_if(&el)) - { - CXX_UNLIKELY; - return; - } - - o[colname] = toJson(el); - } + o[colname] = toJson(el); } +} // namespace -inline -void setCellValue(DataFrame& frame, const ColumnVariant& coldesc, std::int64_t row, json_logic::ValueExpr&& val) -{ +inline void setCellValue(DataFrame& frame, const ColumnVariant& coldesc, + std::int64_t row, jsonlogic::any_expr&& val) { ColumnVariant::pointer_variant_t ptr = coldesc.at_variant(row); if (string_t** s = std::get_if(&ptr)) - **s = frame.persistent_string_std(extractValue(std::move(val), boost::json::string{})); + **s = frame.persistent_string_std( + extractValue(std::move(val), boost::json::string{})); else if (int_t** i = std::get_if(&ptr)) **i = extractValue(std::move(val), std::int64_t{}); else if (uint_t** u = std::get_if(&ptr)) @@ -136,30 +119,26 @@ void setCellValue(DataFrame& frame, const ColumnVariant& coldesc, std::int64_t r throw std::runtime_error{"unknown column type"}; } - -void importJson(DataFrame& frame, const boost::json::value& val, bool useOtherColumn = true) -{ - const boost::json::object& obj = val.as_object(); +void importJson(DataFrame& frame, const boost::json::value& val, + bool useOtherColumn = true) { + const boost::json::object& obj = val.as_object(); std::vector row(frame.columns(), notavail_t{}); - std::vector allColumns = frame.get_column_variants(); - std::vector allColumnNames = frame.get_column_names(); - boost::json::object other; - - for (auto& el : obj) - { - if (ColumnID colid = findColumn(allColumnNames, el.key())) - { + std::vector allColumns = frame.get_column_variants(); + std::vector allColumnNames = frame.get_column_names(); + boost::json::object other; + + for (auto& el : obj) { + if (ColumnID colid = findColumn(allColumnNames, el.key())) { CXX_LIKELY; storeAtColumn(frame, allColumns.at(*colid), row.at(*colid), el.value()); - } - else if (useOtherColumn) + } else if (useOtherColumn) other.insert_or_assign(el.key(), el.value()); else - throw std::logic_error("automatic sparse column creation not yet supported"); + throw std::logic_error( + "automatic sparse column creation not yet supported"); } - if (!other.empty()) - { + if (!other.empty()) { ColumnID colid = findColumn(allColumnNames, OTHER_COLUMN); assert(colid); @@ -176,28 +155,24 @@ void importJson(DataFrame& frame, const boost::json::value& val, bool useOtherCo //~ auto cellToJson(const dataframe_variant_t& el) -> boost::json::value //~ { - //~ return toJson(el); +//~ return toJson(el); //~ } -auto exportJson(const DataFrame& frame, const std::vector& sel, std::size_t rownum) -> boost::json::object -{ - boost::json::object res; - std::vector colsel = sel.size() ? frame.get_index_list_std(sel) - : allColumns(frame); +auto exportJson(const DataFrame& frame, const std::vector& sel, + std::size_t rownum) -> boost::json::object { + boost::json::object res; + std::vector colsel = + sel.size() ? frame.get_index_list_std(sel) : allColumns(frame); std::vector row = frame.get_row_variant(rownum, colsel); - std::vector allColumnNames = frame.get_column_names(); + std::vector allColumnNames = frame.get_column_names(); - for (int idx : colsel) - { + for (int idx : colsel) { const std::string& colname = allColumnNames.at(idx); - if (idx >= 0) - { + if (idx >= 0) { CXX_LIKELY; injectValue(res, colname, row.at(idx)); - } - else - { + } else { throw std::logic_error("other column conversion not yet supported"); // res[colname] = toJsonObject(std::get(row.at(idx))); } @@ -206,6 +181,4 @@ auto exportJson(const DataFrame& frame, const std::vector& sel, std return res; } -} // namespace experimental - - +} // namespace experimental diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 0327a01..ff11887 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -13,12 +13,16 @@ function ( add_test class_name method_name ) # target_include_directories(${target} PRIVATE ${Boost_INCLUDE_DIRS}) include_directories(${CMAKE_CURRENT_SOURCE_DIR} include/) set_target_properties(${target} PROPERTIES OUTPUT_NAME "${method_name}" ) - target_include_directories(${target} PRIVATE ${PROJECT_SOURCE_DIR}/include ${BOOST_INCLUDE_DIRS} include/) + target_include_directories(${target} PRIVATE + ${PROJECT_SOURCE_DIR}/include + ${BOOST_INCLUDE_DIRS} + include/ + ${jsonlogic_SOURCE_DIR}/cpp/include + ) target_link_libraries(${target} PRIVATE Boost::json) endfunction() - add_subdirectory(TestBag) add_subdirectory(TestSet) add_subdirectory(TestFunctions) diff --git a/test/TestBag/remove_if.cpp b/test/TestBag/remove_if.cpp index 2078546..62b172c 100644 --- a/test/TestBag/remove_if.cpp +++ b/test/TestBag/remove_if.cpp @@ -3,12 +3,13 @@ // // SPDX-License-Identifier: MIT -#include "clippy/clippy-eval.hpp" #include #include #include #include +#include #include +// #include namespace boostjsn = boost::json; @@ -34,8 +35,8 @@ int main(int argc, char **argv) { auto apply_jl = [&expression](int value) { boostjsn::object data; data["value"] = value; - json_logic::ValueExpr res = json_logic::apply(expression["rule"], data); - return json_logic::unpackValue(res); + jsonlogic::any_expr res = jsonlogic::apply(expression["rule"], data); + return jsonlogic::unpack_value(res); }; the_bag.remove_if(apply_jl); diff --git a/test/TestGraph/assign.cpp b/test/TestGraph/assign.cpp index fd96529..ffeb548 100644 --- a/test/TestGraph/assign.cpp +++ b/test/TestGraph/assign.cpp @@ -8,9 +8,9 @@ #include #include #include +#include #include -#include "clippy/clippy-eval.hpp" #include "clippy/selector.hpp" #include "testgraph.hpp" #include "where.cpp" diff --git a/test/TestGraph/connected_components.cpp b/test/TestGraph/connected_components.cpp index 22421cc..3316f0a 100644 --- a/test/TestGraph/connected_components.cpp +++ b/test/TestGraph/connected_components.cpp @@ -8,9 +8,9 @@ #include #include #include +#include #include -#include "clippy/clippy-eval.hpp" #include "testgraph.hpp" static const std::string method_name = "connected_components"; diff --git a/test/TestGraph/count.cpp b/test/TestGraph/count.cpp index 2391e4c..e7407fc 100644 --- a/test/TestGraph/count.cpp +++ b/test/TestGraph/count.cpp @@ -7,9 +7,9 @@ #include #include #include +#include #include -#include "clippy/clippy-eval.hpp" #include "clippy/selector.hpp" #include "testgraph.hpp" diff --git a/test/TestGraph/degree.cpp b/test/TestGraph/degree.cpp index 657556d..ec761bd 100644 --- a/test/TestGraph/degree.cpp +++ b/test/TestGraph/degree.cpp @@ -7,9 +7,9 @@ #include #include #include +#include #include -#include "clippy/clippy-eval.hpp" #include "clippy/selector.hpp" #include "testgraph.hpp" diff --git a/test/TestGraph/dump.cpp b/test/TestGraph/dump.cpp index 5c93fbb..2b4b46d 100644 --- a/test/TestGraph/dump.cpp +++ b/test/TestGraph/dump.cpp @@ -8,9 +8,9 @@ #include #include #include +#include #include -#include "clippy/clippy-eval.hpp" #include "clippy/selector.hpp" #include "testgraph.hpp" @@ -69,7 +69,7 @@ int main(int argc, char **argv) { // we need to make a copy here because translateNode modifies the object boost::json::object exp2(expression); auto [_a /*unused*/, vars, _b /*unused*/] = - json_logic::translateNode(exp2["rule"]); + jsonlogic::translateNode(exp2["rule"]); std::cerr << "post-translate expression: " << expression << std::endl; std::cerr << "post-translate expression['rule']: " << expression["rule"] << std::endl; @@ -82,9 +82,9 @@ int main(int argc, char **argv) { std::cerr << " apply_jl: var: " << var << " val: " << val << std::endl; } - json_logic::ValueExpr res = json_logic::apply(expression["rule"], data); + jsonlogic::any_expr res = jsonlogic::apply(expression["rule"], data); std::cerr << " apply_jl: res: " << res << std::endl; - return json_logic::unpackValue(res); + return jsonlogic::unpack_value(res); }; std::string tail_sel = tailsel_opt.value(); diff --git a/test/TestGraph/dump2.cpp b/test/TestGraph/dump2.cpp index 666f4d9..6bde2b1 100644 --- a/test/TestGraph/dump2.cpp +++ b/test/TestGraph/dump2.cpp @@ -8,9 +8,9 @@ #include #include #include +// #include #include -#include "clippy/clippy-eval.hpp" #include "clippy/selector.hpp" #include "testgraph.hpp" #include "where.cpp" diff --git a/test/TestGraph/extrema.cpp b/test/TestGraph/extrema.cpp index cc13f1e..fd537b2 100644 --- a/test/TestGraph/extrema.cpp +++ b/test/TestGraph/extrema.cpp @@ -7,8 +7,8 @@ #include #include #include +#include -#include "clippy/clippy-eval.hpp" #include "clippy/selector.hpp" #include "testgraph.hpp" diff --git a/test/TestGraph/for_all_edges.cpp b/test/TestGraph/for_all_edges.cpp index 80ff892..bd4e90d 100644 --- a/test/TestGraph/for_all_edges.cpp +++ b/test/TestGraph/for_all_edges.cpp @@ -3,14 +3,15 @@ // // SPDX-License-Identifier: MIT -#include "clippy/clippy-eval.hpp" -#include "testgraph.hpp" #include #include #include #include +#include #include +#include "testgraph.hpp" + static const std::string method_name = "add_node"; static const std::string state_name = "INTERNAL"; @@ -39,8 +40,8 @@ int main(int argc, char **argv) { data["src"] = value.first; data["dst"] = value.second; data["loc"] = boost::json::value_from(loc); - json_logic::ValueExpr res = json_logic::apply(expression["rule"], data); - return json_logic::unpackValue(res); + jsonlogic::any_expr res = jsonlogic::apply(expression["rule"], data); + return jsonlogic::unpack_value(res); }; the_graph.for_all_edges(apply_jl); diff --git a/test/TestGraph/series_str.cpp b/test/TestGraph/series_str.cpp index f4ea6e7..c7120f7 100644 --- a/test/TestGraph/series_str.cpp +++ b/test/TestGraph/series_str.cpp @@ -7,8 +7,8 @@ #include #include #include +#include -#include "clippy/clippy-eval.hpp" #include "clippy/selector.hpp" #include "testgraph.hpp" diff --git a/test/TestGraph/where.cpp b/test/TestGraph/where.cpp index be25551..d42a13e 100644 --- a/test/TestGraph/where.cpp +++ b/test/TestGraph/where.cpp @@ -1,11 +1,12 @@ #include #include #include +#include #include +#include #include #include -#include "clippy/clippy-eval.hpp" #include "clippy/selector.hpp" #include "testgraph.hpp" @@ -17,16 +18,27 @@ auto parse_where_expression(M& mvmap_, boost::json::object& expression, boost::json::object exp2(expression); // boost::json::object submission_data{}; - auto [_a /*unused*/, vars, _b /*unused*/] = - json_logic::translateNode(exp2["rule"]); + std::vector vars; + jsonlogic::any_expr expression_rule_; + + // we use expression_rule_ (and expression_rule; see below) in order to avoid + // having to recompute this every time we call the lambda. + std::tie(expression_rule_, vars, std::ignore) = + jsonlogic::create_logic(exp2["rule"]); + + // this works around a deficiency in C++ compilers where + // unique pointers moved into a lambda cannot be moved into + // an std::function. + jsonlogic::expr* rawexpr = expression_rule_.release(); + std::shared_ptr expression_rule{rawexpr}; std::cerr << "parse_where: # of vars: " << vars.size() << std::endl; for (const auto& var : vars) { std::cerr << " apply_jl var dump: var: " << var << std::endl; } - auto apply_jl = [&expression, vars, &mvmap_, - &submission_data](mvmap::locator loc) { + auto apply_jl = [&expression, vars, expression_rule, &mvmap_, + &submission_data](mvmap::locator loc) mutable { std::cerr << " apply_jl: # of vars: " << vars.size() << std::endl; for (const auto& var : vars) { std::cerr << " apply_jl: var: " << var << std::endl; @@ -62,10 +74,10 @@ auto parse_where_expression(M& mvmap_, boost::json::object& expression, } std::cerr << " apply_jl: submission_data: " << submission_data << std::endl; - json_logic::ValueExpr res = - json_logic::apply(expression["rule"], submission_data); + jsonlogic::any_expr res = jsonlogic::apply( + *expression_rule, jsonlogic::data_accessor(submission_data)); std::cerr << " apply_jl: res: " << res << std::endl; - return json_logic::unpackValue(res); + return jsonlogic::unpack_value(res); }; return apply_jl; @@ -78,7 +90,7 @@ std::vector where_nodes(const testgraph::testgraph& g, std::cerr << " where: expression: " << expression << std::endl; // auto [_a /*unused*/, vars, _b /*unused*/] = - // json_logic::translateNode(exp2["rule"]); + // jsonlogic::translateNode(exp2["rule"]); // auto nodemap = g.nodemap(); // boost::json::object submission_data{}; @@ -115,10 +127,10 @@ std::vector where_nodes(const testgraph::testgraph& g, // } // } - // json_logic::ValueExpr res = - // json_logic::apply(expression["rule"], submission_data); + // jsonlogic::any_expr res = + // jsonlogic::apply(expression["rule"], submission_data); // std::cerr << " apply_jl: res: " << res << std::endl; - // return json_logic::unpackValue(res); + // return jsonlogic::unpack_value(res); // }; auto nodemap = g.nodemap(); diff --git a/test/TestSet/remove_if.cpp b/test/TestSet/remove_if.cpp index 6e322e7..28b69ea 100644 --- a/test/TestSet/remove_if.cpp +++ b/test/TestSet/remove_if.cpp @@ -7,10 +7,9 @@ #include #include #include +#include #include -#include "clippy/clippy-eval.hpp" - namespace boostjsn = boost::json; static const std::string method_name = "remove_if"; @@ -34,8 +33,8 @@ int main(int argc, char **argv) { auto apply_jl = [&expression](int value) { boostjsn::object data; data["value"] = value; - json_logic::ValueExpr res = json_logic::apply(expression["rule"], data); - return json_logic::unpackValue(res); + jsonlogic::any_expr res = jsonlogic::apply(expression["rule"], data); + return jsonlogic::unpack_value(res); }; for (auto first = the_set.begin(), last = the_set.end(); first != last;) { diff --git a/test/include/wheredev.cpp b/test/include/wheredev.cpp index aa2fabb..b822259 100644 --- a/test/include/wheredev.cpp +++ b/test/include/wheredev.cpp @@ -1,10 +1,9 @@ #include #include #include +#include #include -#include "clippy/clippy-eval.hpp" - static const std::string json_blob = R"({"rule":{"and":[{"<":[{"var":"temp"},110]},{"==":[{"var":"pie.filling"},"apple"]}]},"data":{"temp":100,"pie":{"filling":"apple"}},"expected":true})"; @@ -12,7 +11,7 @@ int main() { std::cout << json_blob << std::endl; auto data = boost::json::parse(json_blob).as_object(); auto [_a /*unused*/, vars, _b /*unused*/] = - json_logic::translateNode(data["rule"]); + jsonlogic::translateNode(data["rule"]); for (auto &v : vars) { std::cout << v << std::endl; diff --git a/test/requirements.txt b/test/requirements.txt new file mode 100644 index 0000000..bcb9aac --- /dev/null +++ b/test/requirements.txt @@ -0,0 +1,2 @@ +llnl-clippy >= 0.4 +pytest>=7,<8 \ No newline at end of file diff --git a/test/test_clippy.py b/test/test_clippy.py index 6cd1d2c..585c114 100644 --- a/test/test_clippy.py +++ b/test/test_clippy.py @@ -1,9 +1,12 @@ +# This should mirror test_clippy.py from the llnl-clippy repo. import pytest import sys +sys.path.append("src") + +import jsonlogic as jl import clippy from clippy.error import ClippyValidationError, ClippyInvalidSelectorError -from clippy.expressions import Selector import logging @@ -31,6 +34,11 @@ def testsel(): return clippy.TestSelector() +@pytest.fixture() +def testgraph(): + return clippy.TestGraph() + + def test_imports(): assert "TestBag" in clippy.__dict__ @@ -49,6 +57,12 @@ def test_bag(testbag): assert testbag.size() == 2 +def test_clippy_call_with_string(testfun): + assert testfun.call_with_string("Seth") == "Howdy, Seth" + with pytest.raises(ClippyValidationError): + testfun.call_with_string() + + def test_expression_gt_gte(testbag): testbag.insert(10).insert(41).insert(42).insert(50).insert(51).insert(52) assert testbag.size() == 6 @@ -127,12 +141,6 @@ def test_expression_mod(testbag): # assert testbag.size() == 2 -def test_clippy_call_with_string(testfun): - assert testfun.call_with_string("Seth") == "Howdy, Seth" - with pytest.raises(ClippyValidationError): - testfun.call_with_string() - - def test_clippy_returns_int(testfun): assert testfun.returns_int() == 42 @@ -173,11 +181,25 @@ def test_selectors(testsel): assert testsel.nodes.b.__doc__ == "docstring for nodes.b" assert testsel.nodes.b.c.__doc__ == "docstring for nodes.b.c" - assert isinstance(testsel.nodes.b, Selector) - assert isinstance(testsel.nodes.b.c, Selector) + assert isinstance(testsel.nodes.b, jl.Variable) + assert isinstance(testsel.nodes.b.c, jl.Variable) with pytest.raises(ClippyInvalidSelectorError): testsel.add(testsel.nodes, "_bad", desc="this is a bad selector name") # with pytest.raises(ClippyInvalidSelectorError): # testsel.add(testsel, 'bad', desc="this is a top-level selector") + + +def test_graph(testgraph): + testgraph.add_edge("a", "b").add_edge("b", "c").add_edge("a", "c").add_edge( + "c", "d" + ).add_edge("d", "e").add_edge("e", "f").add_edge("f", "g").add_edge("e", "g") + + assert testgraph.nv() == 7 + assert testgraph.ne() == 8 + + testgraph.add_series(testgraph.node, "degree", desc="node degrees") + testgraph.degree(testgraph.node.degree) + c_e_only = testgraph.dump2(testgraph.node.degree, where=testgraph.node.degree > 2) + assert "c" in c_e_only and "e" in c_e_only and len(c_e_only) == 2