Expand description
Precedence-climbing parser, lowering straight to the tatara-lisp quoted form.
The load-bearing design decision, made here and once: the parser’s
output IS a tatara_lisp::Sexp. There is no private blue AST that later
gets converted. That is Tenet 1 — blue source parses to tatara-lisp —
and building it any other way would make homoiconicity a conversion step
rather than an identity, which is the difference between blue’s macro
story working and merely being claimed.
The consequence to keep in view: every surface construct must have a well-defined s-expression it means. Where the mapping is not obvious it is written down in the test module, because the tests are the specification of the surface until the mechanized spec exists.
§Spans are built, not bolted on
Every production returns a Spanned — tatara-lisp’s parallel tree where
each node carries the byte range that produced it — and the spanless
parse_program is a projection of it via Spanned::to_sexp.
That direction is deliberate and it is the second time this file has learned
the lesson: the spanless tree is derivable from the spanned one and the
reverse is not. When the parser produced Sexp and callers wanted positions,
the only answers available were invent one (a squiggle under unrelated
code) or use nothing — and the type checker took the second, so every type
error in the editor was reported at line 0, column 0 regardless of where it
was. A span discarded at the parser cannot be recovered by anything
downstream, exactly as [crate::lex] says of trivia.
Two honest limits, both stated where they are caused rather than inferred:
- A node blue synthesizes (the
defineadeflowers to, thenotanunlesslowers to, theequal?acasearm compares with) has no source text of its own. It is given the span of the token that caused it — thedef, theunless, thewhen— which is where a reader looks when told something about it. - An interpolation’s inner expression is parsed from a sub-source (the
text between
#{and}), so its own offsets are relative to that substring and would point into the wrong buffer. Those nodes are stamped with the whole string literal’s span instead. Sub-span accuracy inside an interpolation needs the lexer to record each part’s offset in the outer source, which it does not.
Structs§
- Comment
- A comment, and where it sat.
- Infix
- What an infix operator binds like, and what it lowers to.
- Parse
Error
Constants§
- BLOCK_
KEYWORDS - The words that are reserved but are not heads of a
SURFACE_KEYWORDSform — block delimiters and the three literals. - INFIX
- The complete infix table. Precedence follows Ruby’s where Ruby has an
opinion;
|>(below) sits under everything soa |> f |> gchains without parentheses. - LOWERED_
ASSERT - Every surface keyword that BEGINS an expression.
- LOWERED_
CONCAT - The callee string interpolation lowers to.
- LOWERED_
MAP - The callee a
{...}map literal lowers to. - MAX_
EXPR_ DEPTH - Maximum expression nesting before the parser refuses.
- SURFACE_
KEYWORDS
Functions§
- comments
- Every comment in
src, with its byte span and whether it sits alone on its line. - is_
reserved_ word - A surface keyword may not be rebound.
if = 1is a mistake, not a binding, and letting it through would shadow the form for the rest of the file. - parse_
expr - Parse a single blue expression. Convenience for tests and the REPL.
- parse_
program - Parse a blue program into a sequence of tatara-lisp forms.
- parse_
program_ in - Parse a program written in a
Yakugosurface. - parse_
program_ spanned - Parse, keeping each top-level form’s source span.
- parse_
program_ spanned_ with_ depth parse_program_spannedwith the nesting bound supplied by the caller.- parse_
program_ tree - Parse a blue program keeping every node’s source span.
- parse_
program_ tree_ in parse_program_inkeeping every node’s source span.- parse_
program_ tree_ with_ depth parse_program_treewith the nesting bound supplied by the caller.- parse_
program_ with_ depth parse_programwith the nesting bound supplied by the caller.