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 - Why a source did not parse, and where.
Constants§
- BLOCK_
KEYWORDS - The words that are reserved but are not heads of a
SURFACE_KEYWORDSform — block delimiters and the three literals. - BUILTIN_
QUALIFIER - The qualifier that names a builtin:
blue::countis the interpreter’scount, whatever a program defines. No package may be named it. - 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 - The callee
assert elowers to. - LOWERED_
ATTEMPTED - LOWERED_
CONCAT - The callee string interpolation lowers to.
- LOWERED_
ENSURE - LOWERED_
ERROR_ MATCH - LOWERED_
FAILED - LOWERED_
FIELD - LOWERED_
HEADS - LOWERED_
KWARGS - LOWERED_
MAP - The callee a
{...}map literal lowers to. - LOWERED_
MERGE - LOWERED_
OPTIONAL_ ARG - LOWERED_
OPTIONAL_ ARITY - LOWERED_
RERAISE - LOWERED_
SPLICE - MASTER_
VERBS - Every surface keyword that BEGINS an expression.
- MAX_
EXPR_ DEPTH - Maximum expression nesting before the parser refuses.
- OPERATOR_
KEYWORDS - OPTIONAL_
MARKER - QUALIFIER
- The character a qualified name’s AST symbol joins its two halves with:
retsu::firstis the symbolretsu/first. The surface never writes it (/is division, and no identifier can contain it), which is what makes a symbol with text on both sides of one/unambiguously qualified. - RESCUED
- SURFACE_
KEYWORDS - SYMBOL_
SPELLINGS
Functions§
- comments
- Every comment in
src, with its byte span and whether it sits alone on its line. - is_
callable_ name - Can
namebe written as the head of a call —name(…)? - 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. - keyword_
doc - One line for a reader on each reserved word: what it writes, and the shape.
- label_
name - Whether
name:lexes as exactly one label spellingname: the parser’s and the formatter’s shared test for a key that can be punned ({name:}). It asks the lexer, so a name ending in?or!puns too and no second list of “plain names” can drift from what a label is. - 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_ documented - Parse keeping the documentation nodes
@docand@moduledoclower to (crate::doc). Every otherparse_program*door strips them. - parse_
program_ documented_ with_ depth parse_program_documentedwith the nesting bound supplied by the caller.- 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.- parses_
on_ this_ thread - How many programs this thread has parsed: an independent count for a caller that promises to parse a text once, read off the parser itself rather than off the caller’s own bookkeeping.
- qualified
retsu/first→Some(("retsu", "first")): the package and the name of a qualified symbol.Nonefor every other symbol,/(division) included.- qualify
- The AST symbol for
pkg::name. - spelling_
notes