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"
43#include <unordered_set>
47#if EXPRESSIONIST_ENABLE_JSON
48#include <nlohmann/json.hpp>
50#if EXPRESSIONIST_ENABLE_TOML
51#include <toml++/toml.hpp>
54namespace Expressionist {
56#if EXPRESSIONIST_ENABLE_JSON
57using json = nlohmann::json;
63enum class EvalMethod {
77 : _message(std::move(message)) {}
78 const char *what()
const noexcept override {
return _message.c_str(); }
93using Seq = std::variant<std::vector<std::int64_t>, std::vector<double>>;
94using Value = std::variant<std::int64_t, double, bool, Seq>;
96inline bool is_int(
const Value &v) {
97 return std::holds_alternative<std::int64_t>(v);
100inline bool is_bool_value(
const Value &v) {
101 return std::holds_alternative<bool>(v);
104inline bool is_seq(
const Value &v) {
return std::holds_alternative<Seq>(v); }
106inline double to_double(
const Value &v) {
107 if (std::holds_alternative<std::int64_t>(v))
108 return static_cast<double>(std::get<std::int64_t>(v));
109 if (std::holds_alternative<double>(v))
110 return std::get<double>(v);
111 if (std::holds_alternative<bool>(v))
112 return std::get<bool>(v) ? 1.0 : 0.0;
113 throw ExpressionistException(
"a sequence cannot be used as a number");
116inline bool to_bool(
const Value &v) {
117 if (std::holds_alternative<bool>(v))
118 return std::get<bool>(v);
119 if (std::holds_alternative<std::int64_t>(v))
120 return std::get<std::int64_t>(v) != 0;
121 if (std::holds_alternative<double>(v))
122 return std::get<double>(v) != 0.0;
123 throw ExpressionistException(
"a sequence cannot be used as a boolean");
130using UnaryFn = std::function<double(
double)>;
131using BinaryFn = std::function<double(
double,
double)>;
134 std::map<std::string, double> constants;
135 std::map<std::string, UnaryFn> unary;
136 std::map<std::string, BinaryFn> binary;
139inline Symbols default_symbols() {
143 s.constants[
"pi"] = 3.14159265358979323846;
144 s.constants[
"e"] = 2.71828182845904523536;
145 s.constants[
"tau"] = 6.28318530717958647692;
147 s.unary[
"sin"] = [](
double x) {
return std::sin(x); };
148 s.unary[
"cos"] = [](
double x) {
return std::cos(x); };
149 s.unary[
"tan"] = [](
double x) {
return std::tan(x); };
150 s.unary[
"asin"] = [](
double x) {
return std::asin(x); };
151 s.unary[
"acos"] = [](
double x) {
return std::acos(x); };
152 s.unary[
"atan"] = [](
double x) {
return std::atan(x); };
153 s.unary[
"sinh"] = [](
double x) {
return std::sinh(x); };
154 s.unary[
"cosh"] = [](
double x) {
return std::cosh(x); };
155 s.unary[
"tanh"] = [](
double x) {
return std::tanh(x); };
156 s.unary[
"exp"] = [](
double x) {
return std::exp(x); };
157 s.unary[
"log"] = [](
double x) {
return std::log(x); };
158 s.unary[
"ln"] = [](
double x) {
return std::log(x); };
159 s.unary[
"log10"] = [](
double x) {
return std::log10(x); };
160 s.unary[
"log2"] = [](
double x) {
return std::log2(x); };
161 s.unary[
"sqrt"] = [](
double x) {
return std::sqrt(x); };
162 s.unary[
"cbrt"] = [](
double x) {
return std::cbrt(x); };
163 s.unary[
"abs"] = [](
double x) {
return std::fabs(x); };
164 s.unary[
"floor"] = [](
double x) {
return std::floor(x); };
165 s.unary[
"ceil"] = [](
double x) {
return std::ceil(x); };
166 s.unary[
"round"] = [](
double x) {
return std::round(x); };
167 s.unary[
"trunc"] = [](
double x) {
return std::trunc(x); };
168 s.unary[
"sign"] = [](
double x) {
169 return x > 0.0 ? 1.0 : (x < 0.0 ? -1.0 : 0.0);
172 s.binary[
"pow"] = [](
double a,
double b) {
return std::pow(a, b); };
173 s.binary[
"atan2"] = [](
double a,
double b) {
return std::atan2(a, b); };
174 s.binary[
"hypot"] = [](
double a,
double b) {
return std::hypot(a, b); };
175 s.binary[
"min"] = [](
double a,
double b) {
return std::min(a, b); };
176 s.binary[
"max"] = [](
double a,
double b) {
return std::max(a, b); };
177 s.binary[
"mod"] = [](
double a,
double b) {
return std::fmod(a, b); };
218 explicit Tokenizer(std::string src) : _src(std::move(src)) {}
220 std::vector<Token> tokenize() {
221 std::vector<Token> tokens;
224 if (_i >= _src.size()) {
225 tokens.push_back({TokType::End,
"", Value{}, _i});
230 (c ==
'.' && _i + 1 < _src.size() && is_digit(_src[_i + 1])))
231 tokens.push_back(read_number());
232 else if (std::isalpha(
static_cast<unsigned char>(c)) || c ==
'_')
233 tokens.push_back(read_ident());
235 tokens.push_back(read_op());
241 static bool is_digit(
char c) {
242 return std::isdigit(
static_cast<unsigned char>(c)) != 0;
246 while (_i < _src.size() &&
247 std::isspace(
static_cast<unsigned char>(_src[_i])))
251 Token read_number() {
252 std::size_t start = _i;
253 bool is_float =
false;
254 while (_i < _src.size() && is_digit(_src[_i]))
256 if (_i < _src.size() && _src[_i] ==
'.') {
259 while (_i < _src.size() && is_digit(_src[_i]))
262 if (_i < _src.size() && (_src[_i] ==
'e' || _src[_i] ==
'E')) {
265 if (_i < _src.size() && (_src[_i] ==
'+' || _src[_i] ==
'-'))
267 while (_i < _src.size() && is_digit(_src[_i]))
270 std::string text = _src.substr(start, _i - start);
276 v =
static_cast<std::int64_t
>(std::stoll(text));
277 }
catch (
const std::exception &) {
281 return {TokType::Number, text, v, start};
285 std::size_t start = _i;
288 (std::isalnum(
static_cast<unsigned char>(_src[_i])) || _src[_i] ==
'_'))
290 return {TokType::Ident, _src.substr(start, _i - start), Value{}, start};
294 std::size_t start = _i;
296 auto two = [&](
char a,
char b) {
297 return _i + 1 < _src.size() && _src[_i] == a && _src[_i + 1] == b;
300 return _i += 2, Token{TokType::Le,
"<=", {}, start};
302 return _i += 2, Token{TokType::Ge,
">=", {}, start};
304 return _i += 2, Token{TokType::EqEq,
"==", {}, start};
306 return _i += 2, Token{TokType::NotEq,
"!=", {}, start};
308 return _i += 2, Token{TokType::And,
"&&", {}, start};
310 return _i += 2, Token{TokType::Or,
"||", {}, start};
313 return ++_i, Token{TokType::Plus,
"+", {}, start};
315 return ++_i, Token{TokType::Minus,
"-", {}, start};
317 return ++_i, Token{TokType::Star,
"*", {}, start};
319 return ++_i, Token{TokType::Slash,
"/", {}, start};
321 return ++_i, Token{TokType::Caret,
"^", {}, start};
323 return ++_i, Token{TokType::LParen,
"(", {}, start};
325 return ++_i, Token{TokType::RParen,
")", {}, start};
327 return ++_i, Token{TokType::Comma,
",", {}, start};
329 return ++_i, Token{TokType::Colon,
":", {}, start};
331 return ++_i, Token{TokType::Lt,
"<", {}, start};
333 return ++_i, Token{TokType::Gt,
">", {}, start};
335 return ++_i, Token{TokType::Not,
"!", {}, start};
337 throw ExpressionistException(
"Unexpected character '" +
338 std::string(1, c) +
"' at position " +
339 std::to_string(start));
366 TokType op = TokType::End;
367 std::vector<std::shared_ptr<Node>> children;
370using NodePtr = std::shared_ptr<Node>;
374 Parser(std::vector<Token> tokens, std::string src)
375 : _tokens(std::move(tokens)), _src(std::move(src)) {}
378 NodePtr n = parse_range();
379 if (!check(TokType::End))
380 throw error(
"unexpected trailing token");
385 const Token &peek()
const {
return _tokens[_pos]; }
386 const Token &advance() {
return _tokens[_pos++]; }
387 bool check(TokType t)
const {
return peek().type == t; }
388 bool match(TokType t) {
396 ExpressionistException error(
const std::string &why)
const {
397 return ExpressionistException(
"Parse error in \"" + _src +
"\": " + why +
398 " ('" + peek().text +
"' at position " +
399 std::to_string(peek().pos) +
")");
402 static NodePtr make_binary(TokType op, NodePtr l, NodePtr r) {
403 auto n = std::make_shared<Node>();
404 n->kind = NodeKind::Binary;
406 n->children = {std::move(l), std::move(r)};
413 NodePtr parse_range() {
414 NodePtr first = parse_or();
415 if (!check(TokType::Colon))
417 auto node = std::make_shared<Node>();
418 node->kind = NodeKind::Range;
419 node->children.push_back(first);
420 while (match(TokType::Colon))
421 node->children.push_back(parse_or());
422 if (node->children.size() > 3)
423 throw error(
"a range has at most three parts (start:stop:step)");
428 NodePtr n = parse_and();
429 while (check(TokType::Or)) {
431 n = make_binary(TokType::Or, n, parse_and());
436 NodePtr parse_and() {
437 NodePtr n = parse_equality();
438 while (check(TokType::And)) {
440 n = make_binary(TokType::And, n, parse_equality());
445 NodePtr parse_equality() {
446 NodePtr n = parse_comparison();
447 while (check(TokType::EqEq) || check(TokType::NotEq)) {
448 TokType op = advance().type;
449 n = make_binary(op, n, parse_comparison());
454 NodePtr parse_comparison() {
455 NodePtr n = parse_additive();
456 while (check(TokType::Lt) || check(TokType::Le) || check(TokType::Gt) ||
457 check(TokType::Ge)) {
458 TokType op = advance().type;
459 n = make_binary(op, n, parse_additive());
464 NodePtr parse_additive() {
465 NodePtr n = parse_multiplicative();
466 while (check(TokType::Plus) || check(TokType::Minus)) {
467 TokType op = advance().type;
468 n = make_binary(op, n, parse_multiplicative());
473 NodePtr parse_multiplicative() {
474 NodePtr n = parse_unary();
475 while (check(TokType::Star) || check(TokType::Slash)) {
476 TokType op = advance().type;
477 n = make_binary(op, n, parse_unary());
482 NodePtr parse_unary() {
483 if (check(TokType::Minus) || check(TokType::Not)) {
484 TokType op = advance().type;
485 auto n = std::make_shared<Node>();
486 n->kind = NodeKind::Unary;
488 n->children = {parse_unary()};
491 return parse_power();
496 NodePtr parse_power() {
497 NodePtr base = parse_primary();
498 if (check(TokType::Caret)) {
500 return make_binary(TokType::Caret, base, parse_unary());
505 NodePtr parse_primary() {
506 const Token &t = peek();
507 if (t.type == TokType::Number) {
509 auto n = std::make_shared<Node>();
510 n->kind = is_int(t.value) ? NodeKind::IntLit : NodeKind::FloatLit;
514 if (t.type == TokType::LParen) {
516 NodePtr n = parse_or();
517 if (!match(TokType::RParen))
518 throw error(
"expected ')'");
521 if (t.type == TokType::Ident) {
523 if (t.text ==
"true" || t.text ==
"false") {
524 auto n = std::make_shared<Node>();
525 n->kind = NodeKind::BoolLit;
526 n->value = (t.text ==
"true");
529 if (check(TokType::LParen)) {
531 auto n = std::make_shared<Node>();
532 n->kind = NodeKind::Call;
534 if (!check(TokType::RParen)) {
535 n->children.push_back(parse_or());
536 while (match(TokType::Comma))
537 n->children.push_back(parse_or());
539 if (!match(TokType::RParen))
540 throw error(
"expected ')' in call to '" + t.text +
"'");
543 auto n = std::make_shared<Node>();
544 n->kind = NodeKind::Ident;
548 throw error(
"unexpected token");
551 std::vector<Token> _tokens;
553 std::size_t _pos = 0;
558inline void collect_idents(
const NodePtr &n, std::vector<std::string> &out) {
561 if (n->kind == NodeKind::Ident)
562 out.push_back(n->name);
563 for (
const auto &c : n->children)
564 collect_idents(c, out);
567using Resolver = std::function<std::optional<Value>(
const std::string &)>;
569inline Value num_binary(
const Value &l,
const Value &r, TokType op) {
570 if (op == TokType::Slash)
571 return to_double(l) / to_double(r);
572 if (op == TokType::Caret) {
573 if (is_int(l) && is_int(r) && std::get<std::int64_t>(r) >= 0) {
574 std::int64_t base = std::get<std::int64_t>(l);
575 std::int64_t exp = std::get<std::int64_t>(r);
576 std::int64_t result = 1;
577 for (std::int64_t k = 0; k < exp; ++k)
581 return std::pow(to_double(l), to_double(r));
583 if (is_int(l) && is_int(r)) {
584 std::int64_t a = std::get<std::int64_t>(l);
585 std::int64_t b = std::get<std::int64_t>(r);
597 double a = to_double(l);
598 double b = to_double(r);
611inline Value eval_node(
const NodePtr &n,
const Symbols &sym,
612 const Resolver &resolve) {
614 case NodeKind::IntLit:
615 case NodeKind::FloatLit:
616 case NodeKind::BoolLit:
618 case NodeKind::Ident: {
619 if (
auto v = resolve(n->name))
621 auto it = sym.constants.find(n->name);
622 if (it != sym.constants.end())
624 throw ExpressionistException(
"Undefined variable or constant: '" + n->name +
627 case NodeKind::Unary: {
628 if (n->op == TokType::Not)
629 return !to_bool(eval_node(n->children[0], sym, resolve));
630 Value c = eval_node(n->children[0], sym, resolve);
632 return -std::get<std::int64_t>(c);
633 return -to_double(c);
635 case NodeKind::Binary: {
636 if (n->op == TokType::And) {
637 if (!to_bool(eval_node(n->children[0], sym, resolve)))
639 return to_bool(eval_node(n->children[1], sym, resolve));
641 if (n->op == TokType::Or) {
642 if (to_bool(eval_node(n->children[0], sym, resolve)))
644 return to_bool(eval_node(n->children[1], sym, resolve));
646 Value l = eval_node(n->children[0], sym, resolve);
647 Value r = eval_node(n->children[1], sym, resolve);
654 return num_binary(l, r, n->op);
656 return to_double(l) < to_double(r);
658 return to_double(l) <= to_double(r);
660 return to_double(l) > to_double(r);
662 return to_double(l) >= to_double(r);
664 if (is_bool_value(l) && is_bool_value(r))
665 return std::get<bool>(l) == std::get<bool>(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 throw ExpressionistException(
"Internal error: bad binary operator");
675 case NodeKind::Range: {
676 if (n->children.size() == 2) {
677 Value a = eval_node(n->children[0], sym, resolve);
678 Value b = eval_node(n->children[1], sym, resolve);
679 if (!is_int(a) || !is_int(b))
680 throw ExpressionistException(
681 "Binary range 'start:stop' requires integer bounds");
682 std::int64_t start = std::get<std::int64_t>(a);
683 std::int64_t stop = std::get<std::int64_t>(b);
684 std::vector<std::int64_t> arr;
685 for (std::int64_t v = start; v <= stop; ++v)
687 return Value(Seq(std::move(arr)));
689 if (n->children.size() == 3) {
690 double start = to_double(eval_node(n->children[0], sym, resolve));
691 double stop = to_double(eval_node(n->children[1], sym, resolve));
692 double step = to_double(eval_node(n->children[2], sym, resolve));
694 throw ExpressionistException(
"Range step must be non-zero");
695 std::vector<double> arr;
696 double span = stop - start;
701 if (span == 0.0 || (span > 0.0) == (step > 0.0)) {
703 static_cast<std::int64_t
>(std::floor(span / step + 1e-9));
704 for (std::int64_t i = 0; i <= count; ++i)
705 arr.push_back(start +
static_cast<double>(i) * step);
707 return Value(Seq(std::move(arr)));
709 throw ExpressionistException(
710 "A range must have two (start:stop) or three (start:stop:step) parts");
712 case NodeKind::Call: {
713 const std::string &fn = n->name;
714 auto u = sym.unary.find(fn);
715 if (u != sym.unary.end()) {
716 if (n->children.size() != 1)
717 throw ExpressionistException(
"Function '" + fn +
718 "' expects 1 argument, got " +
719 std::to_string(n->children.size()));
720 return u->second(to_double(eval_node(n->children[0], sym, resolve)));
722 auto b = sym.binary.find(fn);
723 if (b != sym.binary.end()) {
724 if (n->children.size() != 2)
725 throw ExpressionistException(
"Function '" + fn +
726 "' expects 2 arguments, got " +
727 std::to_string(n->children.size()));
728 double x = to_double(eval_node(n->children[0], sym, resolve));
729 double y = to_double(eval_node(n->children[1], sym, resolve));
730 return b->second(x, y);
732 throw ExpressionistException(
"Unknown function: '" + fn +
"'");
735 throw ExpressionistException(
"Internal error: bad node");
753 virtual ~DocumentModel() =
default;
754 virtual NodeRef root() = 0;
755 virtual bool is_object(NodeRef node)
const = 0;
756 virtual bool is_array(NodeRef node)
const = 0;
758 for_each_member(NodeRef node,
759 const std::function<
void(
const std::string &, NodeRef)> &fn) = 0;
761 for_each_element(NodeRef node,
762 const std::function<
void(std::size_t, NodeRef)> &fn) = 0;
765 virtual std::optional<std::string> as_string(NodeRef node)
const = 0;
769 virtual std::optional<Value> as_scalar(NodeRef node)
const = 0;
771 virtual void assign(NodeRef node,
const Value &v) = 0;
774 virtual bool has_false_member(NodeRef
object,
const std::string &key)
const = 0;
777#if EXPRESSIONIST_ENABLE_JSON
779class JsonModel :
public DocumentModel {
781 explicit JsonModel(json &root) : _root(root) {}
783 NodeRef root()
override {
return NodeRef{&_root}; }
785 bool is_object(NodeRef node)
const override {
786 return as_json(node).is_object();
788 bool is_array(NodeRef node)
const override {
789 return as_json(node).is_array();
792 void for_each_member(
794 const std::function<
void(
const std::string &, NodeRef)> &fn)
override {
795 json &obj = as_json(node);
796 for (
auto it = obj.begin(); it != obj.end(); ++it)
797 fn(it.key(), NodeRef{&it.value()});
800 void for_each_element(
802 const std::function<
void(std::size_t, NodeRef)> &fn)
override {
803 json &arr = as_json(node);
804 for (std::size_t i = 0; i < arr.size(); ++i)
805 fn(i, NodeRef{&arr[i]});
808 std::optional<std::string> as_string(NodeRef node)
const override {
809 const json &v = as_json(node);
812 return v.get<std::string>();
815 std::optional<Value> as_scalar(NodeRef node)
const override {
816 const json &v = as_json(node);
817 if (v.is_number_integer())
818 return Value(
static_cast<std::int64_t
>(v.get<std::int64_t>()));
819 if (v.is_number_float())
820 return Value(v.get<
double>());
822 return Value(v.get<
bool>());
826 void assign(NodeRef node,
const Value &v)
override {
827 json &slot = as_json(node);
828 if (std::holds_alternative<std::int64_t>(v)) {
829 slot = json(std::get<std::int64_t>(v));
830 }
else if (std::holds_alternative<double>(v)) {
831 slot = json(std::get<double>(v));
832 }
else if (std::holds_alternative<bool>(v)) {
833 slot = json(std::get<bool>(v));
835 const Seq &seq = std::get<Seq>(v);
836 json arr = json::array();
837 if (std::holds_alternative<std::vector<std::int64_t>>(seq)) {
838 for (std::int64_t x : std::get<std::vector<std::int64_t>>(seq))
841 for (
double x : std::get<std::vector<double>>(seq))
844 slot = std::move(arr);
848 bool has_false_member(NodeRef
object,
const std::string &key)
const override {
849 const json &obj = as_json(
object);
850 auto it = obj.find(key);
851 return it != obj.end() && *it ==
false;
855 static json &as_json(NodeRef node) {
return *
static_cast<json *
>(node.p); }
862#if EXPRESSIONIST_ENABLE_TOML
864class TomlModel :
public DocumentModel {
866 explicit TomlModel(toml::table &root) {
867 _slots.push_back(Slot{&root,
nullptr, std::string(), 0,
false});
870 NodeRef root()
override {
return NodeRef{&_slots.front()}; }
872 bool is_object(NodeRef node)
const override {
873 return slot(node).self->is_table();
875 bool is_array(NodeRef node)
const override {
876 return slot(node).self->is_array();
879 void for_each_member(
881 const std::function<
void(
const std::string &, NodeRef)> &fn)
override {
882 toml::table *t = slot(node).self->as_table();
883 for (
auto &&[key, val] : *t) {
884 std::string name(key.str());
885 _slots.push_back(Slot{&val, slot(node).self, name, 0,
false});
886 fn(name, NodeRef{&_slots.back()});
890 void for_each_element(
892 const std::function<
void(std::size_t, NodeRef)> &fn)
override {
893 toml::array *a = slot(node).self->as_array();
895 for (
auto &&val : *a) {
896 _slots.push_back(Slot{&val, slot(node).self, std::string(), i,
true});
897 fn(i, NodeRef{&_slots.back()});
902 std::optional<std::string> as_string(NodeRef node)
const override {
903 if (
auto *v = slot(node).self->as_string())
908 std::optional<Value> as_scalar(NodeRef node)
const override {
909 const toml::node *n = slot(node).self;
910 if (
auto *v = n->as_integer())
911 return Value(v->get());
912 if (
auto *v = n->as_floating_point())
913 return Value(v->get());
914 if (
auto *v = n->as_boolean())
915 return Value(v->get());
919 void assign(NodeRef node,
const Value &v)
override {
920 Slot &s = slot(node);
921 if (s.parent_is_array) {
922 toml::array *arr = s.parent->as_array();
923 auto pos = arr->cbegin() +
static_cast<std::ptrdiff_t
>(s.index);
924 with_native(v, [&](
auto &&native) {
925 auto it = arr->replace(pos, std::forward<
decltype(native)>(native));
929 toml::table *tbl = s.parent->as_table();
930 with_native(v, [&](
auto &&native) {
931 tbl->insert_or_assign(s.key, std::forward<
decltype(native)>(native));
933 s.self = tbl->get(s.key);
937 bool has_false_member(NodeRef
object,
const std::string &key)
const override {
938 const toml::table *t = slot(
object).self->as_table();
941 const toml::node *member = t->get(key);
944 const auto *b = member->as_boolean();
945 return b && b->get() ==
false;
957 bool parent_is_array;
960 Slot &slot(NodeRef node) {
return *
static_cast<Slot *
>(node.p); }
961 const Slot &slot(NodeRef node)
const {
962 return *
static_cast<const Slot *
>(node.p);
965 template <
typename Setter>
static void with_native(
const Value &v, Setter &&set) {
966 if (std::holds_alternative<std::int64_t>(v)) {
967 set(std::get<std::int64_t>(v));
968 }
else if (std::holds_alternative<double>(v)) {
969 set(std::get<double>(v));
970 }
else if (std::holds_alternative<bool>(v)) {
971 set(std::get<bool>(v));
973 const Seq &seq = std::get<Seq>(v);
975 if (std::holds_alternative<std::vector<std::int64_t>>(seq)) {
976 for (std::int64_t x : std::get<std::vector<std::int64_t>>(seq))
979 for (
double x : std::get<std::vector<double>>(seq))
986 std::deque<Slot> _slots;
998 Evaluator(
const Symbols &sym, std::string tag, std::string disableKey,
1000 : _sym(sym), _tag(std::move(tag)), _disableKey(std::move(disableKey)),
1003 void run(DocumentModel &doc) {
1007 walk(doc.root(),
nullptr,
"");
1008 if (_method == EvalMethod::GRAPH)
1012 for (
const Cell &c : _cells)
1014 _doc->assign(c.node, c.result);
1019 Scope *parent =
nullptr;
1020 std::map<std::string, std::size_t> keys;
1025 Scope *scope =
nullptr;
1028 bool is_expr =
false;
1035 bool starts_with_tag(
const std::string &s)
const {
1036 return !_tag.empty() && s.rfind(_tag, 0) == 0;
1044 bool is_disabled(NodeRef
object)
const {
1045 return !_disableKey.empty() && _doc->has_false_member(
object, _disableKey);
1048 std::size_t add_cell(NodeRef node, Scope *scope,
const std::string &path) {
1049 std::size_t idx = _cells.size();
1050 _cells.emplace_back();
1051 Cell &cell = _cells.back();
1055 cell.name = last_component(path);
1056 if (
auto s = _doc->as_string(node)) {
1057 if (starts_with_tag(*s)) {
1058 cell.is_expr =
true;
1059 cell.source = s->substr(_tag.size());
1061 Tokenizer tok(cell.source);
1062 Parser parser(tok.tokenize(), cell.source);
1063 cell.ast = parser.parse();
1064 }
catch (
const ExpressionistException &e) {
1065 throw ExpressionistException(cell_context(cell) + e.what());
1072 void walk(NodeRef node, Scope *enclosing,
const std::string &path) {
1073 if (_doc->is_object(node)) {
1074 if (is_disabled(node))
1076 _scopes.emplace_back();
1077 Scope *scope = &_scopes.back();
1078 scope->parent = enclosing;
1079 std::vector<std::pair<std::string, NodeRef>> members;
1080 _doc->for_each_member(node, [&](
const std::string &key, NodeRef child) {
1081 members.emplace_back(key, child);
1085 for (
auto &[key, child] : members)
1086 scope->keys[key] = add_cell(child, scope, join(path, key));
1088 for (
auto &[key, child] : members)
1089 if (_doc->is_object(child) || _doc->is_array(child))
1090 walk(child, scope, join(path, key));
1091 }
else if (_doc->is_array(node)) {
1093 std::vector<NodeRef> elements;
1094 _doc->for_each_element(
1095 node, [&](std::size_t, NodeRef child) { elements.push_back(child); });
1096 for (std::size_t i = 0; i < elements.size(); ++i) {
1097 std::string p = path +
"/" + std::to_string(i);
1098 NodeRef child = elements[i];
1099 if (_doc->is_object(child) || _doc->is_array(child))
1100 walk(child, enclosing, p);
1102 add_cell(child, enclosing, p);
1108 std::size_t resolve_name(
const std::string &name, Scope *scope,
1109 bool &found)
const {
1110 for (Scope *s = scope; s !=
nullptr; s = s->parent) {
1111 auto it = s->keys.find(name);
1112 if (it != s->keys.end()) {
1121 Value literal_value(
const Cell &c)
const {
1122 if (
auto v = _doc->as_scalar(c.node))
1124 throw ExpressionistException(
"variable '" + c.name +
"' is not numeric");
1127 std::string cell_context(
const Cell &c)
const {
1128 return "In '" + c.path +
"' (\"" + _tag + c.source +
"\"): ";
1134 rethrow_with_context(
const Cell &c,
const ExpressionistException &e)
const {
1135 std::string msg = e.what();
1136 if (msg.rfind(
"In '", 0) == 0)
1138 throw ExpressionistException(cell_context(c) + msg);
1143 void eval_recursive() {
1144 std::vector<std::size_t> stack;
1145 for (std::size_t i = 0; i < _cells.size(); ++i)
1146 if (_cells[i].is_expr)
1147 eval_cell(i, stack);
1150 Value eval_cell(std::size_t idx, std::vector<std::size_t> &stack) {
1151 Cell &cell = _cells[idx];
1153 return literal_value(cell);
1154 if (cell.state == 2)
1156 if (cell.state == 1)
1157 throw ExpressionistException(
"Circular dependency: " +
1158 cycle_path(stack, idx));
1160 stack.push_back(idx);
1161 Scope *scope = cell.scope;
1162 Resolver resolver = [&](
const std::string &name) -> std::optional<Value> {
1164 std::size_t tgt = resolve_name(name, scope, found);
1166 return std::nullopt;
1167 return eval_cell(tgt, stack);
1171 v = eval_node(cell.ast, _sym, resolver);
1172 }
catch (
const ExpressionistException &e) {
1173 rethrow_with_context(cell, e);
1181 std::string cycle_path(
const std::vector<std::size_t> &stack,
1182 std::size_t idx)
const {
1183 std::size_t start = 0;
1184 for (std::size_t k = 0; k < stack.size(); ++k)
1185 if (stack[k] == idx) {
1190 for (std::size_t k = start; k < stack.size(); ++k)
1191 m += _cells[stack[k]].name +
" -> ";
1192 m += _cells[idx].name;
1199 std::size_t n = _cells.size();
1200 std::vector<std::vector<std::size_t>> dependents(n);
1201 std::vector<int> indeg(n, 0);
1202 std::vector<std::size_t> expr_cells;
1203 for (std::size_t i = 0; i < n; ++i) {
1204 if (!_cells[i].is_expr)
1206 expr_cells.push_back(i);
1207 std::vector<std::string> idents;
1208 collect_idents(_cells[i].ast, idents);
1209 std::unordered_set<std::size_t> seen;
1210 for (
const std::string &name : idents) {
1212 std::size_t tgt = resolve_name(name, _cells[i].scope, found);
1213 if (found && _cells[tgt].is_expr && seen.insert(tgt).second) {
1214 dependents[tgt].push_back(i);
1219 std::queue<std::size_t> q;
1220 for (std::size_t i : expr_cells)
1223 std::size_t processed = 0;
1224 while (!q.empty()) {
1225 std::size_t i = q.front();
1229 for (std::size_t d : dependents[i])
1230 if (--indeg[d] == 0)
1233 if (processed != expr_cells.size()) {
1235 for (std::size_t i : expr_cells)
1236 if (_cells[i].state != 2)
1237 names += _cells[i].name +
" ";
1238 throw ExpressionistException(
"Circular dependency detected among: " +
1243 void eval_expr_graph(std::size_t idx) {
1244 Cell &cell = _cells[idx];
1245 Scope *scope = cell.scope;
1246 Resolver resolver = [&](
const std::string &name) -> std::optional<Value> {
1248 std::size_t tgt = resolve_name(name, scope, found);
1250 return std::nullopt;
1251 const Cell &t = _cells[tgt];
1254 return literal_value(t);
1257 cell.result = eval_node(cell.ast, _sym, resolver);
1258 }
catch (
const ExpressionistException &e) {
1259 rethrow_with_context(cell, e);
1264 static std::string join(
const std::string &path,
const std::string &key) {
1265 return path +
"/" + key;
1268 static std::string last_component(
const std::string &path) {
1269 std::size_t p = path.find_last_of(
'/');
1270 return p == std::string::npos ? path : path.substr(p + 1);
1273 const Symbols &_sym;
1275 std::string _disableKey;
1277 DocumentModel *_doc =
nullptr;
1278 std::deque<Scope> _scopes;
1279 std::vector<Cell> _cells;
1322#if EXPRESSIONIST_ENABLE_JSON
1325 : _object(std::move(o)), _evalMethod(method),
1326 _symbols(detail::default_symbols()) {}
1330 : _evalMethod(method), _symbols(detail::default_symbols()) {
1332 _object = json::parse(s);
1333 }
catch (
const std::exception &e) {
1335 std::string(e.what()));
1345#if EXPRESSIONIST_ENABLE_TOML
1348 explicit Expressionist(toml::table t, EvalMethod method = EvalMethod::RECURSIVE)
1349 : _toml(std::move(t)), _evalMethod(method),
1350 _symbols(detail::default_symbols()) {}
1353 static Expressionist parse_toml(
const std::string &s,
1354 EvalMethod method = EvalMethod::RECURSIVE) {
1356 toml::table t = toml::parse(s);
1357 return Expressionist(std::move(t), method);
1358 }
catch (
const std::exception &e) {
1359 throw ExpressionistException(
"Failed to parse TOML: " +
1360 std::string(e.what()));
1367 Expressionist(EvalMethod method = EvalMethod::RECURSIVE)
1368 : _evalMethod(method), _symbols(detail::default_symbols()) {}
1372#if EXPRESSIONIST_ENABLE_JSON
1378 detail::JsonModel model(_object);
1379 detail::Evaluator ev(_symbols, _tag, _disableKey, _evalMethod);
1394 detail::JsonModel model(
object);
1395 detail::Evaluator ev(_symbols, _tag, _disableKey, _evalMethod);
1406 json copy = _object;
1407 detail::JsonModel model(copy);
1408 detail::Evaluator ev(_symbols, _tag, _disableKey, _evalMethod);
1417 detail::JsonModel model(copy);
1418 detail::Evaluator ev(_symbols, _tag, _disableKey, _evalMethod);
1424#if EXPRESSIONIST_ENABLE_TOML
1427 void evaluate(toml::table &
object)
const {
1428 detail::TomlModel model(
object);
1429 detail::Evaluator ev(_symbols, _tag, _disableKey, _evalMethod);
1435 toml::table produce(toml::table
object)
const {
1436 toml::table copy = object;
1437 detail::TomlModel model(copy);
1438 detail::Evaluator ev(_symbols, _tag, _disableKey, _evalMethod);
1455 void setTag(
const std::string &tag) { _tag = tag; }
1457 std::string
tag()
const {
return _tag; }
1470#if EXPRESSIONIST_ENABLE_JSON
1472 const json &
object()
const {
return _object; }
1474#if EXPRESSIONIST_ENABLE_TOML
1476 const toml::table &as_toml()
const {
return _toml; }
1482 _symbols.constants[name] = value;
1489 _symbols.unary[name] = std::move(fn);
1497 _symbols.binary[name] = std::move(fn);
1501#if EXPRESSIONIST_ENABLE_JSON
1502 json _object = json::object();
1504#if EXPRESSIONIST_ENABLE_TOML
1507 EvalMethod _evalMethod = EvalMethod::RECURSIVE;
1508 std::string _tag =
"$";
1509 std::string _disableKey =
"expressionist";
1510 detail::Symbols _symbols;
Definition expressionist.hpp:74
Definition expressionist.hpp:1320
void setTag(const std::string &tag)
Definition expressionist.hpp:1455
void addUnaryFunction(const std::string &name, detail::UnaryFn fn)
Definition expressionist.hpp:1488
EvalMethod getEvalMethod() const
The currently selected resolution strategy.
Definition expressionist.hpp:1448
Expressionist(json o, EvalMethod method=EvalMethod::RECURSIVE)
Construct from a JSON object, to be mutated/copied by evaluate()/produce().
Definition expressionist.hpp:1324
void evaluate(json &object) const
Definition expressionist.hpp:1393
std::string tag() const
The currently configured expression tag.
Definition expressionist.hpp:1457
Expressionist(std::string s, EvalMethod method=EvalMethod::RECURSIVE)
Definition expressionist.hpp:1329
json produce(json object) const
Definition expressionist.hpp:1415
std::string disableKey() const
The currently configured disable key.
Definition expressionist.hpp:1468
void setEvalMethod(EvalMethod method)
Definition expressionist.hpp:1446
void addConstant(const std::string &name, double value)
Definition expressionist.hpp:1481
const json & object() const
Access the (possibly evaluated) stored object.
Definition expressionist.hpp:1472
json produce() const
Definition expressionist.hpp:1405
Expressionist(const char *s, EvalMethod method=EvalMethod::RECURSIVE)
Same as the std::string overload; disambiguates string-literal calls.
Definition expressionist.hpp:1342
void evaluate()
Definition expressionist.hpp:1377
void setDisableKey(const std::string &key)
Definition expressionist.hpp:1466
void addBinaryFunction(const std::string &name, detail::BinaryFn fn)
Definition expressionist.hpp:1496