diff --git a/.gitignore b/.gitignore index 04ad69d..fb113a5 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,6 @@ __pycache__ testbracket testmvmap testdf -testgraph \ No newline at end of file +test/TestGraph/testgraph +test/include/wheredev +a.out \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 421b8cf..09a7c61 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -72,20 +72,26 @@ endif() # # Boost -include(setup_boost) -prepare_fetchcontent_boost() -set(FETCHCONTENT_QUIET FALSE) +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 FetchContent_Declare( - Boost - GIT_REPOSITORY https://github.com/boostorg/boost.git - GIT_TAG boost-1.83.0 - GIT_SUBMODULES ${BOOST_REQD_SUBMODULES} - GIT_PROGRESS TRUE - CONFIGURE_COMMAND "" # tell CMake it's not a cmake project + Boost + URL ${BOOST_URL} ) +set(BOOST_INCLUDE_LIBRARIES json) FetchContent_MakeAvailable(Boost) -get_boost_include_dirs() +# +# JSONLogic +FetchContent_Declare(jsonlogic + GIT_REPOSITORY https://github.com/LLNL/jsonlogic.git + GIT_TAG master +) +FetchContent_MakeAvailable(jsonlogic) ### Require out-of-source builds file(TO_CMAKE_PATH "${PROJECT_BINARY_DIR}/CMakeLists.txt" LOC_PATH) diff --git a/include/clippy/clippy-eval.hpp b/include/clippy/clippy-eval.hpp index e805b5b..ad29610 100644 --- a/include/clippy/clippy-eval.hpp +++ b/include/clippy/clippy-eval.hpp @@ -1,3111 +1,2620 @@ #pragma once -#include #include -#include +#include +#include +#include +#include #include #include +#include #include +#include #include -#include -#include - -#include - -#include - -namespace json_logic -{ - constexpr bool DEBUG_OUTPUT = false; - namespace json = boost::json; +namespace json_logic { +constexpr bool DEBUG_OUTPUT = false; - using JsonExpr = json::value; +namespace json = boost::json; - struct Expr; +using JsonExpr = json::value; - template - T& up_cast(T& n) { return n; } +struct Expr; - template - T& down_cast(Expr& e); +template +T& up_cast(T& n) { + return n; +} - struct cast_error : std::runtime_error - { - using base = std::runtime_error; - using base::base; - }; +template +T& down_cast(Expr& e); - struct type_error : std::runtime_error - { - using base = std::runtime_error; - using base::base; - }; +struct cast_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}; - } +struct type_error : std::runtime_error { + using base = std::runtime_error; + using base::base; +}; - return *p; +template +T& deref(T* p, const char* msg = "assertion failed") { + if (p == nullptr) { + CXX_UNLIKELY; + throw Error{msg}; } - template - T& deref(std::unique_ptr& p, const char* msg = "assertion failed") - { - if (p.get() == nullptr) - { - CXX_UNLIKELY; - throw Error{msg}; - } + return *p; +} - return *p; +template +T& deref(std::unique_ptr& p, const char* msg = "assertion failed") { + if (p.get() == nullptr) { + CXX_UNLIKELY; + throw Error{msg}; } - 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 - // \{ + return *p; +} - using AnyExpr = std::unique_ptr; - using ValueExpr = std::unique_ptr; // \todo consider std::unique_ptr +struct Visitor; - 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; - using container_type::size; - using container_type::at; - - // 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()); - } +// the root class +struct Expr { + Expr() = default; + virtual ~Expr() = default; - virtual int num_evaluated_operands() const; - }; + virtual void accept(Visitor&) = 0; - // defines operators that have an upper bound on how many - // arguments are evaluated. - template - struct OperatorN : Operator - { - enum { MAX_OPERANDS = MaxArity }; + private: + Expr(Expr&&) = delete; + Expr(const Expr&) = delete; + Expr& operator=(Expr&&) = delete; + Expr& operator=(const Expr&) = delete; +}; - int num_evaluated_operands() const final; - }; +//~ Expr::~Expr() {} - struct Value : Expr - { - virtual JsonExpr toJson() const = 0; - }; +// +// foundation classes +// \{ - template - struct ValueT : Value - { - using value_type = T; +using AnyExpr = std::unique_ptr; +using ValueExpr = + std::unique_ptr; // \todo consider std::unique_ptr - explicit - ValueT(T t) - : val(std::move(t)) - {} +struct Operator : Expr, private std::vector { + using container_type = std::vector; - T& value() { return val; } - const T& value() const { return val; } + 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; - JsonExpr toJson() const final; + // 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); } - private: - T val; - }; + container_type& operands() { return *this; } + container_type&& move_operands() && { return std::move(*this); } - // \} + Expr& operand(int n) const { return deref(this->at(n).get()); } - // - // expression hierarchy - // comparison + virtual int num_evaluated_operands() const; +}; - // binary - struct Eq : OperatorN<2> - { - void accept(Visitor&) final; - }; +// defines operators that have an upper bound on how many +// arguments are evaluated. +template +struct OperatorN : Operator { + enum { MAX_OPERANDS = MaxArity }; - struct StrictEq : OperatorN<2> - { - void accept(Visitor&) final; - }; + int num_evaluated_operands() const final; +}; - struct Neq : OperatorN<2> - { - void accept(Visitor&) final; - }; +struct Value : Expr { + virtual JsonExpr toJson() const = 0; +}; - struct StrictNeq : OperatorN<2> - { - void accept(Visitor&) final; - }; +template +struct ValueT : Value { + using value_type = T; - // binary or ternary - struct Less : OperatorN<3> - { - void accept(Visitor&) final; - }; + explicit ValueT(T t) : val(std::move(t)) {} + + T& value() { return val; } + const T& value() const { return val; } - struct Greater : OperatorN<3> - { - void accept(Visitor&) final; - }; + JsonExpr toJson() const final; - struct Leq : OperatorN<3> - { - void accept(Visitor&) final; - }; + private: + T val; +}; - struct Geq : OperatorN<3> - { - void accept(Visitor&) final; - }; +// \} - // logical operators +// +// 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; +}; - // unary - struct Not : OperatorN<1> - { - void accept(Visitor&) final; - }; +struct Or : Operator { + void accept(Visitor&) final; +}; - struct NotNot : OperatorN<1> - { - void accept(Visitor&) final; - }; +// control structure +struct If : Operator { + void accept(Visitor&) final; +}; - // n-ary - struct And : Operator - { - void accept(Visitor&) final; - }; +// arithmetics +// n-ary +struct Add : Operator { + void accept(Visitor&) final; +}; - struct Or : Operator - { - void accept(Visitor&) final; - }; +struct Mul : Operator { + void accept(Visitor&) final; +}; +struct Min : Operator { + void accept(Visitor&) final; +}; - // control structure - struct If : Operator - { - void accept(Visitor&) final; - }; +struct Max : Operator { + void accept(Visitor&) final; +}; - // arithmetics - // n-ary - struct Add : Operator - { - void accept(Visitor&) final; - }; +// binary +struct Sub : OperatorN<2> { + void accept(Visitor&) final; +}; - struct Mul : Operator - { - 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; - struct Min : Operator - { - void accept(Visitor&) final; - }; + // define + Array() = default; + Array(Array&&); + Array& operator=(Array&&); +}; - struct Max : Operator - { - void accept(Visitor&) final; - }; +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; +} - // binary - struct Sub : OperatorN<2> - { - void accept(Visitor&) final; - }; +struct Map : OperatorN<2> { + void accept(Visitor&) final; +}; - struct Div : OperatorN<2> - { - void accept(Visitor&) final; - }; +struct Reduce : OperatorN<3> { + void accept(Visitor&) final; +}; - struct Mod : OperatorN<2> - { - void accept(Visitor&) final; - }; +struct Filter : OperatorN<2> { + void accept(Visitor&) final; +}; +struct All : OperatorN<2> { + void accept(Visitor&) final; +}; - // array +struct None : OperatorN<2> { + void accept(Visitor&) final; +}; - // 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; +struct Some : OperatorN<2> { + void accept(Visitor&) final; +}; - // define - Array() = default; - Array(Array&&); - Array& operator=(Array&&); - }; +struct Merge : Operator { + void accept(Visitor&) final; +}; - Array::Array(Array&& other) - : Operator() - { - set_operands(std::move(other).move_operands()); - } +// data access +struct Var : Operator { + enum { computed = -1 }; - Array& - Array::operator=(Array&& other) - { - set_operands(std::move(other).move_operands()); - return *this; - } + void accept(Visitor&) final; - struct Map : OperatorN<2> - { - void accept(Visitor&) final; - }; + void num(int val) { idx = val; } + int num() const { return idx; } - struct Reduce : OperatorN<3> - { - void accept(Visitor&) final; - }; + private: + int idx = computed; +}; - struct Filter : OperatorN<2> - { - void accept(Visitor&) final; - }; +/// 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 All : OperatorN<2> - { - void accept(Visitor&) final; - }; +struct MissingSome : OperatorN<2> { + void accept(Visitor&) final; +}; - struct None : 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; - struct Some : OperatorN<2> - { - void accept(Visitor&) final; - }; + // control structure + virtual void visit(If&) = 0; - struct Merge : Operator - { - void accept(Visitor&) final; - }; + // 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(); +} - // data access - struct Var : Operator - { - enum { computed = -1 }; +JsonExpr NullVal::toJson() const { return value(); } - void accept(Visitor&) final; +// num_evaluated_operands implementations +int Operator::num_evaluated_operands() const { return size(); } - void num(int val) { idx = val; } - int num() const { return idx; } +template +int OperatorN::num_evaluated_operands() const { + return std::min(MaxArity, Operator::num_evaluated_operands()); +} - private: - int idx = computed; - }; +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"); +} - /// 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 VarMap { + void insert(Var& el); + std::vector toVector() const; + bool hasComputedVariables() const { return hasComputed; } - struct MissingSome : OperatorN<2> - { - void accept(Visitor&) final; - }; + private: + using container_type = std::map; - // string operations - struct Cat : Operator - { - void accept(Visitor&) final; - }; + container_type mapping = {}; + bool hasComputed = false; +}; - struct Substr : OperatorN<3> - { - void accept(Visitor&) final; - }; +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()); - // string and array operation - struct In : Operator - { - void accept(Visitor&) final; - }; + var.num(pos->second); + } + } catch (const cast_error&) { + hasComputed = true; + } +} - // values - struct NullVal : Value - { - NullVal() = default; - NullVal(std::nullptr_t) {} +std::vector VarMap::toVector() const { + std::vector res; - void accept(Visitor&) final; + res.resize(mapping.size()); - std::nullptr_t value() const { return nullptr; } + for (const container_type::value_type& el : mapping) + res.at(el.second) = el.first; - JsonExpr toJson() const final; - }; + return res; +} - struct BoolVal : ValueT - { - using base = ValueT; - using base::base; +/// translates all children +/// \{ +Operator::container_type translateChildren(json::array& children, VarMap&); - void accept(Visitor&) final; - }; +Operator::container_type translateChildren(JsonExpr& n, VarMap&); +/// \} - struct IntVal : ValueT - { - using base = ValueT; - using base::base; +template +ExprT& mkOperator_(json::object& n, VarMap& m) { + assert(n.size() == 1); - void accept(Visitor&) final; - }; + ExprT& res = deref(new ExprT); - struct UintVal : ValueT - { - using base = ValueT; - using base::base; + res.set_operands(translateChildren(n.begin()->value(), m)); + return res; +} - void accept(Visitor&) final; - }; +template +Expr& mkOperator(json::object& n, VarMap& m) { + return mkOperator_(n, m); +} - struct DoubleVal : ValueT - { - using base = ValueT; - using base::base; +Expr& mkVar(json::object& n, VarMap& m) { + Var& v = mkOperator_(n, m); - void accept(Visitor&) final; - }; + m.insert(v); + return v; +} - struct StringVal : ValueT - { - using base = ValueT; - using base::base; +Array& mkArrayOperator(json::array& children, VarMap& m) { + Array& res = deref(new Array); - void accept(Visitor&) final; - }; + res.set_operands(translateChildren(children, m)); + return res; +} - struct ObjectVal : Expr, private std::map - { - using base = std::map; - using base::base; +template +ValueT& mkValue(typename ValueT::value_type n) { + return deref(new ValueT(std::move(n))); +} - using base::value_type; - using base::iterator; - using base::const_iterator; - using base::find; - using base::begin; - using base::end; - using base::insert; +NullVal& mkNullValue() { return deref(new NullVal); } - base& elements() { return *this; } +using DispatchTable = std::map; - void accept(Visitor&) final; - }; +DispatchTable::const_iterator lookup(const DispatchTable& m, + const json::object& op) { + if (op.size() != 1) return m.end(); + return m.find(op.begin()->key()); +} - // logger - struct Log : OperatorN<1> - { - void accept(Visitor&) final; - }; +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? - // error node - struct Error : Expr - { - void accept(Visitor&) final; - }; + unsupported(); + } - // - // jsonlogic extensions - // + break; + } - struct RegexMatch : OperatorN<2> - { - void accept(Visitor&) final; - }; + 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; + } - // 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; - }; + case json::kind::uint64: { + res = &mkValue(n.get_uint64()); + break; + } - // 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(); } + case json::kind::double_: { + res = &mkValue(n.get_double()); + break; + } - JsonExpr NullVal::toJson() const { return value(); } + case json::kind::bool_: { + res = &mkValue(n.get_bool()); + break; + } - // num_evaluated_operands implementations - int Operator::num_evaluated_operands() const - { - return size(); - } + case json::kind::null: { + res = &mkNullValue(); + break; + } - template - int OperatorN::num_evaluated_operands() const - { - return std::min(MaxArity, Operator::num_evaluated_operands()); + default: + unsupported(); } - 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; - }; + return AnyExpr(res); +} +inline std::tuple, bool> translateNode( + JsonExpr& n) { + VarMap varmap; + AnyExpr node = translateNode_internal(n, varmap); + bool hasComputedVariables = varmap.hasComputedVariables(); - namespace - { - CXX_NORETURN - void unsupported() - { - throw std::logic_error("not yet implemented"); - } + return {std::move(node), varmap.toVector(), hasComputedVariables}; +} - CXX_NORETURN - void typeError() - { - throw type_error("typing error"); - } +Operator::container_type translateChildren(json::array& children, + VarMap& varmap) { + Operator::container_type res; - CXX_NORETURN - void requiresArgumentError() - { - throw std::runtime_error("insufficient arguments"); - } + res.reserve(children.size()); - struct VarMap - { - void insert(Var& el); - std::vector toVector() const; - bool hasComputedVariables() const { return hasComputed; } + for (JsonExpr& elem : children) + res.emplace_back(translateNode_internal(elem, varmap)); - private: - using container_type = std::map; + return res; +} - container_type mapping = {}; - bool hasComputed = false; - }; +Operator::container_type translateChildren(JsonExpr& n, VarMap& varmap) { + if (json::array* arr = n.if_array()) { + CXX_LIKELY; + return translateChildren(*arr, varmap); + } - 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()); + Operator::container_type res; - var.num(pos->second); - } - } - catch (const cast_error&) - { - hasComputed = true; - } - } + res.emplace_back(translateNode_internal(n, varmap)); + return res; +} +} // namespace - std::vector - VarMap::toVector() const - { - std::vector res; +std::ostream& operator<<(std::ostream& os, ValueExpr& n); - res.resize(mapping.size()); +// +// to value conversion - for (const container_type::value_type& el : mapping) - res.at(el.second) = el.first; +ValueExpr toValueExpr(const json::value& n); - return res; - } +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; - /// translates all children - /// \{ - Operator::container_type - translateChildren(json::array& children, VarMap&); + std::transform( + val.begin(), val.end(), std::back_inserter(elems), + [](const json::value& el) -> ValueExpr { return toValueExpr(el); }); - Operator::container_type - translateChildren(JsonExpr& n, VarMap&); - /// \} + Array& arr = deref(new Array); - template - ExprT& mkOperator_(json::object& n, VarMap& m) - { - assert(n.size() == 1); + arr.set_operands(std::move(elems)); + return ValueExpr(&arr); +} - ExprT& res = deref(new ExprT); +CXX_MAYBE_UNUSED +ValueExpr toValueExpr(const json::value& n) { + ValueExpr res; - res.set_operands(translateChildren(n.begin()->value(), m)); - return res; + switch (n.kind()) { + case json::kind::string: { + res = toValueExpr(n.get_string()); + break; } - template - Expr& mkOperator(json::object& n, VarMap& m) - { - return mkOperator_(n, m); + case json::kind::int64: { + res = toValueExpr(n.get_int64()); + break; } - Expr& mkVar(json::object& n, VarMap& m) - { - Var& v = mkOperator_(n, m); - - m.insert(v); - return v; + case json::kind::uint64: { + res = toValueExpr(n.get_uint64()); + break; } - Array& mkArrayOperator(json::array& children, VarMap& m) - { - Array& res = deref(new Array); + case json::kind::double_: { + res = toValueExpr(n.get_double()); + break; + } - res.set_operands(translateChildren(children, m)); - return res; + case json::kind::bool_: { + res = toValueExpr(n.get_bool()); + break; } - template - ValueT& mkValue(typename ValueT::value_type n) - { - return deref(new ValueT(std::move(n))); + case json::kind::null: { + res = toValueExpr(nullptr); + break; } - NullVal& mkNullValue() - { - return deref(new NullVal); + case json::kind::array: { + res = toValueExpr(n.get_array()); + break; } - using DispatchTable = std::map; + default: + unsupported(); + } - DispatchTable::const_iterator - lookup(const DispatchTable& m, const json::object& op) - { - if (op.size() != 1) return m.end(); + assert(res.get()); + return res; +} - return m.find(op.begin()->key()); - } +// +// coercion functions +struct CoercionError {}; +struct OutsideOfInt64Range : CoercionError {}; +struct OutsideOfUint64Range : CoercionError {}; +struct UnpackedArrayRequired : CoercionError {}; - 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(); - } +/// 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; +} - return AnyExpr(res); - } +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{}; + } - inline - std::tuple, bool> - translateNode(JsonExpr& n) - { - VarMap varmap; - AnyExpr node = translateNode_internal(n, varmap); - bool hasComputedVariables = varmap.hasComputedVariables(); + return v; +} +/// \} - return { std::move(node), varmap.toVector(), hasComputedVariables }; - } +/// 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; +} - Operator::container_type - translateChildren(json::array& children, VarMap& varmap) - { - Operator::container_type res; +inline std::uint64_t toConcreteValue(std::int64_t v, const std::uint64_t&) { + if (v < 0) { + CXX_UNLIKELY; + throw OutsideOfUint64Range{}; + } + + return v; +} +/// \} - res.reserve(children.size()); +/// 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)}; +} - for (JsonExpr& elem : children) - res.emplace_back(translateNode_internal(elem, varmap)); +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; } - return res; - } +// \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(); +} +/// \} - Operator::container_type - translateChildren(JsonExpr& n, VarMap& varmap) - { - if (json::array* arr = n.if_array()) - { - CXX_LIKELY; - return translateChildren(*arr, varmap); - } +struct ComparisonOperatorBase { + enum { + definedForString = true, + definedForDouble = true, + definedForInteger = true, + definedForBool = true, + definedForNull = true, + definedForArray = true + }; - Operator::container_type res; + using result_type = bool; +}; - res.emplace_back(translateNode_internal(n, varmap)); - return res; - } +/// \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 } - std::ostream& operator<<(std::ostream& os, ValueExpr& n); + template + std::tuple coerce(LhsT* lv, RhsT* rv) { + return {std::move(*lv), std::move(*rv)}; + } - // - // to value conversion + std::tuple coerce(std::nullptr_t, + std::nullptr_t) { + return {nullptr, nullptr}; // two null pointers are equal + } - ValueExpr toValueExpr(const json::value& n); + template + std::tuple coerce(LhsT* lv, std::nullptr_t) { + return {std::move(*lv), nullptr}; + } - 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))); } + template + std::tuple coerce(std::nullptr_t, RhsT* rv) { + return {nullptr, std::move(*rv)}; + } +}; - ValueExpr toValueExpr(const json::array& val) - { - Operator::container_type elems; +struct NumericBinaryOperatorBase { + std::tuple coerce(double* lv, double* rv) { + return {*lv, *rv}; + } - std::transform( val.begin(), val.end(), - std::back_inserter(elems), - [](const json::value& el) -> ValueExpr { return toValueExpr(el); } - ); + std::tuple coerce(double* lv, std::int64_t* rv) { + return {*lv, toConcreteValue(*rv, *lv)}; + } - Array& arr = deref(new Array); + std::tuple coerce(double* lv, std::uint64_t* rv) { + return {*lv, toConcreteValue(*rv, *lv)}; + } - arr.set_operands(std::move(elems)); - return ValueExpr(&arr); + std::tuple coerce(std::int64_t* lv, double* rv) { + return {toConcreteValue(*lv, *rv), *rv}; } - CXX_MAYBE_UNUSED - ValueExpr toValueExpr(const json::value& n) - { - ValueExpr res; + std::tuple coerce(std::int64_t* lv, + std::int64_t* rv) { + return {*lv, *rv}; + } - switch (n.kind()) - { - case json::kind::string: - { - res = toValueExpr(n.get_string()); - break; - } + std::tuple coerce(std::int64_t* lv, + std::uint64_t* rv) { + return {*lv, toConcreteValue(*rv, *lv)}; + } - case json::kind::int64: - { - res = toValueExpr(n.get_int64()); - break; - } + std::tuple coerce(std::uint64_t* lv, double* rv) { + return {toConcreteValue(*lv, *rv), *rv}; + } - case json::kind::uint64: - { - res = toValueExpr(n.get_uint64()); - break; - } + std::tuple coerce(std::uint64_t* lv, + std::int64_t* rv) { + return {toConcreteValue(*lv, *rv), *rv}; + } - case json::kind::double_: - { - res = toValueExpr(n.get_double()); - break; - } + std::tuple coerce(std::uint64_t* lv, + std::uint64_t* rv) { + return {*lv, *rv}; + } +}; - case json::kind::bool_: - { - res = toValueExpr(n.get_bool()); - break; - } +/// \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; - case json::kind::null: - { - res = toValueExpr(nullptr); - break; - } + std::tuple coerce(double* lv, json::string* rv) { + return {*lv, toConcreteValue(*rv, *lv)}; + } - case json::kind::array: - { - res = toValueExpr(n.get_array()); - break; - } + std::tuple coerce(double* lv, bool* rv) { + return {*lv, toConcreteValue(*rv, *lv)}; + } - default: - unsupported(); - } + std::tuple coerce(std::int64_t* lv, + json::string* rv) { + return {*lv, toConcreteValue(*rv, *lv)}; + } - assert(res.get()); - return res; + std::tuple coerce(std::int64_t* lv, bool* rv) { + return {*lv, toConcreteValue(*rv, *lv)}; } - // - // coercion functions + std::tuple coerce(std::uint64_t* lv, + json::string* rv) { + return {*lv, toConcreteValue(*rv, *lv)}; + } - struct CoercionError {}; - struct OutsideOfInt64Range : CoercionError {}; - struct OutsideOfUint64Range : CoercionError {}; - struct UnpackedArrayRequired : CoercionError {}; + std::tuple coerce(std::uint64_t* lv, bool* rv) { + return {*lv, toConcreteValue(*rv, *lv)}; + } - /// 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{}; - } + std::tuple coerce(json::string* lv, double* rv) { + return {toConcreteValue(*lv, *rv), *rv}; + } - return v; + std::tuple coerce(bool* lv, double* rv) { + return {toConcreteValue(*lv, *rv), *rv}; } - /// \} - /// 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; } + std::tuple coerce(json::string* lv, + std::int64_t* rv) { + return {toConcreteValue(*lv, *rv), *rv}; + } - inline std::uint64_t toConcreteValue(std::int64_t v, const std::uint64_t&) - { - if (v < 0) - { - CXX_UNLIKELY; - throw OutsideOfUint64Range{}; - } + std::tuple coerce(bool* lv, std::int64_t* rv) { + return {toConcreteValue(*lv, *rv), *rv}; + } - return v; + std::tuple coerce(json::string* lv, + std::uint64_t* rv) { + return {toConcreteValue(*lv, *rv), *rv}; } - /// \} - /// 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; } - /// \} + std::tuple coerce(bool* lv, std::uint64_t* rv) { + return {toConcreteValue(*lv, *rv), *rv}; + } - /// conversion to string - /// \{ - template - inline json::string toConcreteValue(Val v, const json::string&) { return json::string{std::to_string(v)}; } + std::tuple coerce(json::string*, bool* rv) { + // strings and boolean are never equal + return {!*rv, *rv}; + } - 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"}; } - /// \} + 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)}; + } - /// 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(); } - /// \} + std::tuple coerce(bool* lv, bool* rv) { return {*lv, *rv}; } +}; - struct ComparisonOperatorBase - { - enum - { - definedForString = true, - definedForDouble = true, - definedForInteger = true, - definedForBool = true, - definedForNull = true, - definedForArray = true - }; - - using result_type = bool; - }; +struct EqualityOperator : RelationalOperatorBase, ComparisonOperatorBase { + using RelationalOperatorBase::coerce; - /// \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 - } + // due to special conversion rules, the coercion function may just produce + // the result instead of just unpacking and coercing values. - template - std::tuple - coerce(LhsT* lv, RhsT* rv) - { - return { std::move(*lv), std::move(*rv) }; - } + std::tuple coerce(Array*, Array*) { + return {true, false}; // arrays are never equal + } - std::tuple - coerce(std::nullptr_t, std::nullptr_t) - { - return { nullptr, nullptr }; // two null pointers are 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{}; - template - std::tuple - coerce(LhsT* lv, std::nullptr_t) - { - return { std::move(*lv), nullptr }; - } + // (2) or if [] and *lv converts to false + if (rv->num_evaluated_operands() > 1) return {false, true}; - template - std::tuple - coerce(std::nullptr_t, RhsT* rv) - { - return { nullptr, std::move(*rv) }; - } - }; + const bool convToFalse = toConcreteValue(*lv, false) == false; - struct NumericBinaryOperatorBase - { - std::tuple - coerce(double* lv, double* rv) - { - return { *lv, *rv }; - } + return {convToFalse, true /* zero elements */}; + } - std::tuple - coerce(double* lv, std::int64_t* rv) - { - return { *lv, toConcreteValue(*rv, *lv) }; - } + template + std::tuple coerce(Array* lv, T* rv) { + // see comments in coerce(T*,Array*) + if (lv->num_evaluated_operands() == 1) throw UnpackedArrayRequired{}; - std::tuple - coerce(double* lv, std::uint64_t* rv) - { - return { *lv, toConcreteValue(*rv, *lv) }; - } + if (lv->num_evaluated_operands() > 1) return {false, true}; - std::tuple - coerce(std::int64_t* lv, double* rv) - { - return { toConcreteValue(*lv, *rv), *rv }; - } + const bool convToFalse = toConcreteValue(*rv, false) == false; - std::tuple - coerce(std::int64_t* lv, std::int64_t* rv) - { - return { *lv, *rv }; - } + return {true /* zero elements */, convToFalse}; + } - std::tuple - coerce(std::int64_t* lv, std::uint64_t* rv) - { - return { *lv, toConcreteValue(*rv, *lv) }; - } + std::tuple coerce(std::nullptr_t, + std::nullptr_t) { + return {nullptr, nullptr}; // two null pointers are equal + } - std::tuple - coerce(std::uint64_t* lv, double* rv) - { - return { toConcreteValue(*lv, *rv), *rv }; - } + template + std::tuple coerce(T*, std::nullptr_t) { + return {false, true}; // null pointer is only equal to itself + } - std::tuple - coerce(std::uint64_t* lv, std::int64_t* rv) - { - return { toConcreteValue(*lv, *rv), *rv }; - } + template + std::tuple coerce(std::nullptr_t, T*) { + return {true, false}; // null pointer is only equal to itself + } - std::tuple - coerce(std::uint64_t* lv, std::uint64_t* rv) - { - return { *lv, *rv }; - } - }; + 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 + } +}; - /// \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; +struct RelationalOperator : RelationalOperatorBase, ComparisonOperatorBase { + using RelationalOperatorBase::coerce; - std::tuple - coerce(double* lv, json::string* rv) - { - return { *lv, toConcreteValue(*rv, *lv) }; - } + std::tuple coerce(Array* lv, Array* rv) { return {lv, rv}; } - std::tuple - coerce(double* lv, bool* rv) - { - return { *lv, toConcreteValue(*rv, *lv) }; - } + 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{}; - std::tuple - coerce(std::int64_t* lv, json::string* rv) - { - return { *lv, toConcreteValue(*rv, *lv) }; - } + // (2) or if [] and *lv converts to false + if (rv->num_evaluated_operands() > 1) return {false, true}; - std::tuple - coerce(std::int64_t* lv, bool* rv) - { - return { *lv, toConcreteValue(*rv, *lv) }; - } + const bool convToTrue = toConcreteValue(*lv, true) == true; - std::tuple - coerce(std::uint64_t* lv, json::string* rv) - { - return { *lv, toConcreteValue(*rv, *lv) }; - } + return {convToTrue, false /* zero elements */}; + } - std::tuple - coerce(std::uint64_t* lv, bool* rv) - { - return { *lv, toConcreteValue(*rv, *lv) }; - } + template + std::tuple coerce(Array* lv, T* rv) { + // see comments in coerce(T*,Array*) + if (lv->num_evaluated_operands() == 1) throw UnpackedArrayRequired{}; - std::tuple - coerce(json::string* lv, double* rv) - { - return { toConcreteValue(*lv, *rv), *rv }; - } + if (lv->num_evaluated_operands() > 1) return {false, true}; - std::tuple - coerce(bool* lv, double* rv) - { - return { toConcreteValue(*lv, *rv), *rv }; - } + const bool convToTrue = toConcreteValue(*rv, true) == true; - std::tuple - coerce(json::string* lv, std::int64_t* rv) - { - return { toConcreteValue(*lv, *rv), *rv }; - } + return {false /* zero elements */, convToTrue}; + } - std::tuple - coerce(bool* lv, std::int64_t* rv) - { - return { toConcreteValue(*lv, *rv), *rv }; - } + std::tuple coerce(std::nullptr_t, + std::nullptr_t) { + return {nullptr, nullptr}; // two null pointers are equal + } - std::tuple - coerce(json::string* lv, std::uint64_t* rv) - { - return { toConcreteValue(*lv, *rv), *rv }; - } + std::tuple coerce(bool* lv, std::nullptr_t) { + return {*lv, false}; // null pointer -> false + } - std::tuple - coerce(bool* lv, std::uint64_t* rv) - { - return { toConcreteValue(*lv, *rv), *rv }; - } + std::tuple coerce(std::int64_t* lv, + std::nullptr_t) { + return {*lv, 0}; // null pointer -> 0 + } - std::tuple - coerce(json::string*, bool* rv) - { - // strings and boolean are never equal - return { !*rv, *rv }; - } + std::tuple coerce(std::uint64_t* lv, + std::nullptr_t) { + return {*lv, 0}; // null pointer -> 0 + } - std::tuple - coerce(bool* lv, json::string*) - { - // strings and boolean are never equal - return { *lv, !*lv }; - } + std::tuple coerce(double* lv, std::nullptr_t) { + return {*lv, 0}; // null pointer -> 0.0 + } - std::tuple - coerce(json::string* lv, json::string* rv) - { - return { std::move(*lv), std::move(*rv) }; - } + std::tuple coerce(json::string* lv, + std::nullptr_t) { + return {std::move(*lv), nullptr}; // requires special handling + } - std::tuple - coerce(bool* lv, bool* rv) - { - return { *lv, *rv }; - } - }; + std::tuple coerce(std::nullptr_t, bool* rv) { + return {false, *rv}; // null pointer -> false + } - struct EqualityOperator : RelationalOperatorBase, ComparisonOperatorBase - { - using RelationalOperatorBase::coerce; + std::tuple coerce(std::nullptr_t, + std::int64_t* rv) { + return {0, *rv}; // null pointer -> 0 + } - // due to special conversion rules, the coercion function may just produce - // the result instead of just unpacking and coercing values. + std::tuple coerce(std::nullptr_t, + std::uint64_t* rv) { + return {0, *rv}; // null pointer -> 0 + } - std::tuple - coerce(Array*, Array*) - { - return { true, false }; // arrays are never equal - } + std::tuple coerce(std::nullptr_t, double* rv) { + return {0, *rv}; // null pointer -> 0 + } - 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{}; + std::tuple coerce(std::nullptr_t, + json::string* rv) { + return {nullptr, std::move(*rv)}; // requires special handling + } +}; +// @} - // (2) or if [] and *lv converts to false - if (rv->num_evaluated_operands() > 1) - return { false, true }; +// Arith +struct ArithmeticOperator : NumericBinaryOperatorBase { + enum { + definedForString = false, + definedForDouble = true, + definedForInteger = true, + definedForBool = false, + definedForNull = true, + definedForArray = false + }; - const bool convToFalse = toConcreteValue(*lv, false) == false; + using result_type = ValueExpr; - return { convToFalse, true /* zero elements */ }; - } + using NumericBinaryOperatorBase::coerce; - template - std::tuple - coerce(Array* lv, T* rv) - { - // see comments in coerce(T*,Array*) - if (lv->num_evaluated_operands() == 1) - throw UnpackedArrayRequired{}; + std::tuple coerce(double*, std::nullptr_t) { + return {nullptr, nullptr}; + } - if (lv->num_evaluated_operands() > 1) - return { false, true }; + std::tuple coerce(std::int64_t*, + std::nullptr_t) { + return {nullptr, nullptr}; + } - const bool convToFalse = toConcreteValue(*rv, false) == false; + std::tuple coerce(std::uint64_t*, + std::nullptr_t) { + return {nullptr, nullptr}; + } - return { true /* zero elements */, convToFalse }; - } + std::tuple coerce(std::nullptr_t, double*) { + return {nullptr, nullptr}; + } - std::tuple - coerce(std::nullptr_t, std::nullptr_t) - { - return { nullptr, nullptr }; // two null pointers are equal - } + std::tuple coerce(std::nullptr_t, + std::int64_t*) { + return {nullptr, nullptr}; + } - template - std::tuple - coerce(T*, std::nullptr_t) - { - return { false, true }; // null pointer is only equal to itself - } + std::tuple coerce(std::nullptr_t, + std::uint64_t*) { + return {nullptr, nullptr}; + } - template - std::tuple - coerce(std::nullptr_t, T*) - { - return { true, false }; // null pointer is only equal to itself - } + std::tuple coerce(std::nullptr_t, + std::nullptr_t) { + return {nullptr, nullptr}; + } +}; - std::tuple - coerce(Array*, std::nullptr_t) - { - return { true, false }; // null pointer is only equal to itself - } +struct IntegerArithmeticOperator : ArithmeticOperator { + enum { + definedForString = false, + definedForDouble = false, + definedForInteger = true, + definedForBool = false, + definedForNull = false, + definedForArray = false + }; - std::tuple - coerce(std::nullptr_t, Array*) - { - return { true, false }; // null pointer is only equal to itself - } + using ArithmeticOperator::coerce; +}; + +struct StringOperator { + enum { + definedForString = true, + definedForDouble = false, + definedForInteger = false, + definedForBool = false, + definedForNull = false, + definedForArray = false }; - struct RelationalOperator : RelationalOperatorBase, ComparisonOperatorBase - { - using RelationalOperatorBase::coerce; + using result_type = ValueExpr; - std::tuple - coerce(Array* lv, Array* rv) - { - return { lv, rv }; - } + std::tuple coerce(json::string* lv, + json::string* rv) { + return {std::move(*lv), std::move(*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{}; +struct ArrayOperator { + enum { + definedForString = false, + definedForDouble = false, + definedForInteger = false, + definedForBool = false, + definedForNull = false, + definedForArray = true + }; - // (2) or if [] and *lv converts to false - if (rv->num_evaluated_operands() > 1) - return { false, true }; + using result_type = ValueExpr; - const bool convToTrue = toConcreteValue(*lv, true) == true; + std::tuple coerce(Array* lv, Array* rv) { return {lv, rv}; } +}; - return { convToTrue, false /* zero elements */ }; - } +AnyExpr convert(AnyExpr val, ...) { return val; } - template - std::tuple - coerce(Array* lv, T* rv) - { - // see comments in coerce(T*,Array*) - if (lv->num_evaluated_operands() == 1) - throw UnpackedArrayRequired{}; +AnyExpr convert(AnyExpr val, const ArithmeticOperator&) { + struct ArithmeticConverter : FwdVisitor { + explicit ArithmeticConverter(AnyExpr val) : res(std::move(val)) {} - if (lv->num_evaluated_operands() > 1) - return { false, true }; + void visit(Expr&) final { typeError(); } - const bool convToTrue = toConcreteValue(*rv, true) == true; + // defined for the following types + void visit(IntVal&) final {} + void visit(UintVal&) final {} + void visit(DoubleVal&) final {} + void visit(NullVal&) final {} - return { false /* zero elements */, convToTrue }; - } + // 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? - std::tuple - coerce(std::nullptr_t, std::nullptr_t) - { - return { nullptr, nullptr }; // two null pointers are equal + res = (dd != ii) ? toValueExpr(dd) : toValueExpr(ii); } - std::tuple - coerce(bool* lv, std::nullptr_t) - { - return { *lv, false }; // null pointer -> false + void visit(BoolVal&) final { + // \todo correct? + res = toValueExpr(nullptr); } - std::tuple - coerce(std::int64_t* lv, std::nullptr_t) - { - return { *lv, 0 }; // null pointer -> 0 - } + AnyExpr result() && { return std::move(res); } - std::tuple - coerce(std::uint64_t* lv, std::nullptr_t) - { - return { *lv, 0 }; // null pointer -> 0 - } + private: + AnyExpr res; + }; - std::tuple - coerce(double* lv, std::nullptr_t) - { - return { *lv, 0 }; // null pointer -> 0.0 - } + Expr* node = val.get(); + ArithmeticConverter conv{std::move(val)}; - std::tuple - coerce(json::string* lv, std::nullptr_t) - { - return { std::move(*lv), nullptr }; // requires special handling - } + node->accept(conv); + return std::move(conv).result(); +} - std::tuple - coerce(std::nullptr_t, bool* rv) - { - return { false, *rv }; // null pointer -> false - } +AnyExpr convert(AnyExpr val, const IntegerArithmeticOperator&) { + struct IntegerArithmeticConverter : FwdVisitor { + explicit IntegerArithmeticConverter(AnyExpr val) : res(std::move(val)) {} - std::tuple - coerce(std::nullptr_t, std::int64_t* rv) - { - return { 0, *rv }; // null pointer -> 0 - } + void visit(Expr&) final { typeError(); } - std::tuple - coerce(std::nullptr_t, std::uint64_t* rv) - { - return { 0, *rv }; // null pointer -> 0 + // 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{})); } - std::tuple - coerce(std::nullptr_t, double* rv) - { - return { 0, *rv }; // null pointer -> 0 + void visit(BoolVal& el) final { + res = toValueExpr(toConcreteValue(el.value(), std::int64_t{})); } - std::tuple - coerce(std::nullptr_t, json::string* rv) - { - return { nullptr, std::move(*rv) }; // requires special handling + 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; }; - // @} - // Arith - struct ArithmeticOperator : NumericBinaryOperatorBase - { - enum - { - definedForString = false, - definedForDouble = true, - definedForInteger = true, - definedForBool = false, - definedForNull = true, - definedForArray = false - }; + Expr* node = val.get(); + IntegerArithmeticConverter conv{std::move(val)}; - using result_type = ValueExpr; + node->accept(conv); + return std::move(conv).result(); +} - using NumericBinaryOperatorBase::coerce; +AnyExpr convert(AnyExpr val, const StringOperator&) { + struct StringConverter : FwdVisitor { + explicit StringConverter(AnyExpr val) : res(std::move(val)) {} - std::tuple - coerce(double*, std::nullptr_t) - { - return { nullptr, nullptr }; - } + void visit(Expr&) final { typeError(); } - std::tuple - coerce(std::int64_t*, std::nullptr_t) - { - return { nullptr, nullptr }; - } + // defined for the following types + void visit(StringVal&) final {} - std::tuple - coerce(std::uint64_t*, std::nullptr_t) - { - return { nullptr, nullptr }; + // need to convert values + void visit(BoolVal& el) final { + res = toValueExpr(toConcreteValue(el.value(), json::string{})); } - std::tuple - coerce(std::nullptr_t, double*) - { - return { nullptr, nullptr }; + void visit(IntVal& el) final { + res = toValueExpr(toConcreteValue(el.value(), json::string{})); } - std::tuple - coerce(std::nullptr_t, std::int64_t*) - { - return { nullptr, nullptr }; + void visit(UintVal& el) final { + res = toValueExpr(toConcreteValue(el.value(), json::string{})); } - std::tuple - coerce(std::nullptr_t, std::uint64_t*) - { - return { nullptr, nullptr }; + void visit(DoubleVal& el) final { + res = toValueExpr(toConcreteValue(el.value(), json::string{})); } - std::tuple - coerce(std::nullptr_t, std::nullptr_t) - { - return { nullptr, nullptr }; + void visit(NullVal& el) final { + res = toValueExpr(toConcreteValue(el.value(), json::string{})); } - }; - - 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) }; - } - }; + AnyExpr result() && { return std::move(res); } - 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 }; - } + private: + AnyExpr res; }; - AnyExpr convert(AnyExpr val, ...) - { - return val; - } + Expr* node = val.get(); + StringConverter conv{std::move(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); - } + node->accept(conv); + return std::move(conv).result(); +} - void visit(BoolVal&) final - { - // \todo correct? - res = toValueExpr(nullptr); - } +AnyExpr convert(AnyExpr val, const ArrayOperator&) { + struct ArrayConverter : FwdVisitor { + explicit ArrayConverter(AnyExpr val) : val(std::move(val)) {} - AnyExpr result() && { return std::move(res); } - private: - AnyExpr res; - }; + void visit(Expr&) final { typeError(); } - Expr* node = val.get(); - ArithmeticConverter conv{std::move(val)}; + // moves res into + void toArray() { + Array& arr = deref(new Array); + Operator::container_type operands; - node->accept(conv); - return std::move(conv).result(); - } + operands.emplace_back(&arr); - AnyExpr convert(AnyExpr val, const IntegerArithmeticOperator&) - { - struct IntegerArithmeticConverter : FwdVisitor - { - explicit - IntegerArithmeticConverter(AnyExpr val) - : res(std::move(val)) - {} + // swap the operand and result + std::swap(operands.back(), val); - void visit(Expr&) final { typeError(); } + // then set the operands + arr.set_operands(std::move(operands)); + } - // defined for the following types - void visit(IntVal&) final {} - void visit(UintVal&) final {} + // defined for the following types + void visit(Array&) final {} - // need to convert values - void visit(StringVal& el) final - { - res = toValueExpr(toConcreteValue(el.value(), std::int64_t{})); - } + // 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(); } - void visit(BoolVal& el) final - { - res = toValueExpr(toConcreteValue(el.value(), std::int64_t{})); - } + AnyExpr result() && { return std::move(val); } - void visit(DoubleVal& el) final - { - res = toValueExpr(toConcreteValue(el.value(), std::int64_t{})); - } + private: + AnyExpr val; + }; - void visit(NullVal&) final - { - res = toValueExpr(std::int64_t{0}); - } + Expr* node = val.get(); + ArrayConverter conv{std::move(val)}; - AnyExpr result() && { return std::move(res); } + node->accept(conv); + return std::move(conv).result(); +} - private: - AnyExpr res; - }; +template +struct UnpackVisitor : FwdVisitor { + UnpackVisitor() : res() {} - Expr* node = val.get(); - IntegerArithmeticConverter conv{std::move(val)}; + void assign(ValueT& lhs, const ValueT& val) { lhs = val; } - node->accept(conv); - return std::move(conv).result(); + template + void assign(ValueT& lhs, const U& val) { + lhs = toConcreteValue(val, lhs); } - AnyExpr convert(AnyExpr val, const StringOperator&) - { - struct StringConverter : FwdVisitor - { - explicit - StringConverter(AnyExpr val) - : res(std::move(val)) - {} + void visit(Expr&) final { typeError(); } - void visit(Expr&) final { typeError(); } + // defined for the following types + void visit(StringVal& el) final { assign(res, el.value()); } - // defined for the following types - void visit(StringVal&) final {} + // need to convert values + void visit(BoolVal& el) final { assign(res, el.value()); } + void visit(IntVal& el) final { assign(res, el.value()); } - // need to convert values - void visit(BoolVal& el) final - { - res = toValueExpr(toConcreteValue(el.value(), json::string{})); - } + void visit(UintVal& el) final { assign(res, el.value()); } - void visit(IntVal& el) final - { - res = toValueExpr(toConcreteValue(el.value(), json::string{})); - } + void visit(DoubleVal& el) final { assign(res, el.value()); } - void visit(UintVal& el) final - { - res = toValueExpr(toConcreteValue(el.value(), json::string{})); - } + void visit(NullVal& el) final { assign(res, el.value()); } - void visit(DoubleVal& el) final - { - res = toValueExpr(toConcreteValue(el.value(), json::string{})); - } + void visit(Array& el) final { + if constexpr (std::is_same::value) return assign(res, el); - 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(); + typeError(); } - 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; + ValueT result() && { return std::move(res); } - operands.emplace_back(&arr); + private: + ValueT res; +}; - // swap the operand and result - std::swap(operands.back(), val); +template +T unpackValue(Expr& expr) { + UnpackVisitor unpack; - // then set the operands - arr.set_operands(std::move(operands)); - } + expr.accept(unpack); + return std::move(unpack).result(); +} - // defined for the following types - void visit(Array&) final {} +template +T unpackValue(ValueExpr& el) { + return unpackValue(*el); +} - // 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(); } +template +T unpackValue(ValueExpr&& el) { + return unpackValue(*el); +} - AnyExpr result() && { return std::move(val); } +// +// 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)); } - private: - AnyExpr val; - }; +// +// cloning - Expr* node = val.get(); - ArrayConverter conv{std::move(val)}; +AnyExpr cloneExpr(AnyExpr& expr); - node->accept(conv); - return std::move(conv).result(); - } +struct ExprCloner : GVisitor { + using base = GVisitor; + ExprCloner() : base(*this), res(nullptr) {} - template - struct UnpackVisitor : FwdVisitor - { - UnpackVisitor() - : res() - {} + /// init functions that set up children + /// \{ + Expr& init(Operator&, Operator&); + Expr& init(ObjectVal&, ObjectVal&); + /// \} - void assign(ValueT& lhs, const ValueT& val) { lhs = val; } + /// 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(); } - template - void assign(ValueT& lhs, const U& val) - { - lhs = toConcreteValue(val, lhs); - } + Expr& clone(Error&, const Error&) { return deref(new Error); } - void visit(Expr&) final { typeError(); } + template + Expr& clone(TValue& n, const Value&) { + return deref(new TValue(n.value())); + } - // defined for the following types - void visit(StringVal& el) final - { - assign(res, el.value()); - } + template + Expr& clone(TOperator& n, const Operator&) { + return init(n, deref(new TOperator)); + } - // need to convert values - void visit(BoolVal& el) final - { - assign(res, el.value()); - } + Expr& clone(ObjectVal& n, const ObjectVal&) { + return init(n, deref(new ObjectVal)); + } + /// \} - void visit(IntVal& el) final - { - assign(res, el.value()); - } + template + void gvisit(TExpr& n) { + res = &clone(n, n); + } - void visit(UintVal& el) final - { - assign(res, el.value()); - } + Expr* result() { return res; } - void visit(DoubleVal& el) final - { - assign(res, el.value()); - } + private: + Expr* res; +}; - void visit(NullVal& el) final - { - assign(res, el.value()); - } +Expr& ExprCloner::init(Operator& src, Operator& tgt) { + Operator::container_type children; - void visit(Array& el) final - { - if constexpr (std::is_same::value) - return assign(res, el); + std::transform(src.operands().begin(), src.operands().end(), + std::back_inserter(children), + [](AnyExpr& e) -> AnyExpr { return cloneExpr(e); }); - typeError(); - } + tgt.set_operands(std::move(children)); + return tgt; +} - ValueT result() && { return std::move(res); } +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)}; + }); - private: - ValueT res; - }; + return tgt; +} +AnyExpr cloneExpr(AnyExpr& expr) { + ExprCloner cloner; - template - T unpackValue(Expr& expr) - { - UnpackVisitor unpack; + expr->accept(cloner); + return ValueExpr{cloner.result()}; +} - expr.accept(unpack); - return std::move(unpack).result(); - } +// +// internal down cast - template - T unpackValue(ValueExpr& el) - { - return unpackValue(*el); - } +template +struct DownCastVisitor : GVisitor > { + using base = GVisitor >; - template - T unpackValue(ValueExpr&& el) - { - return unpackValue(*el); - } + DownCastVisitor() : base(*this), res(nullptr) {} - // - // 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)); } + void gvisit(Expr&) { /* not found */ } - // - // cloning + void gvisit(ExprT& n) { res = &n; } - AnyExpr cloneExpr(AnyExpr& expr); + ExprT* result() { return res; } - struct ExprCloner : GVisitor - { - using base = GVisitor; + private: + ExprT* res; +}; - ExprCloner() - : base(*this), res(nullptr) - {} +template +ExprT& down_cast(Expr& expr) { + DownCastVisitor vis; - /// init functions that set up children - /// \{ - Expr& init(Operator&, Operator&); - Expr& init(ObjectVal&, ObjectVal&); - /// \} + expr.accept(vis); + return deref(vis.result()); +} - /// 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(); } +template +auto with_type(Expr* e, Fn fn, AltFn altfn) -> decltype(altfn()) { + if (e == nullptr) { + CXX_UNLIKELY; + return altfn(); + } - Expr& clone(Error&, const Error&) { return deref(new Error); } + DownCastVisitor vis; + e->accept(vis); - template - Expr& clone(TValue& n, const Value&) { return deref(new TValue(n.value())); } + return vis.result() ? fn(*vis.result()) : altfn(); +} - template - Expr& clone(TOperator& n, const Operator&) { return init(n, deref(new TOperator)); } +// +// binary operator - double dispatch pattern - Expr& clone(ObjectVal& n, const ObjectVal&) { return init(n, deref(new ObjectVal)); } - /// \} +template +struct BinaryOperatorVisitor_ : FwdVisitor { + using result_type = typename BinaryOperator::result_type; - template - void gvisit(TExpr& n) { res = &clone(n, n); } + BinaryOperatorVisitor_(LhsValue lval, BinaryOperator oper) + : lv(lval), op(oper), res() {} - Expr* result() { return res; } + template + void calc(RhsValue rv) { + auto [ll, rr] = op.coerce(lv, rv); - private: - Expr* res; - }; + res = op(std::move(ll), std::move(rr)); + } - Expr& - ExprCloner::init(Operator& src, Operator& tgt) - { - Operator::container_type children; + void visit(Expr&) final { typeError(); } - std::transform( src.operands().begin(), src.operands().end(), - std::back_inserter(children), - [](AnyExpr& e) -> AnyExpr { return cloneExpr(e); } - ); + void visit(StringVal& n) final { + if constexpr (BinaryOperator::definedForString) return calc(&n.value()); - tgt.set_operands(std::move(children)); - return tgt; + typeError(); } - 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) }; - } - ); + void visit(NullVal&) final { + if constexpr (BinaryOperator::definedForNull) return calc(nullptr); - return tgt; + typeError(); } - AnyExpr cloneExpr(AnyExpr& expr) - { - ExprCloner cloner; + void visit(BoolVal& n) final { + if constexpr (BinaryOperator::definedForBool) return calc(&n.value()); - expr->accept(cloner); - return ValueExpr{ cloner.result() }; + 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"}; + } + } - // - // 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; } + std::uint64_t alt = n.value(); + return calc(&alt); + } - ExprT* result() { return res; } + typeError(); + } - private: - ExprT* res; - }; + 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)"}; + } + } - template - ExprT& down_cast(Expr& expr) - { - DownCastVisitor vis; + std::int64_t alt = n.value(); + return calc(&alt); + } - expr.accept(vis); - return deref(vis.result()); + typeError(); } - template - auto with_type(Expr* e, Fn fn, AltFn altfn) -> decltype(altfn()) - { - if (e == nullptr) { CXX_UNLIKELY; return altfn(); } - - DownCastVisitor vis; - e->accept(vis); + void visit(DoubleVal& n) final { + if constexpr (BinaryOperator::definedForDouble) return calc(&n.value()); - return vis.result() ? fn(*vis.result()) : altfn(); + 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; + } - // - // binary operator - double dispatch pattern + typeError(); + } - template - struct BinaryOperatorVisitor_ : FwdVisitor - { - using result_type = typename BinaryOperator::result_type; + result_type result() && { return std::move(res); } - BinaryOperatorVisitor_(LhsValue lval, BinaryOperator oper) - : lv(lval), op(oper), res() - {} + private: + LhsValue lv; + BinaryOperator op; + result_type res; +}; - template - void calc(RhsValue rv) - { - auto [ll, rr] = op.coerce(lv, rv); +template +struct BinaryOperatorVisitor : FwdVisitor { + using result_type = typename BinaryOperator::result_type; - res = op(std::move(ll), std::move(rr)); - } + BinaryOperatorVisitor(BinaryOperator oper, ValueExpr& rhsarg) + : op(oper), rhs(rhsarg), res() {} - void visit(Expr&) final { typeError(); } + template + void calc(LhsValue lv) { + using RhsVisitor = BinaryOperatorVisitor_; - void visit(StringVal& n) final - { - if constexpr (BinaryOperator::definedForString) - return calc(&n.value()); + RhsVisitor vis{lv, op}; - typeError(); - } + rhs->accept(vis); + res = std::move(vis).result(); + } - void visit(NullVal&) final - { - if constexpr (BinaryOperator::definedForNull) - return calc(nullptr); + void visit(StringVal& n) final { + if constexpr (BinaryOperator::definedForString) return calc(&n.value()); - typeError(); - } + typeError(); + } - void visit(BoolVal& n) final - { - if constexpr (BinaryOperator::definedForBool) - return calc(&n.value()); + void visit(NullVal&) final { + if constexpr (BinaryOperator::definedForNull) return calc(nullptr); - typeError(); - } + 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); - } + void visit(BoolVal& n) final { + if constexpr (BinaryOperator::definedForBool) return calc(&n.value()); - typeError(); - } + 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); + 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)"}; } - - typeError(); } - void visit(DoubleVal& n) final - { - if constexpr (BinaryOperator::definedForDouble) - return calc(&n.value()); + std::uint64_t alt = n.value(); + return calc(&alt); + } - typeError(); - } + 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; + 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"}; } - - 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() - {} + std::int64_t alt = n.value(); + return calc(&alt); + } - template - void calc(LhsValue lv) - { - using RhsVisitor = BinaryOperatorVisitor_; + typeError(); + } - RhsVisitor vis{lv, op}; + void visit(DoubleVal& n) final { + if constexpr (BinaryOperator::definedForDouble) return calc(&n.value()); - rhs->accept(vis); - res = std::move(vis).result(); - } - - void visit(StringVal& n) final - { - if constexpr (BinaryOperator::definedForString) - return calc(&n.value()); + typeError(); + } - 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); } - void visit(NullVal&) final - { - if constexpr (BinaryOperator::definedForNull) - return calc(nullptr); - - typeError(); - } + return; + } - void visit(BoolVal& n) final - { - if constexpr (BinaryOperator::definedForBool) - return calc(&n.value()); + typeError(); + } - typeError(); - } + result_type result() && { return std::move(res); } - 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); - } + private: + BinaryOperator op; + ValueExpr& rhs; + result_type res; +}; - typeError(); - } +// +// compute and sequence functions - 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); - } +template +typename BinaryOperator::result_type compute(ValueExpr& lhs, ValueExpr& rhs, + BinaryOperator op) { + using LhsVisitor = BinaryOperatorVisitor; - typeError(); - } + assert(lhs.get() && rhs.get()); - void visit(DoubleVal& n) final - { - if constexpr (BinaryOperator::definedForDouble) - return calc(&n.value()); + LhsVisitor vis{op, rhs}; - typeError(); - } + lhs->accept(vis); + return std::move(vis).result(); +} - 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; - } +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(); - typeError(); - } + if (lsz == 0) return pred(false, rsz != 0); - result_type result() && { return std::move(res); } + if (rsz == 0) return pred(true, false); - private: - BinaryOperator op; - ValueExpr& rhs; - result_type res; - }; + 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); - // - // compute and sequence functions + // res is conclusive if the reverse test yields a different result + found = res != compute(rv.at(i), lv.at(i), pred); - 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(); + ++i; } - 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(); + return found ? res : pred(lsz, rsz); +} - if (lsz == 0) - return pred( false, rsz != 0 ); +template +bool compareSeq(Array* lv, Array* rv, BinaryPredicate pred) { + return compareSeq(deref(lv), deref(rv), std::move(pred)); +} - if (rsz == 0) - return pred( true, false ); +// convenience template that only returns a type when the types mismatch +template +struct MismatchedTypes { + using type = ResultType; +}; - std::size_t const len = std::min(lsz, rsz); - std::size_t i = 0; - bool res = false; - bool found = false; +template +struct MismatchedTypes { + // using type // triggers SFINAE exclusion +}; - while ((i < len) && !found) - { - res = compute(lv.at(i), rv.at(i), pred); +// +// the calc operator implementations - // res is conclusive if the reverse test yields a different result - found = res != compute(rv.at(i), lv.at(i), pred); +template +struct Calc {}; - ++i; - } +template <> +struct Calc : EqualityOperator { + using EqualityOperator::result_type; - return found ? res : pred(lsz, rsz); + template + auto operator()(const U&, const V&) const -> + typename MismatchedTypes::type // type mismatch + { + return false; } - - template - bool compareSeq(Array* lv, Array* rv, BinaryPredicate pred) - { - return compareSeq(deref(lv), deref(rv), std::move(pred)); + template + auto operator()(const T& lhs, const T& rhs) const -> result_type { + return lhs == rhs; } +}; - // convenience template that only returns a type when the types mismatch - template - struct MismatchedTypes - { - using type = ResultType; - }; +template <> +struct Calc : EqualityOperator { + using EqualityOperator::result_type; - template - struct MismatchedTypes + template + auto operator()(const U&, const V&) const -> + typename MismatchedTypes::type // type mismatch { - // using type // triggers SFINAE exclusion - }; - + return true; + } - // - // the calc operator implementations + template + result_type operator()(const T& lhs, const T& rhs) const { + return lhs != rhs; + } +}; - template - struct Calc {}; +template <> +struct Calc : StrictEqualityOperator { + using StrictEqualityOperator::result_type; - template <> - struct Calc : EqualityOperator + template + auto operator()(const U&, const V&) const -> + typename MismatchedTypes::type // type mismatch { - using EqualityOperator::result_type; + return false; + } - 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 - auto - operator()(const T& lhs, const T& rhs) const -> result_type - { - return lhs == rhs; - } - }; +template <> +struct Calc : StrictEqualityOperator { + using StrictEqualityOperator::result_type; - template <> - struct Calc : EqualityOperator + template + auto operator()(const U&, const V&) const -> + typename MismatchedTypes::type // type mismatch { - using EqualityOperator::result_type; + return true; + } - 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 - result_type - operator()(const T& lhs, const T& rhs) const - { - return lhs != rhs; - } - }; +template <> +struct Calc : RelationalOperator { + using RelationalOperator::result_type; - template <> - struct Calc : StrictEqualityOperator - { - using StrictEqualityOperator::result_type; + result_type operator()(const std::nullptr_t, std::nullptr_t) const { + return false; + } - template - auto - operator()(const U&, const V&) const - -> typename MismatchedTypes::type // type mismatch - { - return false; - } + result_type operator()(Array* lv, Array* rv) const { + return compareSeq(lv, rv, *this); + } - template - result_type - operator()(const T& lhs, const T& rhs) const - { - return lhs == rhs; - } - }; + result_type operator()(const json::string&, std::nullptr_t) const { + return false; + } - template <> - struct Calc : StrictEqualityOperator - { - using StrictEqualityOperator::result_type; + result_type operator()(std::nullptr_t, const json::string&) const { + return false; + } - 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 - result_type - operator()(const T& lhs, const T& rhs) const - { - return lhs != rhs; - } - }; +template <> +struct Calc : RelationalOperator { + using RelationalOperator::result_type; - template <> - struct Calc : RelationalOperator - { - using RelationalOperator::result_type; + result_type operator()(const std::nullptr_t, std::nullptr_t) const { + return false; + } - 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()(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()(const json::string&, std::nullptr_t) const - { - return false; - } + result_type operator()(std::nullptr_t, const json::string&) 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 - result_type - operator()(const T& lhs, const T& rhs) const - { - return lhs < rhs; - } - }; +template <> +struct Calc : RelationalOperator { + using RelationalOperator::result_type; - template <> - struct Calc : RelationalOperator - { - using RelationalOperator::result_type; + result_type operator()(const std::nullptr_t, std::nullptr_t) const { + return true; + } - 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()(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()(const json::string&, std::nullptr_t) const - { - return false; - } + result_type operator()(std::nullptr_t, const json::string& rhs) const { + return rhs.empty(); + } - 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 - result_type - operator()(const T& lhs, const T& rhs) const - { - return rhs < lhs; - } - }; +template <> +struct Calc : RelationalOperator { + using RelationalOperator::result_type; - template <> - struct Calc : RelationalOperator - { - using RelationalOperator::result_type; + result_type operator()(const std::nullptr_t, std::nullptr_t) const { + return true; + } - 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()(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()(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(); + } - 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 - result_type - operator()(const T& lhs, const T& rhs) const - { - return lhs <= rhs; - } - }; +template <> +struct Calc : ArithmeticOperator { + using ArithmeticOperator::result_type; - template <> - struct Calc : RelationalOperator - { - using RelationalOperator::result_type; + result_type operator()(std::nullptr_t, std::nullptr_t) const { + return toValueExpr(nullptr); + } - result_type - operator()(const std::nullptr_t, std::nullptr_t) const - { - return true; - } + template + result_type operator()(const T& lhs, const T& rhs) const { + return toValueExpr(lhs + rhs); + } +}; - result_type - operator()(Array* lv, Array* rv) const - { - return compareSeq(lv, rv, *this); - } +template <> +struct Calc : ArithmeticOperator { + using ArithmeticOperator::result_type; - result_type - operator()(const json::string& lhs, std::nullptr_t) const - { - return lhs.empty(); - } + result_type operator()(std::nullptr_t, std::nullptr_t) const { + return toValueExpr(nullptr); + } - 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 toValueExpr(lhs - rhs); + } +}; - template - result_type - operator()(const T& lhs, const T& rhs) const - { - return rhs <= lhs; - } - }; +template <> +struct Calc : ArithmeticOperator { + using ArithmeticOperator::result_type; - template <> - struct Calc : ArithmeticOperator - { - using ArithmeticOperator::result_type; + result_type operator()(std::nullptr_t, std::nullptr_t) const { + return toValueExpr(nullptr); + } - 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 - result_type - operator()(const T& lhs, const T& rhs) const - { - return toValueExpr(lhs + rhs); - } - }; +template <> +struct Calc
: ArithmeticOperator { + using ArithmeticOperator::result_type; - template <> - struct Calc : ArithmeticOperator - { - using ArithmeticOperator::result_type; + result_type operator()(std::nullptr_t, std::nullptr_t) const { + return toValueExpr(nullptr); + } - 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; - template - result_type - operator()(const T& lhs, const T& rhs) const - { - return toValueExpr(lhs - rhs); - } - }; + // if (isInteger(res)) return toInt(res); + return toValueExpr(res); + } - template <> - struct Calc : ArithmeticOperator - { - using ArithmeticOperator::result_type; + template + result_type operator()(Int_t lhs, Int_t rhs) const { + if (lhs % rhs) return (*this)(double(lhs), double(rhs)); - result_type - operator()(std::nullptr_t, std::nullptr_t) const { return toValueExpr(nullptr); } + return toValueExpr(lhs / rhs); + } +}; - template - result_type - operator()(const T& lhs, const T& rhs) const - { - return toValueExpr(lhs * rhs); - } - }; +template <> +struct Calc : IntegerArithmeticOperator { + using IntegerArithmeticOperator::result_type; - template <> - struct Calc
: ArithmeticOperator - { - using ArithmeticOperator::result_type; + std::nullptr_t operator()(std::nullptr_t, std::nullptr_t) const { + return nullptr; + } - result_type - operator()(std::nullptr_t, std::nullptr_t) const { return toValueExpr(nullptr); } + template + result_type operator()(const T& lhs, const T& rhs) const { + if (rhs == 0) return toValueExpr(nullptr); - result_type - operator()(double lhs, double rhs) const - { - double res = lhs / rhs; + return toValueExpr(lhs % rhs); + } +}; - // if (isInteger(res)) return toInt(res); - return toValueExpr(res); - } +template <> +struct Calc : ArithmeticOperator { + using ArithmeticOperator::result_type; - template - result_type - operator()(Int_t lhs, Int_t rhs) const - { - if (lhs % rhs) return (*this)(double(lhs), double(rhs)); + result_type operator()(const std::nullptr_t, std::nullptr_t) const { + return nullptr; + } - return toValueExpr(lhs / rhs); - } - }; + template + result_type operator()(const T& lhs, const T& rhs) const { + return toValueExpr(std::min(lhs, rhs)); + } +}; - template <> - struct Calc : IntegerArithmeticOperator - { - using IntegerArithmeticOperator::result_type; +template <> +struct Calc : ArithmeticOperator { + using ArithmeticOperator::result_type; - std::nullptr_t - operator()(std::nullptr_t, std::nullptr_t) const { return nullptr; } + result_type operator()(const 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); + template + result_type operator()(const T& lhs, const T& rhs) const { + return toValueExpr(std::max(lhs, rhs)); + } +}; - return toValueExpr(lhs % rhs); - } - }; +template <> +struct Calc { + using result_type = bool; - template <> - struct Calc : ArithmeticOperator - { - using ArithmeticOperator::result_type; + result_type operator()(Expr& val) const { return falsy(val); } +}; - result_type - operator()(const std::nullptr_t, std::nullptr_t) const - { - return nullptr; - } +template <> +struct Calc { + using result_type = bool; - template - result_type - operator()(const T& lhs, const T& rhs) const - { - return toValueExpr(std::min(lhs, rhs)); - } - }; + result_type operator()(Expr& val) const { return truthy(val); } +}; - template <> - struct Calc : ArithmeticOperator - { - using ArithmeticOperator::result_type; +template <> +struct Calc : StringOperator { + using StringOperator::result_type; - result_type - operator()(const std::nullptr_t, std::nullptr_t) const - { - return nullptr; - } + result_type operator()(const json::string& lhs, + const json::string& rhs) const { + json::string tmp; - template - result_type - operator()(const T& lhs, const T& rhs) const - { - return toValueExpr(std::max(lhs, rhs)); - } - }; + tmp.reserve(lhs.size() + rhs.size()); - template <> - struct Calc - { - using result_type = bool; + tmp.append(lhs.begin(), lhs.end()); + tmp.append(rhs.begin(), rhs.end()); - result_type - operator()(Expr& val) const - { - return falsy(val); - } - }; + return toValueExpr(std::move(tmp)); + } +}; - template <> - struct Calc - { - using result_type = bool; +template <> +struct Calc : StringOperator // \todo the conversion rules differ +{ + using StringOperator::result_type; - result_type - operator()(Expr& val) const - { - return truthy(val); - } - }; + result_type operator()(const json::string& lhs, + const json::string& rhs) const { + const bool res = (rhs.find(lhs) != json::string::npos); - template <> - struct Calc : StringOperator - { - using StringOperator::result_type; + return toValueExpr(res); + } +}; - result_type - operator()(const json::string& lhs, const json::string& rhs) const - { - json::string tmp; +template <> +struct Calc : StringOperator // \todo the conversion rules differ +{ + using StringOperator::result_type; - tmp.reserve(lhs.size()+rhs.size()); + result_type operator()(const json::string& lhs, + const json::string& rhs) const { + std::regex rgx(lhs.c_str(), lhs.size()); - tmp.append(lhs.begin(), lhs.end()); - tmp.append(rhs.begin(), rhs.end()); + return toValueExpr(std::regex_search(rhs.begin(), rhs.end(), rgx)); + } +}; - return toValueExpr(std::move(tmp)); - } - }; +template <> +struct Calc : ArrayOperator { + using ArrayOperator::result_type; - template <> - struct Calc : StringOperator // \todo the conversion rules differ - { - using StringOperator::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); - result_type - operator()(const json::string& lhs, const json::string& rhs) const { - const bool res = (rhs.find(lhs) != json::string::npos); + 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; - return toValueExpr(res); - } - }; + // + // opers - template <> - struct Calc : StringOperator // \todo the conversion rules differ - { - using StringOperator::result_type; + /// implements relop : [1, 2, 3, whatever] as 1 relop 2 relop 3 + template + void evalPairShortCircuit(Operator& n, BinaryPredicate pred); - result_type - operator()(const json::string& lhs, const json::string& rhs) const - { - std::regex rgx(lhs.c_str(), lhs.size()); + /// returns the first expression in [ e1, e2, e3 ] that evaluates to val, or + /// the last expression otherwise + void evalShortCircuit(Operator& n, bool val); - return toValueExpr(std::regex_search(rhs.begin(), rhs.end(), rgx)); - } - }; + /// reduction operation on all elements + template + void reduce(Operator& n, BinaryOperator op); - template <> - struct Calc : ArrayOperator - { - using ArrayOperator::result_type; + /// computes unary operation on n[0] + template + void unary(Operator& n, UnaryOperator calc); - 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); + /// binary operation on all elements (invents an element if none is present) + template + void binary(Operator& n, BinaryOperator binop); - { - Operator::container_type& opers = res.operands(); + /// evaluates and unpacks n[argpos] to a fundamental value + template + ValueT unpackOptionalArg(Operator& n, int argpos, const ValueT& defaultVal); - opers.swap(lhs->operands()); + template + void _value(const ValueNode& val) { + calcres = toValueExpr(val.value()); + } - Operator::container_type& ropers = rhs->operands(); + /// auxiliary missing method + std::size_t missing_aux(Array& elems); +}; - opers.insert( opers.end(), - std::make_move_iterator(ropers.begin()), std::make_move_iterator(ropers.end()) - ); - } +struct SequenceFunction { + SequenceFunction(Expr& e, std::ostream& logstream) + : expr(e), logger(logstream) {} - return ValueExpr(&res); - } - }; + ValueExpr operator()(ValueExpr&& elem) const { + ValueExpr* elptr = &elem; // workaround, b/c unique_ptr cannot be captured - 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()); - } + Calculator sub{[elptr](const json::value& keyval, int) -> ValueExpr { + if (const json::string* pkey = keyval.if_string()) { + const json::string& key = *pkey; - /// auxiliary missing method - std::size_t missing_aux(Array& elems); - }; + if (key.size() == 0) return cloneExpr(*elptr); + try { + ObjectVal& o = down_cast(**elptr); - 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); - } + if (auto pos = o.find(key); pos != o.end()) + return cloneExpr(pos->second); + } catch (const cast_error&) { + } + } - private: - Expr& expr; - std::ostream& logger; - }; + return toValueExpr(nullptr); + }, + logger}; - struct SequencePredicate : SequenceFunction - { - using SequenceFunction::SequenceFunction; + return sub.eval(expr); + } - bool operator()(ValueExpr&& elem) const - { - return truthy(SequenceFunction::operator()(std::move(elem))); - } - }; + private: + Expr& expr; + std::ostream& logger; +}; - struct SequencePredicateNondestructive : SequenceFunction - { - using SequenceFunction::SequenceFunction; +struct SequencePredicate : SequenceFunction { + using SequenceFunction::SequenceFunction; - bool operator()(ValueExpr&& elem) const - { - return truthy(SequenceFunction::operator()(cloneExpr(elem))); - } - }; + 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) + accumulate_move(InputIterator pos, InputIterator lim, ValueExpr accu, + BinaryOperation binop) { for ( ; pos != lim; ++pos) accu = binop(std::move(accu), std::move(*first)); @@ -3114,871 +2623,690 @@ namespace json_logic } */ - 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; - }; +struct SequenceReduction { + SequenceReduction(Expr& e, std::ostream& logstream) + : expr(e), logger(logstream) {} - template - ValueT - Calculator::unpackOptionalArg(Operator& n, int argpos, const ValueT& defaultVal) - { - if (std::size_t(argpos) >= n.size()) - { - CXX_UNLIKELY; - return defaultVal; - } + // 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 - return unpackValue(*eval(n.operand(argpos))); - } + Calculator sub{[acptr, elptr](const json::value& keyval, int) -> ValueExpr { + if (const json::string* pkey = keyval.if_string()) { + if (*pkey == "current") return cloneExpr(*elptr); - template - void - Calculator::unary(Operator& n, UnaryPredicate pred) - { - const int num = n.num_evaluated_operands(); - assert(num == 1); + if (*pkey == "accumulator") return cloneExpr(*acptr); + } - const bool res = pred(*eval(n.operand(0))); + return toValueExpr(nullptr); + }, + logger}; - calcres = toValueExpr(res); + return sub.eval(expr); } - 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)); + private: + Expr& expr; + std::ostream& logger; +}; - calcres = compute(lhs, rhs, binop); +template +ValueT Calculator::unpackOptionalArg(Operator& n, int argpos, + const ValueT& defaultVal) { + if (std::size_t(argpos) >= n.size()) { + CXX_UNLIKELY; + return defaultVal; } - template - void - Calculator::reduce(Operator& n, BinaryOperator op) - { - const int num = n.num_evaluated_operands(); - assert(num >= 1); + return unpackValue(*eval(n.operand(argpos))); +} - int idx = -1; - ValueExpr res = eval(n.operand(++idx)); +template +void Calculator::unary(Operator& n, UnaryPredicate pred) { + const int num = n.num_evaluated_operands(); + assert(num == 1); - res = convert(std::move(res), op); + const bool res = pred(*eval(n.operand(0))); - while (idx != (num-1)) - { - ValueExpr rhs = eval(n.operand(++idx)); + calcres = toValueExpr(res); +} - rhs = convert(std::move(rhs), op); - res = compute(res, rhs, op); - } +template +void Calculator::binary(Operator& n, BinaryOperator binop) { + const int num = n.num_evaluated_operands(); + assert(num == 1 || num == 2); - calcres = std::move(res); - } + int idx = -1; + ValueExpr lhs; - template - void - Calculator::evalPairShortCircuit(Operator& n, BinaryPredicate pred) - { - const int num = n.num_evaluated_operands(); - assert(num >= 2); + if (num == 2) { + CXX_LIKELY; + lhs = eval(n.operand(++idx)); + } else { + lhs = toValueExpr(std::int64_t(0)); + } - bool res = true; - int idx = -1; - ValueExpr rhs = eval(n.operand(++idx)); - assert(rhs.get()); + ValueExpr rhs = eval(n.operand(++idx)); - while (res && (idx != (num-1))) - { - ValueExpr lhs = std::move(rhs); - assert(lhs.get()); + calcres = compute(lhs, rhs, binop); +} - rhs = eval(n.operand(++idx)); - assert(rhs.get()); +template +void Calculator::reduce(Operator& n, BinaryOperator op) { + const int num = n.num_evaluated_operands(); + assert(num >= 1); - res = compute(lhs, rhs, pred); - } + int idx = -1; + ValueExpr res = eval(n.operand(++idx)); - calcres = toValueExpr(res); - } + res = convert(std::move(res), op); + while (idx != (num - 1)) { + ValueExpr rhs = eval(n.operand(++idx)); - void - Calculator::evalShortCircuit(Operator& n, bool val) - { - const int num = n.num_evaluated_operands(); + rhs = convert(std::move(rhs), op); + res = compute(res, rhs, op); + } - if (num == 0) { CXX_UNLIKELY; requiresArgumentError(); } + calcres = std::move(res); +} - int idx = -1; - ValueExpr oper = eval(n.operand(++idx)); - //~ std::cerr << idx << ") " << oper << std::endl; +template +void Calculator::evalPairShortCircuit(Operator& n, BinaryPredicate pred) { + const int num = n.num_evaluated_operands(); + assert(num >= 2); - bool found = (idx == num-1) || (truthy(*oper) == val); + bool res = true; + int idx = -1; + ValueExpr rhs = eval(n.operand(++idx)); + assert(rhs.get()); - // 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; + while (res && (idx != (num - 1))) { + ValueExpr lhs = std::move(rhs); + assert(lhs.get()); - found = (idx == (num-1)) || (truthy(*oper) == val); - } + rhs = eval(n.operand(++idx)); + assert(rhs.get()); - calcres = std::move(oper); + res = compute(lhs, rhs, pred); } - ValueExpr - Calculator::eval(Expr& n) - { - ValueExpr res; + calcres = toValueExpr(res); +} - n.accept(*this); - res.swap(calcres); +void Calculator::evalShortCircuit(Operator& n, bool val) { + const int num = n.num_evaluated_operands(); - return res; + if (num == 0) { + CXX_UNLIKELY; + requiresArgumentError(); } - void Calculator::visit(Eq& n) - { - evalPairShortCircuit(n, Calc{}); - } + int idx = -1; + ValueExpr oper = eval(n.operand(++idx)); + //~ std::cerr << idx << ") " << oper << std::endl; - void Calculator::visit(StrictEq& n) - { - evalPairShortCircuit(n, Calc{}); - } + bool found = (idx == num - 1) || (truthy(*oper) == val); - void Calculator::visit(Neq& n) - { - evalPairShortCircuit(n, Calc{}); - } + // 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; - void Calculator::visit(StrictNeq& n) - { - evalPairShortCircuit(n, Calc{}); + found = (idx == (num - 1)) || (truthy(*oper) == val); } - void Calculator::visit(Less& n) - { - evalPairShortCircuit(n, Calc{}); - } + calcres = std::move(oper); +} - void Calculator::visit(Greater& n) - { - evalPairShortCircuit(n, Calc{}); - } +ValueExpr Calculator::eval(Expr& n) { + ValueExpr res; - void Calculator::visit(Leq& n) - { - evalPairShortCircuit(n, Calc{}); - } + n.accept(*this); + res.swap(calcres); - void Calculator::visit(Geq& n) - { - evalPairShortCircuit(n, Calc{}); - } + return res; +} - void Calculator::visit(And& n) - { - evalShortCircuit(n, false); - } +void Calculator::visit(Eq& n) { evalPairShortCircuit(n, Calc{}); } - void Calculator::visit(Or& n) - { - evalShortCircuit(n, true); - } +void Calculator::visit(StrictEq& n) { + evalPairShortCircuit(n, Calc{}); +} - void Calculator::visit(Not& n) - { - unary(n, Calc{}); - } +void Calculator::visit(Neq& n) { evalPairShortCircuit(n, Calc{}); } - void Calculator::visit(NotNot& n) - { - unary(n, Calc{}); - } +void Calculator::visit(StrictNeq& n) { + evalPairShortCircuit(n, Calc{}); +} - void Calculator::visit(Add& n) - { - reduce(n, Calc{}); - } +void Calculator::visit(Less& n) { evalPairShortCircuit(n, Calc{}); } - void Calculator::visit(Sub& n) - { - binary(n, Calc{}); - } +void Calculator::visit(Greater& n) { evalPairShortCircuit(n, Calc{}); } - void Calculator::visit(Mul& n) - { - reduce(n, Calc{}); - } +void Calculator::visit(Leq& n) { evalPairShortCircuit(n, Calc{}); } - void Calculator::visit(Div& n) - { - binary(n, Calc
{}); - } +void Calculator::visit(Geq& n) { evalPairShortCircuit(n, Calc{}); } - void Calculator::visit(Mod& n) - { - binary(n, Calc{}); - } +void Calculator::visit(And& n) { evalShortCircuit(n, false); } - void Calculator::visit(Min& n) - { - reduce(n, Calc{}); - } +void Calculator::visit(Or& n) { evalShortCircuit(n, true); } - void Calculator::visit(Max& n) - { - reduce(n, Calc{}); - } +void Calculator::visit(Not& n) { unary(n, Calc{}); } - void Calculator::visit(Cat& n) - { - reduce(n, Calc{}); - } +void Calculator::visit(NotNot& n) { unary(n, Calc{}); } - void Calculator::visit(In& n) - { - binary(n, Calc{}); - } +void Calculator::visit(Add& n) { reduce(n, Calc{}); } - void Calculator::visit(RegexMatch& n) - { - binary(n, Calc{}); - } +void Calculator::visit(Sub& n) { binary(n, Calc{}); } - void Calculator::visit(Substr& n) - { - assert(n.num_evaluated_operands() >= 1); +void Calculator::visit(Mul& n) { reduce(n, Calc{}); } - json::string str = unpackValue(*eval(n.operand(0))); - std::int64_t ofs = unpackOptionalArg(n, 1, 0); - std::int64_t cnt = unpackOptionalArg(n, 2, 0); +void Calculator::visit(Div& n) { binary(n, Calc
{}); } - if (ofs < 0) - { - CXX_UNLIKELY; - ofs = std::max(std::int64_t(str.size()) + ofs, std::int64_t(0)); - } +void Calculator::visit(Mod& n) { binary(n, Calc{}); } - if (cnt < 0) - { - CXX_UNLIKELY; - cnt = std::max(std::int64_t(str.size()) - ofs + cnt, std::int64_t(0)); - } +void Calculator::visit(Min& n) { reduce(n, Calc{}); } - calcres = toValueExpr(json::string{str.subview(ofs, cnt)}); - } +void Calculator::visit(Max& n) { reduce(n, Calc{}); } - 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); - } - ); +void Calculator::visit(Cat& n) { reduce(n, Calc{}); } - Array& res = deref(new Array); +void Calculator::visit(In& n) { binary(n, Calc{}); } - res.set_operands(std::move(elems)); +void Calculator::visit(RegexMatch& n) { binary(n, Calc{}); } - calcres = ValueExpr(&res); - } +void Calculator::visit(Substr& n) { + assert(n.num_evaluated_operands() >= 1); - void Calculator::visit(Merge& n) - { - reduce(n, Calc{}); + 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)); } - 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; + if (cnt < 0) { + CXX_UNLIKELY; + cnt = std::max(std::int64_t(str.size()) - ofs + cnt, std::int64_t(0)); + } - std::transform( std::make_move_iterator(arrop.begin()), std::make_move_iterator(arrop.end()), - std::back_inserter(mapped_elements), - SequenceFunction{expr, *calclogger} - ); + calcres = toValueExpr(json::string{str.subview(ofs, cnt)}); +} - arrop.set_operands(std::move(mapped_elements)); - return std::move(arr); - }; +void Calculator::visit(Array& n) { + Operator::container_type elems; + Calculator* self = this; - calcres = with_type( arr.get(), - mapper, - []()->ValueExpr { return ValueExpr{new Array}; } - ); - } + // \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); }); - 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; + Array& res = deref(new Array); - // 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} - ); + res.set_operands(std::move(elems)); - arrop.set_operands(std::move(filtered_elements)); - return std::move(arr); - }; + calcres = ValueExpr(&res); +} - calcres = with_type( arr.get(), - filter, - []()->ValueExpr { return ValueExpr{new Array}; } - ); - } +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; - 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} - ); + 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 = toValueExpr(res); - } + calcres = + with_type(arr.get(), op, []() -> ValueExpr { return nullptr; }); +} - 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} - ); +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; - calcres = toValueExpr(res); - } + 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); + }; - 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 = with_type( + arr.get(), mapper, []() -> ValueExpr { return ValueExpr{new Array}; }); +} - calcres = toValueExpr(res); - } +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; - void Calculator::visit(Error&) { unsupported(); } + // 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}); - void Calculator::visit(Var& n) - { - assert(n.num_evaluated_operands() >= 1); + arrop.set_operands(std::move(filtered_elements)); + return std::move(arr); + }; - AnyExpr elm = eval(n.operand(0)); - Value& val = down_cast(*elm); + calcres = with_type( + arr.get(), filter, []() -> ValueExpr { return ValueExpr{new Array}; }); +} - try - { - calcres = vars(val.toJson(), n.num()); - } - catch (...) - { - calcres = (n.num_evaluated_operands() > 1) ? eval(n.operand(1)) - : toValueExpr(nullptr); - } - } +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}); - 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; - } + calcres = toValueExpr(res); +} - void Calculator::visit(Missing& n) - { - ValueExpr arg = eval(n.operand(0)); - auto nonArrayHandler = [&arg, &n, calc = this]() -> Array& - { - Array& res = deref(new Array); +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}); - res.set_operands(std::move(n).move_operands()); - res.operands().front().swap(arg); - arg.reset(&res); - calc->visit(res); + calcres = toValueExpr(res); +} - return 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}); - Array& elems = with_type( arg.get(), - [](Array& array) -> Array& { return array; }, // ignore other args - nonArrayHandler - ); + calcres = toValueExpr(res); +} - missing_aux(elems); - calcres = std::move(arg); - } +void Calculator::visit(Error&) { unsupported(); } - 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); +void Calculator::visit(Var& n) { + assert(n.num_evaluated_operands() >= 1); - if (avail >= minreq) - elems.operands().clear(); + AnyExpr elm = eval(n.operand(0)); + Value& val = down_cast(*elm); - calcres = std::move(arr); + try { + calcres = vars(val.toJson(), n.num()); + } catch (...) { + calcres = (n.num_evaluated_operands() > 1) ? eval(n.operand(1)) + : toValueExpr(nullptr); } +} - void Calculator::visit(If& n) - { - const int num = n.num_evaluated_operands(); +std::size_t Calculator::missing_aux(Array& elems) { + auto avail = [calc = this](ValueExpr& v) -> bool { + try { + Value& val = down_cast(*v); - if (num == 0) - { - calcres = toValueExpr(nullptr); - return; + calc->vars(val.toJson(), -1 /* not in varmap */); + } catch (...) { + return false; } - const int lim = num-1; - int pos = 0; + return true; + }; - while (pos < lim) - { - if (truthy(eval(n.operand(pos)))) - { - calcres = eval(n.operand(pos+1)); - return; - } + 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); - pos+=2; - } + elems.operands().erase(pos, lim); + return res; +} - calcres = (pos < num) ? eval(n.operand(pos)) : toValueExpr(nullptr); - } +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); - void Calculator::visit(Log& n) - { - assert(n.num_evaluated_operands() == 1); + return res; + }; - calcres = eval(n.operand(0)); + Array& elems = with_type( + arg.get(), + [](Array& array) -> Array& { return array; }, // ignore other args + nonArrayHandler); - logger << calcres << std::endl; - } + missing_aux(elems); + calcres = std::move(arg); +} - 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 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); - CXX_MAYBE_UNUSED - ValueExpr calculate(Expr& exp, const Calculator::VarAccess& vars) - { - Calculator calc{vars, std::cerr}; + if (avail >= minreq) elems.operands().clear(); - return calc.eval(exp); - } + calcres = std::move(arr); +} - CXX_MAYBE_UNUSED - ValueExpr calculate(AnyExpr& exp, const Calculator::VarAccess& vars) - { - assert(exp.get()); - return calculate(*exp, vars); +void Calculator::visit(If& n) { + const int num = n.num_evaluated_operands(); + + if (num == 0) { + calcres = toValueExpr(nullptr); + return; } - CXX_MAYBE_UNUSED - ValueExpr calculate(AnyExpr& exp) - { - return calculate(exp, [](const json::value&, int) -> ValueExpr { unsupported(); }); + 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); +} - 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()); +void Calculator::visit(Log& n) { + assert(n.num_evaluated_operands() == 1); - 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); + calcres = eval(n.operand(0)); - return evalPath(suffix, obj.at(selector).as_object()); - } + logger << calcres << std::endl; +} - throw std::out_of_range("json_logic - unable to locate path"); - } +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); } - template - ValueExpr evalIdx(IntT idx, const json::array& arr) - { - return json_logic::toValueExpr(arr[idx]); - } +CXX_MAYBE_UNUSED +ValueExpr calculate(Expr& exp, const Calculator::VarAccess& vars) { + Calculator calc{vars, std::cerr}; + return calc.eval(exp); +} - 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); - } +CXX_MAYBE_UNUSED +ValueExpr calculate(AnyExpr& exp, const Calculator::VarAccess& vars) { + assert(exp.get()); + return calculate(*exp, vars); +} - if (const std::int64_t* pidx = keyval.if_int64()) - return evalIdx(*pidx, data.as_array()); +CXX_MAYBE_UNUSED +ValueExpr calculate(AnyExpr& exp) { + return calculate(exp, + [](const json::value&, int) -> ValueExpr { unsupported(); }); +} - if (const std::uint64_t* pidx = keyval.if_uint64()) - return evalIdx(*pidx, data.as_array()); +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()); - throw std::logic_error{"json_logic - unsupported var access"}; - }; + 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 calculate(ast, varlookup); + return evalPath(suffix, obj.at(selector).as_object()); } - 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()); - } + throw std::out_of_range("json_logic - unable to locate path"); +} - void visit(Array& n) final - { - bool first = true; +template +ValueExpr evalIdx(IntT idx, const json::array& arr) { + return json_logic::toValueExpr(arr[idx]); +} - os << "["; - for (AnyExpr& el : n) - { - if (first) first = false; else os << ","; +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); + } - deref(el).accept(*this); - } + if (const std::int64_t* pidx = keyval.if_int64()) + return evalIdx(*pidx, data.as_array()); - os << "]"; - } + if (const std::uint64_t* pidx = keyval.if_uint64()) + return evalIdx(*pidx, data.as_array()); - private: - std::ostream& os; + throw std::logic_error{"json_logic - unsupported var access"}; }; - std::ostream& operator<<(std::ostream& os, ValueExpr& n) - { - ValuePrinter prn{os}; - - deref(n).accept(prn); - return os; - } + return calculate(ast, varlookup); } +struct ValuePrinter : FwdVisitor { + explicit ValuePrinter(std::ostream& stream) : os(stream) {} -#if SUPPLEMENTAL + void prn(json::value val) { os << val; } - /// 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); + void visit(Value& n) final { prn(n.toJson()); } + void visit(Array& n) final { + bool first = true; - // 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); - } - ); - } + os << "["; + for (AnyExpr& el : n) { + if (first) + first = false; + else + os << ","; - void traverseChildren(Visitor& v, const Operator& node) - { - Operator::const_iterator aa = node.begin(); + deref(el).accept(*this); + } - _traverseChildren(v, aa, aa + node.num_evaluated_operands()); + os << "]"; } - void traverseAllChildren(Visitor& v, const Operator& node) - { - _traverseChildren(v, node.begin(), node.end()); - } + private: + std::ostream& os; +}; - 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::ostream& operator<<(std::ostream& os, ValueExpr& n) { + ValuePrinter prn{os}; - std::for_each( aa, zz, - [&v](const AnyExpr& e) -> void - { - e->accept(v); - } - ); - } + deref(n).accept(prn); + return os; +} +} // namespace json_logic - 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); - } +#if SUPPLEMENTAL - 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); } - } - - /// 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}; +/// 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); - e.accept(trav); - } +// 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/test/TestDF/testdf.hpp b/test/TestDF/testdf.hpp index c6c9f13..4761da6 100644 --- a/test/TestDF/testdf.hpp +++ b/test/TestDF/testdf.hpp @@ -168,7 +168,11 @@ class testdf { for (const auto &k : ks) { std::cout << k << ","; for (auto col = cols.begin(); col != cols.end(); ++col) { - auto d = data.get_as_variant(col->first, k); + auto d_opt = data.get_as_variant(col->first, k); + if (!d_opt.has_value()) { + continue; + } + auto d = d_opt.value(); bool last = col == std::prev(cols.end()); std::visit( [&d, last](auto &&arg) { diff --git a/test/TestGraph/CMakeLists.txt b/test/TestGraph/CMakeLists.txt index defbc49..edfaff4 100644 --- a/test/TestGraph/CMakeLists.txt +++ b/test/TestGraph/CMakeLists.txt @@ -1,5 +1,8 @@ add_test(TestGraph __init__) add_test(TestGraph __str__) +# add_test(TestGraph assign) +# add_test(TestGraph dump) +add_test(TestGraph dump2) add_test(TestGraph add_edge) add_test(TestGraph add_node) add_test(TestGraph nv) @@ -9,6 +12,7 @@ add_test(TestGraph add_series) add_test(TestGraph connected_components) add_test(TestGraph drop_series) add_test(TestGraph copy_series) +add_test(TestGraph series_str) add_test(TestGraph extrema) add_test(TestGraph count) add_custom_command( diff --git a/test/TestGraph/assign.cpp b/test/TestGraph/assign.cpp new file mode 100644 index 0000000..fd96529 --- /dev/null +++ b/test/TestGraph/assign.cpp @@ -0,0 +1,341 @@ + + +// Copyright 2021 Lawrence Livermore National Security, LLC and other CLIPPy +// Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: MIT + +#include +#include +#include +#include + +#include "clippy/clippy-eval.hpp" +#include "clippy/selector.hpp" +#include "testgraph.hpp" +#include "where.cpp" + +static const std::string method_name = "assign"; +static const std::string state_name = "INTERNAL"; +static const std::string sel_state_name = "selectors"; + +static const std::string always_true = R"({"rule":{"==":[1,1]}})"; +static const std::string never_true = R"({"rule":{"==":[2,1]}})"; + +static const boost::json::object always_true_obj = + boost::json::parse(always_true).as_object(); + +using variants = + std::variant; + +std::optional obj_to_val(boost::json::object expr, + std::string extract = "var") { + if (expr["expression_type"].as_string() != std::string("jsonlogic")) { + std::cerr << " NOT A THINGY " << std::endl; + return std::nullopt; + } + + boost::json::value v; + v = expr["rule"].as_object()[extract]; + return v; +} +int main(int argc, char **argv) { + clippy::clippy clip{method_name, "Populates a column with a value"}; + clip.add_required( + "selector", + "Existing selector name into which the value will be written"); + + clip.add_required("value", "the value to assign"); + + clip.add_optional("where", "where filter", + always_true_obj); + + clip.add_optional("desc", "Description of the series", ""); + + clip.add_required_state(state_name, + "Internal container"); + clip.add_required_state>( + sel_state_name, "Internal container for selectors"); + clip.returns_self(); + // no object-state requirements in constructor + if (clip.parse(argc, argv)) { + return 0; + } + + auto sel_json = clip.get("selector"); + auto sel_str_opt = obj_to_val(sel_json); + if (!sel_str_opt.has_value()) { + std::cerr << "!! ERROR !!" << std::endl; + exit(-1); + } + std::string sel_str = sel_str_opt.value().as_string().c_str(); + + auto sel = selector(sel_str); + bool is_node_sel = sel.headeq("node"); + if (!is_node_sel && !sel.headeq("edge")) { + std::cerr << "Selector must start with \"node\" or \"edge\"" << std::endl; + return 1; + } + + auto sel_tail_opt = sel.tail(); + if (!sel_tail_opt.has_value()) { + std::cerr << "Selector must have a tail" << std::endl; + return 1; + } + + selector subsel = sel_tail_opt.value(); + auto the_graph = clip.get_state(state_name); + + auto selectors = + clip.get_state>(sel_state_name); + if (!selectors.contains(sel)) { + std::cerr << "Selector not found" << std::endl; + return 1; + } + + auto desc = clip.get("desc"); + + auto val = clip.get("value"); + + auto where_exp = clip.get("where"); + + if (where_exp["expression_type"].as_string() != std::string("jsonlogic")) { + std::cerr << "!! ERROR in where statement !!" << std::endl; + exit(-1); + } + + boost::json::object submission_data; + + std::cerr << "val = " << val << ", val.kind() = " << val.kind() << std::endl; + if (is_node_sel) { + if (the_graph.has_node_series(subsel)) { + std::cerr << "Selector already populated" << std::endl; + return 1; + } + + auto nodemap = the_graph.nodemap(); + auto where = parse_where_expression(nodemap, where_exp, submission_data); + if (std::holds_alternative(val)) { + auto col_opt = the_graph.add_node_series(subsel, desc); + if (!col_opt.has_value()) { + std::cerr << "Unable to manifest node series" << std::endl; + return 1; + } + auto col = col_opt.value(); + auto v = std::get(val); + the_graph.for_all_nodes( + [&col, &v, &where](auto /* unused */, mvmap::locator l) { + if (where(l)) { + col[l] = v; + } + }); + } else if (std::holds_alternative(val)) { + auto col_opt = the_graph.add_node_series(subsel, desc); + if (!col_opt.has_value()) { + std::cerr << "Unable to manifest node series" << std::endl; + return 1; + } + auto col = col_opt.value(); + auto v = std::get(val); + the_graph.for_all_nodes( + [&col, &v, &where](auto /* unused */, mvmap::locator l) { + if (where(l)) { + col[l] = v; + } + }); + } else if (std::holds_alternative(val)) { + auto col_opt = the_graph.add_node_series(subsel, desc); + if (!col_opt.has_value()) { + std::cerr << "Unable to manifest node series" << std::endl; + return 1; + } + auto col = col_opt.value(); + auto v = std::get(val); + the_graph.for_all_nodes( + [&col, &v, &where](auto /* unused */, mvmap::locator l) { + if (where(l)) { + col[l] = v; + } + }); + } else if (std::holds_alternative(val)) { + auto col_opt = the_graph.add_node_series(subsel, desc); + if (!col_opt.has_value()) { + std::cerr << "Unable to manifest node series" << std::endl; + return 1; + } + auto col = col_opt.value(); + auto v = std::get(val).c_str(); + the_graph.for_all_nodes( + [&col, &v, &where](auto /* unused */, mvmap::locator l) { + if (where(l)) { + col[l] = v; + } + }); + } else if (std::holds_alternative(val)) { + auto col_opt = + the_graph.add_node_series(subsel, desc); + if (!col_opt.has_value()) { + std::cerr << "Unable to manifest node series" << std::endl; + return 1; + } + case boost::json::kind::bool_: { + auto col_opt = the_graph.add_node_series(subsel, desc); + if (!col_opt.has_value()) { + std::cerr << "Unable to manifest node series" << std::endl; + return 1; + } + auto col = col_opt.value(); + auto v = val.as_bool(); + the_graph.for_all_nodes( + [&col, &v, &where](auto /* unused */, mvmap::locator l) { + if (where(l)) { + col[l] = v; + } + }); + + break; + } + case boost::json::kind::double_: { + auto col_opt = the_graph.add_node_series(subsel, desc); + if (!col_opt.has_value()) { + std::cerr << "Unable to manifest node series" << std::endl; + return 1; + } + auto col = col_opt.value(); + auto v = val.as_double(); + the_graph.for_all_nodes( + [&col, &v, &where](auto /* unused */, mvmap::locator l) { + if (where(l)) { + col[l] = v; + } + }); + + break; + } + + case boost::json::kind::int64: { + auto col_opt = the_graph.add_node_series(subsel, desc); + if (!col_opt.has_value()) { + std::cerr << "Unable to manifest node series" << std::endl; + return 1; + } + auto col = col_opt.value(); + auto v = val.as_int64(); + the_graph.for_all_nodes( + [&col, &v, &where](auto /* unused */, mvmap::locator l) { + if (where(l)) { + col[l] = v; + } + }); + break; + } + + case boost::json::kind::string: { + auto col_opt = the_graph.add_node_series(subsel, desc); + if (!col_opt.has_value()) { + std::cerr << "Unable to manifest node series" << std::endl; + return 1; + } + auto col = col_opt.value(); + auto v = val.as_string().c_str(); + the_graph.for_all_nodes( + [&col, &v, &where](auto /* unused */, mvmap::locator l) { + if (where(l)) { + col[l] = v; + } + }); + break; + } + default: + std::cerr << "Unsupported type" << std::endl; + return 1; + } + } else { // edge map + if (the_graph.has_edge_series(subsel)) { + std::cerr << "Selector already populated" << std::endl; + return 1; + } + + auto edgemap = the_graph.edgemap(); + auto where = parse_where_expression(edgemap, where_exp, submission_data); + switch (val.kind()) { + case boost::json::kind::bool_: { + auto col_opt = the_graph.add_edge_series(subsel, desc); + if (!col_opt.has_value()) { + std::cerr << "Unable to manifest edge series" << std::endl; + return 1; + } + auto col = col_opt.value(); + auto v = val.as_bool(); + the_graph.for_all_edges( + [&col, &v, &where](auto /* unused */, mvmap::locator l) { + if (where(l)) { + col[l] = v; + } + }); + break; + } + case boost::json::kind::double_: { + auto col_opt = the_graph.add_edge_series(subsel, desc); + if (!col_opt.has_value()) { + std::cerr << "Unable to manifest edge series" << std::endl; + return 1; + } + auto col = col_opt.value(); + auto v = val.as_double(); + the_graph.for_all_edges( + [&col, &v, &where](auto /* unused */, mvmap::locator l) { + if (where(l)) { + col[l] = v; + } + }); + break; + } + + case boost::json::kind::int64: { + auto col_opt = the_graph.add_edge_series(subsel, desc); + if (!col_opt.has_value()) { + std::cerr << "Unable to manifest edge series" << std::endl; + return 1; + } + auto col = col_opt.value(); + auto v = val.as_int64(); + the_graph.for_all_edges( + [&col, &v, &where](auto /* unused */, mvmap::locator l) { + if (where(l)) { + col[l] = v; + } + }); + + break; + } + + case boost::json::kind::string: { + auto col_opt = the_graph.add_edge_series(subsel, desc); + if (!col_opt.has_value()) { + std::cerr << "Unable to manifest edge series" << std::endl; + return 1; + } + auto col = col_opt.value(); + auto v = val.as_string().c_str(); + the_graph.for_all_edges( + [&col, &v, &where](auto /* unused */, mvmap::locator l) { + if (where(l)) { + col[l] = v; + } + }); + break; + } + default: + std::cerr << "Unsupported type" << std::endl; + return 1; + } + } + + clip.set_state(state_name, the_graph); + clip.set_state(sel_state_name, selectors); + clip.update_selectors(selectors); + + clip.return_self(); + return 0; +} diff --git a/test/TestGraph/dump.cpp b/test/TestGraph/dump.cpp new file mode 100644 index 0000000..5c93fbb --- /dev/null +++ b/test/TestGraph/dump.cpp @@ -0,0 +1,129 @@ + +// Copyright 2021 Lawrence Livermore National Security, LLC and other CLIPPy +// Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: MIT + +#include +#include +#include +#include +#include + +#include "clippy/clippy-eval.hpp" +#include "clippy/selector.hpp" +#include "testgraph.hpp" + +static const std::string method_name = "dump"; +static const std::string state_name = "INTERNAL"; +static const std::string sel_state_name = "selectors"; + +static const std::string always_true = R"({"rule":{"==":[1,1]}})"; +static const std::string never_true = R"({"rule":{"==":[2,1]}})"; + +static const boost::json::object always_true_obj = + boost::json::parse(always_true).as_object(); + +int main(int argc, char **argv) { + clippy::clippy clip{method_name, + "returns a map of key: value " + "corresponding to the selector."}; + clip.add_required("selector", + "Existing selector name to obtain values"); + + clip.add_optional("where", "where filter", + always_true_obj); + + clip.add_required_state(state_name, + "Internal container"); + + // no object-state requirements in constructor + if (clip.parse(argc, argv)) { + return 0; + } + + auto sel_str = clip.get("selector"); + selector sel{sel_str}; + + auto the_graph = clip.get_state(state_name); + + bool is_edge_sel = testgraph::testgraph::is_edge_selector(sel); + bool is_node_sel = testgraph::testgraph::is_node_selector(sel); + + if (!is_edge_sel && !is_node_sel) { + std::cerr << "Selector must start with either \"edge\" or \"node\"" + << std::endl; + return 1; + } + + auto tailsel_opt = sel.tail(); + if (!tailsel_opt) { + std::cerr << "no tail" << std::endl; + return 1; + } + + auto expression = clip.get("where"); + // auto expression = boost::json::parse(always_true).as_object(); + std::cerr << "expression: " << expression << std::endl; + + // 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"]); + std::cerr << "post-translate expression: " << expression << std::endl; + std::cerr << "post-translate expression['rule']: " << expression["rule"] + << std::endl; + auto apply_jl = [&expression, &vars](int value) { + boost::json::object data; + boost::json::value val = value; + std::cerr << " apply_jl expression: " << expression << std::endl; + for (auto var : vars) { + data[var] = val; + std::cerr << " apply_jl: var: " << var << " val: " << val << std::endl; + } + + json_logic::ValueExpr res = json_logic::apply(expression["rule"], data); + std::cerr << " apply_jl: res: " << res << std::endl; + return json_logic::unpackValue(res); + }; + + std::string tail_sel = tailsel_opt.value(); + if (is_node_sel) { + if (the_graph.has_node_series(tail_sel)) { + auto pxy = the_graph.get_node_series(tail_sel).value(); + std::map filtered_data; + pxy.for_all( + [&filtered_data, &apply_jl](const auto &key, auto, const auto &val) { + std::cerr << "key: " << key << " val: " << val << std::endl; + if (apply_jl(val)) { // apply the where clause + filtered_data[key] = val; + std::cerr << "applied!" << std::endl; + } + }); + + clip.returns>( + "map of key: value corresponding to the selector"); + clip.to_return(filtered_data); + return 0; + } else if (the_graph.has_node_series(tail_sel)) { + // clip.returns>( + // "map of key: value corresponding to the selector"); + } else if (the_graph.has_node_series(tail_sel)) { + // clip.returns>( + // "map of key: value corresponding to the selector"); + } else if (the_graph.has_node_series(tail_sel)) { + // clip.returns>( + // "map of key: value corresponding to the selector"); + } else { + std::cerr << "Node series is an invalid type" << std::endl; + return 1; + } + } + + clip.returns>( + "map of key: value corresponding to the selector"); + + clip.to_return(std::map{}); + clip.set_state(state_name, the_graph); + return 0; +} diff --git a/test/TestGraph/dump2.cpp b/test/TestGraph/dump2.cpp new file mode 100644 index 0000000..666f4d9 --- /dev/null +++ b/test/TestGraph/dump2.cpp @@ -0,0 +1,89 @@ + +// Copyright 2021 Lawrence Livermore National Security, LLC and other CLIPPy +// Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: MIT + +#include +#include +#include +#include +#include + +#include "clippy/clippy-eval.hpp" +#include "clippy/selector.hpp" +#include "testgraph.hpp" +#include "where.cpp" + +static const std::string method_name = "dump2"; +static const std::string state_name = "INTERNAL"; +static const std::string sel_state_name = "selectors"; + +static const std::string always_true = R"({"rule":{"==":[1,1]}})"; +static const std::string never_true = R"({"rule":{"==":[2,1]}})"; + +static const boost::json::object always_true_obj = + boost::json::parse(always_true).as_object(); + +int main(int argc, char **argv) { + std::cerr << "starting" << std::endl; + clippy::clippy clip{method_name, + "returns a map of key: value " + "corresponding to the selector."}; + clip.add_required("selector", + "Existing selector name to obtain values"); + + clip.add_optional("where", "where filter", + always_true_obj); + + clip.add_required_state(state_name, + "Internal container"); + + // no object-state requirements in constructor + if (clip.parse(argc, argv)) { + return 0; + } + + std::cerr << "past parse" << std::endl; + + auto sel_str = clip.get("selector"); + selector sel{sel_str}; + + auto the_graph = clip.get_state(state_name); + + bool is_edge_sel = testgraph::testgraph::is_edge_selector(sel); + bool is_node_sel = testgraph::testgraph::is_node_selector(sel); + + if (!is_edge_sel && !is_node_sel) { + std::cerr << "Selector must start with either \"edge\" or \"node\"" + << std::endl; + return 1; + } + + std::cerr << "before tailsel_opt" << std::endl; + auto tailsel_opt = sel.tail(); + if (!tailsel_opt) { + std::cerr << "no tail" << std::endl; + return 1; + } + + auto expression = clip.get("where"); + // auto expression = boost::json::parse(always_true).as_object(); + std::cerr << "expression: " << expression << std::endl; + + std::string tail_sel = tailsel_opt.value(); + if (is_node_sel) { + clip.returns>( + "vector of node keys that match the selector"); + auto filtered_data = where_nodes(the_graph, expression); + clip.to_return(filtered_data); + return 0; + } + + clip.returns>( + "map of key: value corresponding to the selector"); + + clip.to_return(std::map{}); + clip.set_state(state_name, the_graph); + return 0; +} diff --git a/test/TestGraph/remove_if.cpp b/test/TestGraph/remove_if.cpp deleted file mode 100644 index 628b061..0000000 --- a/test/TestGraph/remove_if.cpp +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2021 Lawrence Livermore National Security, LLC and other CLIPPy -// Project Developers. See the top-level COPYRIGHT file for details. -// -// SPDX-License-Identifier: MIT - -#include "clippy/clippy-eval.hpp" -#include -#include -#include -#include -#include - -namespace boostjsn = boost::json; - -static const std::string method_name = "remove_if"; -static const std::string state_name = "INTERNAL"; - -int main(int argc, char **argv) { - clippy::clippy clip{method_name, "Removes a string from a TestSet"}; - clip.add_required("expression", "Remove If Expression"); - clip.add_required_state>(state_name, "Internal container"); - clip.returns_self(); - // no object-state requirements in constructor - if (clip.parse(argc, argv)) { - return 0; - } - - auto expression = clip.get("expression"); - auto the_set = clip.get_state>(state_name); - - // - // Expression here - 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); - }; - - for (auto first = the_set.begin(), last = the_set.end(); first != last;) { - if (apply_jl(*first)) - first = the_set.erase(first); - else - ++first; - } - - clip.set_state(state_name, the_set); - clip.return_self(); - return 0; -} diff --git a/test/TestGraph/series_str.cpp b/test/TestGraph/series_str.cpp new file mode 100644 index 0000000..f4ea6e7 --- /dev/null +++ b/test/TestGraph/series_str.cpp @@ -0,0 +1,65 @@ + +// Copyright 2021 Lawrence Livermore National Security, LLC and other CLIPPy +// Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: MIT + +#include +#include +#include + +#include "clippy/clippy-eval.hpp" +#include "clippy/selector.hpp" +#include "testgraph.hpp" + +static const std::string method_name = "series_str"; +static const std::string state_name = "INTERNAL"; +static const std::string sel_state_name = "selectors"; + +int main(int argc, char **argv) { + clippy::clippy clip{method_name, + "returns the values of a series based on selector"}; + clip.add_required("selector", "Existing selector name"); + clip.add_required_state(state_name, + "Internal container"); + + clip.returns("String of data."); + + // no object-state requirements in constructor + if (clip.parse(argc, argv)) { + return 0; + } + auto sel_str = clip.get("selector"); + selector sel{sel_str}; + + bool is_edge_sel = testgraph::testgraph::is_edge_selector(sel); + bool is_node_sel = testgraph::testgraph::is_node_selector(sel); + + if (!is_edge_sel && !is_node_sel) { + std::cerr << "Selector must start with either \"edge\" or \"node\"" + << std::endl; + return 1; + } + + auto tail_opt = sel.tail(); + if (!tail_opt) { + std::cerr << "Selector must have a tail" << std::endl; + return 1; + } + auto tail_sel = tail_opt.value(); + + auto the_graph = clip.get_state(state_name); + + if (is_edge_sel) { + clip.to_return(the_graph.str_edge_col(tail_sel)); + } else if (is_node_sel) { + clip.to_return(the_graph.str_node_col(tail_sel)); + } else { + std::cerr << "Selector must start with either \"edge\" or \"node\"" + << std::endl; + return 1; + } + + // clip.set_state(state_name, the_graph); + return 0; +} diff --git a/test/TestGraph/testgraph.hpp b/test/TestGraph/testgraph.hpp index f5027ea..b939f06 100644 --- a/test/TestGraph/testgraph.hpp +++ b/test/TestGraph/testgraph.hpp @@ -17,7 +17,9 @@ using edge_t = std::pair; template using sparsevec = std::map; -// using variants = std::variant; +enum series_type { ser_bool, ser_int64, ser_double, ser_string, ser_invalid }; + +using variants = std::variant; class testgraph { using edge_mvmap = mvmap::mvmap; using node_mvmap = mvmap::mvmap; @@ -29,6 +31,15 @@ class testgraph { edge_mvmap edge_table; public: + const mvmap::mvmap &nodemap() + const { + return node_table; + } + + const mvmap::mvmap &edgemap() + const { + return edge_table; + } static inline bool is_edge_selector(const std::string &sel) { return sel.starts_with("edge."); } @@ -217,6 +228,16 @@ class testgraph { return out_neighbors(node).size(); } + std::string str_edge_col(const std::string &col) { + std::vector cols{col}; + return edge_table.str_cols(cols); + } + + std::string str_node_col(const std::string &col) { + std::vector cols{col}; + return node_table.str_cols(cols); + } + }; // class testgraph } // namespace testgraph diff --git a/test/TestGraph/where.cpp b/test/TestGraph/where.cpp index 628b061..be25551 100644 --- a/test/TestGraph/where.cpp +++ b/test/TestGraph/where.cpp @@ -1,50 +1,137 @@ -// Copyright 2021 Lawrence Livermore National Security, LLC and other CLIPPy -// Project Developers. See the top-level COPYRIGHT file for details. -// -// SPDX-License-Identifier: MIT - -#include "clippy/clippy-eval.hpp" -#include #include -#include +#include #include -#include +#include +#include +#include -namespace boostjsn = boost::json; +#include "clippy/clippy-eval.hpp" +#include "clippy/selector.hpp" +#include "testgraph.hpp" -static const std::string method_name = "remove_if"; -static const std::string state_name = "INTERNAL"; +template +auto parse_where_expression(M& mvmap_, boost::json::object& expression, + boost::json::object& submission_data) { + std::cerr << " parse_where_expression: expression: " << expression + << std::endl; + boost::json::object exp2(expression); -int main(int argc, char **argv) { - clippy::clippy clip{method_name, "Removes a string from a TestSet"}; - clip.add_required("expression", "Remove If Expression"); - clip.add_required_state>(state_name, "Internal container"); - clip.returns_self(); - // no object-state requirements in constructor - if (clip.parse(argc, argv)) { - return 0; - } + // boost::json::object submission_data{}; + auto [_a /*unused*/, vars, _b /*unused*/] = + json_logic::translateNode(exp2["rule"]); - auto expression = clip.get("expression"); - auto the_set = clip.get_state>(state_name); + 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; + } - // - // Expression here - auto apply_jl = [&expression](int value) { - boostjsn::object data; - data["value"] = value; - json_logic::ValueExpr res = json_logic::apply(expression["rule"], data); + auto apply_jl = [&expression, vars, &mvmap_, + &submission_data](mvmap::locator loc) { + std::cerr << " apply_jl: # of vars: " << vars.size() << std::endl; + for (const auto& var : vars) { + std::cerr << " apply_jl: var: " << var << std::endl; + auto var_sel = selector(std::string(var)); + std::cerr << " apply_jl: var_sel = " << var_sel << std::endl; + // if (!var_sel.headeq("node")) { + // std::cerr << "selector is not a node selector; skipping." << + // std::endl; continue; + // } + auto var_tail = var_sel.tail().value(); + std::string var_str = std::string(var_sel); + std::cerr << " apply_jl: var: " << var_sel << std::endl; + if (mvmap_.has_series(var_tail)) { + std::cerr << " apply_jl: has series: " << var_sel << std::endl; + auto val = mvmap_.get_as_variant(var_tail, loc); + if (val.has_value()) { + std::cerr << " apply_jl: val has value" << std::endl; + std::visit( + [&submission_data, &loc, &var_str](auto&& v) { + submission_data[var_str] = boost::json::value(v); + std::cerr << " apply_jl: submission_data[" << var_str + << "] = " << v << " at loc " << loc << "." + << std::endl; + }, + *val); + } else { + std::cerr << " apply_jl: no value for " << var_sel << std::endl; + submission_data[var_str] = boost::json::value(); + } + } else { + std::cerr << " apply_jl: no series for " << var_sel << std::endl; + } + } + std::cerr << " apply_jl: submission_data: " << submission_data + << std::endl; + json_logic::ValueExpr res = + json_logic::apply(expression["rule"], submission_data); + std::cerr << " apply_jl: res: " << res << std::endl; return json_logic::unpackValue(res); }; - for (auto first = the_set.begin(), last = the_set.end(); first != last;) { - if (apply_jl(*first)) - first = the_set.erase(first); - else - ++first; - } - - clip.set_state(state_name, the_set); - clip.return_self(); - return 0; + return apply_jl; } + +std::vector where_nodes(const testgraph::testgraph& g, + boost::json::object& expression) { + std::vector filtered_results; + // boost::json::object exp2(expression); + + std::cerr << " where: expression: " << expression << std::endl; + // auto [_a /*unused*/, vars, _b /*unused*/] = + // json_logic::translateNode(exp2["rule"]); + + // auto nodemap = g.nodemap(); + // boost::json::object submission_data{}; + // auto apply_jl = [&expression, &vars, &nodemap, + // &submission_data](testgraph::node_t key) { + // for (const auto& var : vars) { + // auto var_sel = selector(std::string(var)); + // if (!var_sel.headeq("node")) { + // std::cerr << "selector is not a node selector; skipping." << + // std::endl; continue; + // } + // auto var_tail = var_sel.tail().value(); + // std::string var_str = std::string(var_sel); + // std::cerr << " apply_jl: var: " << var_sel << std::endl; + // if (nodemap.has_series(var_tail)) { + // std::cerr << " apply_jl: has series: " << var_sel << std::endl; + // auto val = nodemap.get_as_variant(var_tail, key); + // if (val.has_value()) { + // std::cerr << " apply_jl: val has value" << std::endl; + // std::visit( + // [&submission_data, &key, &var_str](auto&& v) { + // submission_data[var_str] = boost::json::value(v); + // std::cerr << " apply_jl: submission_data[" << var_str + // << "] = " << v << " at key " << key << "." + // << std::endl; + // }, + // val.value()); + // } else { + // std::cerr << " apply_jl: no value for " << var_sel << std::endl; + // submission_data[var_str] = boost::json::value(); + // } + // } else { + // std::cerr << " apply_jl: no series for " << var_sel << std::endl; + // } + // } + + // json_logic::ValueExpr res = + // json_logic::apply(expression["rule"], submission_data); + // std::cerr << " apply_jl: res: " << res << std::endl; + // return json_logic::unpackValue(res); + // }; + + auto nodemap = g.nodemap(); + boost::json::object submission_data; + auto apply_jl = parse_where_expression(nodemap, expression, submission_data); + nodemap.for_all([&filtered_results, &apply_jl, &nodemap, &expression]( + const auto& key, const auto& loc) { + std::cerr << " where for_all key: " << key << std::endl; + if (apply_jl(loc)) { + std::cerr << " where: applied!" << std::endl; + filtered_results.push_back(key); + } + }); + + return filtered_results; +} \ No newline at end of file diff --git a/test/TestSet/remove_if.cpp b/test/TestSet/remove_if.cpp index 628b061..6e322e7 100644 --- a/test/TestSet/remove_if.cpp +++ b/test/TestSet/remove_if.cpp @@ -3,13 +3,14 @@ // // SPDX-License-Identifier: MIT -#include "clippy/clippy-eval.hpp" #include #include #include #include #include +#include "clippy/clippy-eval.hpp" + namespace boostjsn = boost::json; static const std::string method_name = "remove_if"; diff --git a/test/include/mvmap.hpp b/test/include/mvmap.hpp index a3d54e3..2643888 100644 --- a/test/include/mvmap.hpp +++ b/test/include/mvmap.hpp @@ -13,6 +13,12 @@ #include #include +template +std::ostream &operator<<(std::ostream &os, const std::pair &p) { + os << "(" << p.first << ", " << p.second << ")"; + return os; +} + namespace mvmap { using index = uint64_t; @@ -24,6 +30,14 @@ class locator { public: template friend class mvmap; + friend std::ostream &operator<<(std::ostream &os, const locator &l) { + if (l.is_valid()) { + os << "locator: " << l.loc; + } else { + os << "locator: invalid"; + } + return os; + } friend void tag_invoke(boost::json::value_from_tag /*unused*/, boost::json::value &v, locator l); friend locator tag_invoke(boost::json::value_to_tag /*unused*/, @@ -533,31 +547,32 @@ class mvmap { } } - void print_cols(const std::vector &cols) { - for_all([this, &cols](auto key, auto loc) { - std::cout << key << " -> " << loc.loc; + std::string str_cols(const std::vector &cols) { + std::stringstream sstr; + for_all([this, &cols, &sstr](auto key, auto loc) { + sstr << key << "@" << loc.loc; for (auto &col : cols) { auto v = get_as_variant(col, loc); if (v.has_value()) { - std::cout << "at col " << col << ", v has a value\n"; std::visit( - [&col](auto &&arg) { + [&col, &sstr](auto &&arg) { using T = std::decay_t; if constexpr (std::is_same_v) { - std::cout << " (str) " << col << ": " << arg; + sstr << " (str) " << col << ": " << arg; } else if constexpr (std::is_same_v) { - std::cout << " (dbl) " << col << ": " << arg; + sstr << " (dbl) " << col << ": " << arg; } else if constexpr (std::is_same_v) { - std::cout << " (int) " << col << ": " << arg; + sstr << " (int) " << col << ": " << arg; } else if constexpr (std::is_same_v) { - std::cout << " (bool) " << col << ": " << arg; + sstr << " (bool) " << col << ": " << arg; } }, v.value()); } } - std::cout << std::endl; + sstr << std::endl; }); + return sstr.str(); } std::map get_series_vals_at( diff --git a/test/include/wheredev.cpp b/test/include/wheredev.cpp new file mode 100644 index 0000000..aa2fabb --- /dev/null +++ b/test/include/wheredev.cpp @@ -0,0 +1,27 @@ +#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})"; + +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"]); + + for (auto &v : vars) { + std::cout << v << std::endl; + } + + // grab the series proxies for the variables. + // for all rows in the matrix, grab the values from the series proxies + // and evaluate the expression. + // will need to convert the values to JSON values - since we don't know + // the type of the data per series proxy, will need to use visit. Then just + // data[variable] = value +}