Skip to content

feat(conditionals): extensible operator engine + in/string/existence operators + grouping #128

Description

@vaceslav

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/.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions