Summary
Introduce new operators to the condition evaluator and rearchitect its core to be truly extensible, so future operators can be added without breaking existing stored conditions/templates or existing tests and without rewriting the parser.
Motivation
The condition evaluator is used both by the template engine ({{#if ...}}) and as a standalone API (IConditionEvaluator, used e.g. in ViasPro). Today it only supports =, ==, !=, >, <, >=, <=, and, or, not. Real-world conditions on customer data need membership, substring and existence checks. The current engine is a flat left-to-right token loop with a hardcoded switch — every new operator means editing the core, and there is no grouping/precedence support.
Scope
New operators
in — collection membership (Status in Roles). Negation via not (not Status in Roles).
- Right-hand side forms: collection variable, list literal
("A", "B"), comma-separated string "A,B".
- String operators (case-sensitive, ordinal — consistent with
=): contains, startswith, endswith.
- Existence/emptiness (postfix):
Foo exists, Foo is empty, Foo is not empty.
Grouping
- Parentheses for explicit grouping:
(A or B) and C.
Architecture (future-proofing)
- Rewrite the core as lexer → parser (AST) → operator registry → evaluator.
- Adding a future operator = registering one
IConditionOperator class; no parser changes.
Backward compatibility (hard requirement)
- All existing unit and integration tests must pass unchanged — this is the compatibility oracle.
and/or keep equal precedence, left-associative (current left-to-right behavior). We do NOT introduce standard and > or precedence, since that would silently change existing stored conditions.
- Public API signatures (
IConditionEvaluator, ConditionEvaluator, ConditionContext) unchanged.
Semantics notes
in element comparison reuses existing equality semantics (AreEqual).
exists = variable is present in context (TryResolveVariable == true), regardless of value.
is empty = value is null / empty-or-whitespace string / empty collection; a missing variable is also is empty.
Out of scope
- Collection size comparison in conditions (
Items > 0) — separate concern.
- Regex
matches, between — deferred (YAGNI).
- User-defined operators via public registry — architecture allows it, not implemented now.
A full design spec will be added under docs/superpowers/specs/.
Summary
Introduce new operators to the condition evaluator and rearchitect its core to be truly extensible, so future operators can be added without breaking existing stored conditions/templates or existing tests and without rewriting the parser.
Motivation
The condition evaluator is used both by the template engine (
{{#if ...}}) and as a standalone API (IConditionEvaluator, used e.g. in ViasPro). Today it only supports=,==,!=,>,<,>=,<=,and,or,not. Real-world conditions on customer data need membership, substring and existence checks. The current engine is a flat left-to-right token loop with a hardcodedswitch— every new operator means editing the core, and there is no grouping/precedence support.Scope
New operators
in— collection membership (Status in Roles). Negation vianot(not Status in Roles).("A", "B"), comma-separated string"A,B".=):contains,startswith,endswith.Foo exists,Foo is empty,Foo is not empty.Grouping
(A or B) and C.Architecture (future-proofing)
IConditionOperatorclass; no parser changes.Backward compatibility (hard requirement)
and/orkeep equal precedence, left-associative (current left-to-right behavior). We do NOT introduce standardand > orprecedence, since that would silently change existing stored conditions.IConditionEvaluator,ConditionEvaluator,ConditionContext) unchanged.Semantics notes
inelement comparison reuses existing equality semantics (AreEqual).exists= variable is present in context (TryResolveVariable == true), regardless of value.is empty= value is null / empty-or-whitespace string / empty collection; a missing variable is alsois empty.Out of scope
Items > 0) — separate concern.matches,between— deferred (YAGNI).A full design spec will be added under
docs/superpowers/specs/.