Skip to content

PATTERN Cited by 1 source

Expression-def triples DSL

What it is

A boolean-logic DSL built from a single primitive — [left, op, right] triples — composed via and / or / not combinators. The universal shape:

Expression :=
  | [Field, Op, Value | FieldRef]                 -- binary expression
  | { and: Expression[] }
  | { or:  Expression[] }
  | { not: Expression  }                          -- sometimes included

Op ∈ { "=", "<>", ">", "<", ">=", "<=" }          -- often extensible

A Field is typically a "table.column" or entity.attribute string. A FieldRef distinguishes a reference from a literal — Figma uses {type: "field", ref: "user.id"} — so ["file.owner_id", "=", {ref: "user.id"}] means equal to the user's id, vs ["file.status", "=", "open"] which compares to the scalar string "open".

Why it matters

  • Minimal core — six binary operators and three combinators is expressive enough for most policy / filter / query workloads.
  • JSON-serializable by construction — the entire DSL is plain data (concepts/json-serializable-dsl); any language with a JSON parser can consume it.
  • Trivial static analysis — a recursive walk is enough to extract data dependencies, detect logic bugs, or transform the tree.
  • Cross-language evaluator parity — Figma reports 2–3 days for a senior engineer to write a new language's evaluator; shared test suites guarantee semantic consistency.
  • Composable helpers — wrap common sub-expressions as functions in the authoring surface (e.g. isOrgFile(File), teamUserHasPaidStatusOnFile(...)) that return ExpressionDef; this is how Figma scales without losing structural simplicity.

Canonical instances

  • Figma's permissions DSL — the ExpressionDef type: binary expressions + and + or + not, with {type:"field",ref:"..."} references — canonical instance per the 2026-04-21 article. LiveGraph's original ExpressionDef predates and inspired this.
  • Elasticsearch Query DSLbool + must / should / must_not trees over term/range queries.
  • MongoDB query operators$and / $or / $not over field-operator-value documents.
  • AWS IAM Condition block — structurally similar (operator keys + field-value pairs), though with more embedded semantics.

Design knobs

  • Binary operators — the six comparison ops are the minimum useful set; projects add in, like, exists, regex, set-membership as needed, weighing added expressivity against evaluator complexity + analyzability loss.
  • Field-reference representation — an explicit wrapper ({type:"field", ref:"..."}) is cleaner than inferring by prefix convention; prevents ambiguity when strings are also valid scalars.
  • Operator extensibility — keeping the set small is a feature; every addition must be implemented in every evaluator.
  • Combinator setand + or is the minimal boolean basis; not is convenient but can be avoided by transforming (e.g. De Morgan).

Caveats

  • Arithmetic and function calls don't fit cleanly — can't express "file.size > team.quota * 0.9". Either extended with special operators or kept out.
  • Recursion / loops can't be expressed directly. Most policy/query workloads don't need them; some do (e.g. ReBAC-style "transitively a member of").
  • The triples DSL doesn't force the JSON-serializable property — a Lisp-like AST is structurally similar but usually code, not data. Serializability is the follow-on choice (concepts/json-serializable-dsl).

Seen in

Last updated · 200 distilled / 1,178 read