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