- Rust 98.5%
- Shell 1.5%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
Step 30 of `docs/parser/`: the expression grammar — a Pratt / precedence-climbing
reader over the binding-power table in `language-spec.md` ▸ Syntax ▸ Precedence
and Associativity.
## What's here
- `infix_op()` — the whole operator table in one place, so a transposed row is a
one-line diff rather than a scattered edit.
- `parse_bp` / `parse_unary` / `parse_postfix` / `parse_primary`, plus
`parse_tuple_or_group`, `parse_array_value`, `parse_new_value`,
`parse_new_array`, `parse_call_args`, `parse_brace_elems`.
- No backtracking. A `(` after a completed primary is a call; a `(` starting a
primary is a tuple or a group — position alone settles it. The one two-token
peek (`[` `]` `{`, telling `[] { 1, 2 }` from `[]int`) uses a new bounded
`Cursor::nth`.
- `array_size` is now shared by `[N]T` and `new [N]T`, so the two cannot
disagree about the 64-bit limit.
Ranges and `is` are non-repeating per the grammar's `[ ... ]`, so `a..b..c` and
`x is A is B` are refused at the second operator. Parenthesized, `(a..b)..c`
still parses.
## Four conflicts I resolved rather than papered over
The step doc says to stop and flag rather than silently reorder, so these are
the calls worth reviewing:
1. **`a??b??c`.** `docs/parser/30-expressions.md` asserted `(?? a (?? b c))` and
labelled it "left-assoc" — self-contradictory. The spec's table (level 16,
Left-to-right) and `language-grammar.md` both give `(?? (?? a b) c)`. I
implemented the spec's reading and corrected the doc.
2. **`BinOp::Is` was unbuildable.** `is` reads a *type* on the right, so it
cannot be a `BinOp` over two `Expr`s. Replaced with
`Expr::TypeTest(Expr, TypeRef)`, symmetric with the existing `Cast`.
3. **`Expr::Paren` removed.** With it, `(1+2)*3` dumps `(* ((+ 1 2)) 3)`,
contradicting the step doc's own acceptance line. Parens now group without a
node, exactly as `(int)` yields `int` in the type grammar.
4. **`AssignOp` gained `&&=` / `||=`**, which the spec's level-18 row lists and
the lexer already emits. `IntLit` widened to `u128` to match the scanner's
payload — narrowing would have refused legal `s128`/`u128` literals.
Plus one grammar fix: `postfix` made `f()` underivable, but
`null-coalescing.cat` writes `obj?.DoMethod()`. Now `"(" [ call-args ] ")"`.
## Deferred to step 50 (as the step doc says)
`func-value` lambdas, and `this` as a primary. `this` is a reserved keyword with
no AST node and no fixture — its only occurrences are inside `${...}`
interpolation, which the lexer keeps verbatim. Inventing a node without a
fixture would break the fixture-first contract.
## Verification
14 new unit tests, RED before GREEN: one per boundary between adjacent
precedence levels, both associativity directions, the `-x as u16` case that
tells the two readings of `as` apart, postfix chains, call-args vs tuples,
malformed-input offsets, and span coverage.
- `cargo fmt --check` — clean
- `cargo clippy --all --all-targets -- -D warnings` — clean
- `cargo test --all` — 56 passed, 0 failed
The `parse`-phase fixture driver stays `#[ignore]`d; it goes green at step 70,
per its own note.
## Incidental finding
`a.1` never reaches the member rule — the scanner reads `.1` as a float literal.
Harmless today, since Catlang has no `.0` tuple-field syntax (the fixtures index
tuples with `point[0]`). Noted in the test rather than worked around.
Co-authored-by: Alic Szecsei <aszecsei@gmail.com>
Reviewed-on: #8
|
||
| .githooks | ||
| docs | ||
| lexer | ||
| parser | ||
| tests/fixtures | ||
| .gitignore | ||
| AGENTS.md | ||
| Cargo.toml | ||
| CLAUDE.md | ||
| CRATE.md | ||
| README.md | ||
Catlang
A systems programming language for low-level work (game development et al.), with a C-like syntax, static typing, and no automatic memory management.
Designed for both speed and kindness.
Layout
AGENTS.md working conventions (read this first)
README.md you are here
docs/prd/ product + language specification
├─ 0-overview.md vision, tenets, memory model, object model
├─ 0-feature-manifest.md every feature → its test fixture + compiler phase
└─ language-spec.md the language reference
tests/fixtures/ one tiny .cat program per feature (the TDD contract)
├─ lex/ must tokenize
├─ parse/ must build a syntax tree
└─ semantics/ must pass semantic analysis (full front end)
The project so far
The spec is source-of-truth: docs/prd/language-spec.md alongside the
overview and the feature manifest. The fixtures are the tests: each one is a
small program lifted from the spec's own examples, and every language feature maps
to exactly one fixture that the growing compiler pipeline must accept.
The compiler itself does not exist yet — that is the work. Development is
test-driven and proceeds front-end first: make the lex fixtures tokenize, then
parse, then compile, adding features as the fixtures demand (RED → implement →
GREEN → commit).
See AGENTS.md for the full conventions.