21#if !defined(EXPRESSIONIST_ENABLE_JSON)
22#define EXPRESSIONIST_ENABLE_JSON 1
24#if !defined(EXPRESSIONIST_ENABLE_TOML)
25#define EXPRESSIONIST_ENABLE_TOML 0
27#if !EXPRESSIONIST_ENABLE_JSON && !EXPRESSIONIST_ENABLE_TOML
28#error "Expressionist: enable at least one of EXPRESSIONIST_ENABLE_JSON / EXPRESSIONIST_ENABLE_TOML"
33#define EXPRESSIONIST_VERSION_MAJOR 0
34#define EXPRESSIONIST_VERSION_MINOR 2
35#define EXPRESSIONIST_VERSION_PATCH 0
36#define EXPRESSIONIST_STRINGIFY_(x) #x
37#define EXPRESSIONIST_STRINGIFY(x) EXPRESSIONIST_STRINGIFY_(x)
39#define EXPRESSIONIST_VERSION \
40 EXPRESSIONIST_STRINGIFY(EXPRESSIONIST_VERSION_MAJOR) \
41 "." EXPRESSIONIST_STRINGIFY(EXPRESSIONIST_VERSION_MINOR) "." \
42 EXPRESSIONIST_STRINGIFY(EXPRESSIONIST_VERSION_PATCH)
56#include <unordered_set>
60#if EXPRESSIONIST_ENABLE_JSON
61#include <nlohmann/json.hpp>
63#if EXPRESSIONIST_ENABLE_TOML
64#include <toml++/toml.hpp>
67namespace Expressionist {
69#if EXPRESSIONIST_ENABLE_JSON
70using json = nlohmann::json;
74inline constexpr int version_major = EXPRESSIONIST_VERSION_MAJOR;
76inline constexpr int version_minor = EXPRESSIONIST_VERSION_MINOR;
78inline constexpr int version_patch = EXPRESSIONIST_VERSION_PATCH;
80inline constexpr const char *version = EXPRESSIONIST_VERSION;
85enum class EvalMethod {
99 : _message(std::move(message)) {}
100 const char *what()
const noexcept override {
return _message.c_str(); }
103 std::string _message;
117using Scalar = std::variant<std::int64_t, double, bool>;
118using Seq = std::vector<Scalar>;
119using Value = std::variant<std::int64_t, double, bool, Seq>;
121inline bool is_int(
const Value &v) {
122 return std::holds_alternative<std::int64_t>(v);
125inline bool is_bool_value(
const Value &v) {
126 return std::holds_alternative<bool>(v);
129inline bool is_seq(
const Value &v) {
return std::holds_alternative<Seq>(v); }
131inline double to_double(
const Value &v) {
132 if (std::holds_alternative<std::int64_t>(v))
133 return static_cast<double>(std::get<std::int64_t>(v));
134 if (std::holds_alternative<double>(v))
135 return std::get<double>(v);
136 if (std::holds_alternative<bool>(v))
137 return std::get<bool>(v) ? 1.0 : 0.0;
138 throw ExpressionistException(
"a sequence cannot be used as a number");
141inline bool to_bool(
const Value &v) {
142 if (std::holds_alternative<bool>(v))
143 return std::get<bool>(v);
144 if (std::holds_alternative<std::int64_t>(v))
145 return std::get<std::int64_t>(v) != 0;
146 if (std::holds_alternative<double>(v))
147 return std::get<double>(v) != 0.0;
148 throw ExpressionistException(
"a sequence cannot be used as a boolean");
151inline Value to_value(
const Scalar &s) {
152 return std::visit([](
auto x) {
return Value(x); }, s);
155inline Scalar to_scalar(
const Value &v) {
156 if (std::holds_alternative<std::int64_t>(v))
157 return std::get<std::int64_t>(v);
158 if (std::holds_alternative<double>(v))
159 return std::get<double>(v);
160 if (std::holds_alternative<bool>(v))
161 return std::get<bool>(v);
162 throw ExpressionistException(
"nested sequences are not supported");
169using UnaryFn = std::function<double(
double)>;
170using BinaryFn = std::function<double(
double,
double)>;
173 std::map<std::string, double> constants;
174 std::map<std::string, UnaryFn> unary;
175 std::map<std::string, BinaryFn> binary;
178inline Symbols default_symbols() {
182 s.constants[
"pi"] = 3.14159265358979323846;
183 s.constants[
"e"] = 2.71828182845904523536;
184 s.constants[
"tau"] = 6.28318530717958647692;
186 s.unary[
"sin"] = [](
double x) {
return std::sin(x); };
187 s.unary[
"cos"] = [](
double x) {
return std::cos(x); };
188 s.unary[
"tan"] = [](
double x) {
return std::tan(x); };
189 s.unary[
"asin"] = [](
double x) {
return std::asin(x); };
190 s.unary[
"acos"] = [](
double x) {
return std::acos(x); };
191 s.unary[
"atan"] = [](
double x) {
return std::atan(x); };
192 s.unary[
"sinh"] = [](
double x) {
return std::sinh(x); };
193 s.unary[
"cosh"] = [](
double x) {
return std::cosh(x); };
194 s.unary[
"tanh"] = [](
double x) {
return std::tanh(x); };
195 s.unary[
"exp"] = [](
double x) {
return std::exp(x); };
196 s.unary[
"log"] = [](
double x) {
return std::log(x); };
197 s.unary[
"ln"] = [](
double x) {
return std::log(x); };
198 s.unary[
"log10"] = [](
double x) {
return std::log10(x); };
199 s.unary[
"log2"] = [](
double x) {
return std::log2(x); };
200 s.unary[
"sqrt"] = [](
double x) {
return std::sqrt(x); };
201 s.unary[
"cbrt"] = [](
double x) {
return std::cbrt(x); };
202 s.unary[
"abs"] = [](
double x) {
return std::fabs(x); };
203 s.unary[
"floor"] = [](
double x) {
return std::floor(x); };
204 s.unary[
"ceil"] = [](
double x) {
return std::ceil(x); };
205 s.unary[
"round"] = [](
double x) {
return std::round(x); };
206 s.unary[
"trunc"] = [](
double x) {
return std::trunc(x); };
207 s.unary[
"sign"] = [](
double x) {
208 return x > 0.0 ? 1.0 : (x < 0.0 ? -1.0 : 0.0);
211 s.binary[
"pow"] = [](
double a,
double b) {
return std::pow(a, b); };
212 s.binary[
"atan2"] = [](
double a,
double b) {
return std::atan2(a, b); };
213 s.binary[
"hypot"] = [](
double a,
double b) {
return std::hypot(a, b); };
214 s.binary[
"min"] = [](
double a,
double b) {
return std::min(a, b); };
215 s.binary[
"max"] = [](
double a,
double b) {
return std::max(a, b); };
216 s.binary[
"mod"] = [](
double a,
double b) {
return std::fmod(a, b); };
257 explicit Tokenizer(std::string src) : _src(std::move(src)) {}
259 std::vector<Token> tokenize() {
260 std::vector<Token> tokens;
263 if (_i >= _src.size()) {
264 tokens.push_back({TokType::End,
"", Value{}, _i});
269 (c ==
'.' && _i + 1 < _src.size() && is_digit(_src[_i + 1])))
270 tokens.push_back(read_number());
271 else if (std::isalpha(
static_cast<unsigned char>(c)) || c ==
'_')
272 tokens.push_back(read_ident());
274 tokens.push_back(read_op());
280 static bool is_digit(
char c) {
281 return std::isdigit(
static_cast<unsigned char>(c)) != 0;
285 while (_i < _src.size() &&
286 std::isspace(
static_cast<unsigned char>(_src[_i])))
290 Token read_number() {
291 std::size_t start = _i;
292 bool is_float =
false;
293 while (_i < _src.size() && is_digit(_src[_i]))
295 if (_i < _src.size() && _src[_i] ==
'.') {
298 while (_i < _src.size() && is_digit(_src[_i]))
301 if (_i < _src.size() && (_src[_i] ==
'e' || _src[_i] ==
'E')) {
304 if (_i < _src.size() && (_src[_i] ==
'+' || _src[_i] ==
'-'))
306 while (_i < _src.size() && is_digit(_src[_i]))
309 std::string text = _src.substr(start, _i - start);
315 v =
static_cast<std::int64_t
>(std::stoll(text));
316 }
catch (
const std::exception &) {
320 return {TokType::Number, text, v, start};
324 std::size_t start = _i;
327 (std::isalnum(
static_cast<unsigned char>(_src[_i])) || _src[_i] ==
'_'))
329 return {TokType::Ident, _src.substr(start, _i - start), Value{}, start};
333 std::size_t start = _i;
335 auto two = [&](
char a,
char b) {
336 return _i + 1 < _src.size() && _src[_i] == a && _src[_i + 1] == b;
339 return _i += 2, Token{TokType::Le,
"<=", {}, start};
341 return _i += 2, Token{TokType::Ge,
">=", {}, start};
343 return _i += 2, Token{TokType::EqEq,
"==", {}, start};
345 return _i += 2, Token{TokType::NotEq,
"!=", {}, start};
347 return _i += 2, Token{TokType::And,
"&&", {}, start};
349 return _i += 2, Token{TokType::Or,
"||", {}, start};
352 return ++_i, Token{TokType::Plus,
"+", {}, start};
354 return ++_i, Token{TokType::Minus,
"-", {}, start};
356 return ++_i, Token{TokType::Star,
"*", {}, start};
358 return ++_i, Token{TokType::Slash,
"/", {}, start};
360 return ++_i, Token{TokType::Caret,
"^", {}, start};
362 return ++_i, Token{TokType::LParen,
"(", {}, start};
364 return ++_i, Token{TokType::RParen,
")", {}, start};
366 return ++_i, Token{TokType::Comma,
",", {}, start};
368 return ++_i, Token{TokType::Colon,
":", {}, start};
370 return ++_i, Token{TokType::Lt,
"<", {}, start};
372 return ++_i, Token{TokType::Gt,
">", {}, start};
374 return ++_i, Token{TokType::Not,
"!", {}, start};
376 throw ExpressionistException(
"Unexpected character '" +
377 std::string(1, c) +
"' at position " +
378 std::to_string(start));
405 TokType op = TokType::End;
406 std::vector<std::shared_ptr<Node>> children;
409using NodePtr = std::shared_ptr<Node>;
413 Parser(std::vector<Token> tokens, std::string src)
414 : _tokens(std::move(tokens)), _src(std::move(src)) {}
417 NodePtr n = parse_range();
418 if (!check(TokType::End))
419 throw error(
"unexpected trailing token");
424 const Token &peek()
const {
return _tokens[_pos]; }
425 const Token &advance() {
return _tokens[_pos++]; }
426 bool check(TokType t)
const {
return peek().type == t; }
427 bool match(TokType t) {
435 ExpressionistException error(
const std::string &why)
const {
436 return ExpressionistException(
"Parse error in \"" + _src +
"\": " + why +
437 " ('" + peek().text +
"' at position " +
438 std::to_string(peek().pos) +
")");
441 static NodePtr make_binary(TokType op, NodePtr l, NodePtr r) {
442 auto n = std::make_shared<Node>();
443 n->kind = NodeKind::Binary;
445 n->children = {std::move(l), std::move(r)};
452 NodePtr parse_range() {
453 NodePtr first = parse_or();
454 if (!check(TokType::Colon))
456 auto node = std::make_shared<Node>();
457 node->kind = NodeKind::Range;
458 node->children.push_back(first);
459 while (match(TokType::Colon))
460 node->children.push_back(parse_or());
461 if (node->children.size() > 3)
462 throw error(
"a range has at most three parts (start:stop:step)");
467 NodePtr n = parse_and();
468 while (check(TokType::Or)) {
470 n = make_binary(TokType::Or, n, parse_and());
475 NodePtr parse_and() {
476 NodePtr n = parse_equality();
477 while (check(TokType::And)) {
479 n = make_binary(TokType::And, n, parse_equality());
484 NodePtr parse_equality() {
485 NodePtr n = parse_comparison();
486 while (check(TokType::EqEq) || check(TokType::NotEq)) {
487 TokType op = advance().type;
488 n = make_binary(op, n, parse_comparison());
493 NodePtr parse_comparison() {
494 NodePtr n = parse_additive();
495 while (check(TokType::Lt) || check(TokType::Le) || check(TokType::Gt) ||
496 check(TokType::Ge)) {
497 TokType op = advance().type;
498 n = make_binary(op, n, parse_additive());
503 NodePtr parse_additive() {
504 NodePtr n = parse_multiplicative();
505 while (check(TokType::Plus) || check(TokType::Minus)) {
506 TokType op = advance().type;
507 n = make_binary(op, n, parse_multiplicative());
512 NodePtr parse_multiplicative() {
513 NodePtr n = parse_unary();
514 while (check(TokType::Star) || check(TokType::Slash)) {
515 TokType op = advance().type;
516 n = make_binary(op, n, parse_unary());
521 NodePtr parse_unary() {
522 if (check(TokType::Minus) || check(TokType::Not)) {
523 TokType op = advance().type;
524 auto n = std::make_shared<Node>();
525 n->kind = NodeKind::Unary;
527 n->children = {parse_unary()};
530 return parse_power();
535 NodePtr parse_power() {
536 NodePtr base = parse_primary();
537 if (check(TokType::Caret)) {
539 return make_binary(TokType::Caret, base, parse_unary());
544 NodePtr parse_primary() {
545 const Token &t = peek();
546 if (t.type == TokType::Number) {
548 auto n = std::make_shared<Node>();
549 n->kind = is_int(t.value) ? NodeKind::IntLit : NodeKind::FloatLit;
553 if (t.type == TokType::LParen) {
555 NodePtr n = parse_or();
556 if (!match(TokType::RParen))
557 throw error(
"expected ')'");
560 if (t.type == TokType::Ident) {
562 if (t.text ==
"true" || t.text ==
"false") {
563 auto n = std::make_shared<Node>();
564 n->kind = NodeKind::BoolLit;
565 n->value = (t.text ==
"true");
568 if (check(TokType::LParen)) {
570 auto n = std::make_shared<Node>();
571 n->kind = NodeKind::Call;
573 if (!check(TokType::RParen)) {
574 n->children.push_back(parse_or());
575 while (match(TokType::Comma))
576 n->children.push_back(parse_or());
578 if (!match(TokType::RParen))
579 throw error(
"expected ')' in call to '" + t.text +
"'");
582 auto n = std::make_shared<Node>();
583 n->kind = NodeKind::Ident;
587 throw error(
"unexpected token");
590 std::vector<Token> _tokens;
592 std::size_t _pos = 0;
597inline void collect_idents(
const NodePtr &n, std::vector<std::string> &out) {
600 if (n->kind == NodeKind::Ident)
601 out.push_back(n->name);
602 for (
const auto &c : n->children)
603 collect_idents(c, out);
606using Resolver = std::function<std::optional<Value>(
const std::string &)>;
608inline Value num_binary(
const Value &l,
const Value &r, TokType op) {
609 if (op == TokType::Slash)
610 return to_double(l) / to_double(r);
611 if (op == TokType::Caret) {
612 if (is_int(l) && is_int(r) && std::get<std::int64_t>(r) >= 0) {
613 std::int64_t base = std::get<std::int64_t>(l);
614 std::int64_t exp = std::get<std::int64_t>(r);
615 std::int64_t result = 1;
616 for (std::int64_t k = 0; k < exp; ++k)
620 return std::pow(to_double(l), to_double(r));
622 if (is_int(l) && is_int(r)) {
623 std::int64_t a = std::get<std::int64_t>(l);
624 std::int64_t b = std::get<std::int64_t>(r);
636 double a = to_double(l);
637 double b = to_double(r);
651inline Value scalar_binary(
const Value &l,
const Value &r, TokType op) {
658 return num_binary(l, r, op);
660 return to_double(l) < to_double(r);
662 return to_double(l) <= to_double(r);
664 return to_double(l) > to_double(r);
666 return to_double(l) >= to_double(r);
668 if (is_bool_value(l) && is_bool_value(r))
669 return std::get<bool>(l) == std::get<bool>(r);
670 return to_double(l) == to_double(r);
672 if (is_bool_value(l) && is_bool_value(r))
673 return std::get<bool>(l) != std::get<bool>(r);
674 return to_double(l) != to_double(r);
676 throw ExpressionistException(
"Internal error: bad binary operator");
680inline const char *op_text(TokType op) {
717Value broadcast(
const Value &l,
const Value &r, F f,
const std::string &what) {
718 bool ls = is_seq(l), rs = is_seq(r);
723 const Seq &a = std::get<Seq>(l);
724 const Seq &b = std::get<Seq>(r);
725 if (a.size() != b.size())
726 throw ExpressionistException(
"Size mismatch in element-wise " + what +
727 ": " + std::to_string(a.size()) +
" vs " +
728 std::to_string(b.size()));
729 out.reserve(a.size());
730 for (std::size_t i = 0; i < a.size(); ++i)
731 out.push_back(to_scalar(f(to_value(a[i]), to_value(b[i]))));
733 const Seq &a = std::get<Seq>(l);
734 out.reserve(a.size());
735 for (
const Scalar &x : a)
736 out.push_back(to_scalar(f(to_value(x), r)));
738 const Seq &b = std::get<Seq>(r);
739 out.reserve(b.size());
740 for (
const Scalar &x : b)
741 out.push_back(to_scalar(f(l, to_value(x))));
743 return Value(std::move(out));
748template <
typename F> Value map_unary(
const Value &v, F f) {
751 const Seq &a = std::get<Seq>(v);
753 out.reserve(a.size());
754 for (
const Scalar &x : a)
755 out.push_back(to_scalar(f(to_value(x))));
756 return Value(std::move(out));
759inline Value eval_node(
const NodePtr &n,
const Symbols &sym,
760 const Resolver &resolve) {
762 case NodeKind::IntLit:
763 case NodeKind::FloatLit:
764 case NodeKind::BoolLit:
766 case NodeKind::Ident: {
767 if (
auto v = resolve(n->name))
769 auto it = sym.constants.find(n->name);
770 if (it != sym.constants.end())
772 throw ExpressionistException(
"Undefined variable or constant: '" + n->name +
775 case NodeKind::Unary: {
776 Value c = eval_node(n->children[0], sym, resolve);
777 if (n->op == TokType::Not)
778 return map_unary(c, [](
const Value &x) {
return Value(!to_bool(x)); });
779 return map_unary(c, [](
const Value &x) {
781 return Value(-std::get<std::int64_t>(x));
782 return Value(-to_double(x));
785 case NodeKind::Binary: {
786 if (n->op == TokType::And || n->op == TokType::Or) {
787 bool is_and = n->op == TokType::And;
788 Value l = eval_node(n->children[0], sym, resolve);
791 if (!is_seq(l) && to_bool(l) != is_and)
793 Value r = eval_node(n->children[1], sym, resolve);
796 [is_and](
const Value &a,
const Value &b) {
797 return Value(is_and ? (to_bool(a) && to_bool(b))
798 : (to_bool(a) || to_bool(b)));
802 Value l = eval_node(n->children[0], sym, resolve);
803 Value r = eval_node(n->children[1], sym, resolve);
807 [op](
const Value &a,
const Value &b) {
return scalar_binary(a, b, op); },
810 case NodeKind::Range: {
811 if (n->children.size() == 2) {
812 Value a = eval_node(n->children[0], sym, resolve);
813 Value b = eval_node(n->children[1], sym, resolve);
814 if (!is_int(a) || !is_int(b))
815 throw ExpressionistException(
816 "Binary range 'start:stop' requires integer bounds");
817 std::int64_t start = std::get<std::int64_t>(a);
818 std::int64_t stop = std::get<std::int64_t>(b);
820 for (std::int64_t v = start; v <= stop; ++v)
822 return Value(Seq(std::move(arr)));
824 if (n->children.size() == 3) {
825 double start = to_double(eval_node(n->children[0], sym, resolve));
826 double stop = to_double(eval_node(n->children[1], sym, resolve));
827 double step = to_double(eval_node(n->children[2], sym, resolve));
829 throw ExpressionistException(
"Range step must be non-zero");
831 double span = stop - start;
836 if (span == 0.0 || (span > 0.0) == (step > 0.0)) {
838 static_cast<std::int64_t
>(std::floor(span / step + 1e-9));
839 for (std::int64_t i = 0; i <= count; ++i)
840 arr.push_back(start +
static_cast<double>(i) * step);
842 return Value(Seq(std::move(arr)));
844 throw ExpressionistException(
845 "A range must have two (start:stop) or three (start:stop:step) parts");
847 case NodeKind::Call: {
848 const std::string &fn = n->name;
849 auto u = sym.unary.find(fn);
850 if (u != sym.unary.end()) {
851 if (n->children.size() != 1)
852 throw ExpressionistException(
"Function '" + fn +
853 "' expects 1 argument, got " +
854 std::to_string(n->children.size()));
855 const UnaryFn &f = u->second;
856 return map_unary(eval_node(n->children[0], sym, resolve),
857 [&f](
const Value &x) { return Value(f(to_double(x))); });
859 auto b = sym.binary.find(fn);
860 if (b != sym.binary.end()) {
861 if (n->children.size() != 2)
862 throw ExpressionistException(
"Function '" + fn +
863 "' expects 2 arguments, got " +
864 std::to_string(n->children.size()));
865 const BinaryFn &f = b->second;
866 Value x = eval_node(n->children[0], sym, resolve);
867 Value y = eval_node(n->children[1], sym, resolve);
870 [&f](
const Value &a,
const Value &c) {
871 return Value(f(to_double(a), to_double(c)));
873 "call to '" + fn +
"'");
875 throw ExpressionistException(
"Unknown function: '" + fn +
"'");
878 throw ExpressionistException(
"Internal error: bad node");
896 virtual ~DocumentModel() =
default;
897 virtual NodeRef root() = 0;
898 virtual bool is_object(NodeRef node)
const = 0;
899 virtual bool is_array(NodeRef node)
const = 0;
901 for_each_member(NodeRef node,
902 const std::function<
void(
const std::string &, NodeRef)> &fn) = 0;
904 for_each_element(NodeRef node,
905 const std::function<
void(std::size_t, NodeRef)> &fn) = 0;
908 virtual std::optional<std::string> as_string(NodeRef node)
const = 0;
912 virtual std::optional<Value> as_scalar(NodeRef node)
const = 0;
914 virtual void assign(NodeRef node,
const Value &v) = 0;
917 virtual bool has_false_member(NodeRef
object,
const std::string &key)
const = 0;
920#if EXPRESSIONIST_ENABLE_JSON
922class JsonModel :
public DocumentModel {
924 explicit JsonModel(json &root) : _root(root) {}
926 NodeRef root()
override {
return NodeRef{&_root}; }
928 bool is_object(NodeRef node)
const override {
929 return as_json(node).is_object();
931 bool is_array(NodeRef node)
const override {
932 return as_json(node).is_array();
935 void for_each_member(
937 const std::function<
void(
const std::string &, NodeRef)> &fn)
override {
938 json &obj = as_json(node);
939 for (
auto it = obj.begin(); it != obj.end(); ++it)
940 fn(it.key(), NodeRef{&it.value()});
943 void for_each_element(
945 const std::function<
void(std::size_t, NodeRef)> &fn)
override {
946 json &arr = as_json(node);
947 for (std::size_t i = 0; i < arr.size(); ++i)
948 fn(i, NodeRef{&arr[i]});
951 std::optional<std::string> as_string(NodeRef node)
const override {
952 const json &v = as_json(node);
955 return v.get<std::string>();
958 std::optional<Value> as_scalar(NodeRef node)
const override {
959 const json &v = as_json(node);
960 if (v.is_number_integer())
961 return Value(
static_cast<std::int64_t
>(v.get<std::int64_t>()));
962 if (v.is_number_float())
963 return Value(v.get<
double>());
965 return Value(v.get<
bool>());
969 void assign(NodeRef node,
const Value &v)
override {
970 json &slot = as_json(node);
971 if (std::holds_alternative<std::int64_t>(v)) {
972 slot = json(std::get<std::int64_t>(v));
973 }
else if (std::holds_alternative<double>(v)) {
974 slot = json(std::get<double>(v));
975 }
else if (std::holds_alternative<bool>(v)) {
976 slot = json(std::get<bool>(v));
978 json arr = json::array();
979 for (
const Scalar &x : std::get<Seq>(v))
980 std::visit([&arr](auto e) { arr.push_back(e); }, x);
981 slot = std::move(arr);
985 bool has_false_member(NodeRef
object,
const std::string &key)
const override {
986 const json &obj = as_json(
object);
987 auto it = obj.find(key);
988 return it != obj.end() && *it ==
false;
992 static json &as_json(NodeRef node) {
return *
static_cast<json *
>(node.p); }
999#if EXPRESSIONIST_ENABLE_TOML
1001class TomlModel :
public DocumentModel {
1003 explicit TomlModel(toml::table &root) {
1004 _slots.push_back(Slot{&root,
nullptr, std::string(), 0,
false});
1007 NodeRef root()
override {
return NodeRef{&_slots.front()}; }
1009 bool is_object(NodeRef node)
const override {
1010 return slot(node).self->is_table();
1012 bool is_array(NodeRef node)
const override {
1013 return slot(node).self->is_array();
1016 void for_each_member(
1018 const std::function<
void(
const std::string &, NodeRef)> &fn)
override {
1019 toml::table *t = slot(node).self->as_table();
1020 for (
auto &&[key, val] : *t) {
1021 std::string name(key.str());
1022 _slots.push_back(Slot{&val, slot(node).self, name, 0,
false});
1023 fn(name, NodeRef{&_slots.back()});
1027 void for_each_element(
1029 const std::function<
void(std::size_t, NodeRef)> &fn)
override {
1030 toml::array *a = slot(node).self->as_array();
1032 for (
auto &&val : *a) {
1033 _slots.push_back(Slot{&val, slot(node).self, std::string(), i,
true});
1034 fn(i, NodeRef{&_slots.back()});
1039 std::optional<std::string> as_string(NodeRef node)
const override {
1040 if (
auto *v = slot(node).self->as_string())
1042 return std::nullopt;
1045 std::optional<Value> as_scalar(NodeRef node)
const override {
1046 const toml::node *n = slot(node).self;
1047 if (
auto *v = n->as_integer())
1048 return Value(v->get());
1049 if (
auto *v = n->as_floating_point())
1050 return Value(v->get());
1051 if (
auto *v = n->as_boolean())
1052 return Value(v->get());
1053 return std::nullopt;
1056 void assign(NodeRef node,
const Value &v)
override {
1057 Slot &s = slot(node);
1058 if (s.parent_is_array) {
1059 toml::array *arr = s.parent->as_array();
1060 auto pos = arr->cbegin() +
static_cast<std::ptrdiff_t
>(s.index);
1061 with_native(v, [&](
auto &&native) {
1062 auto it = arr->replace(pos, std::forward<
decltype(native)>(native));
1066 toml::table *tbl = s.parent->as_table();
1067 with_native(v, [&](
auto &&native) {
1068 tbl->insert_or_assign(s.key, std::forward<
decltype(native)>(native));
1070 s.self = tbl->get(s.key);
1074 bool has_false_member(NodeRef
object,
const std::string &key)
const override {
1075 const toml::table *t = slot(
object).self->as_table();
1078 const toml::node *member = t->get(key);
1081 const auto *b = member->as_boolean();
1082 return b && b->get() ==
false;
1094 bool parent_is_array;
1097 Slot &slot(NodeRef node) {
return *
static_cast<Slot *
>(node.p); }
1098 const Slot &slot(NodeRef node)
const {
1099 return *
static_cast<const Slot *
>(node.p);
1102 template <
typename Setter>
static void with_native(
const Value &v, Setter &&set) {
1103 if (std::holds_alternative<std::int64_t>(v)) {
1104 set(std::get<std::int64_t>(v));
1105 }
else if (std::holds_alternative<double>(v)) {
1106 set(std::get<double>(v));
1107 }
else if (std::holds_alternative<bool>(v)) {
1108 set(std::get<bool>(v));
1111 for (
const Scalar &x : std::get<Seq>(v))
1112 std::visit([&arr](auto e) { arr.push_back(e); }, x);
1113 set(std::move(arr));
1117 std::deque<Slot> _slots;
1129 Evaluator(
const Symbols &sym, std::string tag, std::string disableKey,
1131 : _sym(sym), _tag(std::move(tag)), _disableKey(std::move(disableKey)),
1134 void run(DocumentModel &doc) {
1138 walk(doc.root(),
nullptr,
"");
1139 if (_method == EvalMethod::GRAPH)
1143 for (
const Cell &c : _cells)
1145 _doc->assign(c.node, c.result);
1150 Scope *parent =
nullptr;
1151 std::map<std::string, std::size_t> keys;
1156 Scope *scope =
nullptr;
1159 bool is_expr =
false;
1167 bool is_array =
false;
1168 bool numeric_array =
true;
1169 std::vector<std::size_t> elements;
1172 bool starts_with_tag(
const std::string &s)
const {
1173 return !_tag.empty() && s.rfind(_tag, 0) == 0;
1181 bool is_disabled(NodeRef
object)
const {
1182 return !_disableKey.empty() && _doc->has_false_member(
object, _disableKey);
1185 std::size_t add_cell(NodeRef node, Scope *scope,
const std::string &path) {
1186 std::size_t idx = _cells.size();
1187 _cells.emplace_back();
1188 Cell &cell = _cells.back();
1192 cell.name = last_component(path);
1193 if (
auto s = _doc->as_string(node)) {
1194 if (starts_with_tag(*s)) {
1195 cell.is_expr =
true;
1196 cell.source = s->substr(_tag.size());
1198 Tokenizer tok(cell.source);
1199 Parser parser(tok.tokenize(), cell.source);
1200 cell.ast = parser.parse();
1201 }
catch (
const ExpressionistException &e) {
1202 throw ExpressionistException(cell_context(cell) + e.what());
1211 void walk(NodeRef node, Scope *enclosing,
const std::string &path,
1212 std::optional<std::size_t> owner = std::nullopt) {
1213 if (_doc->is_object(node)) {
1214 if (is_disabled(node))
1216 _scopes.emplace_back();
1217 Scope *scope = &_scopes.back();
1218 scope->parent = enclosing;
1219 std::vector<std::pair<std::string, NodeRef>> members;
1220 _doc->for_each_member(node, [&](
const std::string &key, NodeRef child) {
1221 members.emplace_back(key, child);
1225 for (
auto &[key, child] : members)
1226 scope->keys[key] = add_cell(child, scope, join(path, key));
1228 for (
auto &[key, child] : members)
1229 if (_doc->is_object(child) || _doc->is_array(child))
1230 walk(child, scope, join(path, key), scope->keys[key]);
1231 }
else if (_doc->is_array(node)) {
1234 _cells[*owner].is_array =
true;
1235 std::vector<NodeRef> elements;
1236 _doc->for_each_element(
1237 node, [&](std::size_t, NodeRef child) { elements.push_back(child); });
1238 for (std::size_t i = 0; i < elements.size(); ++i) {
1239 std::string p = path +
"/" + std::to_string(i);
1240 NodeRef child = elements[i];
1241 if (_doc->is_object(child) || _doc->is_array(child)) {
1243 _cells[*owner].numeric_array =
false;
1244 walk(child, enclosing, p);
1246 std::size_t idx = add_cell(child, enclosing, p);
1248 _cells[*owner].elements.push_back(idx);
1255 std::size_t resolve_name(
const std::string &name, Scope *scope,
1256 bool &found)
const {
1257 for (Scope *s = scope; s !=
nullptr; s = s->parent) {
1258 auto it = s->keys.find(name);
1259 if (it != s->keys.end()) {
1268 Value literal_value(
const Cell &c)
const {
1269 if (
auto v = _doc->as_scalar(c.node))
1271 throw ExpressionistException(
"variable '" + c.name +
"' is not numeric");
1276 template <
typename Getter>
1277 Value array_value(
const Cell &c, Getter &&expr_value)
const {
1278 auto not_numeric = [&c]() {
1279 return ExpressionistException(
"variable '" + c.name +
1280 "' is not a numeric array");
1282 if (!c.numeric_array)
1283 throw not_numeric();
1285 out.reserve(c.elements.size());
1286 for (std::size_t i : c.elements) {
1288 if (_cells[i].is_expr) {
1290 }
else if (
auto s = _doc->as_scalar(_cells[i].node)) {
1293 throw not_numeric();
1296 throw not_numeric();
1297 out.push_back(to_scalar(v));
1299 return Value(std::move(out));
1302 std::string cell_context(
const Cell &c)
const {
1303 return "In '" + c.path +
"' (\"" + _tag + c.source +
"\"): ";
1309 rethrow_with_context(
const Cell &c,
const ExpressionistException &e)
const {
1310 std::string msg = e.what();
1311 if (msg.rfind(
"In '", 0) == 0)
1313 throw ExpressionistException(cell_context(c) + msg);
1318 void eval_recursive() {
1319 std::vector<std::size_t> stack;
1320 for (std::size_t i = 0; i < _cells.size(); ++i)
1321 if (_cells[i].is_expr)
1322 eval_cell(i, stack);
1325 Value eval_cell(std::size_t idx, std::vector<std::size_t> &stack) {
1326 Cell &cell = _cells[idx];
1327 if (!cell.is_expr && cell.is_array)
1328 return array_value(cell,
1329 [&](std::size_t i) {
return eval_cell(i, stack); });
1331 return literal_value(cell);
1332 if (cell.state == 2)
1334 if (cell.state == 1)
1335 throw ExpressionistException(
"Circular dependency: " +
1336 cycle_path(stack, idx));
1338 stack.push_back(idx);
1339 Scope *scope = cell.scope;
1340 Resolver resolver = [&](
const std::string &name) -> std::optional<Value> {
1342 std::size_t tgt = resolve_name(name, scope, found);
1344 return std::nullopt;
1345 return eval_cell(tgt, stack);
1349 v = eval_node(cell.ast, _sym, resolver);
1350 }
catch (
const ExpressionistException &e) {
1351 rethrow_with_context(cell, e);
1359 std::string cycle_path(
const std::vector<std::size_t> &stack,
1360 std::size_t idx)
const {
1361 std::size_t start = 0;
1362 for (std::size_t k = 0; k < stack.size(); ++k)
1363 if (stack[k] == idx) {
1368 for (std::size_t k = start; k < stack.size(); ++k)
1369 m += _cells[stack[k]].name +
" -> ";
1370 m += _cells[idx].name;
1377 std::size_t n = _cells.size();
1378 std::vector<std::vector<std::size_t>> dependents(n);
1379 std::vector<int> indeg(n, 0);
1380 std::vector<std::size_t> expr_cells;
1381 for (std::size_t i = 0; i < n; ++i) {
1382 if (!_cells[i].is_expr)
1384 expr_cells.push_back(i);
1385 std::vector<std::string> idents;
1386 collect_idents(_cells[i].ast, idents);
1387 std::unordered_set<std::size_t> seen;
1388 for (
const std::string &name : idents) {
1390 std::size_t tgt = resolve_name(name, _cells[i].scope, found);
1394 std::vector<std::size_t> deps;
1395 if (_cells[tgt].is_array)
1396 deps = _cells[tgt].elements;
1398 deps.push_back(tgt);
1399 for (std::size_t d : deps)
1400 if (_cells[d].is_expr && seen.insert(d).second) {
1401 dependents[d].push_back(i);
1406 std::queue<std::size_t> q;
1407 for (std::size_t i : expr_cells)
1410 std::size_t processed = 0;
1411 while (!q.empty()) {
1412 std::size_t i = q.front();
1416 for (std::size_t d : dependents[i])
1417 if (--indeg[d] == 0)
1420 if (processed != expr_cells.size()) {
1422 for (std::size_t i : expr_cells)
1423 if (_cells[i].state != 2)
1424 names += _cells[i].name +
" ";
1425 throw ExpressionistException(
"Circular dependency detected among: " +
1430 void eval_expr_graph(std::size_t idx) {
1431 Cell &cell = _cells[idx];
1432 Scope *scope = cell.scope;
1433 Resolver resolver = [&](
const std::string &name) -> std::optional<Value> {
1435 std::size_t tgt = resolve_name(name, scope, found);
1437 return std::nullopt;
1438 const Cell &t = _cells[tgt];
1443 t, [
this](std::size_t i) {
return _cells[i].result; });
1444 return literal_value(t);
1447 cell.result = eval_node(cell.ast, _sym, resolver);
1448 }
catch (
const ExpressionistException &e) {
1449 rethrow_with_context(cell, e);
1454 static std::string join(
const std::string &path,
const std::string &key) {
1455 return path +
"/" + key;
1458 static std::string last_component(
const std::string &path) {
1459 std::size_t p = path.find_last_of(
'/');
1460 return p == std::string::npos ? path : path.substr(p + 1);
1463 const Symbols &_sym;
1465 std::string _disableKey;
1467 DocumentModel *_doc =
nullptr;
1468 std::deque<Scope> _scopes;
1469 std::vector<Cell> _cells;
1512#if EXPRESSIONIST_ENABLE_JSON
1515 : _object(std::move(o)), _evalMethod(method),
1516 _symbols(detail::default_symbols()) {}
1520 : _evalMethod(method), _symbols(detail::default_symbols()) {
1522 _object = json::parse(s);
1523 }
catch (
const std::exception &e) {
1525 std::string(e.what()));
1535#if EXPRESSIONIST_ENABLE_TOML
1538 explicit Expressionist(toml::table t, EvalMethod method = EvalMethod::RECURSIVE)
1539 : _toml(std::move(t)), _evalMethod(method),
1540 _symbols(detail::default_symbols()) {}
1543 static Expressionist parse_toml(
const std::string &s,
1544 EvalMethod method = EvalMethod::RECURSIVE) {
1546 toml::table t = toml::parse(s);
1547 return Expressionist(std::move(t), method);
1548 }
catch (
const std::exception &e) {
1549 throw ExpressionistException(
"Failed to parse TOML: " +
1550 std::string(e.what()));
1557 Expressionist(EvalMethod method = EvalMethod::RECURSIVE)
1558 : _evalMethod(method), _symbols(detail::default_symbols()) {}
1562#if EXPRESSIONIST_ENABLE_JSON
1568 detail::JsonModel model(_object);
1569 detail::Evaluator ev(_symbols, _tag, _disableKey, _evalMethod);
1584 detail::JsonModel model(
object);
1585 detail::Evaluator ev(_symbols, _tag, _disableKey, _evalMethod);
1596 json copy = _object;
1597 detail::JsonModel model(copy);
1598 detail::Evaluator ev(_symbols, _tag, _disableKey, _evalMethod);
1607 detail::JsonModel model(copy);
1608 detail::Evaluator ev(_symbols, _tag, _disableKey, _evalMethod);
1614#if EXPRESSIONIST_ENABLE_TOML
1617 void evaluate(toml::table &
object)
const {
1618 detail::TomlModel model(
object);
1619 detail::Evaluator ev(_symbols, _tag, _disableKey, _evalMethod);
1625 toml::table produce(toml::table
object)
const {
1626 toml::table copy = object;
1627 detail::TomlModel model(copy);
1628 detail::Evaluator ev(_symbols, _tag, _disableKey, _evalMethod);
1645 void setTag(
const std::string &tag) { _tag = tag; }
1647 std::string
tag()
const {
return _tag; }
1660#if EXPRESSIONIST_ENABLE_JSON
1662 const json &
object()
const {
return _object; }
1664#if EXPRESSIONIST_ENABLE_TOML
1666 const toml::table &as_toml()
const {
return _toml; }
1672 _symbols.constants[name] = value;
1679 _symbols.unary[name] = std::move(fn);
1687 _symbols.binary[name] = std::move(fn);
1691#if EXPRESSIONIST_ENABLE_JSON
1692 json _object = json::object();
1694#if EXPRESSIONIST_ENABLE_TOML
1697 EvalMethod _evalMethod = EvalMethod::RECURSIVE;
1698 std::string _tag =
"$";
1699 std::string _disableKey =
"expressionist";
1700 detail::Symbols _symbols;
Definition expressionist.hpp:96
Definition expressionist.hpp:1510
void setTag(const std::string &tag)
Definition expressionist.hpp:1645
void addUnaryFunction(const std::string &name, detail::UnaryFn fn)
Definition expressionist.hpp:1678
EvalMethod getEvalMethod() const
The currently selected resolution strategy.
Definition expressionist.hpp:1638
Expressionist(json o, EvalMethod method=EvalMethod::RECURSIVE)
Construct from a JSON object, to be mutated/copied by evaluate()/produce().
Definition expressionist.hpp:1514
void evaluate(json &object) const
Definition expressionist.hpp:1583
std::string tag() const
The currently configured expression tag.
Definition expressionist.hpp:1647
Expressionist(std::string s, EvalMethod method=EvalMethod::RECURSIVE)
Definition expressionist.hpp:1519
json produce(json object) const
Definition expressionist.hpp:1605
std::string disableKey() const
The currently configured disable key.
Definition expressionist.hpp:1658
void setEvalMethod(EvalMethod method)
Definition expressionist.hpp:1636
void addConstant(const std::string &name, double value)
Definition expressionist.hpp:1671
const json & object() const
Access the (possibly evaluated) stored object.
Definition expressionist.hpp:1662
json produce() const
Definition expressionist.hpp:1595
Expressionist(const char *s, EvalMethod method=EvalMethod::RECURSIVE)
Same as the std::string overload; disambiguates string-literal calls.
Definition expressionist.hpp:1532
void evaluate()
Definition expressionist.hpp:1567
void setDisableKey(const std::string &key)
Definition expressionist.hpp:1656
void addBinaryFunction(const std::string &name, detail::BinaryFn fn)
Definition expressionist.hpp:1686