Skip to content

parser: clear arity error for deep prefix-binop chains#339

Merged
danieljohnmorris merged 2 commits into
mainfrom
fix/deep-prefix-arity
May 17, 2026
Merged

parser: clear arity error for deep prefix-binop chains#339
danieljohnmorris merged 2 commits into
mainfrom
fix/deep-prefix-arity

Conversation

@danieljohnmorris
Copy link
Copy Markdown
Collaborator

Summary

qa-tester P1 rerun #6: +++a b c and other N-prefix-with-N-operands shapes used to exit silently with a bare ILO-P010 expected expression, got EOF at line 1 col 1. The agent had no signal about which chain was at fault or how to reshape it. The parser now surfaces an ILO-P003 pointing at the chain start with the actual glyph and the operand shortfall.

Manifesto framing: every silent-EOF on a real input is a retry the agent can't avoid, because there's no information to act on. A focused arity message lets the next call land first time, which is exactly the token-cost win the language is here for.

Repro

Before:

$ ilo 'main>n;a=1;b=2;c=3;+++a b c'
{"code":"ILO-P010","labels":[{"col":1,"end":0,"line":1,"message":"here","primary":true,"start":0}],"message":"expected expression, got EOF",...}

After:

$ ilo 'main>n;a=1;b=2;c=3;+++a b c'
{"code":"ILO-P003","labels":[{"col":20,...}],"message":"prefix chain `+++` needs 4 operands but found 3","suggestion":"a run of 3 prefix operators takes 4 operands. Add operands (e.g. `+++a b c d`), shorten the chain, or bind intermediate results first: `r=+a b;…r` keeps each step explicit."}

What's in the diff

  • parser: detect deep prefix-binop chains with too few operands — adds prefix_chain_arity_diagnostic at the head of parse_prefix_binop, gated on scan_prefix_binary_end returning None so valid shapes (+a b, ++a b c, +++a b c d, +*a b c) stay on the existing path. The detector is conservative: only fires on K >= 2 leading prefix-binops, skips && / || (their dedicated hint still wins), and aborts on any unknown token in the operand area rather than risk a false positive. Counts unary wrappers (!~^$-) and atom postfix (.field, [i], (), !, with ...) so it doesn't miscount rich operands. Regression tests cover depths 2, 3, 4, a mixed-operator chain, and two positive shapes that must continue to parse cleanly.
  • examples: prefix-chain-arity shows the K+1 operand rule — companion .ilo file so an agent that hits the new diagnostic has an in-context template for either fix (provide K+1 operands or bind intermediate results). Runs across every engine via the existing examples harness.

Test plan

  • cargo test --release --features cranelift clean
  • cargo clippy --release --features cranelift --all-targets -- -D warnings clean
  • cargo fmt --check clean
  • Original repro now emits ILO-P003 with chain glyph + shortfall
  • Valid prefix forms (+a b, ++a b c, +++a b c d, +*a b c, &!x y, +p2.x p2.y) unchanged
  • &&x y and ||x y still hit their dedicated hint, not the new one
  • Examples harness exercises both deeparity and deepbind across all engines

Follow-ups

  • The detector's K >= 2 gate keeps single-op +a shapes on the existing ILO-P010 path. If a future feedback round shows +a (missing second operand) is also a friction source, the same helper can drop the gate once operand-postfix accounting handles every shape parse_operand does.

A run of N prefix-binop tokens (e.g. `+++a b c`) needs N+1 leaf
operands. When the input supplies only N, parse_prefix_binop used to
unwind recursively until parse_atom hit EOF and returned a bare
ILO-P010 "expected expression, got EOF" at line 1 col 1, leaving
the agent no signal about which chain was at fault.

The new prefix_chain_arity_diagnostic runs at the head of
parse_prefix_binop when scan_prefix_binary_end reports the chain
won't parse. It counts the leading prefix-binop run (K), then
counts atom-starting tokens up to the statement boundary, and
emits an ILO-P003 hint pointing at the chain start with the
actual glyph (`+++`, `+*>`, etc.) and the operand shortfall.

The heuristic only fires on K >= 2 to keep single-op forms like
`+p2.x p2.y` and `+r with f:v 2` on the existing path, since rich
postfix structure on operands can't be enumerated without
re-implementing operand parsing. Unknown tokens in the operand
area abort the diagnostic and fall through, so any shape this
scanner can't classify keeps its current error path. `&&` / `||`
are also skipped so their dedicated hint downstream still wins.

Regression tests cover depths 2, 3, 4, a mixed-operator chain,
and two positive shapes that must continue to parse cleanly.
Companion example for the parser fix. An agent that has hit the
new ILO-P003 arity diagnostic can read this file and pick up a
working template for either shape: providing K+1 operands, or
binding intermediate results step by step.

The example harness runs both functions across every engine, so
the file also doubles as a higher-level regression that the
canonical fixed forms keep evaluating to 10.
@codecov
Copy link
Copy Markdown

codecov Bot commented May 17, 2026

Codecov Report

❌ Patch coverage is 65.24064% with 65 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/parser/mod.rs 65.24% 65 Missing ⚠️

📢 Thoughts on this report? Let us know!

@danieljohnmorris danieljohnmorris merged commit 66daa59 into main May 17, 2026
4 of 5 checks passed
@danieljohnmorris danieljohnmorris deleted the fix/deep-prefix-arity branch May 17, 2026 12:19
danieljohnmorris added a commit that referenced this pull request May 17, 2026
fifteen fixes since 0.11.5, all from rerun5/rerun6 personas plus standing asks: ListView foundation (#334), window-text-perf reshape via ListView (#336), inner-flt predicate inlining (#340), double-minus trap ILO-P021 (#331), bare-ident bang silent-nil regression (#324), Cranelift JIT span plumbing (#335), bool-prefix ternary (#330), wh prefix-cond reparse (#332), --run-engine auto-pick main (#329), subcommand helper hyphens+non-ident (#328), runtime error spans (#335), persona-diagnostic batch 3 (#327), rgxall1+ct (#333), single-line body diagnostic (#322 carry), lambda type-var defensive test (#326), N-deep prefix arity error (#339), prefix-minus span column drift (#338), doc-sync (#337).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant