Files
stefan-gorules ffdfd807d1 feat: implement slots instead of natural language (#527)
* feat: implement slots instead of natural language

Replace the natural-language projection (`nl`) with engine-owned slot
completion: for a cursor position the engine classifies the slot being
edited (operand, operator, literal, member access, ...) and returns typed
options, literal facts and the expected type, for policies and graphs.

- zen-expression: new `slot` module (classification, literals, operators)
  and a lenient lexer mode for incomplete input; `nl` removed
- Intellisense: typed diagnostic codes with arguments, richer inspect for
  hover, dedicated parser error variants (messages unchanged)
- Engine: `Workspace::cursor_scope`, `slot`, `facts`; `rename_from` and
  `references_from`; editor spans in UTF-16 units; renames reach imported
  policies, `$nodes` reads, child graphs and output column heads; no `$root`
  completion in policies
- Node bindings: `slot`, `facts`, `cursorScope`, `slotBatch` replace
  `nl`, `nlTokenize`, `nlEncodeString`, `nlTokenizeBatch`

No change to expression evaluation: the VM, compiler, functions and the
default lexer path are untouched, and parser error messages keep their text.

* fix: harden slot classification, scopes and graph expectations

- restore master execution order; slot scopes hide writes of the edited
  block and its dependents instead of relying on block ranking
- token-start editing gated by slot state; operator, list and operand
  spans no longer splice into neighbouring tokens
- interval-aware bracket pairing for half-open and reversed intervals
- lenient lexer recovers from unknown characters (slots only)
- graph expected type follows switch and pass-through nodes and merges
  compatible output schemas
- sibling inference caps distinct values and analyses lazily
- deterministic overload return type display

* fix: order policy blocks by entity reads through closures and relationships

- record reads for `#.field` in closures so entity fields read through
  lists become dependencies
- link reads through derived lists (filter/map results) to the entity
  fields they carry and order the list after those fields
- evaluate entity blocks on single relationships, and demand entity
  paths for plain reads through relationships
- slot scopes keep the top-level variable when hiding written fields
2026-09-28 21:47:40 +02:00

383 lines
7.4 KiB
TOML

# Cursor scope per cell: kind, role, expected type and unary subject.
# Optional `text` (with `|` caret) checks the slot the editor gets there.
policies = ["slots.json"]
graphs = ["slots_graph_dicts.json", "slots_graph.json"]
# Policy decision table
[[test]]
name = "input cell on a declared field is unary over the field"
policy = "slots.json"
block_id = "dt"
target = "cell"
id = "in_declared"
row = "r1"
kind = "unary"
role = "unary"
subject = "number"
[[test]]
name = "input cell on a dictionary field offers labelled values"
policy = "slots.json"
block_id = "dt"
target = "cell"
id = "in_dict"
row = "r2"
kind = "unary"
subject = "status"
text = "|"
state = "unaryStart"
options = ["open=Open case", "closed=Closed"]
[[test]]
name = "input cell on a declared date field is a real date"
policy = "slots.json"
block_id = "dt"
target = "cell"
id = "in_since"
row = "r1"
kind = "unary"
subject = "date"
[[test]]
name = "input cell on d(field) is a real date"
policy = "slots.json"
block_id = "dt"
target = "cell"
id = "in_since_date"
row = "r1"
kind = "unary"
subject = "date"
[[test]]
name = "input cell on a computed field is unary over its result"
policy = "slots.json"
block_id = "dt"
target = "cell"
id = "in_computed"
row = "r1"
kind = "unary"
subject = "number"
[[test]]
name = "input column without a field is a condition"
policy = "slots.json"
block_id = "dt"
target = "cell"
id = "in_condition"
row = "r1"
kind = "standard"
role = "condition"
expected = "bool"
[[test]]
name = "output cell with a dictionary type offers labelled values"
policy = "slots.json"
block_id = "dt"
target = "cell"
id = "out_dict"
row = "r3"
role = "value"
expected = "status"
text = "|"
state = "value"
options = ["open=Open case", "closed=Closed"]
[[test]]
name = "output cell with a declared type"
policy = "slots.json"
block_id = "dt"
target = "cell"
id = "out_number"
row = "r3"
role = "value"
expected = "number"
[[test]]
name = "output cell on a declared field expects the field type"
policy = "slots.json"
block_id = "dt"
target = "cell"
id = "out_declared"
row = "r3"
role = "value"
expected = '"gold" | "silver"'
[[test]]
name = "undeclared output cell uses the sibling literal union"
policy = "slots.json"
block_id = "dt"
target = "cell"
id = "out_union"
row = "r3"
role = "value"
expected = '"a" | "b"'
[[test]]
name = "policy tables keep the written union for every row"
policy = "slots.json"
block_id = "dt"
target = "cell"
id = "out_union"
row = "r1"
expected = '"a" | "b"'
[[test]]
name = "output cell with non-literal siblings has no expectation"
policy = "slots.json"
block_id = "dt"
target = "cell"
id = "out_untyped"
row = "r3"
role = "value"
expected = ""
[[test]]
name = "input column head is an expression"
policy = "slots.json"
block_id = "dt"
target = "head"
id = "in_computed"
kind = "standard"
role = "value"
# Policy expression, assertion and match blocks
[[test]]
name = "expression value expects the declared key type"
policy = "slots.json"
block_id = "ex_declared"
target = "expression"
id = "ex_declared"
role = "value"
expected = '"gold" | "silver"'
[[test]]
name = "expression value with an undeclared key has no expectation"
policy = "slots.json"
block_id = "ex_computed"
target = "expression"
id = "ex_computed"
role = "value"
expected = ""
[[test]]
name = "expression key is a path"
policy = "slots.json"
block_id = "ex_declared"
target = "expression_key"
role = "path"
[[test]]
name = "assertion condition"
policy = "slots.json"
block_id = "as1"
target = "expression"
id = "c1"
role = "condition"
expected = "bool"
[[test]]
name = "assertion output is a path"
policy = "slots.json"
block_id = "as1"
target = "assertion_output"
role = "path"
[[test]]
name = "match target is a path"
policy = "slots.json"
block_id = "m_declared"
target = "match_target"
role = "path"
[[test]]
name = "match arm condition"
policy = "slots.json"
block_id = "m_declared"
target = "expression"
id = "a1"
role = "condition"
expected = "bool"
[[test]]
name = "match value on a declared key offers labelled values"
policy = "slots.json"
block_id = "m_declared"
target = "match_value"
id = "a1"
role = "value"
expected = "status"
text = "|"
state = "value"
options = ["open=Open case", "closed=Closed"]
[[test]]
name = "match value on an undeclared key ignores sibling arms"
policy = "slots.json"
block_id = "m_union"
target = "match_value"
id = "b2"
role = "value"
expected = ""
[[test]]
name = "data model has no scope"
policy = "slots.json"
block_id = "dm"
target = "data_model_name"
[[test]]
name = "unknown block has no scope"
policy = "slots.json"
block_id = "missing"
target = "expression"
id = "x"
# Graph
[[test]]
name = "graph expression row not wired to an output has no expectation"
policy = "slots_graph.json"
block_id = "calc"
target = "expression"
id = "row_total"
role = "value"
expected = ""
[[test]]
name = "graph expression row outside the schema has no expectation"
policy = "slots_graph.json"
block_id = "calc"
target = "expression"
id = "row_extra"
role = "value"
expected = ""
[[test]]
name = "graph expression key is a path"
policy = "slots_graph.json"
block_id = "calc"
target = "expression_key"
role = "path"
[[test]]
name = "graph transform input is an expression"
policy = "slots_graph.json"
block_id = "calc"
target = "transform_input"
role = "value"
[[test]]
name = "switch node has no transform input"
policy = "slots_graph.json"
block_id = "sw"
target = "transform_input"
[[test]]
name = "graph switch statement is a condition"
policy = "slots_graph.json"
block_id = "sw"
target = "expression"
id = "s1"
role = "condition"
expected = "bool"
[[test]]
name = "graph table input cell is unary over the field"
policy = "slots_graph.json"
block_id = "dt"
target = "cell"
id = "c_field"
row = "r1"
kind = "unary"
subject = "number"
[[test]]
name = "graph table condition column"
policy = "slots_graph.json"
block_id = "dt"
target = "cell"
id = "c_condition"
row = "r1"
role = "condition"
[[test]]
name = "graph output cell with a dictionary type offers labelled values"
policy = "slots_graph.json"
block_id = "dt"
target = "cell"
id = "o_dict"
row = "r1"
role = "value"
expected = "customerTier"
text = "|"
state = "value"
options = ["VIP=Very important", "STD=Standard"]
[[test]]
name = "graph output cell falls back to the output schema"
policy = "slots_graph.json"
block_id = "dt"
target = "cell"
id = "o_schema"
row = "r1"
expected = "number"
[[test]]
name = "graph output schema dictionary offers labelled values"
policy = "slots_graph.json"
block_id = "dt"
target = "cell"
id = "o_schema_dict"
row = "r1"
expected = "customerTier"
text = "|"
state = "value"
options = ["VIP=Very important", "STD=Standard"]
[[test]]
name = "graph undeclared bool column uses sibling literals"
policy = "slots_graph.json"
block_id = "dt_untyped"
target = "cell"
id = "o_bool"
row = "u3"
expected = "bool"
[[test]]
name = "graph undeclared string column uses the sibling union"
policy = "slots_graph.json"
block_id = "dt_untyped"
target = "cell"
id = "o_str"
row = "u3"
expected = '"a" | "b"'
[[test]]
name = "graph sibling union excludes the edited row"
policy = "slots_graph.json"
block_id = "dt_untyped"
target = "cell"
id = "o_str"
row = "u1"
expected = '"b"'
[[test]]
name = "graph mixed sibling literals have no expectation"
policy = "slots_graph.json"
block_id = "dt_untyped"
target = "cell"
id = "o_mixed"
row = "u3"
expected = ""
[[test]]
name = "graph column head is a path"
policy = "slots_graph.json"
block_id = "dt"
target = "head"
id = "o_dict"
role = "path"