Skip to content

fix(parser): prev of binds unary-or-tighter, rejects non-name operand (#634)#667

Merged
InauguralPhysicist merged 1 commit into
mainfrom
fix/prev-of-precedence-634
Jul 18, 2026
Merged

fix(parser): prev of binds unary-or-tighter, rejects non-name operand (#634)#667
InauguralPhysicist merged 1 commit into
mainfrom
fix/prev-of-precedence-634

Conversation

@InauguralPhysicist

Copy link
Copy Markdown
Collaborator

Closes #634

prev of was the only of in the language that didn't bind unary-or-tighter. The operand parser called parse_expression where every other of uses parse_unary, so prev of x + 1 parsed as prev of (x + 1) and silently returned null — the whole trailing expression was swallowed.

x is 10
x is 20
prev of x          # 10
(prev of x) + 1    # 11
prev of x + 1      # null   <- should be 11

It's now (prev of x) + 1, matching SPEC.md's Application rule (item 2: "of binds tighter than arithmetic: f of x + 1 is (f of x) + 1") — which prev was the lone exception to. Every occurrence in the suite was written parenthesized, so the form the spec's own rule requires was never tested — textbook "works only because the examples are too easy."

Secondary silent-tolerance

The parser's own comment says "only IDENT operands are meaningful", yet a non-name operand returned null rather than erroring. prev looks back through a variable's assignment history — a literal, index/dot, or parenthesised expression has no trajectory. It's now a clean parse error, so the program doesn't run instead of propagating a null:

$ echo 'x is 5; r is prev of (x + 1)' | eigenscript
Parse error line 1: 'prev of' requires a variable name

Interactions verified unchanged

This was the design-sensitive part — prev is a soft keyword with special forms:

  • at suffix: prev of x at L still works (the suffix is parsed after the operand).
  • identifier fallback: bare prev as a variable with postfix — prev[0], prev.k — untouched (different parse branch).
  • test_temporal.eigs and test_soft_keyword_idents.eigs both still pass.

(parse_unary needed a forward declaration before parse_primary.)

Tests

  • test_soft_keyword_idents.eigs SK19/b/c: the unparenthesized +/*/- forms — each returned null before the fix.
  • Runner [70b]: prev of (x+1) is a parse error and the program doesn't run.
  • SPEC.md's temporal section documents the operand rule (prose, outside the byte-checked example blocks).

Also backfills the [Unreleased] Fixed CHANGELOG entries for #630, #632, #631, which merged without them.

Gates

  • Release suite 3086/3086
  • ASan + UBSan detect_leaks=1 3090/3090, leak tally 0
  • Doc examples 71/71, doc-drift clean

🤖 Generated with Claude Code

…perand (#634)

prev of was the only `of` in the language that did not bind unary-or-tighter.
The operand parser called parse_expression where every other `of` uses
parse_unary (the OF handler's documented rule), so `prev of x + 1` parsed as
`prev of (x + 1)` and silently returned null — the whole trailing expression
was swallowed. It is now `(prev of x) + 1`, matching SPEC.md's Application
rule (item 2), which prev was the lone exception to.

Every occurrence in the suite was written parenthesized — `(prev of x)` — so
the unparenthesized form the spec's own rule requires was never exercised.

Secondary silent-tolerance, from the parser's own comment ("only IDENT
operands are meaningful"): a non-name operand — a literal, an index/dot, or a
parenthesised expression — has no assignment history to look back through, yet
returned null rather than erroring. It is now a clean parse error, so the
program does not run instead of propagating a null.

Interactions verified unchanged: the `at` suffix (`prev of x at L`), the bare
`prev` identifier fallback with postfix (`prev[0]`, `prev.k`), and
test_temporal / test_soft_keyword_idents all still pass. parse_unary needed a
forward declaration before parse_primary.

Tests: SK19/SK19b/SK19c in test_soft_keyword_idents.eigs assert the
unparenthesized +/*/- forms (fail before the fix, which returned null); a new
[70b] runner block asserts `prev of (x+1)` is a parse error and does not run.
SPEC.md's temporal section documents the operand rule.

Also backfills the CHANGELOG [Unreleased] Fixed entries for #630, #632, #631,
which merged without them.

Suite 3086/3086 release, 3090/3090 ASan+UBSan (detect_leaks=1, tally 0); doc
examples 71/71, doc-drift clean.
@InauguralPhysicist
InauguralPhysicist merged commit 01a1ef1 into main Jul 18, 2026
18 checks passed
@InauguralPhysicist
InauguralPhysicist deleted the fix/prev-of-precedence-634 branch July 18, 2026 05:22
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.

prev of x + 1 parses as prev of (x + 1) and silently yields null — the only 'of' that breaks unary-or-tighter

1 participant