Skip to content

warn on same-precedence prefix-pair traps (*/a b c, +-, etc.)#301

Merged
danieljohnmorris merged 3 commits into
mainfrom
fix/mul-div-precedence-diag
May 16, 2026
Merged

warn on same-precedence prefix-pair traps (*/a b c, +-, etc.)#301
danieljohnmorris merged 3 commits into
mainfrom
fix/mul-div-precedence-diag

Conversation

@danieljohnmorris
Copy link
Copy Markdown
Collaborator

Summary

*/a b c parses as (a/b)*c, not (a*b)/c. Same trap for /*, +-, -+. This is by design (outer prefix op binds inner subexpression as its left operand, matching the canonical +*a b c == (a*b)+c rule) but it consistently misleads agents reading the tokens left-to-right. Three persona logs in the assessment doc have hit it across three rerun sessions (logs-forensics entries at lines 844, 1056, 1651, 2833).

The token cost is real: a persona spends a turn debugging "why does my percentage formula give the wrong number?" then a second turn rewriting to r=*imp gl;pct=*r 100. A one-line hint at the terminal lets attempt #1 land correctly.

Manifesto framing: this is pure additive token-saving. The hint fires after a successful run, costs zero tokens when not relevant (suppressible with -nh), and saves a multi-turn debug loop when it is.

Repro before / after

$ ilo 'test a:n b:n c:n>n;*/a b c' test 6 2 3
9

Before: result is 9 (= (6/2)*3) with no warning. Persona reading the source as "multiply, then divide" expected 4 and silently believed the program.

After: same 9 result, plus

hint: `*/a b c` parses as `(a/b)*c` (inner prefix op binds first). For the other order, swap the ops (`/*a b c`) or bind: `r=*a b;/r c`

What's in the diff (per commit)

  1. warn on same-precedence prefix-pair traps in collect_hints — adds the detector in src/main.rs. Walks the token stream from lexer::lex(source), fires on the four mixed same-precedence pairs (*,/), (/,*), (+,-), (-,+) when both ops are in prefix position (predecessor is not value-yielding). Same-op repeats and different-precedence pairs are skipped because they match the left-to-right reading. 12 unit tests pin the firing, non-firing, predecessor, and negative-literal cases.
  2. add examples/prefix-mul-div.ilo for engine-harness coverage — three named functions exercise the trap, the swap, and the bind rewrite, with -- run: / -- out: assertions for tree / VM / Cranelift. Also serves as in-context learning material for agents who hit the trap and read examples to understand the fix.
  3. document the same-precedence prefix-pair trap — SPEC.md gets a section after the canonical prefix-nesting examples, ai.txt gets a matching row in the compact AI spec, and skills/ilo/SKILL.md gets a one-liner in the core-syntax block. Matching site update at ilo-lang/site@57bf147.

Test plan

  • cargo test --release --features cranelift — 22 collect_hints tests (12 new) pass, examples_engines runs the new example across tree / VM / Cranelift and passes.
  • cargo fmt --all -- --check clean.
  • cargo clippy --workspace --all-targets --features cranelift -- -D warnings clean.
  • Manual smoke: ilo run examples/prefix-mul-div.ilo mul-div-trap 6 2 3 prints 9 + hint. ilo 'f a:n b:n c:n>n;+*a b c' f 2 3 4 prints 10 with NO hint (different precedence, intuition matches). ilo 'f a:n b:n c:n>n;++a b c' f 2 3 4 prints 9 with NO hint (same-op repeat).
  • -nh suppresses the hint as expected.

Follow-ups

None. The fix is self-contained and the persona pattern is fully covered by the four shapes in the detector.

`*/a b c` parses as `*(/a b) c == (a/b)*c`, because the outer prefix
op binds the inner prefix subexpression as its left operand. Personas
reading `* / a b c` left-to-right expect `(a*b)/c` and silently get
the wrong answer (logs-forensics has hit this three sessions running).

Add a post-execution hint in `collect_hints` that detects the four
mixed same-precedence pairs (`*/`, `/*`, `+-`, `-+`) when both
tokens are in prefix position. The hint names the actual parse, the
swap form, and a concrete bind rewrite for the other grouping.

Same-op repeats (`++a b c == (a+b)+c`) and different-precedence
pairs (`+*a b c == (a*b)+c`) keep matching the left-to-right reading
and don't fire. Negative literals (`*-5 b c`) are absorbed by the
lexer so the pair never appears.

12 unit tests pin the four firing shapes, the three non-firing shapes
(same-op, different-precedence, infix), and the predecessor check
(values vs prefix position).
Three named functions cover the trap (`mul-div-trap`), the swap
(`mul-div-flip`), and the bind (`mul-then-div`). Each carries
`-- run:` / `-- out:` assertions so `tests/examples_engines.rs`
exercises them across tree, VM, and Cranelift on every CI run.

Also acts as an in-context learning example for agents who run into
the precedence trap and read the example file to understand the fix.
Adds a section to SPEC.md after the canonical prefix-nesting examples
explaining the four shapes that disagree with left-to-right reading,
plus the matching note in ai.txt (compact AI-spec format) and
skills/ilo/SKILL.md (Claude plugin marketplace skill).

Site has a matching update at ilo-lang/site#main (separate repo).
@codecov
Copy link
Copy Markdown

codecov Bot commented May 16, 2026

Codecov Report

❌ Patch coverage is 99.09910% with 1 line in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/main.rs 99.09% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@danieljohnmorris danieljohnmorris merged commit bdcb42a into main May 16, 2026
5 checks passed
@danieljohnmorris danieljohnmorris deleted the fix/mul-div-precedence-diag branch May 16, 2026 09:53
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