Skip to main content

blue_lang_syntax/
parse.rs

1//! Precedence-climbing parser, lowering straight to the tatara-lisp
2//! quoted form.
3//!
4//! **The load-bearing design decision, made here and once:** the parser's
5//! output IS a `tatara_lisp::Sexp`. There is no private blue AST that later
6//! gets converted. That is Tenet 1 — *blue source parses to tatara-lisp* —
7//! and building it any other way would make homoiconicity a conversion step
8//! rather than an identity, which is the difference between blue's macro
9//! story working and merely being claimed.
10//!
11//! The consequence to keep in view: every surface construct must have a
12//! well-defined s-expression it means. Where the mapping is not obvious it
13//! is written down in the test module, because the tests are the
14//! specification of the surface until the mechanized spec exists.
15
16use tatara_lisp::{Atom, Sexp};
17
18use crate::lex::{lex, Span, Token, TokenKind};
19
20#[derive(Clone, Debug, PartialEq)]
21pub struct ParseError {
22    pub message: String,
23    pub span: Span,
24}
25
26impl std::fmt::Display for ParseError {
27    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28        write!(f, "{} at {}..{}", self.message, self.span.start, self.span.end)
29    }
30}
31
32impl std::error::Error for ParseError {}
33
34impl From<crate::lex::LexError> for ParseError {
35    fn from(e: crate::lex::LexError) -> Self {
36        Self {
37            message: e.message,
38            span: e.span,
39        }
40    }
41}
42
43/// Parse a blue program into a sequence of tatara-lisp forms.
44pub fn parse_program(src: &str) -> Result<Vec<Sexp>, ParseError> {
45    Ok(parse_program_spanned(src)?
46        .into_iter()
47        .map(|(form, _)| form)
48        .collect())
49}
50
51/// Parse, keeping each top-level form's source span.
52///
53/// The spans are what let the formatter put comments back. A comment is not
54/// part of a program's *meaning*, so it has no place in the `Sexp` tree —
55/// putting it there would break canonicality, since two programs differing only
56/// in a comment would stop formatting identically. Instead the formatter renders
57/// the tree and re-interleaves comments by position, which needs to know where
58/// each form started and ended.
59pub fn parse_program_spanned(src: &str) -> Result<Vec<(Sexp, Span)>, ParseError> {
60    let toks: Vec<Token> = lex(src)?
61        .into_iter()
62        .filter(|t| !matches!(t.kind, TokenKind::Comment(_)))
63        .collect();
64    let mut p = Parser { toks, pos: 0 };
65    p.program_spanned()
66}
67
68/// Every comment in `src`, with its byte span and whether it sits alone on its
69/// line.
70///
71/// `own_line` is the distinction that decides placement: a comment alone on its
72/// line belongs *before* the form that follows, while one after code on the same
73/// line belongs *to* that line. Conflating them moves a trailing note onto the
74/// wrong row.
75pub fn comments(src: &str) -> Vec<Comment> {
76    lex(src)
77        .map(|toks| {
78            toks.iter()
79                .filter_map(|t| match &t.kind {
80                    TokenKind::Comment(text) => Some(Comment {
81                        text: text.clone(),
82                        span: t.span,
83                        own_line: line_before_is_blank(src, t.span.start),
84                    }),
85                    _ => None,
86                })
87                .collect()
88        })
89        .unwrap_or_default()
90}
91
92/// A comment, and where it sat.
93#[derive(Clone, Debug, PartialEq, Eq)]
94pub struct Comment {
95    /// The text, including the leading `#`.
96    pub text: String,
97    pub span: Span,
98    /// Nothing but whitespace precedes it on its line.
99    pub own_line: bool,
100}
101
102fn line_before_is_blank(src: &str, start: usize) -> bool {
103    src[..start.min(src.len())]
104        .rsplit('\n')
105        .next()
106        .is_some_and(|prefix| prefix.trim().is_empty())
107}
108
109/// Parse a single blue expression. Convenience for tests and the REPL.
110pub fn parse_expr(src: &str) -> Result<Sexp, ParseError> {
111    let forms = parse_program(src)?;
112    match forms.len() {
113        1 => Ok(forms.into_iter().next().expect("checked len")),
114        n => Err(ParseError {
115            message: format!("expected exactly one expression, found {n}"),
116            span: Span::new(0, src.len()),
117        }),
118    }
119}
120
121/// What an infix operator binds like, and what it lowers to.
122///
123/// **One row per operator, carrying both facts.** Precedence and callee
124/// used to live apart, and the cost was immediate: the surface spelling was
125/// emitted verbatim, so `a == b` lowered to `(== a b)` — a symbol no
126/// interpreter binds — and the program died at *runtime* with `unbound
127/// symbol ==`. Splitting the two invites exactly that: add an operator to
128/// the precedence table, forget the lowering, ship a parse that cannot run.
129/// Joined here, "has a precedence" and "has a callee" are the same fact.
130#[derive(Clone, Copy, Debug)]
131pub struct Infix {
132    /// Surface spelling.
133    pub op: &'static str,
134    /// Left and right binding power. Higher binds tighter.
135    pub power: (u8, u8),
136    /// The tatara-lisp callee this lowers to.
137    pub callee: &'static str,
138}
139
140/// The complete infix table. Precedence follows Ruby's where Ruby has an
141/// opinion; `|>` (below) sits under everything so `a |> f |> g` chains
142/// without parentheses.
143pub const INFIX: &[Infix] = &[
144    Infix { op: "||", power: (1, 2), callee: "or" },
145    Infix { op: "&&", power: (3, 4), callee: "and" },
146    // `==` is STRUCTURAL equality, so it lowers to `equal?` and not to `=`.
147    //
148    // tatara's `=` is NUMERIC comparison: `"a" = "a"` is a type error, not
149    // false. Lowering `==` to it meant every string, list or nil comparison
150    // failed with "expected number, got string" — found by blue's own spec
151    // suite the moment a test compared two strings, which is the first thing
152    // anybody does.
153    //
154    // `equal?` is structural and total over the value domain: strings, lists
155    // and nil all compare, and numbers still compare as numbers.
156    Infix { op: "==", power: (5, 6), callee: "equal?" },
157    Infix { op: "!=", power: (5, 6), callee: "not=" },
158    Infix { op: "<", power: (5, 6), callee: "<" },
159    Infix { op: "<=", power: (5, 6), callee: "<=" },
160    Infix { op: ">", power: (5, 6), callee: ">" },
161    Infix { op: ">=", power: (5, 6), callee: ">=" },
162    Infix { op: "+", power: (7, 8), callee: "+" },
163    Infix { op: "-", power: (7, 8), callee: "-" },
164    Infix { op: "*", power: (9, 10), callee: "*" },
165    Infix { op: "/", power: (9, 10), callee: "/" },
166    Infix { op: "%", power: (9, 10), callee: "mod" },
167];
168
169/// Every surface keyword that BEGINS an expression.
170///
171/// Exists so the formatter's corpus can be checked against it. Three separate
172/// times a form was added to this parser, the formatter was not extended, and
173/// all three formatting laws still passed — because the corpus is
174/// hand-maintained and contained no example of the new form. The annotated
175/// `def` rendered as a method send; `defmacro` rendered as
176/// `defmacro(double, x(), …)`, which does not re-parse at all.
177///
178/// A law cannot notice a case nobody wrote down. `blue-lang-fmt`'s
179/// `every_surface_keyword_appears_in_the_corpus` closes that by making the
180/// omission itself the failure, so adding a keyword here forces the corpus
181/// entry that exercises the other three laws over it.
182///
183/// `do` and `end` are absent deliberately: they are block delimiters, not
184/// expression heads, and have no standalone rendering to test.
185/// The callee `assert e` lowers to.
186///
187/// **Owned here, by the lowering itself, and read by every consumer.** It was
188/// briefly a literal here and a separate `const` in `blue-lang-test`, and the
189/// shadowing gate then could not see a parser that lowered to the wrong name:
190/// the gate checked its own copy. Same duplication class as the operator table.
191pub const LOWERED_ASSERT: &str = "blue-assert";
192
193/// The callee a `{...}` map literal lowers to.
194///
195/// **`hash-map`, not `map`.** tatara binds `map` to the higher-order function,
196/// so a literal lowering to `(map …)` called the HOF with the key/value pairs
197/// as arguments and failed with "expected list, got int". Same shadowing class
198/// as [`LOWERED_ASSERT`], caught by the same gate once the name was listed
199/// there — it was not, which is why this shipped.
200pub const LOWERED_MAP: &str = "hash-map";
201
202/// The callee string interpolation lowers to.
203///
204/// blue's own `concat`, which renders either side through `to_s` — that is what
205/// lets `"n=#{42}"` interpolate a number. Not `+`, which is arithmetic.
206pub const LOWERED_CONCAT: &str = "concat";
207
208pub const SURFACE_KEYWORDS: &[&str] = &[
209    "if",
210    "unless",
211    "def",
212    "defmacro",
213    "quote",
214    "unquote",
215    "unquote_splice",
216    "test",
217    "assert",
218    "fn",
219    "case",
220];
221
222/// A surface keyword may not be rebound. `if = 1` is a mistake, not a binding,
223/// and letting it through would shadow the form for the rest of the file.
224fn is_reserved_word(name: &str) -> bool {
225    SURFACE_KEYWORDS.contains(&name) || matches!(name, "do" | "end" | "else" | "true" | "false" | "nil")
226}
227
228fn infix(op: &str) -> Option<&'static Infix> {
229    INFIX.iter().find(|i| i.op == op)
230}
231
232const PIPE_POWER: (u8, u8) = (0, 1);
233
234struct Parser {
235    toks: Vec<Token>,
236    pos: usize,
237}
238
239impl Parser {
240    fn peek(&self) -> &TokenKind {
241        &self.toks[self.pos.min(self.toks.len() - 1)].kind
242    }
243
244    fn peek_span(&self) -> Span {
245        self.toks[self.pos.min(self.toks.len() - 1)].span
246    }
247
248    fn bump(&mut self) -> TokenKind {
249        let k = self.toks[self.pos.min(self.toks.len() - 1)].kind.clone();
250        if self.pos < self.toks.len() {
251            self.pos += 1;
252        }
253        k
254    }
255
256    fn at(&self, k: &TokenKind) -> bool {
257        self.peek() == k
258    }
259
260    fn eat(&mut self, k: &TokenKind) -> bool {
261        if self.at(k) {
262            self.bump();
263            true
264        } else {
265            false
266        }
267    }
268
269    fn expect(&mut self, k: &TokenKind, what: &str) -> Result<(), ParseError> {
270        if self.eat(k) {
271            Ok(())
272        } else {
273            Err(self.error(format!("expected {what}, found {:?}", self.peek())))
274        }
275    }
276
277    fn error(&self, message: impl Into<String>) -> ParseError {
278        ParseError {
279            message: message.into(),
280            span: self.peek_span(),
281        }
282    }
283
284    /// Skip statement separators (newlines and semicolon-free layout).
285    fn skip_newlines(&mut self) {
286        while matches!(self.peek(), TokenKind::Newline) {
287            self.bump();
288        }
289    }
290
291    fn at_ident(&self, name: &str) -> bool {
292        matches!(self.peek(), TokenKind::Ident(n) if n == name)
293    }
294
295    fn program_spanned(&mut self) -> Result<Vec<(Sexp, Span)>, ParseError> {
296        let mut out = Vec::new();
297        loop {
298            self.skip_newlines();
299            if matches!(self.peek(), TokenKind::Eof) {
300                break;
301            }
302            let start = self.peek_span().start;
303            let form = self.statement()?;
304            // The last token consumed ends the form. `pos` has already advanced
305            // past it, so look one back.
306            let end = self
307                .toks
308                .get(self.pos.saturating_sub(1))
309                .map_or(start, |t| t.span.end);
310            out.push((form, Span::new(start, end)));
311        }
312        Ok(out)
313    }
314
315    /// A statement: either a binding or an expression.
316    ///
317    /// `x = 5` lowers to `(define x 5)`. Blue had NO way to name a value — a
318    /// capability probe found `x = 5` was a parse error, which makes every
319    /// program a single expression. That is more fundamental than anything else
320    /// the probe found.
321    ///
322    /// Only at STATEMENT position, never inside an expression, so `f(x = 1)` is
323    /// still an error rather than a silent binding. Ruby allows assignment as an
324    /// expression and it is a well-known footgun — `if x = 1` where `==` was
325    /// meant. Blue declines it, and the cost is only that a walrus-style idiom
326    /// has to be two lines.
327    fn statement(&mut self) -> Result<Sexp, ParseError> {
328        if let TokenKind::Ident(name) = self.peek().clone() {
329            if self.peek_at(1) == "=" && !is_reserved_word(&name) {
330                self.bump(); // name
331                self.bump(); // =
332                self.skip_newlines();
333                let value = self.expr(0)?;
334                return Ok(Sexp::List(vec![sym("define"), sym(&name), value]));
335            }
336        }
337        self.expr(0)
338    }
339
340    /// The token `n` positions ahead, for the two-token lookahead a binding
341    /// needs. Returns `Eof` past the end rather than panicking.
342    fn peek_at(&self, n: usize) -> String {
343        match self.toks.get(self.pos + n).map(|t| &t.kind) {
344            Some(TokenKind::Op(o)) => o.clone(),
345            _ => String::new(),
346        }
347    }
348
349    /// Pratt loop.
350    fn expr(&mut self, min_bp: u8) -> Result<Sexp, ParseError> {
351        let mut lhs = self.prefix()?;
352
353        loop {
354            // Postfix: `.name`, `.name(args)`, `(args)`
355            match self.peek() {
356                TokenKind::Dot => {
357                    self.bump();
358                    lhs = self.finish_send(lhs)?;
359                    continue;
360                }
361                TokenKind::LParen => {
362                    // A call on an expression already parsed: `f(x)`.
363                    let args = self.paren_args()?;
364                    let mut list = vec![lhs];
365                    list.extend(args);
366                    lhs = Sexp::List(list);
367                    continue;
368                }
369                _ => {}
370            }
371
372            // Infix
373            // `callee == None` marks the pipeline, which is a rewrite rather
374            // than a call.
375            let (callee, (lbp, rbp)) = match self.peek() {
376                TokenKind::Pipe => (None, PIPE_POWER),
377                TokenKind::Op(o) => match infix(o) {
378                    Some(i) => (Some(i.callee), i.power),
379                    None => break,
380                },
381                _ => break,
382            };
383            if lbp < min_bp {
384                break;
385            }
386            self.bump();
387            self.skip_newlines();
388            let rhs = self.expr(rbp)?;
389
390            lhs = if callee.is_none() {
391                // `x |> f`      => (f x)
392                // `x |> f(a)`   => (f x a)   — the pipeline threads into
393                //                  the FIRST argument position, as Elixir's
394                //                  does; that is what makes it composable.
395                match rhs {
396                    Sexp::List(mut items) if !items.is_empty() => {
397                        items.insert(1, lhs);
398                        Sexp::List(items)
399                    }
400                    callee => Sexp::List(vec![callee, lhs]),
401                }
402            } else {
403                Sexp::List(vec![sym(callee.unwrap()), lhs, rhs])
404            };
405        }
406
407        Ok(lhs)
408    }
409
410    fn prefix(&mut self) -> Result<Sexp, ParseError> {
411        let span = self.peek_span();
412        match self.bump() {
413            TokenKind::Int(v) => Ok(Sexp::Atom(Atom::Int(v))),
414            TokenKind::Float(v) => Ok(Sexp::Atom(Atom::Float(v))),
415            TokenKind::Str(s) => Ok(Sexp::Atom(Atom::Str(s))),
416
417            // `"a#{x}b"` → `(concat (concat "a" x) "b")`.
418            //
419            // Lowered to `concat`, not to `+`: blue's `+` is arithmetic (see
420            // the INFIX table), and interpolation must render a value of ANY
421            // type — `concat` goes through `to_s`, which is what makes
422            // `"n=#{42}"` work.
423            //
424            // The expression source is parsed HERE with the ordinary parser
425            // rather than lexed inside the string, so an interpolation can hold
426            // anything an expression can and the two can never drift.
427            TokenKind::InterpolatedStr { parts, exprs } => {
428                let mut acc = Sexp::Atom(Atom::Str(parts[0].clone()));
429                for (i, raw) in exprs.iter().enumerate() {
430                    let inner = parse_expr(raw).map_err(|e| ParseError {
431                        message: format!("in interpolation `#{{{raw}}}`: {}", e.message),
432                        span,
433                    })?;
434                    acc = Sexp::List(vec![sym(LOWERED_CONCAT), acc, inner]);
435                    // `parts.len() == exprs.len() + 1` by construction, so this
436                    // index is always in range.
437                    acc = Sexp::List(vec![
438                        sym(LOWERED_CONCAT),
439                        acc,
440                        Sexp::Atom(Atom::Str(parts[i + 1].clone())),
441                    ]);
442                }
443                Ok(acc)
444            }
445            TokenKind::Sym(s) => Ok(Sexp::Atom(Atom::Keyword(s))),
446            TokenKind::True => Ok(Sexp::Atom(Atom::Bool(true))),
447            TokenKind::False => Ok(Sexp::Atom(Atom::Bool(false))),
448            TokenKind::Nil => Ok(Sexp::Nil),
449
450            TokenKind::Op(o) if o == "-" => {
451                let rhs = self.expr(11)?; // binds tighter than `*`
452                Ok(Sexp::List(vec![sym("-"), Sexp::Atom(Atom::Int(0)), rhs]))
453            }
454            TokenKind::Op(o) if o == "!" => {
455                let rhs = self.expr(11)?;
456                Ok(Sexp::List(vec![sym("not"), rhs]))
457            }
458
459            TokenKind::LParen => {
460                self.skip_newlines();
461                let inner = self.expr(0)?;
462                self.skip_newlines();
463                self.expect(&TokenKind::RParen, "`)`")?;
464                Ok(inner)
465            }
466
467            TokenKind::LBracket => self.list_literal(),
468            TokenKind::LBrace => self.map_literal(),
469
470            TokenKind::Ident(name) => match name.as_str() {
471                "if" => self.if_form(false),
472                "unless" => self.if_form(true),
473                "def" => self.def_form(),
474                "defmacro" => self.defmacro_form(),
475                "case" => self.case_form(),
476                "fn" => self.lambda_form(),
477                "test" => self.test_form(),
478                "assert" => self.assert_form(),
479                "quote" => self.quote_form(),
480                "unquote" => self.unquote_form(false),
481                "unquote_splice" => self.unquote_form(true),
482                "do" => Err(ParseError {
483                    message: "`do` without a preceding call".into(),
484                    span,
485                }),
486                "end" => Err(ParseError {
487                    message: "unexpected `end`".into(),
488                    span,
489                }),
490                _ => Ok(sym(&name)),
491            },
492
493            other => Err(ParseError {
494                message: format!("expected an expression, found {other:?}"),
495                span,
496            }),
497        }
498    }
499
500    /// After a `.`: `recv.name` or `recv.name(args)`.
501    ///
502    /// **A bare `recv.name` is a SEND, not a field read.** Blue commits to
503    /// the uniform access principle here: a structure exposes no public
504    /// fields, so a field can later become a computed method without
505    /// breaking a caller.
506    fn finish_send(&mut self, recv: Sexp) -> Result<Sexp, ParseError> {
507        let name = match self.bump() {
508            TokenKind::Ident(n) => n,
509            other => {
510                return Err(self.error(format!("expected a method name after `.`, found {other:?}")))
511            }
512        };
513        let mut list = vec![sym(&name), recv];
514        if self.at(&TokenKind::LParen) {
515            list.extend(self.paren_args()?);
516        }
517        Ok(Sexp::List(list))
518    }
519
520    fn paren_args(&mut self) -> Result<Vec<Sexp>, ParseError> {
521        self.expect(&TokenKind::LParen, "`(`")?;
522        let mut args = Vec::new();
523        self.skip_newlines();
524        if self.eat(&TokenKind::RParen) {
525            return Ok(args);
526        }
527        loop {
528            self.skip_newlines();
529            args.push(self.expr(0)?);
530            self.skip_newlines();
531            if self.eat(&TokenKind::Comma) {
532                continue;
533            }
534            self.expect(&TokenKind::RParen, "`,` or `)`")?;
535            break;
536        }
537        Ok(args)
538    }
539
540    fn list_literal(&mut self) -> Result<Sexp, ParseError> {
541        let mut items = vec![sym("list")];
542        self.skip_newlines();
543        if self.eat(&TokenKind::RBracket) {
544            return Ok(Sexp::List(items));
545        }
546        loop {
547            self.skip_newlines();
548            items.push(self.expr(0)?);
549            self.skip_newlines();
550            if self.eat(&TokenKind::Comma) {
551                continue;
552            }
553            self.expect(&TokenKind::RBracket, "`,` or `]`")?;
554            break;
555        }
556        Ok(Sexp::List(items))
557    }
558
559    /// `{a: 1, "k" => v}` — both spellings, one tree.
560    ///
561    /// This is §V.13's rendering law at the parser: `a: 1` and `:a => 1`
562    /// produce the *same* s-expression, which is precisely why the
563    /// formatter may always choose the shorthand. The rocket survives only
564    /// where the key is not a plain symbol.
565    fn map_literal(&mut self) -> Result<Sexp, ParseError> {
566        let mut items = vec![sym(LOWERED_MAP)];
567        self.skip_newlines();
568        if self.eat(&TokenKind::RBrace) {
569            return Ok(Sexp::List(items));
570        }
571        loop {
572            self.skip_newlines();
573            match self.peek().clone() {
574                TokenKind::Label(name) => {
575                    self.bump();
576                    self.skip_newlines();
577                    items.push(Sexp::Atom(Atom::Keyword(name)));
578                    items.push(self.expr(0)?);
579                }
580                _ => {
581                    let k = self.expr(0)?;
582                    self.skip_newlines();
583                    self.expect(&TokenKind::Rocket, "`=>` in a map literal")?;
584                    self.skip_newlines();
585                    items.push(k);
586                    items.push(self.expr(0)?);
587                }
588            }
589            self.skip_newlines();
590            if self.eat(&TokenKind::Comma) {
591                continue;
592            }
593            self.expect(&TokenKind::RBrace, "`,` or `}`")?;
594            break;
595        }
596        Ok(Sexp::List(items))
597    }
598
599    /// `if c ... [else ...] end`, and `unless` as its negation.
600    ///
601    /// `unless` lowers to `(if (not c) ...)` rather than to a distinct
602    /// form: one tree per meaning, so the formatter and every downstream
603    /// tool see exactly one shape.
604    fn if_form(&mut self, negate: bool) -> Result<Sexp, ParseError> {
605        let cond = self.expr(0)?;
606        let cond = if negate {
607            Sexp::List(vec![sym("not"), cond])
608        } else {
609            cond
610        };
611        let then = self.body(&["else", "end"])?;
612        let els = if self.at_ident("else") {
613            self.bump();
614            let e = self.body(&["end"])?;
615            self.expect_ident("end")?;
616            Some(e)
617        } else {
618            self.expect_ident("end")?;
619            None
620        };
621        let mut out = vec![sym("if"), cond, then];
622        if let Some(e) = els {
623            out.push(e);
624        }
625        Ok(Sexp::List(out))
626    }
627
628    /// `def name(a, b) ... end`            => `(define (name a b) body)`
629    /// `def name(a: T, b: T) -> R ... end`  => `(define-typed (name (a T) (b T)) R body)`
630    ///
631    /// **The two shapes are deliberately different heads.** §0 says an
632    /// unannotated program gets ZERO analysis, and the cleanest way to
633    /// mean that is for untyped code not to reach the typing machinery at
634    /// all — not to reach it and be waved through. A checker that must
635    /// walk every node to discover there is nothing to check has already
636    /// paid the cost the ladder exists to avoid.
637    ///
638    /// Annotations are per-parameter, so a signature may be partially
639    /// annotated. That is the ladder at its finest grain: `a: Int` is
640    /// checked and a bare `b` stays `dyn`, in the same signature.
641    /// `case subject / when a / … / else / … / end` => a `cond` over equality.
642    ///
643    /// **Value matching, not destructuring.** Elixir's `case` binds pattern
644    /// variables; blue's compares with the same `equal?` the `==` operator uses,
645    /// so `when [1, 2]` matches a list by value. Destructuring needs a pattern
646    /// language and a binder, which blue does not have — and a `case` that
647    /// *looked* like Elixir's while silently only comparing would be worse than
648    /// one that plainly compares.
649    ///
650    /// The subject is evaluated ONCE, into a binding, so `case expensive()` does
651    /// not re-run per arm. That is a correctness property, not an optimisation:
652    /// a subject with a side effect would fire once per `when`.
653    fn case_form(&mut self) -> Result<Sexp, ParseError> {
654        let subject = self.expr(0)?;
655        self.skip_newlines();
656
657        // A fresh name the surface cannot spell, so it cannot capture a user
658        // binding of the same name.
659        let subject_var = "case-subject";
660        let mut arms: Vec<Sexp> = Vec::new();
661        let mut otherwise: Option<Sexp> = None;
662
663        loop {
664            self.skip_newlines();
665            if self.at_ident("end") {
666                break;
667            }
668            if self.eat_ident("else") {
669                otherwise = Some(self.body(&["end"])?);
670                continue;
671            }
672            if !self.eat_ident("when") {
673                return Err(self.error(format!(
674                    "expected `when`, `else` or `end` in a case, found {:?}",
675                    self.peek()
676                )));
677            }
678            self.skip_newlines();
679            let pattern = self.expr(0)?;
680            let body = self.body(&["when", "else", "end"])?;
681            arms.push(Sexp::List(vec![
682                Sexp::List(vec![
683                    sym("equal?"),
684                    sym(subject_var),
685                    pattern,
686                ]),
687                body,
688            ]));
689        }
690        self.expect_ident("end")?;
691
692        if arms.is_empty() && otherwise.is_none() {
693            return Err(self.error("a case needs at least one `when` or an `else`".to_string()));
694        }
695
696        // A case with no matching arm and no else is NIL, matching Ruby. Elixir
697        // raises CaseClauseError; blue follows Ruby because its `if` without an
698        // else is already nil, and having two different answers to "no branch
699        // taken" in one language is the inconsistency.
700        let mut cond = vec![sym("cond")];
701        cond.extend(arms);
702        cond.push(Sexp::List(vec![
703            sym("else"),
704            otherwise.unwrap_or(Sexp::Nil),
705        ]));
706
707        Ok(Sexp::List(vec![
708            sym("let"),
709            Sexp::List(vec![Sexp::List(vec![sym(subject_var), subject])]),
710            Sexp::List(cond),
711        ]))
712    }
713
714    /// `fn(a, b) ... end` => `(lambda (a b) body)`
715    ///
716    /// Without this the higher-order functions are unreachable in practice:
717    /// `map(inc, xs)` works only because `inc` happens to be a named stdlib
718    /// function, and there was no way to write the one-off the call site
719    /// actually wants.
720    ///
721    /// `fn` rather than Ruby's `->` or `lambda`: `->` collides with the return-
722    /// type arrow the typed `def` already uses, and reusing one glyph for two
723    /// unrelated things is the ambiguity the FORM axis exists to prevent.
724    fn lambda_form(&mut self) -> Result<Sexp, ParseError> {
725        let mut params: Vec<String> = Vec::new();
726        if self.at(&TokenKind::LParen) {
727            self.bump();
728            self.skip_newlines();
729            if !self.eat(&TokenKind::RParen) {
730                loop {
731                    self.skip_newlines();
732                    match self.bump() {
733                        TokenKind::Ident(p) => params.push(p),
734                        other => {
735                            return Err(self
736                                .error(format!("expected a parameter name, found {other:?}")))
737                        }
738                    }
739                    self.skip_newlines();
740                    if self.eat(&TokenKind::Comma) {
741                        continue;
742                    }
743                    self.expect(&TokenKind::RParen, "`,` or `)`")?;
744                    break;
745                }
746            }
747        }
748        let body = self.body(&["end"])?;
749        self.expect_ident("end")?;
750        Ok(Sexp::List(vec![
751            sym("lambda"),
752            Sexp::List(params.iter().map(|p| sym(p)).collect()),
753            body,
754        ]))
755    }
756
757    /// `test "name" ... end` => `(deftest "name" body)`
758    ///
759    /// A string, not an identifier: a test name is prose for a human report,
760    /// and forcing it into an identifier is how test names become
761    /// `test_adds_two_numbers_correctly`.
762    fn test_form(&mut self) -> Result<Sexp, ParseError> {
763        let name = match self.bump() {
764            TokenKind::Str(s) => s,
765            other => {
766                return Err(self.error(format!(
767                    "expected a string name after `test`, found {other:?}"
768                )))
769            }
770        };
771        let body = self.body(&["end"])?;
772        self.expect_ident("end")?;
773        Ok(Sexp::List(vec![
774            sym("deftest"),
775            Sexp::Atom(Atom::Str(name)),
776            body,
777        ]))
778    }
779
780    /// `assert expr` => `(blue-assert 'expr expr)`
781    ///
782    /// **`blue-assert`, not `assert`.** tatara-lisp's stdlib already defines
783    /// `assert` as a macro — `(defmacro assert (pred message) …)` — and a macro
784    /// in the expander is consulted before any primitive in the registry. So
785    /// lowering to `assert` bound `pred` to the *quoted form*, which is
786    /// truthy, and **every assertion silently passed**. A test framework whose
787    /// assertions always pass is the worst defect it can have: every test in
788    /// the suite goes green.
789    ///
790    /// The lesson generalizes: any name blue lowers to that tatara already
791    /// binds is silently captured. `blue_lang_test`'s
792    /// `no_lowered_name_is_shadowed_by_the_runtime` gates the whole class.
793    ///
794    /// **Both the form and the value.** A test framework whose failure says
795    /// only "assertion failed" makes the author re-derive what they were
796    /// checking; one that shows the expression does not. The quoted form is
797    /// the expression as DATA, so the runner can render it — and it renders it
798    /// through `blue-lang-fmt`, meaning the failure message is in canonical
799    /// blue syntax rather than the underlying tatara-lisp.
800    ///
801    /// This is homoiconicity paying for itself: the capture needs no source
802    /// map, no macro hygiene, and no string of the original text.
803    fn assert_form(&mut self) -> Result<Sexp, ParseError> {
804        let e = self.expr(0)?;
805        Ok(Sexp::List(vec![
806            sym(LOWERED_ASSERT),
807            Sexp::Quote(Box::new(e.clone())),
808            e,
809        ]))
810    }
811
812    /// `defmacro name(a, b) ... end` => `(defmacro name (a b) body)`
813    ///
814    /// **Deliberately untyped.** A macro's parameters are *source forms*, not
815    /// values, so `a: Int` would be a category error: the argument at expansion
816    /// time is a fragment of syntax. §IV's ladder types values; macro
817    /// parameters are not on it. Annotating one is rejected rather than
818    /// silently ignored — an ignored annotation is how an author comes to
819    /// believe a check is running.
820    fn defmacro_form(&mut self) -> Result<Sexp, ParseError> {
821        let name = match self.bump() {
822            TokenKind::Ident(n) => n,
823            other => {
824                return Err(self.error(format!(
825                    "expected a name after `defmacro`, found {other:?}"
826                )))
827            }
828        };
829        let mut params: Vec<String> = Vec::new();
830        if self.at(&TokenKind::LParen) {
831            self.bump();
832            self.skip_newlines();
833            if !self.eat(&TokenKind::RParen) {
834                loop {
835                    self.skip_newlines();
836                    match self.bump() {
837                        TokenKind::Ident(p) => params.push(p),
838                        TokenKind::Label(p) => {
839                            return Err(self.error(format!(
840                                "macro parameter `{p}` cannot be typed: a macro receives \
841                                 source forms, not values"
842                            )))
843                        }
844                        other => {
845                            return Err(self
846                                .error(format!("expected a parameter name, found {other:?}")))
847                        }
848                    }
849                    self.skip_newlines();
850                    if self.eat(&TokenKind::Comma) {
851                        continue;
852                    }
853                    self.expect(&TokenKind::RParen, "`,` or `)`")?;
854                    break;
855                }
856            }
857        }
858        if matches!(self.peek(), TokenKind::Op(o) if o == "->") {
859            return Err(self.error(
860                "a macro has no return type: it produces source forms, not values".to_string(),
861            ));
862        }
863
864        let body = self.body(&["end"])?;
865        self.expect_ident("end")?;
866
867        // `(defmacro name (params) body)` — tatara-lisp's own shape, so blue
868        // registers into the SAME expander rather than a parallel one.
869        Ok(Sexp::List(vec![
870            sym("defmacro"),
871            sym(&name),
872            Sexp::List(params.iter().map(|p| sym(p)).collect()),
873            body,
874        ]))
875    }
876
877    /// `quote ... end` => `` `body `` (a quasiquote).
878    ///
879    /// Quasiquote rather than plain quote, because a macro body that could not
880    /// splice its arguments in would be useless — this is Elixir's `quote do`,
881    /// which is likewise a template and not inert data.
882    fn quote_form(&mut self) -> Result<Sexp, ParseError> {
883        let body = self.body(&["end"])?;
884        self.expect_ident("end")?;
885        // `Sexp::Quasiquote`, NOT `(quasiquote body)` as a list.
886        //
887        // The list form Displays as the text `(quasiquote …)`, which the
888        // tatara-lisp reader reads back as an ordinary list whose head happens
889        // to be the symbol `quasiquote` — losing the structure. The evaluator
890        // then reached the inner `,x` with no enclosing quasiquote and rejected
891        // it: "unquote outside of quasiquote". Building the real variant makes
892        // it Display as `` ` `` and survive the round trip.
893        Ok(Sexp::Quasiquote(Box::new(body)))
894    }
895
896    /// `unquote(expr)` => `,expr`; `unquote_splice(expr)` => `,@expr`.
897    fn unquote_form(&mut self, splice: bool) -> Result<Sexp, ParseError> {
898        self.expect(&TokenKind::LParen, "`(` after unquote")?;
899        self.skip_newlines();
900        let inner = self.expr(0)?;
901        self.skip_newlines();
902        self.expect(&TokenKind::RParen, "`)`")?;
903        Ok(if splice {
904            Sexp::UnquoteSplice(Box::new(inner))
905        } else {
906            Sexp::Unquote(Box::new(inner))
907        })
908    }
909
910    fn def_form(&mut self) -> Result<Sexp, ParseError> {
911        let name = match self.bump() {
912            TokenKind::Ident(n) => n,
913            other => return Err(self.error(format!("expected a name after `def`, found {other:?}"))),
914        };
915        let mut params: Vec<(String, Option<Sexp>)> = Vec::new();
916        if self.at(&TokenKind::LParen) {
917            self.bump();
918            self.skip_newlines();
919            if !self.eat(&TokenKind::RParen) {
920                loop {
921                    self.skip_newlines();
922                    match self.bump() {
923                        // `a` — unannotated
924                        TokenKind::Ident(p) => params.push((p, None)),
925                        // `a:` came through as one token, so a type follows
926                        TokenKind::Label(p) => {
927                            self.skip_newlines();
928                            let ty = self.type_expr()?;
929                            params.push((p, Some(ty)));
930                        }
931                        other => {
932                            return Err(self.error(format!(
933                                "expected a parameter name, found {other:?}"
934                            )))
935                        }
936                    }
937                    self.skip_newlines();
938                    if self.eat(&TokenKind::Comma) {
939                        continue;
940                    }
941                    self.expect(&TokenKind::RParen, "`,` or `)`")?;
942                    break;
943                }
944            }
945        }
946
947        // Optional `-> R`
948        let ret = if matches!(self.peek(), TokenKind::Op(o) if o == "->") {
949            self.bump();
950            self.skip_newlines();
951            Some(self.type_expr()?)
952        } else {
953            None
954        };
955
956        let body = self.body(&["end"])?;
957        self.expect_ident("end")?;
958
959        let annotated = ret.is_some() || params.iter().any(|(_, t)| t.is_some());
960        if !annotated {
961            let mut sig = vec![sym(&name)];
962            sig.extend(params.into_iter().map(|(p, _)| sym(&p)));
963            return Ok(Sexp::List(vec![sym("define"), Sexp::List(sig), body]));
964        }
965
966        // Typed shape. An un-annotated parameter in an otherwise annotated
967        // signature is written `(p dyn)` so the checker sees the ladder
968        // position explicitly rather than inferring it from absence.
969        let mut sig = vec![sym(&name)];
970        for (p, t) in params {
971            let ty = t.unwrap_or_else(|| sym("dyn"));
972            sig.push(Sexp::List(vec![sym(&p), ty]));
973        }
974        Ok(Sexp::List(vec![
975            sym("define-typed"),
976            Sexp::List(sig),
977            ret.unwrap_or_else(|| sym("dyn")),
978            body,
979        ]))
980    }
981
982    /// A type expression. Currently a bare name (`Int`, `Str`, `dyn`) or a
983    /// one-argument constructor (`List(Int)`).
984    fn type_expr(&mut self) -> Result<Sexp, ParseError> {
985        let name = match self.bump() {
986            TokenKind::Ident(n) => n,
987            other => return Err(self.error(format!("expected a type name, found {other:?}"))),
988        };
989        if self.at(&TokenKind::LParen) {
990            let args = self.paren_args()?;
991            let mut list = vec![sym(&name)];
992            list.extend(args);
993            return Ok(Sexp::List(list));
994        }
995        Ok(sym(&name))
996    }
997
998    /// Consume `name` if it is the next token, else leave the position alone.
999    fn eat_ident(&mut self, name: &str) -> bool {
1000        if self.at_ident(name) {
1001            self.bump();
1002            true
1003        } else {
1004            false
1005        }
1006    }
1007
1008    fn expect_ident(&mut self, name: &str) -> Result<(), ParseError> {
1009        if self.at_ident(name) {
1010            self.bump();
1011            Ok(())
1012        } else {
1013            Err(self.error(format!("expected `{name}`, found {:?}", self.peek())))
1014        }
1015    }
1016
1017    /// A sequence of expressions up to one of `terminators`, wrapped in
1018    /// `(begin ...)` when there is more than one.
1019    fn body(&mut self, terminators: &[&str]) -> Result<Sexp, ParseError> {
1020        let mut forms = Vec::new();
1021        loop {
1022            self.skip_newlines();
1023            if matches!(self.peek(), TokenKind::Eof) {
1024                return Err(self.error(format!(
1025                    "unterminated block: expected one of {terminators:?}"
1026                )));
1027            }
1028            if terminators.iter().any(|t| self.at_ident(t)) {
1029                break;
1030            }
1031            forms.push(self.statement()?);
1032        }
1033        Ok(match forms.len() {
1034            0 => Sexp::Nil,
1035            1 => forms.into_iter().next().expect("checked len"),
1036            _ => {
1037                let mut list = vec![sym("begin")];
1038                list.extend(forms);
1039                Sexp::List(list)
1040            }
1041        })
1042    }
1043}
1044
1045fn sym(s: &str) -> Sexp {
1046    Sexp::Atom(Atom::Symbol(s.to_string()))
1047}
1048
1049#[cfg(test)]
1050mod tests {
1051    use super::*;
1052
1053    /// Render an `Sexp` to canonical text so tests can state the expected
1054    /// quoted form as a string. This is `Display`, which tatara-lisp owns —
1055    /// blue does not build Lisp syntax by concatenation.
1056    fn q(src: &str) -> String {
1057        parse_expr(src).map(|s| s.to_string()).unwrap_or_else(|e| panic!("{src:?}: {e}"))
1058    }
1059
1060    // ---- the thesis: Ruby-shaped source becomes tatara-lisp ----------
1061
1062    #[test]
1063    fn arithmetic_respects_precedence() {
1064        assert_eq!(q("1 + 2 * 3"), "(+ 1 (* 2 3))");
1065        assert_eq!(q("(1 + 2) * 3"), "(* (+ 1 2) 3)");
1066    }
1067
1068    #[test]
1069    fn comparison_binds_looser_than_arithmetic() {
1070        assert_eq!(q("a + 1 < b"), "(< (+ a 1) b)");
1071    }
1072
1073    /// The expected tree names `or`/`and`, not `||`/`&&`: the surface
1074    /// spelling is the SURFACE's, and lowering renames it to the form
1075    /// tatara-lisp actually has. This test previously asserted the verbatim
1076    /// spelling, which is how `(== a b)` — a symbol nothing binds — shipped.
1077    #[test]
1078    fn logical_operators_bind_loosest_and_lower_to_tataras_names() {
1079        assert_eq!(q("a && b || c"), "(or (and a b) c)");
1080    }
1081
1082    #[test]
1083    fn left_associativity() {
1084        assert_eq!(q("1 - 2 - 3"), "(- (- 1 2) 3)");
1085    }
1086
1087    /// A bare `recv.name` is a SEND. Blue commits to uniform access here,
1088    /// so a field can later become a computed method without breaking
1089    /// callers.
1090    #[test]
1091    fn method_call_without_parens_is_a_send() {
1092        assert_eq!(q("user.name"), "(name user)");
1093    }
1094
1095    #[test]
1096    fn method_call_with_args() {
1097        assert_eq!(q("user.greet(1, 2)"), "(greet user 1 2)");
1098    }
1099
1100    #[test]
1101    fn chained_sends_read_left_to_right() {
1102        assert_eq!(q("a.b.c"), "(c (b a))");
1103    }
1104
1105    #[test]
1106    fn plain_call() {
1107        assert_eq!(q("f(1, 2)"), "(f 1 2)");
1108    }
1109
1110    /// The pipeline threads into the FIRST argument, as Elixir's does —
1111    /// that is what makes `|>` composable rather than decorative.
1112    #[test]
1113    fn pipeline_threads_into_first_argument() {
1114        assert_eq!(q("x |> f"), "(f x)");
1115        assert_eq!(q("x |> f(1)"), "(f x 1)");
1116        assert_eq!(q("x |> f |> g"), "(g (f x))");
1117    }
1118
1119    #[test]
1120    fn pipeline_binds_looser_than_arithmetic() {
1121        assert_eq!(q("1 + 2 |> f"), "(f (+ 1 2))");
1122    }
1123
1124    // ---- §V.13's rendering law, enforced at the parser ---------------
1125
1126    /// `a: 1` and `:a => 1` are the SAME TREE. That is exactly why the
1127    /// formatter may always render the shorthand: they are not two
1128    /// spellings of two things, they are two spellings of one thing.
1129    #[test]
1130    fn label_and_rocket_produce_the_same_tree_for_a_symbol_key() {
1131        assert_eq!(q("{a: 1}"), q("{:a => 1}"));
1132        assert_eq!(q("{a: 1}"), "(hash-map :a 1)");
1133    }
1134
1135    /// And where the key is NOT a plain symbol, the rocket is the only
1136    /// spelling — so it survives because it must, never as a style choice.
1137    #[test]
1138    fn a_string_key_has_no_shorthand() {
1139        assert_eq!(q(r#"{"k" => 1}"#), r#"(hash-map "k" 1)"#);
1140    }
1141
1142    #[test]
1143    fn list_literal() {
1144        assert_eq!(q("[1, 2, 3]"), "(list 1 2 3)");
1145        assert_eq!(q("[]"), "(list)");
1146    }
1147
1148    // ---- blocks ------------------------------------------------------
1149
1150    #[test]
1151    fn if_else_end() {
1152        assert_eq!(q("if a\n  1\nelse\n  2\nend"), "(if a 1 2)");
1153    }
1154
1155    #[test]
1156    fn if_without_else() {
1157        assert_eq!(q("if a\n  1\nend"), "(if a 1)");
1158    }
1159
1160    /// `unless` lowers to `(if (not c) …)` — one tree per meaning, so
1161    /// every downstream tool sees exactly one shape.
1162    #[test]
1163    fn unless_is_a_negated_if() {
1164        assert_eq!(q("unless a\n  1\nend"), "(if (not a) 1)");
1165    }
1166
1167    #[test]
1168    fn multi_statement_body_becomes_begin() {
1169        assert_eq!(q("if a\n  1\n  2\nend"), "(if a (begin 1 2))");
1170    }
1171
1172    #[test]
1173    fn def_lowers_to_define() {
1174        assert_eq!(
1175            q("def add(a, b)\n  a + b\nend"),
1176            "(define (add a b) (+ a b))"
1177        );
1178    }
1179
1180    #[test]
1181    fn def_with_no_params() {
1182        assert_eq!(q("def zero()\n  0\nend"), "(define (zero) 0)");
1183    }
1184
1185    // ---- literals ----------------------------------------------------
1186
1187    #[test]
1188    fn literals_lower_to_atoms() {
1189        assert_eq!(q("42"), "42");
1190        assert_eq!(q("true"), "#t");
1191        assert_eq!(q(":ok"), ":ok");
1192        assert_eq!(q(r#""hi""#), r#""hi""#);
1193    }
1194
1195    #[test]
1196    fn unary_minus_and_not() {
1197        assert_eq!(q("-x"), "(- 0 x)");
1198        assert_eq!(q("!x"), "(not x)");
1199    }
1200
1201    // ---- programs and errors -----------------------------------------
1202
1203    #[test]
1204    fn a_program_is_a_sequence_of_forms() {
1205        let forms = parse_program("def f()\n  1\nend\nf()").expect("parse");
1206        assert_eq!(forms.len(), 2);
1207        assert_eq!(forms[1].to_string(), "(f)");
1208    }
1209
1210    #[test]
1211    fn unterminated_block_is_an_error_naming_what_was_expected() {
1212        let e = parse_program("if a\n  1").expect_err("must fail");
1213        assert!(e.message.contains("unterminated"), "{}", e.message);
1214    }
1215
1216    #[test]
1217    fn a_parse_error_carries_a_span_into_the_source() {
1218        let src = "1 + )";
1219        let e = parse_program(src).expect_err("must fail");
1220        assert!(e.span.start < src.len(), "span {:?} outside source", e.span);
1221    }
1222
1223    /// Anti-vacuity: `q` must be able to FAIL. If every input parsed, the
1224    /// assertions above would be worthless.
1225    #[test]
1226    fn the_parser_rejects_garbage() {
1227        assert!(parse_program("def").is_err());
1228        assert!(parse_program("(1").is_err());
1229        assert!(parse_program("end").is_err());
1230    }
1231}