← Back to projects
Summary

Everything upstream of semantic analysis. The grammar defines Gazprea's surface syntax across roughly 30 rules, a custom token filter resolves an ambiguity the lexer cannot express on its own, and the AST builder walks the resulting parse tree into a hand-designed node hierarchy that every later pass visits. The design goal was that no pass after this one should ever have to think about parse-tree shape or token positions.

Stack

What it's built with.

ANTLR4C++17AST DesignVisitor PatternLexer/Parser
Details

How it works.

Fixing the lexer without touching the grammar

Gazprea allows real literals written without a leading digit, and it also uses the dot as an operator elsewhere. That means the character sequence in something like .5E1 is genuinely ambiguous at the lexer level: it is either a single real literal in scientific notation, or a DOT token followed by something else entirely. A lexer has no lookahead context to decide, and the obvious fix, encoding the disambiguation into grammar rules, spreads the problem across every rule that can be preceded by a dot.

The narrower fix is a token stream that sits between the lexer and the parser. It fills the buffer once, scans for a DOT immediately followed by a real literal whose text matches a scientific-notation pattern, and merges the pair into a single token before the parser ever sees them. One class, one pass over the tokens, and the grammar stays honest.

Highlights

The things I'm proudest of.

  • ▹Solved a lexing ambiguity in the token stream rather than the grammar. Pre-dot scientific notation (.5E1) cannot be distinguished from a DOT followed by a real literal by the lexer alone, so a custom CommonTokenStream subclass pre-scans the filled token buffer and merges the DOT and REAL_LITERAL pair into one token when the following text matches a scientific-notation pattern.
  • ▹Kept that fix out of the grammar deliberately: bending the grammar to accommodate it would have made every downstream rule that mentions DOT ambiguous, so the repair happens once at the token layer and the parser never learns about it.
  • ▹Built the parse tree into a hand-designed AST hierarchy over a visitor interface, so the eight passes that follow all traverse the same node types rather than each re-interpreting ANTLR contexts.
  • ▹Wrote two separate printers, an AST printer and a tree printer, because a compiler you cannot see the intermediate state of is a compiler you debug by guessing.
  • ▹Routed parse errors through a custom error listener into the same diagnostic type used by the rest of the pipeline, so a syntax error and a type error are reported the same way rather than through two unrelated paths.