194 enum class Kind : std::uint8_t {
219 [[nodiscard]]
static std::expected<RuleExpr, SchemaError>
parse(detail::engine::Json
const& node,
220 std::string
const& path) {
221 auto parsed = parseNode(node, path);
225 detail::engine::RuleOperands
operands;
226 detail::engine::Json
const translation = parsed->verdictExpression(
operands);
229 return std::unexpected(
231 .message =
"the rule does not translate into an expression: " + compiled.error().reason});
233 parsed->_expression = translation;
234 parsed->_compiled = *std::move(compiled);
235 parsed->_operands = std::move(
operands).take();
241 [[nodiscard]]
Kind kind() const noexcept {
return _kind; }
245 [[nodiscard]] std::string_view
kindName() const noexcept {
return _kindName; }
249 [[nodiscard]] std::span<std::string const>
fields() const noexcept {
return _fields; }
254 [[nodiscard]]
bool names(std::string_view field)
const noexcept {
255 return std::ranges::find(_fields, field) != _fields.end();
262 return hasWhen && !_children.empty() ? &_children.front() :
nullptr;
267 [[nodiscard]] std::span<RuleExpr const>
conditions() const noexcept {
269 return composite ? std::span<RuleExpr const>{_children} : std::span<RuleExpr const>{};
287 [[nodiscard]] detail::engine::Json
const&
expression() const noexcept {
return _expression; }
291 [[nodiscard]] std::span<std::string const>
operands() const noexcept {
return _operands; }
300 if (_compiled ==
nullptr) {
303 Environment
const env{_operands, lookup};
311 if (_whenCompiled ==
nullptr) {
314 Environment
const env{_operands, lookup};
323 : _operands{
operands}, _lookup{&lookup} {}
325 [[nodiscard]] ui::Value ref(std::string_view head, std::span<std::string const> path)
const override {
326 if (head.size() < 2 || head.front() !=
'f' || path.size() != 1) {
329 std::size_t index = 0;
330 for (
char const chr : head.substr(1)) {
331 if (chr <
'0' || chr >
'9' || index > (std::numeric_limits<std::size_t>::max() / 10U) - 1U) {
334 index = (index * 10U) +
static_cast<std::size_t
>(chr -
'0');
336 if (index >= _operands.size()) {
339 RuleOperand
const operand = (*_lookup)(*std::next(_operands.begin(),
static_cast<std::ptrdiff_t
>(index)));
340 if (path.front() ==
"engaged") {
341 switch (operand.state) {
352 return operand.value;
357 [[nodiscard]] ui::Value row(std::span<std::string const> path)
const override {
358 static_cast<void>(path);
362 [[nodiscard]] ui::Value control(ui::ExprKind form, std::string_view name)
const override {
363 static_cast<void>(
form);
364 static_cast<void>(name);
368 [[nodiscard]] ui::Value pref(std::string_view point, std::span<std::string const> path)
const override {
369 static_cast<void>(point);
370 static_cast<void>(path);
374 [[nodiscard]] ui::Value client(ui::ExprKind form, std::string_view name)
const override {
375 static_cast<void>(
form);
376 static_cast<void>(name);
380 [[nodiscard]] ui::FormatContext
const&
formatting()
const override {
381 static ui::FormatContext
const kContext{};
388 void report(std::string_view path, std::string_view reason)
const override {
389 static_cast<void>(path);
390 static_cast<void>(reason);
394 std::span<std::string const> _operands;
398 [[nodiscard]]
static Tri verdictOf(ui::Value
const& value)
noexcept {
399 bool const*
const flag = value.asBool();
400 if (flag ==
nullptr) {
403 return *flag ? Tri::True : Tri::False;
408 [[nodiscard]]
static std::expected<RuleExpr, SchemaError> parseNode(detail::engine::Json
const& node,
409 std::string
const& path) {
410 using detail::engine::Json;
411 if (!node.is_object()) {
412 return std::unexpected(SchemaError{.path = path, .message =
"a rule must be a JSON object"});
414 auto const*
const kind = detail::engine::stringAt(node,
"kind");
415 if (
kind ==
nullptr) {
416 return std::unexpected(SchemaError{.path = path, .message =
"a rule needs a string \"kind\""});
419 expr._kindName = *
kind;
420 expr._kind = kindOf(*
kind);
421 if (Json
const*
const fields = detail::engine::member(node,
"fields")) {
422 if (!
fields->is_array()) {
423 return std::unexpected(SchemaError{.path = path, .message =
"\"fields\" must be an array of strings"});
425 for (Json
const& name :
fields->get_array()) {
426 auto const*
const text = name.get_if<std::string>();
427 if (text ==
nullptr) {
428 return std::unexpected(
429 SchemaError{.path = path, .message =
"\"fields\" must be an array of strings"});
431 expr._fields.push_back(*text);
435 auto const child = [&](std::string_view key) -> std::expected<void, SchemaError> {
436 Json
const*
const nested = detail::engine::member(node, key);
437 if (nested ==
nullptr) {
438 return std::unexpected(
439 SchemaError{.path = path, .message = expr._kindName +
" needs \"" + std::string{key} +
"\""});
441 auto parsed = parseNode(*nested, path +
"." + std::string{key});
443 return std::unexpected(parsed.error());
445 expr._children.push_back(*std::move(parsed));
448 auto const needFields = [&](std::size_t
count,
bool exact) -> std::expected<void, SchemaError> {
449 if (exact ? expr._fields.size() != count : expr._fields.size() < count) {
450 return std::unexpected(SchemaError{.path = path,
451 .message =
"\"" + expr._kindName +
"\" needs " +
452 (exact ?
"exactly " :
"at least ") +
453 std::to_string(count) +
" field(s)"});
457 std::expected<void, SchemaError> shape{};
458 switch (expr._kind) {
462 shape = needFields(1,
false);
464 shape = child(
"when");
468 shape = child(
"condition");
472 Json
const*
const list = detail::engine::member(node,
"conditions");
473 if (list ==
nullptr || !
list->is_array()) {
474 shape = std::unexpected(SchemaError{
475 .path = path, .message =
"\"" + expr._kindName + R
"(" needs a "conditions" array)"});
478 std::size_t index = 0;
479 for (Json
const& nested :
list->get_array()) {
480 auto parsed = parseNode(nested, path +
".conditions[" + std::to_string(index++) +
"]");
482 return std::unexpected(parsed.error());
484 expr._children.push_back(*std::move(parsed));
492 shape = needFields(2,
true);
495 shape = needFields(1,
false);
496 expr._literal = literalOf(node);
503 shape = needFields(1,
false);
510 return std::unexpected(shape.error());
515 [[nodiscard]]
static Kind kindOf(std::string_view name)
noexcept {
516 constexpr std::array<std::pair<std::string_view, Kind>, 16> kNames{{
534 for (
auto const& [text,
kind] : kNames) {
545 [[nodiscard]]
static detail::engine::Json literalOf(detail::engine::Json
const& node) {
546 using detail::engine::Json;
547 auto const wide = [](std::string_view digits) -> Json {
548 auto const parsed = render::parseDecimal(digits, render::NumericLocale{});
549 if (!parsed || !parsed->isInteger()) {
550 return detail::engine::nullLiteral();
552 return detail::engine::integer(parsed->numerator);
554 if (
auto const*
const digits = detail::engine::stringAt(node,
"valueText");
555 digits !=
nullptr && detail::engine::isCanonicalInteger(*digits)) {
556 return wide(*digits);
558 Json
const*
const value = detail::engine::member(node,
"value");
559 if (value ==
nullptr) {
560 return detail::engine::nullLiteral();
562 if (
auto const*
const flag = value->get_if<
bool>()) {
563 return detail::engine::literal(*flag);
565 if (
auto const*
const text = value->get_if<std::string>()) {
566 return detail::engine::jsonText(*text);
568 if (value->get_if<std::int64_t>() !=
nullptr || value->get_if<std::uint64_t>() !=
nullptr) {
569 return wide(detail::engine::numberText(*value).value_or(std::string{}));
571 std::optional<math::Rational> exact;
572 if (
auto const number = detail::engine::numberText(*value)) {
573 exact = render::parseDecimal(*number, render::NumericLocale{});
574 }
else if (value->is_object()) {
575 auto const decoded = ui::valueFromJson(*value);
576 if (decoded && decoded->asDecimal() !=
nullptr) {
577 exact = decoded->asDecimal()->value;
581 return detail::engine::nullLiteral();
583 Json decimal = detail::engine::emptyObject();
584 decimal.get_object().insert_or_assign(
"num", detail::engine::integer(exact->numerator));
585 decimal.get_object().insert_or_assign(
"den", detail::engine::integer(exact->denominator));
586 decimal.get_object().insert_or_assign(
587 "dp", detail::engine::integer(
static_cast<std::int64_t
>(exact->decimalPlaces.value)));
588 return detail::engine::single(
"lit", std::move(decimal));
591 [[nodiscard]]
static detail::engine::Json engagedRef(detail::engine::RuleOperands&
operands,
592 std::string
const& field) {
593 return detail::engine::single(
594 "ref", detail::engine::jsonText(
"f" + std::to_string(
operands.indexOf(field)) +
".engaged"));
597 [[nodiscard]]
static detail::engine::Json valueRef(detail::engine::RuleOperands&
operands,
598 std::string
const& field) {
599 return detail::engine::single(
600 "ref", detail::engine::jsonText(
"f" + std::to_string(
operands.indexOf(field)) +
".value"));
604 [[nodiscard]] detail::engine::Json verdictExpression(detail::engine::RuleOperands&
operands) {
605 using detail::engine::call;
606 using detail::engine::choose;
607 using detail::engine::Json;
609 Json
const condition = _children.front().holds(
operands);
611 engaged.reserve(_fields.size());
612 for (std::string
const& field : _fields) {
615 compileWhen(condition);
618 return choose(call(
"coalesce", {condition, detail::engine::literal(
false)}),
619 call(
"and", std::move(engaged)), detail::engine::literal(
true));
622 Json condition = _children.front().holds(
operands);
623 compileWhen(condition);
629 void compileWhen(detail::engine::Json
const& condition) {
630 if (
auto compiled = ui::parseExpression(condition)) {
631 _whenCompiled = *std::move(compiled);
638 [[nodiscard]] detail::engine::Json holds(detail::engine::RuleOperands&
operands)
const {
639 using detail::engine::call;
640 using detail::engine::choose;
641 using detail::engine::integer;
642 using detail::engine::Json;
643 using detail::engine::literal;
644 using detail::engine::single;
647 return engagedRef(
operands, _fields.front());
649 return call(
"not", {engagedRef(
operands, _fields.front())});
652 return choose(engagedRef(
operands, _fields.front()),
653 call(
"eq", {valueRef(
operands, _fields.front()), _literal}), literal(
false));
658 constexpr std::array<std::string_view, 4> kOps = {
"gt",
"ge",
"lt",
"le"};
659 auto const op = kOps.at(
static_cast<std::size_t
>(_kind) -
static_cast<std::size_t
>(
Kind::Greater));
660 std::string
const& lhs = _fields.front();
661 std::string
const& rhs = _fields.back();
663 return choose(call(
"and", {engagedRef(
operands, lhs), engagedRef(
operands, rhs)}),
664 call(op, {valueRef(
operands, lhs), valueRef(
operands, rhs)}), literal(
true));
670 engaged.reserve(_fields.size());
671 for (std::string
const& field : _fields) {
675 list.data.emplace<Json::array_t>(std::move(engaged));
676 Json
const all = single(
"list", list);
677 auto const counted = [&](Json where) {
678 Json comprehension = detail::engine::emptyObject();
679 comprehension.get_object().insert_or_assign(
"filter", all);
680 comprehension.get_object().insert_or_assign(
"as", detail::engine::jsonText(
"x"));
681 comprehension.get_object().insert_or_assign(
"where", std::move(where));
682 return call(
"count", {std::move(comprehension)});
684 Json
const x = single(
"ref", detail::engine::jsonText(
"x"));
685 Json
const definite = counted(x);
686 Json
const open = counted(call(
"not", {call(
"engaged", {x})}));
687 Json
const undecided = detail::engine::nullLiteral();
690 call(
"ge", {definite, integer(2)}), literal(
false),
691 choose(call(
"eq", {
open, integer(0)}), call(
"eq", {definite, integer(1)}), undecided));
694 return choose(call(
"ge", {definite, integer(1)}), literal(
true),
695 choose(call(
"eq", {
open, integer(0)}), literal(
false), undecided));
698 call(
"ge", {definite, integer(2)}), literal(
false),
699 choose(call(
"le", {call(
"add", {definite,
open}), integer(1)}), literal(
true), undecided));
703 if (_children.empty()) {
706 std::vector<Json> parts;
707 parts.reserve(_children.size());
708 for (RuleExpr
const& operand : _children) {
709 parts.push_back(operand.holds(
operands));
711 return call(_kind ==
Kind::And ?
"and" :
"or", std::move(parts));
714 return call(
"not", {_children.front().holds(
operands)});
720 return detail::engine::nullLiteral();
725 std::string _kindName;
726 std::vector<std::string> _fields;
727 std::vector<RuleExpr> _children;
728 detail::engine::Json _literal;
729 detail::engine::Json _expression;
730 std::vector<std::string> _operands;
731 ui::ExprPtr _compiled;
732 ui::ExprPtr _whenCompiled;