Sitelet https://github.com/starkware-libs/cairo/pull/10141
Skip to content

performance(parser): Stream the lexer with a lookahead window instead of cloning all tokens. - #10141

Merged
orizi merged 1 commit into
mainfrom
orizi/06-21-performance_parser_stream_the_lexer_with_a_lookahead_window_instead_of_cloning_all_tokens
Jun 22, 2026
Merged

orizi merged 1 commit into
mainfrom
orizi/06-21-performance_parser_stream_the_lexer_with_a_lookahead_window_instead_of_cloning_all_tokens

Conversation

@orizi

@orizi orizi commented Jun 21, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Replaces the pre-tokenized Deque<LexerTerminal> in Parser with a lazily-driven Lexer instance. Instead of calling tokenize_all upfront to lex the entire file before parsing begins, the parser now pulls tokens from the lexer on demand via a small VecDeque<LexerTerminal> lookahead window (current_terminals). The window is kept filled to at least two terminals (for next_terminal/next_next_terminal) and refilled to three before each advance() call. An eof flag tracks when the lexer has emitted TerminalEndOfFile so no further pulls are attempted. Lexer::new and Lexer::match_terminal are made pub to support this.


Type of change

Please check one:

  • Bug fix (fixes incorrect behavior)
  • New feature
  • Performance improvement
  • Documentation change with concrete technical impact
  • Style, wording, formatting, or typo-only change

Why is this change needed?

Previously, the parser eagerly lexed the entire source file into a Deque before any parsing work began. This meant the full token stream was allocated in memory upfront. By switching to lazy lexing, tokens are produced only as the parser consumes them, reducing peak memory usage and avoiding unnecessary work for early-exit or partial-parse scenarios.


What was the behavior or documentation before?

Parser::new called tokenize_all, which lexed the entire input into a Deque<LexerTerminal> before returning. The parser then consumed from that pre-filled deque.


What is the behavior or documentation after?

Parser::new constructs a Lexer directly and pre-fills only the first two terminals needed for lookahead. Subsequent terminals are pulled from the lexer one at a time as parsing advances, with the lookahead window never growing beyond a small constant size.


Related issue or discussion (if any)


Additional context

The cairo_lang_utils::deque::Deque import is replaced by std::collections::VecDeque, and the tokenize_all function is no longer used by the parser.

@reviewable-StarkWare

Copy link
Copy Markdown

This change is Reviewable

orizi commented Jun 21, 2026

Copy link
Copy Markdown
Collaborator Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

@orizi
orizi requested a review from eytan-starkware June 21, 2026 11:23
@orizi
orizi marked this pull request as ready for review June 21, 2026 11:23
@cursor

cursor Bot commented Jun 21, 2026 •

Copy link
Copy Markdown

PR Summary

Medium Risk
Touches the core parse loop and token consumption path; behavior should match eager lexing but regressions could show up in lookahead, EOF, or unglue edge cases.

Overview
Lazy lexing in the parser replaces upfront tokenize_all with an owned Lexer and a small VecDeque lookahead (current_terminals). Parser::new seeds two terminals; advance refills to three before popping so next_terminal / next_next_terminal stay valid; an eof flag stops pulling after EOF. Token-splitting (unglue) still prepends synthetic terminals into the same deque.

Lexer API cleanup: the salsa-tracked tokenize_all in lexer.rs is removed; Lexer::new and public match_terminal support on-demand lexing. Tests keep a local tokenize_all in lexer_test.rs (plain Vec, no Tracked argument).

Reviewed by Cursor Bugbot for commit dcae7ca. Bugbot is set up for automated code reviews on this repo. Configure here.

@eytan-starkware eytan-starkware left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@eytan-starkware reviewed 2 files and all commit messages, and made 2 comments.
Reviewable status: all files reviewed, 2 unresolved discussions (waiting on orizi).


crates/cairo-lang-parser/src/lexer.rs line 257 at r1 (raw file):

    }

    pub fn match_terminal<'a>(&mut self, db: &'a dyn Database) -> LexerTerminal<'a> {

why pub?


crates/cairo-lang-parser/src/parser.rs line 168 at r1 (raw file):

        diagnostics: &'mt mut DiagnosticsBuilder<'a, ParserDiagnostic<'a>>,
    ) -> Self {
        let mut parser = Parser {

Can we remove tokenize all now?

… of cloning all tokens.

  `Parser::new` called the salsa-cached `tokenize_all` and deep-cloned the whole
  resulting `Deque<LexerTerminal>` into the parser on every parse (each terminal
  owns two trivia vectors). That full per-file token buffer is unnecessary: the
  parse is already memoized by `file_syntax_data`.

  Have the parser drive the `Lexer` directly, pulling terminals lazily into a
  small lookahead `VecDeque` (refilled on demand via `ensure_next_k_exists`),
  rather than consuming a clone of the cached token list. `tokenize_all` is left
  as-is (now used only by the lexer tests).

  -7.35% of cairo-to-diagnostics heap allocations on corelib; parser/lexer
  golden tests and corelib diagnostics unchanged.
@orizi
orizi force-pushed the orizi/06-21-performance_parser_stream_the_lexer_with_a_lookahead_window_instead_of_cloning_all_tokens branch from 081644c to dcae7ca Compare June 22, 2026 07:35

@orizi orizi left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@orizi made 2 comments.
Reviewable status: all files reviewed, 2 unresolved discussions (waiting on eytan-starkware).


crates/cairo-lang-parser/src/lexer.rs line 257 at r1 (raw file):

Previously, eytan-starkware wrote…

why pub?

since it is used instead of the tokenize_all.


crates/cairo-lang-parser/src/parser.rs line 168 at r1 (raw file):

Previously, eytan-starkware wrote…

Can we remove tokenize all now?

moving it to the test - will see what else.

@eytan-starkware eytan-starkware left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:lgtm:

@eytan-starkware reviewed 2 files and all commit messages, made 1 comment, and resolved 2 discussions.
Reviewable status: :shipit: complete! all files reviewed, all discussions resolved (waiting on orizi).

@orizi
orizi added this pull request to the merge queue Jun 22, 2026
Merged via the queue into main with commit 798b0ab Jun 22, 2026
55 checks passed
@orizi
orizi deleted the orizi/06-21-performance_parser_stream_the_lexer_with_a_lookahead_window_instead_of_cloning_all_tokens branch June 23, 2026 07:45
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.

3 participants