Skip to content

tailored ILO-P001 hint + docs for glued-negative-literal misparse#512

Merged
danieljohnmorris merged 5 commits into
mainfrom
fix/negative-literal-in-chain
May 21, 2026
Merged

tailored ILO-P001 hint + docs for glued-negative-literal misparse#512
danieljohnmorris merged 5 commits into
mainfrom
fix/negative-literal-in-chain

Conversation

@danieljohnmorris
Copy link
Copy Markdown
Collaborator

Summary

Closes pending.md #5ap (negative literal in operator chain).

When a user writes 0 -1.5 (no space before the dash, meaning "zero minus one point five") the lexer packs -1.5 into a single Number(-1.5) token because the previous token is an Ident/Number/RParen and not one of the keep-glued contexts where the lexer auto-splits -N back into Minus + Number. That's deliberate: the glued form is load-bearing for call-arg, list-element, and binop-operand shapes (mod n -2, +a -3, [1 -2 3], <r -0.05) - the existing regression_neg_literal_papercut.rs / regression_negative_literal_after_op.rs suites pin all of them. Splitting the glued token universally would break those.

So this isn't a parser bug to fix - it's a spacing convention that the docs and diagnostics never spelled out. The earlier session correctly stopped at the gate after discovering the lexer-rewrite path conflicts with load-bearing tests. This PR is the doc + diagnostic-only path: tell the user what shape they meant, in the moment they get it wrong.

Repro before / after

m > n
  0 -1.5
m

Before:

ILO-P001: expected declaration, got number `-1.5`

No hint. The user has no idea the issue is a missing space.

After:

ILO-P001: expected declaration, got number `-1.5`
hint: for subtraction, write `a - b` with spaces both sides (e.g. `0 - 1.5`).
      for a negative value as an expression, wrap in parens: `(-1.5)`.
      a glued `-N` (no space before) is only parsed as a negative literal
      when it's a call argument or inside a list.

The hint interpolates the offending value so the example is self-explanatory (works for ints too: 4 -1 suggests 0 - 1 and (-1)).

What's in the diff

Commit 1 (parser + tests + example): new Number(n) if *n < 0.0 arm in the parse_decl hint match in src/parser/mod.rs. No lexer changes. New cross-engine regression test tests/regression_negative_literal_diag.rs (4 tests) pinning the tailored hint text and the four idiomatic positive shapes. New examples/glued-negative-literal-spacing.ilo pinning the three shapes with -- run: / -- out: assertions, picked up by the existing examples harness.

Commit 2 (docs): SPEC.md arithmetic section, ai.txt neg-literal paragraph, skills/ilo/ilo-language.md operators line - all gain a "subtraction spacing" sentence pointing at the canonical form and the (-N) paren workaround. Site gotchas.md gets a new dedicated section (separate commit in the site repo, already pushed: ilo-lang/ilo-site@4be236d).

Test plan

  • new regression suite: cargo test --release --features cranelift --test regression_negative_literal_diag -> 4 passed
  • full neg-literal family unchanged: regression_neg_literal_papercut, regression_negative_literal_after_op, regression_minus_prefix_call, regression_minus_zero_decl_parse, regression_neg_literal_edge_pin all green cross-engine
  • 319 parser unit tests pass
  • cross-engine examples_engines test passes (includes the new example)
  • cargo fmt --check clean
  • cargo clippy --release --features cranelift --all-targets -- -D warnings clean
  • manual repro: 0 -1.5 now shows the tailored hint; 0 - 1.5 -> -1.5; mod n -2, [1 -2 3], (-1.5) all still work
  • CI green on push

Follow-ups

None - the alternative paths (lexer rewrite, comma-mode call args, context-sensitive Pratt) were ruled out by the earlier investigation as they'd break load-bearing tests. This is the right scope for a diagnostic papercut.

When a user writes 0 -1.5 (no space before the dash, meaning to subtract
1.5 from 0), the lexer packs -1.5 into one Number(-1.5) token because
the preceding Number does not trigger the split. The parser then sees
Number(0), Number(-1.5) and bails out of the body, re-entering
parse_decl which raised a generic 'expected declaration, got number
-1.5' with no hint.

Add a Number(n) if n < 0 arm to the parse_decl hint match that spells
out the spaces-both-sides convention and points at (-N) parens as the
workaround for a bare negative value. The hint interpolates the
offending number so the example is self-explanatory.

Lexer behaviour is unchanged: glued -N is still load-bearing for
mod n -2, +a -3, [1 -2 3], and similar call-arg / binop-operand /
list-element shapes (pinned by the existing neg-literal regression
suite). This is a diagnostic-only fix.

Regression test pins the hint text and the four positive shapes
(spaces-both-sides subtract, glued call-arg, list element, paren
negation) cross-engine. examples/glued-negative-literal-spacing.ilo
gives agents an in-context learning example of the three idiomatic
forms.
SPEC.md, ai.txt, and the ilo-language skill all mention the lexer's
glued-negative-literal behaviour but none of them flagged the
'spaces both sides for a - b' rule explicitly. Add a paragraph to
each spelling out the convention and pointing at (-N) parens as the
workaround for a bare negative value. Matches the new tailored
ILO-P001 hint emitted by the parser.
@codecov
Copy link
Copy Markdown

codecov Bot commented May 20, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ All tests successful. No failed tests found.

📢 Thoughts on this report? Let us know!

The subtraction-spacing addition pushed ilo-language.md from 987 to
1095 tokens, over the 1000-token per-module budget enforced by
scripts/check-skill-tokens.py. The SPEC, ai.txt, site gotchas page,
and the new tailored ILO-P001 hint already cover the rule end-to-end
- the skill module's job is rapid in-context recall, not duplicate
spec text, so the sentence is the right thing to drop.
The header used to claim the positive shapes ran on "all three engines",
but the tree-walker has been off the public CLI since 0.12.x. There is
no --tree flag to pass, so the only backends a CLI-level test like this
can drive are VM and (feature-gated) Cranelift JIT. Tree still runs
in-process as the HOF-callback fallback, so the VM arm transitively
covers it - note that here so future reviewers don't try to add a third
arm and find --tree rejected.
PR #512 added the tailored ILO-P001 hint and updated SPEC/ai.txt/site,
but the skill module ended up with no mention because the first attempt
blew the 1000-token budget and got dropped. Reviewer flagged that the
PR body still claimed a skill change that wasn't there.

Tighten the rest of the operators paragraph (drop a couple of `not` /
spacing words, trim "No compound: <=a b, not =<a b" to just the example)
to make room for one terse line covering the rule: glued -n is a
negative literal, bare `0 -1` errs ILO-P001. Lands the module at 999
tokens, one under the cap.
@danieljohnmorris danieljohnmorris merged commit 3fb2b97 into main May 21, 2026
5 checks passed
@danieljohnmorris danieljohnmorris deleted the fix/negative-literal-in-chain branch May 21, 2026 08:27
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