Skip to content

Replace pest expression grammar with a Pratt parser and add first-class function support#13

Merged
walker84837 merged 4 commits into
mainfrom
feature/pratt-parser-for-expr
Jul 11, 2026
Merged

Replace pest expression grammar with a Pratt parser and add first-class function support#13
walker84837 merged 4 commits into
mainfrom
feature/pratt-parser-for-expr

Conversation

@walker84837

Copy link
Copy Markdown
Collaborator

Motivation

The expression grammar was implemented as 13 mutually recursive pest rules (expr -> assign -> logical_or -> ... -> primary), one per precedence level. Because each level recurses into the next, deeply nested or long expressions could blow past the default 1 MB stack on Windows, causing the compiler to crash instead of reporting a parse error.

Separately, the language had no way to treat a function as a value. Calls were parsed as a bare callee name (Expr::Call { callee: String, .. }), so expressions like passing a function as an argument and invoking it indirectly (g(f, 4)) could not be represented, let alone type-checked or transpiled to C function pointers.

Solution

  • Introduced a hand-written Pratt parser that replaces the old pest-based recursive-descent expression rules with a single binding-power loop. This reduces the call stack depth for chained/nested expressions.

    Precedence and associativity are now defined declaratively, and parser-internal errors are represented as a small structured ExprParseError type that is only converted into the public CompilationError at one seam. Pest now hands each expr pair's source text to the new parser.

  • Added a Logos-based lexer to tokenize expression source text for the Pratt parser, with byte spans on each token to support future diagnostics.

  • Changed Expr::Call.callee from a plain String to a boxed Expr, so any expression can be called.

    A callee_name helper recovers the simple/qualified name for the common cases (named functions, etc.) while indirect calls fall back to inferring the callee's type.

  • Added a Type::Function variant & parsing of function type annotations, so functions can be registered as first-class values in the symbol table and passed around.

  • Updated code generation to emit proper C function ptr syntax (ret (*name)(params)) for function-typed parameters, variables, and struct/enum fields, and to transpile indirect calls as (callee_expr)(args).

  • Added an indirect_call test trying a function passed as an argument and invoked through a parameter.

The pest-based expression parser ran out of stack on Windows. The
recursive descent consumed one boxed `Pair<Rule>` per grammar level,
and the grammar had grown deep enough (struct inits, array indexing,
range literals) that 1MB of stack was no longer enough.

Replace it with a hand-written Pratt parser that operates on a token
stream instead, so recursion depth tracks expression nesting rather
than grammar-tree depth.

Range literal (a...b) support moves into the Pratt infix layer with
binding power 2/2 (looser than assignment, tighter than logical or) and
produces Expr::RangeLiteral, matching the pest grammar's range_expr
structure.

Summary of changes:
- Re-add logos to dependencies
- Introduce Tok, SpannedTok, tokenize, and tok_name in lexer.rs
- Add kitlang/src/codegen/parser/diagnostics.rs
- Add kitlang/src/codegen/parser/expr_pratt.rs, containing Pratt core
- Wire Parser::parse_expr to PestExpr -> ExprParser pipeline
- Add Tok::Ellipsis as a Pratt infix op building Expr::RangeLiteral
- Move struct-init / field-init semantics into the Pratt path
- Delete kitlang/src/codegen/parser/expr.rs (pest-based parse_expr)
- Drop dead parse_bool_literal and unescape helpers from parser/mod.rs
- Add range-literal unit tests:
  * range_literal_simple
  * range_literal_with_expressions
- Mark diagnostic seams with #[allow(dead_code)] for the follow-up that
  will wire them in
The single 1200-line expr_pratt.rs mixed parser core, primary expression
parsing, and tests, making it hard to navigate.

The diagnostics module also duplicated its token-to-name mapping between
tok_name and a local expected_name helper.

Summary of changes:
- Split expr_pratt.rs into mod.rs, primary.rs, and tests.rs via include!
- Make diagnostics::expected_name the single canonical token-name match
  and derive tok_name from it instead of duplicating the match
- Remove PlusPlus/MinusMinus from prefix binding power (not prefix ops)
- Drop now-unneeded #[allow(dead_code)] attributes on used items
The lexer now properly reports unrecognized characters and integer
overflow instead of silently dropping them.

The parser validates no trailing tokens remain and provides better EOF
error messages.

Unsupported constructs like indirect calls are now explicitly rejected
with clear diagnostics.

Summary of changes:
- Change tokenize() to return Result and report lexical errors
- Add LexicalError enum for UnexpectedCharacter and IntegerOverflow
- Validate trailing tokens in parse_kit_expr()
- Distinguish UnexpectedEof from UnexpectedToken in errors
- Reject indirect function calls with clear diagnostic message
- Fix binding power checks in assignment operator handling
- Update expect() to return UnexpectedEof when at EOF
- Improve documentation accuracy and clarity
- Add comprehensive test coverage for all error cases
kitc now supports passing functions as first-class values, enabling
higher-order programming patterns. Functions can be passed as arguments,
returned from functions, and stored in variables. The parser, type
system, and code generator were updated to handle function types
throughout the compilation pipeline.

Summary of changes:
- Change Call expression callee from String to Box<Expr> for indirect
  calls
- Add Function type variant to support function-type values
- Update Pratt parser to allow and parse indirect call expressions
- Implement type inference for indirect calls via three paths: named,
  indirect, C interop
- Register functions as first-class values in symbol table with function
  type
- Add type unification logic for function types with parameter matching
- Add function pointer parameter formatting with correct C declarator
  syntax
- Support transpiling both named and indirect function calls
- Remove parse-time rejection of indirect calls; validate at type-check
  time
- Add test example demonstrating indirect function calls
- Update parser tests to reflect indirect call support
- Remove unused expr_to_callee_name function and replace with
  callee_name
@walker84837 walker84837 marked this pull request as ready for review July 11, 2026 10:32
@walker84837 walker84837 merged commit 73f08b1 into main Jul 11, 2026
3 checks passed
@walker84837 walker84837 deleted the feature/pratt-parser-for-expr branch July 11, 2026 10:32
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