1use tatara_lisp::{Atom, Sexp, Spanned, SpannedForm};
46
47use crate::lex::{lex, Span, Token, TokenKind};
48use crate::spelling::{Note, Spelling};
49
50#[derive(Clone, Debug, PartialEq)]
65pub struct ParseError {
66 pub message: String,
67 pub span: Span,
68 pub line: usize,
70 pub col: usize,
72}
73
74impl ParseError {
75 fn new(message: impl Into<String>, span: Span) -> Self {
79 Self {
80 message: message.into(),
81 span,
82 line: 0,
83 col: 0,
84 }
85 }
86
87 fn located(mut self, src: &str) -> Self {
89 let (line, col) = Span::line_col(src, self.span.start.min(src.len()));
90 self.line = line;
91 self.col = col;
92 self
93 }
94}
95
96impl std::fmt::Display for ParseError {
97 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
98 write!(f, "{}:{}: {}", self.line, self.col, self.message)
99 }
100}
101
102impl std::error::Error for ParseError {}
103
104impl From<crate::lex::LexError> for ParseError {
105 fn from(e: crate::lex::LexError) -> Self {
106 Self::new(e.message, e.span)
107 }
108}
109
110thread_local! {
111 static PARSES: std::cell::Cell<u64> = const { std::cell::Cell::new(0) };
112}
113
114#[must_use]
118pub fn parses_on_this_thread() -> u64 {
119 PARSES.with(std::cell::Cell::get)
120}
121
122pub fn parse_program(src: &str) -> Result<Vec<Sexp>, ParseError> {
124 parse_program_with_depth(src, MAX_EXPR_DEPTH)
125}
126
127pub fn parse_program_tree(src: &str) -> Result<Vec<Spanned>, ParseError> {
135 parse_program_tree_with_depth(src, MAX_EXPR_DEPTH)
136}
137
138pub fn parse_program_tree_with_depth(
140 src: &str,
141 max_depth: usize,
142) -> Result<Vec<Spanned>, ParseError> {
143 PARSES.with(|n| n.set(n.get() + 1));
144 let mut p = Parser::over(src, lex(src).map_err(|e| ParseError::from(e).located(src))?, max_depth);
145 p.program().map_err(|e| e.located(src))
146}
147
148pub fn spelling_notes(src: &str) -> Result<Vec<Note>, ParseError> {
149 let mut p = Parser::over(src, lex(src).map_err(|e| ParseError::from(e).located(src))?, MAX_EXPR_DEPTH);
150 p.program().map_err(|e| e.located(src))?;
151 Ok(p.notes)
152}
153
154pub fn parse_program_in(src: &str, pack: &crate::yakugo::Yakugo) -> Result<Vec<Sexp>, ParseError> {
167 Ok(parse_program_tree_in(src, pack)?
168 .iter()
169 .map(Spanned::to_sexp)
170 .collect())
171}
172
173pub fn parse_program_tree_in(
189 src: &str,
190 pack: &crate::yakugo::Yakugo,
191) -> Result<Vec<Spanned>, ParseError> {
192 let toks = crate::yakugo::canonical_tokens(src, pack)
193 .map_err(|e| ParseError::from(e).located(src))?;
194 PARSES.with(|n| n.set(n.get() + 1));
195 let mut p = Parser::over(src, toks, MAX_EXPR_DEPTH);
196 p.program().map_err(|e| e.located(src))
197}
198
199pub fn parse_program_with_depth(src: &str, max_depth: usize) -> Result<Vec<Sexp>, ParseError> {
208 Ok(parse_program_tree_with_depth(src, max_depth)?
209 .iter()
210 .map(Spanned::to_sexp)
211 .collect())
212}
213
214pub fn parse_program_spanned(src: &str) -> Result<Vec<(Sexp, Span)>, ParseError> {
227 parse_program_spanned_with_depth(src, MAX_EXPR_DEPTH)
228}
229
230pub fn parse_program_spanned_with_depth(
232 src: &str,
233 max_depth: usize,
234) -> Result<Vec<(Sexp, Span)>, ParseError> {
235 Ok(parse_program_tree_with_depth(src, max_depth)?
236 .iter()
237 .map(|f| (f.to_sexp(), f.span))
238 .collect())
239}
240
241pub fn comments(src: &str) -> Vec<Comment> {
249 lex(src)
250 .map(|toks| {
251 toks.iter()
252 .filter_map(|t| match &t.kind {
253 TokenKind::Comment(text) => Some(Comment {
254 text: text.clone(),
255 span: t.span,
256 own_line: line_before_is_blank(src, t.span.start),
257 }),
258 _ => None,
259 })
260 .collect()
261 })
262 .unwrap_or_default()
263}
264
265#[derive(Clone, Debug, PartialEq, Eq)]
267pub struct Comment {
268 pub text: String,
270 pub span: Span,
271 pub own_line: bool,
273}
274
275fn line_before_is_blank(src: &str, start: usize) -> bool {
276 src[..start.min(src.len())]
277 .rsplit('\n')
278 .next()
279 .is_some_and(|prefix| prefix.trim().is_empty())
280}
281
282pub fn parse_expr(src: &str) -> Result<Sexp, ParseError> {
284 let forms = parse_program(src)?;
285 match forms.len() {
286 1 => Ok(forms.into_iter().next().expect("checked len")),
287 n => Err(ParseError::new(
288 format!("expected exactly one expression, found {n}"),
289 Span::new(0, src.len()),
290 )
291 .located(src)),
292 }
293}
294
295#[derive(Clone, Copy, Debug)]
305pub struct Infix {
306 pub op: &'static str,
308 pub power: (u8, u8),
310 pub callee: &'static str,
312 pub doc: &'static str,
316}
317
318pub const INFIX: &[Infix] = &[
322 Infix {
323 op: "or",
324 power: (1, 2),
325 callee: "or",
326 doc: "Logical or: the left value if truthy, otherwise the right; the right is evaluated only when needed. `||` is read as `or` and written `or` (B0025).",
327 },
328 Infix {
329 op: "and",
330 power: (3, 4),
331 callee: "and",
332 doc: "Logical and: the right value if the left is truthy, otherwise the left; short-circuits. `&&` is read as `and` and written `and` (B0025).",
333 },
334 Infix {
345 op: "==",
346 power: (5, 6),
347 callee: "equal?",
348 doc: "Structural equality over every value (okite D0001): 4 == 4.0, lists and maps by content, nil only equals nil.",
349 },
350 Infix {
351 op: "!=",
352 power: (5, 6),
353 callee: "not=",
354 doc: "Exactly not(a == b) (okite D0002).",
355 },
356 Infix {
357 op: "<",
358 power: (5, 6),
359 callee: "<",
360 doc: "Numeric less-than. A string operand is a type error: order text with compare(a, b).",
361 },
362 Infix {
363 op: "<=",
364 power: (5, 6),
365 callee: "<=",
366 doc: "Numeric less-than-or-equal. Numbers only, like <.",
367 },
368 Infix {
369 op: ">",
370 power: (5, 6),
371 callee: ">",
372 doc: "Numeric greater-than. Numbers only, like <.",
373 },
374 Infix {
375 op: ">=",
376 power: (5, 6),
377 callee: ">=",
378 doc: "Numeric greater-than-or-equal. Numbers only, like <.",
379 },
380 Infix {
381 op: "+",
382 power: (7, 8),
383 callee: "+",
384 doc: "Numeric addition; an Int and a Float give a Float. Not string concatenation: interpolate instead.",
385 },
386 Infix {
387 op: "-",
388 power: (7, 8),
389 callee: "-",
390 doc: "Numeric subtraction. Written before an operand with nothing on its left, it is unary minus: -n.",
391 },
392 Infix {
393 op: "*",
394 power: (9, 10),
395 callee: "*",
396 doc: "Numeric multiplication.",
397 },
398 Infix {
399 op: "/",
400 power: (9, 10),
401 callee: "/",
402 doc: "Division that never truncates (okite D0008): 7 / 2 is 3.5, while 6 / 2 stays the Int 3. Integer division is floor(a / b).",
403 },
404 Infix {
405 op: "%",
406 power: (9, 10),
407 callee: "mod",
408 doc: "Euclidean remainder: never negative, so (0 - 7) % 3 is 2, not -1. A zero divisor is a runtime error.",
409 },
410];
411
412pub const LOWERED_ASSERT: &str = "blue-assert";
435
436pub const LOWERED_MAP: &str = "hash-map";
444
445pub const LOWERED_CONCAT: &str = "concat";
450
451pub const LOWERED_FIELD: &str = "blue-field";
452
453pub const LOWERED_ERROR_MATCH: &str = "blue-rescues?";
454
455pub const LOWERED_ENSURE: &str = "blue-ensure";
456
457pub const LOWERED_RERAISE: &str = "blue-reraise";
458
459pub const LOWERED_ATTEMPTED: &str = "blue-attempted";
460
461pub const LOWERED_FAILED: &str = "blue-failed";
462
463pub const RESCUED: &str = "rescued-error";
464
465pub const SURFACE_KEYWORDS: &[&str] = &[
466 "if",
467 "begin",
468 "unless",
469 "def",
470 "defmacro",
471 "quote",
472 "unquote",
473 "unquote_splice",
474 "test",
475 "assert",
476 "fn",
477 "case",
478];
479
480pub const BLOCK_KEYWORDS: &[&str] = &[
492 "do", "end", "else", "elsif", "rescue", "ensure", "true", "false", "nil",
493];
494
495pub const OPERATOR_KEYWORDS: &[&str] = &["and", "or", "not"];
496
497pub fn is_reserved_word(name: &str) -> bool {
504 SURFACE_KEYWORDS.contains(&name)
505 || BLOCK_KEYWORDS.contains(&name)
506 || OPERATOR_KEYWORDS.contains(&name)
507}
508
509pub fn is_callable_name(name: &str) -> bool {
518 if qualified(name).is_some() {
520 return true;
521 }
522 !is_reserved_word(name)
523 && matches!(
524 lex(name).as_deref(),
525 Ok([t, e]) if matches!(&t.kind, TokenKind::Ident(n) if n == name)
526 && e.kind == TokenKind::Eof
527 )
528}
529
530pub const QUALIFIER: char = '/';
535
536pub const BUILTIN_QUALIFIER: &str = "blue";
539
540#[must_use]
542pub fn qualify(pkg: &str, name: &str) -> String {
543 format!("{pkg}{QUALIFIER}{name}")
544}
545
546#[must_use]
552pub fn qualified(sym: &str) -> Option<(&str, &str)> {
553 let (pkg, name) = sym.split_once(QUALIFIER)?;
554 let ident = |s: &str| {
555 matches!(
556 lex(s).as_deref(),
557 Ok([t, e]) if matches!(&t.kind, TokenKind::Ident(n) if n == s) && e.kind == TokenKind::Eof
558 )
559 };
560 let named = |s: &str| ident(s) || matches!(s, "true" | "false" | "nil");
561 (ident(pkg) && !is_reserved_word(pkg) && named(name)).then_some((pkg, name))
562}
563
564#[must_use]
572pub fn keyword_doc(word: &str) -> &'static str {
573 match word {
574 "if" => "`if c … elsif d … else … end`: an expression; its value is the branch taken, nil when none is. An `else` that is empty or `nil` is dropped (B0035).",
575 "unless" => "`unless c … end` is read as `if not c` and written that way (B0026).",
576 "begin" => "`begin … rescue :kind as e … rescue e … ensure … end`: run the statements; a `rescue` clause catches an error by kind, binding it with `as`, and `rescue e` catches every error; `ensure` always runs. A def body takes the same clauses without `begin`.",
577 "rescue" => "A clause of `begin` or `def`: `rescue :a, :b as e`, `rescue e` (every error, last). Inline, `x rescue fallback` is fallback when x raises; it binds loosest, below `|>`.",
578 "ensure" => "The last clause of `begin` or `def`: statements that run whether the block returned or raised; the block's value or error is kept.",
579 "and" => "`a and b`: logical and; `&&` is read as `and` and written `and` (B0025).",
580 "or" => "`a or b`: logical or; `||` is read as `or` and written `or` (B0025).",
581 "not" => "`not x`: true when x is false or nil; `!x` is read as `not x` and written `not x` (B0025). `not (a == b)` is written `a != b` (B0034).",
582 "def" => "`def name(a, b) … end`: a named function; its value is the last expression. Parameters may be typed: `def f(n: Int) -> Int`.",
583 "defmacro" => "`defmacro name(x) quote … unquote(x) … end end`: a macro, a rewrite of the unevaluated form.",
584 "quote" => "`quote … end`: the form itself as data, not its value. Used inside `defmacro`.",
585 "unquote" => "`unquote(x)`: inside `quote`, splice in the value of `x`.",
586 "unquote_splice" => "`unquote_splice(xs)`: inside `quote`, splice the items of the form `xs` in place, not the form itself.",
587 "test" => "`test \"what must hold\" … end`: a test block, run by `blue test` and ignored by `blue run`.",
588 "assert" => "`assert expr`: inside a test, fail it unless `expr` is truthy; the failure quotes the expression.",
589 "fn" => "`fn(x) x * 2 end`: an anonymous function, a value. Blue has no brace or do blocks.",
590 "case" => "`case v when 1 … when 2 … else … end`: compares with `==`; it does not destructure.",
591 "do" => "Reserved and refused: blue has no do-blocks. Pass `fn(x) … end` instead.",
592 "end" => "Closes `def`, `defmacro`, `if`, `case`, `fn`, `test` and `quote`.",
593 "else" => "The branch of `if` or `case` taken when nothing before it matched.",
594 "elsif" => "A further condition in an `if` chain; an `else` holding only an `if` is written `elsif` (B0027).",
595 "true" => "The boolean true.",
596 "false" => "The boolean false. Only `false` and `nil` are falsy; 0, \"\" and [] are truthy.",
597 "nil" => "The absent value. It is not `[]` (okite D0004), and only `nil == nil`.",
598 _ => "",
599 }
600}
601
602fn infix(op: &str) -> Option<&'static Infix> {
603 INFIX.iter().find(|i| i.op == op)
604}
605
606pub const SYMBOL_SPELLINGS: &[(&str, &str)] = &[("&&", "and"), ("||", "or"), ("!", "not")];
607
608fn word_for(op: &str) -> &str {
609 SYMBOL_SPELLINGS
610 .iter()
611 .find(|(sym, _)| *sym == op)
612 .map_or(op, |(_, word)| word)
613}
614
615fn infix_of(kind: &TokenKind) -> Option<&'static Infix> {
616 match kind {
617 TokenKind::Op(o) => infix(word_for(o)),
618 TokenKind::Ident(w) if w == "and" || w == "or" => infix(w),
619 _ => None,
620 }
621}
622
623const PIPE_POWER: (u8, u8) = (0, 1);
624const RESCUE_POWER: (u8, u8) = (0, 1);
625
626struct Parser {
627 toks: Vec<Token>,
628 pos: usize,
629 depth: usize,
637 max_depth: usize,
644 src: String,
645 comment_spans: Vec<Span>,
646 notes: Vec<Note>,
647}
648
649pub const MAX_EXPR_DEPTH: usize = 256;
656
657type Fail = Box<ParseError>;
669
670fn fail(message: impl Into<String>, span: Span) -> Fail {
671 Box::new(ParseError::new(message, span))
672}
673
674impl Parser {
675 fn over(src: &str, all: Vec<Token>, max_depth: usize) -> Self {
676 let comment_spans = all
677 .iter()
678 .filter(|t| matches!(t.kind, TokenKind::Comment(_)))
679 .map(|t| t.span)
680 .collect();
681 let toks = all
682 .into_iter()
683 .filter(|t| !matches!(t.kind, TokenKind::Comment(_)))
684 .collect();
685 Parser {
686 toks,
687 pos: 0,
688 depth: 0,
689 max_depth,
690 src: src.to_string(),
691 comment_spans,
692 notes: Vec::new(),
693 }
694 }
695
696 fn note(&mut self, spelling: Spelling, span: Span, written: &str, canonical: &str) {
697 self.notes.push(Note {
698 spelling,
699 span,
700 written: written.to_string(),
701 canonical: canonical.to_string(),
702 });
703 }
704
705 fn text(&self, span: Span) -> &str {
706 self.src.get(span.start..span.end).unwrap_or("")
707 }
708
709 fn comment_within(&self, lo: usize, hi: usize) -> bool {
710 self.comment_spans.iter().any(|c| c.start >= lo && c.start < hi)
711 }
712
713 fn touches_previous(&self) -> bool {
714 let here = self.peek_span();
715 self.toks
716 .get(self.pos.wrapping_sub(1))
717 .is_some_and(|prev| prev.span.end == here.start)
718 }
719
720 fn peek(&self) -> &TokenKind {
721 &self.toks[self.pos.min(self.toks.len() - 1)].kind
722 }
723
724 fn peek_span(&self) -> Span {
725 self.toks[self.pos.min(self.toks.len() - 1)].span
726 }
727
728 fn bump(&mut self) -> TokenKind {
729 let k = self.toks[self.pos.min(self.toks.len() - 1)].kind.clone();
730 if self.pos < self.toks.len() {
731 self.pos += 1;
732 }
733 k
734 }
735
736 fn at(&self, k: &TokenKind) -> bool {
737 self.peek() == k
738 }
739
740 fn eat(&mut self, k: &TokenKind) -> bool {
741 if self.at(k) {
742 self.bump();
743 true
744 } else {
745 false
746 }
747 }
748
749 fn expect(&mut self, k: &TokenKind, what: &str) -> Result<(), Fail> {
750 if self.eat(k) {
751 Ok(())
752 } else {
753 Err(self.error(format!("expected {what}, found {}", self.peek())))
754 }
755 }
756
757 fn error(&self, message: impl Into<String>) -> Fail {
758 fail(message, self.peek_span())
759 }
760
761 fn skip_newlines(&mut self) {
763 while matches!(self.peek(), TokenKind::Newline) {
764 self.bump();
765 }
766 }
767
768 fn at_ident(&self, name: &str) -> bool {
769 matches!(self.peek(), TokenKind::Ident(n) if n == name)
770 }
771
772 fn mark(&self) -> usize {
775 self.peek_span().start
776 }
777
778 fn span_since(&self, start: usize) -> Span {
784 let end = self
785 .toks
786 .get(self.pos.saturating_sub(1))
787 .map_or(start, |t| t.span.end);
788 Span::new(start, end.max(start))
789 }
790
791 fn program(&mut self) -> Result<Vec<Spanned>, Fail> {
792 let mut out = Vec::new();
793 loop {
794 self.skip_newlines();
795 if matches!(self.peek(), TokenKind::Eof) {
796 break;
797 }
798 out.push(self.statement()?);
799 self.end_of_statement(&[])?;
800 }
801 Ok(out)
802 }
803
804 fn end_of_statement(&self, terminators: &[&str]) -> Result<(), Fail> {
822 let next = self.peek();
823 if matches!(next, TokenKind::Newline | TokenKind::Eof)
824 || terminators.iter().any(|t| self.at_ident(t))
825 {
826 return Ok(());
827 }
828 let prev = self.toks.get(self.pos.wrapping_sub(1)).map(|t| &t.kind);
829 let prev_name = match prev {
830 Some(TokenKind::Ident(n)) => Some(n.as_str()),
831 _ => None,
832 };
833 let callee = prev_name.unwrap_or("f");
834 let why = match (prev, next) {
835 (Some(TokenKind::Ident(t)), _) if t == "then" => {
836 "blue's `if` has no `then`: put the branch on the next line".to_string()
837 }
838 (_, TokenKind::Ident(d)) if d == "do" => "blue has no `do` blocks: pass a function \
839 as an argument, `f(xs, fn(x) … end)`"
840 .to_string(),
841 (_, TokenKind::LBracket) => {
842 let index = match (self.toks.get(self.pos + 1), self.toks.get(self.pos + 2)) {
843 (Some(i), Some(close)) if close.kind == TokenKind::RBracket => match &i.kind {
844 TokenKind::Int(n) => n.to_string(),
845 TokenKind::Ident(n) => n.clone(),
846 _ => "i".to_string(),
847 },
848 _ => "i".to_string(),
849 };
850 let list = prev_name.unwrap_or("xs");
851 format!(
852 "blue has no `xs[i]` indexing: write `nth({index}, {list})` \
853 (or `first({list})`, `last({list})`)"
854 )
855 }
856 (_, TokenKind::LBrace) => "blue has no brace blocks: pass a function as an \
857 argument, `map(xs, fn(x) … end)`"
858 .to_string(),
859 (
860 Some(TokenKind::Ident(_)),
861 TokenKind::Ident(_)
862 | TokenKind::Int(_)
863 | TokenKind::Float(_)
864 | TokenKind::Str(_)
865 | TokenKind::InterpolatedStr { .. }
866 | TokenKind::Sym(_)
867 | TokenKind::True
868 | TokenKind::False
869 | TokenKind::Nil,
870 ) => {
871 let arg = match next {
872 TokenKind::Ident(a) => a.clone(),
873 _ => "x".to_string(),
874 };
875 format!("a call needs parentheses: write `{callee}({arg})`")
876 }
877 _ => "put each statement on its own line".to_string(),
878 };
879 Err(self.error(format!("expected the end of the line, found {next}: {why}")))
880 }
881
882 fn statement(&mut self) -> Result<Spanned, Fail> {
895 let max = self.max_depth;
909 if self.depth >= max {
910 return Err(self.error(format!(
911 "statement nests deeper than {max}; refusing to \
912 recurse further (this is a limit, not a syntax error)"
913 )));
914 }
915 self.depth += 1;
916 let r = self.statement_inner();
917 self.depth -= 1;
918 r
919 }
920
921 fn statement_inner(&mut self) -> Result<Spanned, Fail> {
922 if let TokenKind::Ident(name) = self.peek().clone() {
923 if self.peek_at(1) == "=" && !is_reserved_word(&name) {
924 let name_span = self.peek_span();
925 self.bump(); self.bump(); self.skip_newlines();
928 let value = self.expr(0)?;
929 return Ok(list_at(
933 self.span_since(name_span.start),
934 vec![sym_at(name_span, "define"), sym_at(name_span, &name), value],
935 ));
936 }
937 }
938 let e = self.expr(0)?;
939 if let SpannedForm::List(items) = &e.form {
940 if items.len() == 3
941 && items[0].as_symbol() == Some("define")
942 && items[1].as_symbol().is_some()
943 && self.text(items[0].span) == "define"
944 {
945 self.note(Spelling::OperatorSpelling, e.span, "define(x, v)", "x = v");
946 }
947 }
948 Ok(e)
949 }
950
951 fn peek_at(&self, n: usize) -> String {
954 match self.toks.get(self.pos + n).map(|t| &t.kind) {
955 Some(TokenKind::Op(o)) => o.clone(),
956 _ => String::new(),
957 }
958 }
959
960 fn expr(&mut self, min_bp: u8) -> Result<Spanned, Fail> {
962 let max = self.max_depth;
972 if self.depth >= max {
973 return Err(self.error(format!(
974 "expression nests deeper than {max}; refusing to \
975 recurse further (this is a limit, not a syntax error)"
976 )));
977 }
978 self.depth += 1;
979 let r = self.expr_inner(min_bp);
980 self.depth -= 1;
981 r
982 }
983
984 #[inline(never)]
985 fn rescue_node(&self, start: usize, op_span: Span, lhs: Spanned, fallback: Spanned) -> Spanned {
986 list_at(
987 self.span_since(start),
988 vec![
989 sym_at(op_span, "try"),
990 lhs,
991 list_at(
992 op_span.merge(fallback.span),
993 vec![
994 sym_at(op_span, "catch"),
995 list_at(op_span, vec![sym_at(op_span, "_")]),
996 fallback,
997 ],
998 ),
999 ],
1000 )
1001 }
1002
1003 #[inline(never)]
1004 fn infix_spelling(&mut self, callee: Option<&str>, op_span: Span) {
1005 let written = self.text(op_span).to_string();
1006 match (callee, self.peek().clone()) {
1007 (None, _) => self.note(Spelling::CallForm, op_span, "|>", "a call"),
1008 (Some(c), TokenKind::Op(o)) if word_for(&o) != o => {
1009 self.note(Spelling::LogicalSymbol, op_span, &written, c);
1010 }
1011 (Some(_), TokenKind::Op(o)) if written != o => {
1012 self.note(Spelling::OperatorSpelling, op_span, &written, &o);
1013 }
1014 _ => {}
1015 }
1016 }
1017
1018 #[inline(never)]
1019 fn infix_node(&mut self, start: usize, callee: Option<&str>, op_span: Span, lhs: Spanned, rhs: Spanned) -> Spanned {
1020 if callee == Some("-") && matches!(lhs.form, SpannedForm::Atom(Atom::Int(0))) {
1021 self.note(Spelling::OperatorSpelling, lhs.span.merge(op_span), "0 - x", "-x");
1022 }
1023
1024 if callee.is_none() {
1025 let rhs_span = rhs.span;
1030 let threaded = match rhs.form {
1031 SpannedForm::List(mut items) if !items.is_empty() => {
1032 items.insert(1, lhs);
1033 list_at(self.span_since(start), items)
1034 }
1035 callee => list_at(
1036 self.span_since(start),
1037 vec![Spanned::new(rhs_span, callee), lhs],
1038 ),
1039 };
1040 match &threaded.form {
1041 SpannedForm::List(items)
1042 if items.len() == 2 && items[0].as_symbol() == Some("not") =>
1043 {
1044 let (head, operand) = (items[0].span, items[1].clone());
1045 self.negation(head, threaded.span, operand)
1046 }
1047 SpannedForm::List(items)
1048 if items.len() == 4
1049 && items[0].as_symbol() == Some("if")
1050 && matches!(items[3].form, SpannedForm::Nil) =>
1051 {
1052 list_at(threaded.span, items[..3].to_vec())
1053 }
1054 _ => threaded,
1055 }
1056 } else {
1057 list_at(
1063 self.span_since(start),
1064 vec![sym_at(op_span, callee.unwrap()), lhs, rhs],
1065 )
1066 }
1067 }
1068
1069 fn expr_inner(&mut self, min_bp: u8) -> Result<Spanned, Fail> {
1070 let start = self.mark();
1071 let mut lhs = self.prefix()?;
1072
1073 loop {
1074 match self.peek() {
1076 TokenKind::Dot => {
1077 let dot = self.peek_span();
1078 self.bump();
1079 lhs = self.finish_send(start, lhs, dot)?;
1080 continue;
1081 }
1082 TokenKind::LParen => {
1083 let (args, splat) = self.call_args()?;
1084 let call = self.call_node(self.span_since(start), vec![lhs], args, splat);
1085 lhs = self.call_spelling(call);
1086 continue;
1087 }
1088 _ => {}
1089 }
1090
1091 let op_span = self.peek_span();
1095 if self.at_ident("rescue") {
1096 if RESCUE_POWER.0 < min_bp {
1097 break;
1098 }
1099 self.bump();
1100 self.skip_newlines();
1101 let fallback = self.expr(RESCUE_POWER.1)?;
1102 lhs = self.rescue_node(start, op_span, lhs, fallback);
1103 continue;
1104 }
1105 let (callee, (lbp, rbp)) = match self.peek() {
1106 TokenKind::Pipe => (None, PIPE_POWER),
1107 other => match infix_of(other) {
1108 Some(i) => (Some(i.callee), i.power),
1109 None => break,
1110 },
1111 };
1112 if lbp < min_bp {
1113 break;
1114 }
1115 self.infix_spelling(callee, op_span);
1116 self.bump();
1117 self.skip_newlines();
1118 let rhs = self.expr(rbp)?;
1119 lhs = self.infix_node(start, callee, op_span, lhs, rhs);
1120 }
1121
1122 Ok(lhs)
1123 }
1124
1125 #[inline(never)]
1126 fn interpolation(span: Span, parts: &[String], exprs: &[String]) -> Result<Spanned, Fail> {
1127 let mut acc = atom_at(span, Atom::Str(parts[0].clone()));
1137 for (i, raw) in exprs.iter().enumerate() {
1138 let inner = parse_expr(raw).map_err(|e| {
1139 fail(
1140 format!("in interpolation `#{{{raw}}}`: {}", e.message),
1141 span,
1142 )
1143 })?;
1144 let inner = Spanned::from_sexp_at(&inner, span);
1145 acc = list_at(span, vec![sym_at(span, LOWERED_CONCAT), acc, inner]);
1146 acc = list_at(
1149 span,
1150 vec![
1151 sym_at(span, LOWERED_CONCAT),
1152 acc,
1153 atom_at(span, Atom::Str(parts[i + 1].clone())),
1154 ],
1155 );
1156 }
1157 Ok(acc)
1158 }
1159
1160 #[inline(never)]
1161 fn not_prefix(&mut self, span: Span) -> Result<Spanned, Fail> {
1162 if self.at(&TokenKind::LParen) && self.touches_previous() {
1163 self.note(Spelling::OperatorSpelling, span, "not(x)", "not x");
1164 let (args, splat) = self.call_args()?;
1165 if args.len() != 1 || splat.is_some() {
1166 let call = self.call_node(self.span_since(span.start), vec![sym_at(span, "not")], args, splat);
1167 return Ok(call);
1168 }
1169 let operand = args.into_iter().next().expect("one argument");
1170 return Ok(self.negation(span, self.span_since(span.start), operand));
1171 }
1172 let rhs = self.expr(11)?;
1173 Ok(self.negation(span, self.span_since(span.start), rhs))
1174 }
1175
1176 fn prefix(&mut self) -> Result<Spanned, Fail> {
1177 if self.at(&TokenKind::LParen) {
1178 self.bump();
1179 return self.group();
1180 }
1181 self.prefix_token()
1182 }
1183
1184 fn group(&mut self) -> Result<Spanned, Fail> {
1185 self.skip_newlines();
1186 let inner = self.expr(0)?;
1187 self.skip_newlines();
1188 self.expect(&TokenKind::RParen, "`)`")?;
1189 Ok(inner)
1190 }
1191
1192 #[inline(never)]
1193 fn prefix_token(&mut self) -> Result<Spanned, Fail> {
1194 let span = self.peek_span();
1195 match self.bump() {
1196 TokenKind::Int(v) => Ok(atom_at(span, Atom::Int(v))),
1197 TokenKind::Float(v) => Ok(atom_at(span, Atom::Float(v))),
1198 TokenKind::Str(s) => Ok(atom_at(span, Atom::Str(s))),
1199
1200 TokenKind::InterpolatedStr { parts, exprs } => Self::interpolation(span, &parts, &exprs),
1211 TokenKind::Sym(s) => Ok(atom_at(span, Atom::Keyword(s))),
1212 TokenKind::True => Ok(atom_at(span, Atom::Bool(true))),
1213 TokenKind::False => Ok(atom_at(span, Atom::Bool(false))),
1214 TokenKind::Nil => Ok(Spanned::new(span, SpannedForm::Nil)),
1215
1216 TokenKind::Op(o) if o == "-" => {
1217 let rhs = self.expr(11)?; Ok(list_at(
1219 self.span_since(span.start),
1220 vec![sym_at(span, "-"), atom_at(span, Atom::Int(0)), rhs],
1221 ))
1222 }
1223 TokenKind::Op(o) if o == "!" => {
1224 let written = self.text(span).to_string();
1225 self.note(Spelling::LogicalSymbol, span, &written, "not");
1226 let rhs = self.expr(11)?;
1227 Ok(self.negation(span, self.span_since(span.start), rhs))
1228 }
1229 TokenKind::Ident(w) if w == "not" => self.not_prefix(span),
1230 TokenKind::Ident(w) if (w == "and" || w == "or") && self.at(&TokenKind::LParen) => {
1231 Ok(sym_at(span, &w))
1232 }
1233 TokenKind::Ident(w) if w == "and" || w == "or" => Err(fail(
1234 format!("`{w}` joins two expressions: `a {w} b`"),
1235 span,
1236 )),
1237
1238 TokenKind::LParen => {
1239 self.skip_newlines();
1240 let inner = self.expr(0)?;
1241 self.skip_newlines();
1242 self.expect(&TokenKind::RParen, "`)`")?;
1243 Ok(inner)
1247 }
1248
1249 TokenKind::LBracket => self.list_literal(span),
1250 TokenKind::LBrace => self.map_literal(span),
1251
1252 TokenKind::Ident(name) if self.at(&TokenKind::PathSep) => {
1253 self.qualified_name(span, &name)
1254 }
1255 TokenKind::PathSep => Err(fail(
1256 "a qualified name is `package::name`, with the package written before `::`",
1257 span,
1258 )),
1259 TokenKind::Ident(name) => match name.as_str() {
1260 "if" => self.if_form(span, false),
1261 "begin" if !(self.at(&TokenKind::LParen) && self.touches_previous()) => {
1262 self.begin_form(span)
1263 }
1264 "rescue" | "ensure" => Err(fail(
1265 format!("`{name}` belongs to a `begin … end` block or a `def` body, after the statements it protects"),
1266 span,
1267 )),
1268 "unless" => {
1269 self.note(Spelling::Unless, span, "unless", "if not");
1270 self.if_form(span, true)
1271 }
1272 "def" => self.def_form(span),
1273 "defmacro" => self.defmacro_form(span),
1274 "case" => self.case_form(span),
1275 "fn" => self.lambda_form(span),
1276 "test" => self.test_form(span),
1277 "assert" => self.assert_form(span),
1278 "quote" => self.quote_form(span),
1279 "unquote" => self.unquote_form(span, false),
1280 "unquote_splice" => self.unquote_form(span, true),
1281 "do" => Err(fail(
1282 "blue has no `do` blocks: pass a function as an argument, \
1283 `f(xs, fn(x) … end)`",
1284 span,
1285 )),
1286 "end" => Err(fail(
1287 "unexpected `end`: nothing open here for it to close",
1288 span,
1289 )),
1290 _ => Ok(sym_at(span, &name)),
1291 },
1292
1293 other => Err(fail(format!("expected an expression, found {other}"), span)),
1294 }
1295 }
1296
1297 fn qualified_name(&mut self, pkg_span: Span, pkg: &str) -> Result<Spanned, Fail> {
1307 if is_reserved_word(pkg) {
1308 return Err(fail(
1309 format!("`{pkg}` is a reserved word and cannot name a package"),
1310 pkg_span,
1311 ));
1312 }
1313 self.bump(); let name_span = self.peek_span();
1315 let name = match self.bump() {
1316 TokenKind::Ident(n) => n,
1317 TokenKind::True => "true".to_string(),
1319 TokenKind::False => "false".to_string(),
1320 TokenKind::Nil => "nil".to_string(),
1321 other => {
1322 return Err(fail(
1323 format!("expected a name after `{pkg}::`, found {other}"),
1324 name_span,
1325 ))
1326 }
1327 };
1328 if self.at(&TokenKind::PathSep) {
1329 return Err(self.error(format!(
1330 "a qualified name has one qualifier: `package::name`, and `{pkg}::{name}::…` has two"
1331 )));
1332 }
1333 Ok(sym_at(
1334 self.span_since(pkg_span.start),
1335 &qualify(pkg, &name),
1336 ))
1337 }
1338
1339 #[inline(never)]
1346 fn negation(&mut self, head: Span, whole: Span, operand: Spanned) -> Spanned {
1347 if let SpannedForm::List(items) = &operand.form {
1348 let flipped = match items.first().and_then(Spanned::as_symbol) {
1349 Some("equal?") => Some("not="),
1350 Some("not=") => Some("equal?"),
1351 _ => None,
1352 };
1353 if let (Some(to), 3) = (flipped, items.len()) {
1354 let (written, canonical) = if to == "not=" {
1355 ("not (a == b)", "a != b")
1356 } else {
1357 ("not (a != b)", "a == b")
1358 };
1359 self.note(Spelling::NegatedComparison, whole, written, canonical);
1360 let mut out = items.clone();
1361 out[0] = sym_at(items[0].span, to);
1362 return list_at(whole, out);
1363 }
1364 }
1365 list_at(whole, vec![sym_at(head, "not"), operand])
1366 }
1367
1368 #[inline(never)]
1369 fn call_spelling(&mut self, call: Spanned) -> Spanned {
1370 let SpannedForm::List(items) = &call.form else {
1371 return call;
1372 };
1373 let Some(head) = items.first().and_then(Spanned::as_symbol) else {
1374 return call;
1375 };
1376 let argc = items.len() - 1;
1377 let operator = match (head, argc) {
1378 ("and" | "or" | "mod" | "equal?", 2) => Some(head),
1379 ("list", _) => Some("list"),
1380 _ => None,
1381 };
1382 if let Some(op) = operator {
1383 let canonical = match op {
1384 "list" => "[…]".to_string(),
1385 "mod" => "a % b".to_string(),
1386 "equal?" => "a == b".to_string(),
1387 other => format!("a {other} b"),
1388 };
1389 self.note(Spelling::OperatorSpelling, call.span, &format!("{head}(…)"), &canonical);
1390 return call;
1391 }
1392 if head == "apply"
1393 && self.text(items[0].span) == "apply"
1394 && argc >= 2
1395 && items[1].as_symbol().is_some_and(is_callable_name)
1396 {
1397 self.note(Spelling::Apply, call.span, "apply(f, …, xs)", "f(…, *xs)");
1398 return call;
1399 }
1400 if head == "try" {
1401 return self.try_spelling(call);
1402 }
1403 call
1404 }
1405
1406 fn try_spelling(&mut self, call: Spanned) -> Spanned {
1407 let SpannedForm::List(items) = &call.form else {
1408 return call;
1409 };
1410 let Some((catch, body)) = items[1..].split_last() else {
1411 return call;
1412 };
1413 let Some(clause) = catch.as_list() else {
1414 return call;
1415 };
1416 let binder = match clause {
1417 [head, binding, handler @ ..]
1418 if head.as_symbol() == Some("catch") && !handler.is_empty() =>
1419 {
1420 match binding.as_list() {
1421 Some([b]) => b.as_symbol().map(|n| (n.to_string(), b.span)),
1422 _ => None,
1423 }
1424 }
1425 _ => None,
1426 };
1427 let Some((name, binder_span)) = binder else {
1428 return call;
1429 };
1430 if body.is_empty() {
1431 return call;
1432 }
1433 self.note(Spelling::TryCatch, call.span, "try(…, catch(…))", "rescue");
1434 let handler = &clause[2..];
1435 let rename = name.starts_with('_') && name != "_" && !handler.iter().any(|h| mentions(h, &name));
1436 let body = if body.len() == 1 {
1437 body[0].clone()
1438 } else {
1439 let span = cover(body, call.span);
1440 let mut list = vec![sym_at(Span::new(span.start, span.start), "begin")];
1441 list.extend(body.iter().cloned());
1442 list_at(span, list)
1443 };
1444 let handler = if handler.len() == 1 {
1445 handler[0].clone()
1446 } else {
1447 let span = cover(handler, catch.span);
1448 let mut list = vec![sym_at(Span::new(span.start, span.start), "begin")];
1449 list.extend(handler.iter().cloned());
1450 list_at(span, list)
1451 };
1452 let binder = sym_at(binder_span, if rename { "_" } else { &name });
1453 list_at(
1454 call.span,
1455 vec![
1456 items[0].clone(),
1457 body,
1458 list_at(
1459 catch.span,
1460 vec![
1461 clause[0].clone(),
1462 list_at(clause[1].span, vec![binder]),
1463 handler,
1464 ],
1465 ),
1466 ],
1467 )
1468 }
1469
1470 fn finish_send(&mut self, start: usize, recv: Spanned, dot: Span) -> Result<Spanned, Fail> {
1471 let name_span = self.peek_span();
1472 let name = match self.bump() {
1473 TokenKind::Ident(n) if is_reserved_word(&n) => {
1487 return Err(self.error(format!(
1488 "`{n}` is a reserved word and cannot be a method name — \
1489 `recv.{n}` would lower onto the `{n}` form itself"
1490 )))
1491 }
1492 TokenKind::Ident(n) => n,
1493 other => {
1494 return Err(fail(
1495 format!("expected a method name after `.`, found {other}"),
1496 name_span,
1497 ))
1498 }
1499 };
1500 if !self.at(&TokenKind::LParen) {
1501 return Ok(list_at(
1502 self.span_since(start),
1503 vec![
1504 sym_at(dot, LOWERED_FIELD),
1505 recv,
1506 atom_at(name_span, Atom::Keyword(name)),
1507 ],
1508 ));
1509 }
1510 self.note(Spelling::CallForm, dot.merge(name_span), &format!("x.{name}(…)"), &format!("{name}(x, …)"));
1511 let (args, splat) = self.call_args()?;
1512 Ok(self.call_node(self.span_since(start), vec![sym_at(name_span, &name), recv], args, splat))
1513 }
1514
1515 fn call_node(&self, span: Span, head: Vec<Spanned>, args: Vec<Spanned>, splat: Option<Span>) -> Spanned {
1516 let mut list = Vec::with_capacity(head.len() + args.len() + 1);
1517 if let Some(at) = splat {
1518 list.push(sym_at(at, "apply"));
1519 }
1520 list.extend(head);
1521 list.extend(args);
1522 list_at(span, list)
1523 }
1524
1525 fn paren_args(&mut self) -> Result<Vec<Spanned>, Fail> {
1526 let (args, splat) = self.call_args()?;
1527 if let Some(at) = splat {
1528 return Err(fail("a splat `*xs` passes a list as a call's last arguments, and this is not a call", at));
1529 }
1530 Ok(args)
1531 }
1532
1533 fn call_args(&mut self) -> Result<(Vec<Spanned>, Option<Span>), Fail> {
1534 self.expect(&TokenKind::LParen, "`(`")?;
1535 let mut args = Vec::new();
1536 let mut splat = None;
1537 self.skip_newlines();
1538 if self.eat(&TokenKind::RParen) {
1539 return Ok((args, splat));
1540 }
1541 loop {
1542 self.skip_newlines();
1543 if splat.is_some() {
1544 return Err(self.error("a splat `*xs` passes a list as the LAST arguments: write it last"));
1545 }
1546 if matches!(self.peek(), TokenKind::Op(o) if o == "*") {
1547 splat = Some(self.peek_span());
1548 self.bump();
1549 }
1550 args.push(self.expr(0)?);
1551 self.skip_newlines();
1552 if self.eat(&TokenKind::Comma) {
1553 if self.trailing_comma(&TokenKind::RParen) {
1554 break;
1555 }
1556 continue;
1557 }
1558 self.expect(&TokenKind::RParen, "`,` or `)`")?;
1559 break;
1560 }
1561 Ok((args, splat))
1562 }
1563
1564 fn trailing_comma(&mut self, closer: &TokenKind) -> bool {
1565 let comma = self.toks.get(self.pos.wrapping_sub(1)).map_or(self.peek_span(), |t| t.span);
1566 self.skip_newlines();
1567 if self.at(closer) {
1568 self.note(Spelling::TrailingComma, comma, ",", "");
1569 self.bump();
1570 return true;
1571 }
1572 false
1573 }
1574
1575 fn list_literal(&mut self, open: Span) -> Result<Spanned, Fail> {
1576 let mut items = vec![sym_at(open, "list")];
1577 self.skip_newlines();
1578 if self.eat(&TokenKind::RBracket) {
1579 return Ok(list_at(self.span_since(open.start), items));
1580 }
1581 loop {
1582 self.skip_newlines();
1583 items.push(self.expr(0)?);
1584 self.skip_newlines();
1585 if self.eat(&TokenKind::Comma) {
1586 if self.trailing_comma(&TokenKind::RBracket) {
1587 break;
1588 }
1589 continue;
1590 }
1591 self.expect(&TokenKind::RBracket, "`,` or `]`")?;
1592 break;
1593 }
1594 Ok(list_at(self.span_since(open.start), items))
1595 }
1596
1597 fn map_literal(&mut self, open: Span) -> Result<Spanned, Fail> {
1604 let mut items = vec![sym_at(open, LOWERED_MAP)];
1605 self.skip_newlines();
1606 if self.eat(&TokenKind::RBrace) {
1607 return Ok(list_at(self.span_since(open.start), items));
1608 }
1609 loop {
1610 self.skip_newlines();
1611 match self.peek().clone() {
1612 TokenKind::Label(name) => {
1613 let label_span = self.peek_span();
1614 self.bump();
1615 self.skip_newlines();
1616 items.push(atom_at(label_span, Atom::Keyword(name)));
1617 items.push(self.expr(0)?);
1618 }
1619 _ => {
1620 let k = self.expr(0)?;
1621 self.skip_newlines();
1622 if let SpannedForm::Atom(Atom::Keyword(name)) = &k.form {
1623 if blue_lang_name(name) {
1624 let written = format!(":{name} =>");
1625 let canonical = format!("{name}:");
1626 self.note(Spelling::KeywordRocket, k.span, &written, &canonical);
1627 }
1628 }
1629 self.expect(&TokenKind::Rocket, "`=>` in a map literal")?;
1630 self.skip_newlines();
1631 items.push(k);
1632 items.push(self.expr(0)?);
1633 }
1634 }
1635 self.skip_newlines();
1636 if self.eat(&TokenKind::Comma) {
1637 if self.trailing_comma(&TokenKind::RBrace) {
1638 break;
1639 }
1640 continue;
1641 }
1642 self.expect(&TokenKind::RBrace, "`,` or `}`")?;
1643 break;
1644 }
1645 Ok(list_at(self.span_since(open.start), items))
1646 }
1647
1648 fn if_chain(&mut self, head: Span) -> Result<Spanned, Fail> {
1658 let cond = self.expr(0)?;
1659 let then = self.body(&["elsif", "else", "end"])?;
1660 let els = self.else_part()?;
1661 let mut out = vec![sym_at(head, "if"), cond, then];
1662 if let Some(e) = els {
1663 out.push(e);
1664 }
1665 Ok(list_at(self.span_since(head.start), out))
1666 }
1667
1668 fn else_part(&mut self) -> Result<Option<Spanned>, Fail> {
1669 if self.at_ident("elsif") {
1670 let elsif = self.peek_span();
1671 self.bump();
1672 return Ok(Some(self.if_chain(elsif)?));
1673 }
1674 if !self.at_ident("else") {
1675 self.expect_ident("end")?;
1676 return Ok(None);
1677 }
1678 let else_span = self.peek_span();
1679 self.bump();
1680 let save = self.pos;
1681 self.skip_newlines();
1682 let leads_with_if = self.at_ident("if") || self.at_ident("unless");
1683 self.pos = save;
1684 let e = self.body(&["end"])?;
1685 let end_at = self.peek_span().start;
1686 self.expect_ident("end")?;
1687 if matches!(e.form, SpannedForm::Nil) && !self.comment_within(else_span.start, end_at) {
1688 self.note(Spelling::ElseNil, else_span, "else nil", "no else");
1689 return Ok(None);
1690 }
1691 let single_if = matches!(&e.form, SpannedForm::List(items)
1692 if (items.len() == 3 || items.len() == 4) && items[0].as_symbol() == Some("if"));
1693 if leads_with_if && single_if {
1694 self.note(Spelling::ElseIf, else_span, "else if", "elsif");
1695 }
1696 Ok(Some(e))
1697 }
1698
1699 fn if_form(&mut self, head: Span, negate: bool) -> Result<Spanned, Fail> {
1700 let cond = self.expr(0)?;
1701 let cond = if negate {
1702 let inner = cond.span;
1705 self.negation(head, head.merge(inner), cond)
1706 } else {
1707 cond
1708 };
1709 let then = self.body(&["elsif", "else", "end"])?;
1722 let els = self.else_part()?;
1723 let mut out = vec![sym_at(head, "if"), cond, then];
1724 if let Some(e) = els {
1725 out.push(e);
1726 }
1727 Ok(list_at(self.span_since(head.start), out))
1728 }
1729
1730 fn case_form(&mut self, head: Span) -> Result<Spanned, Fail> {
1756 let subject = self.expr(0)?;
1757 self.skip_newlines();
1758
1759 let subject_var = "case-subject";
1762 let mut arms: Vec<Spanned> = Vec::new();
1763 let mut otherwise: Option<Spanned> = None;
1764 let mut else_kw: Option<Span> = None;
1767
1768 loop {
1769 self.skip_newlines();
1770 if self.at_ident("end") {
1771 break;
1772 }
1773 let maybe_else = self.peek_span();
1774 if self.eat_ident("else") {
1775 else_kw = Some(maybe_else);
1776 otherwise = Some(self.body(&["end"])?);
1777 continue;
1778 }
1779 let when = self.peek_span();
1780 if !self.eat_ident("when") {
1781 return Err(self.error(format!(
1782 "expected `when`, `else` or `end` in a case, found {}",
1783 self.peek()
1784 )));
1785 }
1786 self.skip_newlines();
1787 let pattern = self.expr(0)?;
1788 let body = self.body(&["when", "else", "end"])?;
1789 let test = list_at(
1793 when.merge(pattern.span),
1794 vec![sym_at(when, "equal?"), sym_at(when, subject_var), pattern],
1795 );
1796 arms.push(list_at(when.merge(body.span), vec![test, body]));
1797 }
1798 self.expect_ident("end")?;
1799
1800 if arms.is_empty() && otherwise.is_none() {
1801 return Err(self.error("a case needs at least one `when` or an `else`".to_string()));
1802 }
1803
1804 let mut cond = vec![sym_at(head, "cond")];
1809 cond.extend(arms);
1810 let else_span = else_kw.unwrap_or(head);
1817 let else_body = otherwise.unwrap_or_else(|| Spanned::new(else_span, SpannedForm::Nil));
1818 cond.push(list_at(
1819 else_span.merge(else_body.span),
1820 vec![sym_at(else_span, "else"), else_body],
1821 ));
1822
1823 let whole = self.span_since(head.start);
1824 let binding = list_at(
1825 head.merge(subject.span),
1826 vec![sym_at(head, subject_var), subject],
1827 );
1828 Ok(list_at(
1829 whole,
1830 vec![
1831 sym_at(head, "let"),
1832 list_at(binding.span, vec![binding]),
1833 list_at(whole, cond),
1834 ],
1835 ))
1836 }
1837
1838 fn lambda_form(&mut self, head: Span) -> Result<Spanned, Fail> {
1849 let mut params: Vec<Spanned> = Vec::new();
1850 if !self.at(&TokenKind::LParen) {
1851 self.note(Spelling::ParameterList, head, "fn", "fn()");
1852 }
1853 if self.at(&TokenKind::LParen) {
1854 self.bump();
1855 self.skip_newlines();
1856 if !self.eat(&TokenKind::RParen) {
1857 loop {
1858 self.skip_newlines();
1859 let p_span = self.peek_span();
1860 match self.bump() {
1861 TokenKind::Ident(p) => params.push(sym_at(p_span, &p)),
1862 other => {
1863 return Err(fail(
1864 format!("expected a parameter name, found {other}"),
1865 p_span,
1866 ))
1867 }
1868 }
1869 self.skip_newlines();
1870 if self.eat(&TokenKind::Comma) {
1871 if self.trailing_comma(&TokenKind::RParen) {
1872 break;
1873 }
1874 continue;
1875 }
1876 self.expect(&TokenKind::RParen, "`,` or `)`")?;
1877 break;
1878 }
1879 }
1880 }
1881 let params_span = cover(¶ms, head);
1882 let body = self.body(&["end"])?;
1883 self.expect_ident("end")?;
1884 Ok(list_at(
1885 self.span_since(head.start),
1886 vec![sym_at(head, "lambda"), list_at(params_span, params), body],
1887 ))
1888 }
1889
1890 fn test_form(&mut self, head: Span) -> Result<Spanned, Fail> {
1896 let name_span = self.peek_span();
1897 let name = match self.bump() {
1898 TokenKind::Str(s) => s,
1899 other => {
1900 return Err(fail(
1901 format!("expected a string name after `test`, found {other}"),
1902 name_span,
1903 ))
1904 }
1905 };
1906 let body = self.body(&["end"])?;
1907 self.expect_ident("end")?;
1908 Ok(list_at(
1909 self.span_since(head.start),
1910 vec![
1911 sym_at(head, "deftest"),
1912 atom_at(name_span, Atom::Str(name)),
1913 body,
1914 ],
1915 ))
1916 }
1917
1918 fn assert_form(&mut self, head: Span) -> Result<Spanned, Fail> {
1942 let e = self.expr(0)?;
1943 let quoted = Spanned::new(e.span, SpannedForm::Quote(Box::new(e.clone())));
1944 Ok(list_at(
1945 self.span_since(head.start),
1946 vec![sym_at(head, LOWERED_ASSERT), quoted, e],
1947 ))
1948 }
1949
1950 fn defmacro_form(&mut self, head: Span) -> Result<Spanned, Fail> {
1959 let name_span = self.peek_span();
1960 let name = match self.bump() {
1961 TokenKind::Ident(n) => n,
1962 other => {
1963 return Err(fail(
1964 format!("expected a name after `defmacro`, found {other}"),
1965 name_span,
1966 ))
1967 }
1968 };
1969 let mut params: Vec<Spanned> = Vec::new();
1970 if self.at(&TokenKind::LParen) {
1971 self.bump();
1972 self.skip_newlines();
1973 if !self.eat(&TokenKind::RParen) {
1974 loop {
1975 self.skip_newlines();
1976 let p_span = self.peek_span();
1977 match self.bump() {
1978 TokenKind::Ident(p) => params.push(sym_at(p_span, &p)),
1979 TokenKind::Label(p) => {
1980 return Err(self.error(format!(
1981 "macro parameter `{p}` cannot be typed: a macro receives \
1982 source forms, not values"
1983 )))
1984 }
1985 other => {
1986 return Err(fail(
1987 format!("expected a parameter name, found {other}"),
1988 p_span,
1989 ))
1990 }
1991 }
1992 self.skip_newlines();
1993 if self.eat(&TokenKind::Comma) {
1994 if self.trailing_comma(&TokenKind::RParen) {
1995 break;
1996 }
1997 continue;
1998 }
1999 self.expect(&TokenKind::RParen, "`,` or `)`")?;
2000 break;
2001 }
2002 }
2003 }
2004 if matches!(self.peek(), TokenKind::Op(o) if o == "->") {
2005 return Err(self.error(
2006 "a macro has no return type: it produces source forms, not values".to_string(),
2007 ));
2008 }
2009
2010 let params_span = cover(¶ms, name_span);
2011 let body = self.body(&["end"])?;
2012 self.expect_ident("end")?;
2013
2014 Ok(list_at(
2017 self.span_since(head.start),
2018 vec![
2019 sym_at(head, "defmacro"),
2020 sym_at(name_span, &name),
2021 list_at(params_span, params),
2022 body,
2023 ],
2024 ))
2025 }
2026
2027 fn quote_form(&mut self, head: Span) -> Result<Spanned, Fail> {
2033 let body = self.body(&["end"])?;
2034 self.expect_ident("end")?;
2035 Ok(Spanned::new(
2044 self.span_since(head.start),
2045 SpannedForm::Quasiquote(Box::new(body)),
2046 ))
2047 }
2048
2049 fn unquote_form(&mut self, head: Span, splice: bool) -> Result<Spanned, Fail> {
2051 self.expect(&TokenKind::LParen, "`(` after unquote")?;
2052 self.skip_newlines();
2053 let inner = self.expr(0)?;
2054 self.skip_newlines();
2055 self.expect(&TokenKind::RParen, "`)`")?;
2056 let span = self.span_since(head.start);
2057 Ok(Spanned::new(
2058 span,
2059 if splice {
2060 SpannedForm::UnquoteSplice(Box::new(inner))
2061 } else {
2062 SpannedForm::Unquote(Box::new(inner))
2063 },
2064 ))
2065 }
2066
2067 fn protected_body(&mut self, head: Span) -> Result<Spanned, Fail> {
2068 let body = self.body(&["rescue", "ensure", "end"])?;
2069 if self.at_ident("rescue") || self.at_ident("ensure") {
2070 return self.rescue_tail(head, body);
2071 }
2072 Ok(body)
2073 }
2074
2075 fn begin_form(&mut self, head: Span) -> Result<Spanned, Fail> {
2076 let body = self.body(&["rescue", "ensure", "end"])?;
2077 if !(self.at_ident("rescue") || self.at_ident("ensure")) {
2078 return Err(self.error(
2079 "a `begin` block protects its statements with `rescue` or `ensure`; without either, write the statements directly",
2080 ));
2081 }
2082 let tree = self.rescue_tail(head, body)?;
2083 self.expect_ident("end")?;
2084 Ok(Spanned::new(self.span_since(head.start), tree.form))
2085 }
2086
2087 fn rescue_tail(&mut self, head: Span, body: Spanned) -> Result<Spanned, Fail> {
2088 let mut clauses: Vec<Spanned> = Vec::new();
2089 let mut catch_all: Option<(Spanned, Spanned, Span)> = None;
2090 while self.at_ident("rescue") {
2091 let kw = self.peek_span();
2092 self.bump();
2093 if catch_all.is_some() {
2094 return Err(fail(
2095 "a `rescue NAME` catches every error, so it is the last `rescue`; put the kinds it would shadow above it",
2096 kw,
2097 ));
2098 }
2099 match self.peek().clone() {
2100 TokenKind::Sym(_) => {
2101 let mut kinds = Vec::new();
2102 loop {
2103 let k_span = self.peek_span();
2104 match self.bump() {
2105 TokenKind::Sym(k) => kinds.push(atom_at(k_span, Atom::Keyword(k))),
2106 other => {
2107 return Err(fail(
2108 format!("expected an error kind such as `:not_found`, found {other}"),
2109 k_span,
2110 ))
2111 }
2112 }
2113 if self.eat(&TokenKind::Comma) {
2114 self.skip_newlines();
2115 continue;
2116 }
2117 break;
2118 }
2119 let binder = if self.eat_ident("as") {
2120 let b_span = self.peek_span();
2121 match self.bump() {
2122 TokenKind::Ident(b) if !is_reserved_word(&b) => Some(sym_at(b_span, &b)),
2123 other => {
2124 return Err(fail(
2125 format!("expected the name the error is bound to after `as`, found {other}"),
2126 b_span,
2127 ))
2128 }
2129 }
2130 } else {
2131 None
2132 };
2133 let handler = self.body(&["rescue", "ensure", "end"])?;
2134 let mut test = vec![sym_at(kw, LOWERED_ERROR_MATCH), sym_at(kw, RESCUED)];
2135 test.extend(kinds);
2136 let test = list_at(cover(&test, kw), test);
2137 let handler = match binder {
2138 Some(b) => let_bound(b, handler, kw),
2139 None => handler,
2140 };
2141 clauses.push(list_at(kw.merge(handler.span), vec![test, handler]));
2142 }
2143 TokenKind::Ident(b) if !is_reserved_word(&b) => {
2144 let b_span = self.peek_span();
2145 self.bump();
2146 let handler = self.body(&["rescue", "ensure", "end"])?;
2147 catch_all = Some((sym_at(b_span, &b), handler, kw));
2148 }
2149 _ => {
2150 return Err(fail(
2151 "`rescue` names what it catches: `rescue :kind`, `rescue :a, :b as e`, or `rescue e` for every error (`rescue _` discards it)",
2152 kw,
2153 ))
2154 }
2155 }
2156 }
2157 let ensure = if self.at_ident("ensure") {
2158 self.bump();
2159 let cleanup = self.body(&["rescue", "end"])?;
2160 if self.at_ident("rescue") {
2161 return Err(self.error("`ensure` comes after every `rescue` of its block"));
2162 }
2163 Some(cleanup)
2164 } else {
2165 None
2166 };
2167 let whole = self.span_since(head.start);
2168 let protected = if clauses.is_empty() {
2169 match catch_all {
2170 None => body,
2171 Some((binder, handler, kw)) => list_at(
2172 whole,
2173 vec![
2174 sym_at(head, "try"),
2175 body,
2176 list_at(
2177 kw.merge(handler.span),
2178 vec![sym_at(kw, "catch"), list_at(binder.span, vec![binder]), handler],
2179 ),
2180 ],
2181 ),
2182 }
2183 } else {
2184 let first_kw = clauses[0].span;
2185 let otherwise = match catch_all {
2186 Some((binder, handler, kw)) => {
2187 let bound = let_bound(binder, handler, kw);
2188 list_at(kw.merge(bound.span), vec![sym_at(kw, "else"), bound])
2189 }
2190 None => list_at(
2191 head,
2192 vec![
2193 sym_at(head, "else"),
2194 list_at(head, vec![sym_at(head, LOWERED_RERAISE), sym_at(head, RESCUED)]),
2195 ],
2196 ),
2197 };
2198 let mut cond = vec![sym_at(first_kw, "cond")];
2199 cond.extend(clauses);
2200 cond.push(otherwise);
2201 let cond_span = cover(&cond, first_kw);
2202 list_at(
2203 whole,
2204 vec![
2205 sym_at(head, "try"),
2206 body,
2207 list_at(
2208 cond_span,
2209 vec![
2210 sym_at(first_kw, "catch"),
2211 list_at(first_kw, vec![sym_at(first_kw, RESCUED)]),
2212 list_at(cond_span, cond),
2213 ],
2214 ),
2215 ],
2216 )
2217 };
2218 Ok(match ensure {
2219 None => protected,
2220 Some(cleanup) => {
2221 let attempted = list_at(protected.span, vec![sym_at(head, LOWERED_ATTEMPTED), protected]);
2222 let failed = list_at(head, vec![sym_at(head, LOWERED_FAILED), sym_at(head, RESCUED)]);
2223 let caught = list_at(
2224 whole,
2225 vec![
2226 sym_at(head, "try"),
2227 attempted,
2228 list_at(head, vec![sym_at(head, "catch"), list_at(head, vec![sym_at(head, RESCUED)]), failed]),
2229 ],
2230 );
2231 list_at(whole, vec![sym_at(head, LOWERED_ENSURE), caught, thunk(cleanup, head)])
2232 }
2233 })
2234 }
2235
2236 fn def_form(&mut self, head: Span) -> Result<Spanned, Fail> {
2237 let name_span = self.peek_span();
2238 let name = match self.bump() {
2239 TokenKind::Ident(n) => n,
2240 other => {
2241 return Err(fail(
2242 format!("expected a name after `def`, found {other}"),
2243 name_span,
2244 ))
2245 }
2246 };
2247 let mut params: Vec<(String, Span, Option<Spanned>)> = Vec::new();
2248 if !self.at(&TokenKind::LParen) {
2249 self.note(Spelling::ParameterList, name_span, &format!("def {name}"), &format!("def {name}()"));
2250 }
2251 if self.at(&TokenKind::LParen) {
2252 self.bump();
2253 self.skip_newlines();
2254 if !self.eat(&TokenKind::RParen) {
2255 loop {
2256 self.skip_newlines();
2257 let p_span = self.peek_span();
2258 match self.bump() {
2259 TokenKind::Ident(p) => params.push((p, p_span, None)),
2261 TokenKind::Label(p) => {
2263 self.skip_newlines();
2264 let ty = self.type_expr()?;
2265 params.push((p, p_span, Some(ty)));
2266 }
2267 other => {
2268 return Err(fail(
2269 format!("expected a parameter name, found {other}"),
2270 p_span,
2271 ))
2272 }
2273 }
2274 self.skip_newlines();
2275 if self.eat(&TokenKind::Comma) {
2276 if self.trailing_comma(&TokenKind::RParen) {
2277 break;
2278 }
2279 continue;
2280 }
2281 self.expect(&TokenKind::RParen, "`,` or `)`")?;
2282 break;
2283 }
2284 }
2285 }
2286 let sig_span = self.span_since(name_span.start);
2288
2289 let ret = if matches!(self.peek(), TokenKind::Op(o) if o == "->") {
2291 self.bump();
2292 self.skip_newlines();
2293 Some(self.type_expr()?)
2294 } else {
2295 None
2296 };
2297
2298 let begins = {
2299 let save = self.pos;
2300 self.skip_newlines();
2301 let at = self.at_ident("begin").then(|| self.peek_span());
2302 self.pos = save;
2303 at
2304 };
2305 let body = self.protected_body(head)?;
2306 if let Some(b) = begins {
2307 let protects = matches!(&body.form, SpannedForm::List(items)
2308 if matches!(items.first().and_then(Spanned::as_symbol), Some("try") | Some(LOWERED_ENSURE)));
2309 if protects && body.span.start == b.start {
2310 self.note(Spelling::TryCatch, b, "begin … end as a whole def body", "the clauses on the def itself");
2311 }
2312 }
2313 self.expect_ident("end")?;
2314 let whole = self.span_since(head.start);
2315
2316 let annotated = ret.is_some() || params.iter().any(|(_, _, t)| t.is_some());
2317 if !annotated {
2318 let mut sig = vec![sym_at(name_span, &name)];
2319 sig.extend(params.into_iter().map(|(p, s, _)| sym_at(s, &p)));
2320 return Ok(list_at(
2321 whole,
2322 vec![sym_at(head, "define"), list_at(sig_span, sig), body],
2323 ));
2324 }
2325
2326 let mut sig = vec![sym_at(name_span, &name)];
2334 for (p, s, t) in params {
2335 let ty = t.unwrap_or_else(|| sym_at(s, "dyn"));
2336 sig.push(list_at(s.merge(ty.span), vec![sym_at(s, &p), ty]));
2337 }
2338 Ok(list_at(
2339 whole,
2340 vec![
2341 sym_at(head, "define-typed"),
2342 list_at(sig_span, sig),
2343 ret.unwrap_or_else(|| sym_at(head, "dyn")),
2344 body,
2345 ],
2346 ))
2347 }
2348
2349 fn type_expr(&mut self) -> Result<Spanned, Fail> {
2352 let name_span = self.peek_span();
2353 let name = match self.bump() {
2354 TokenKind::Ident(n) => n,
2355 other => {
2356 return Err(fail(
2357 format!("expected a type name, found {other}"),
2358 name_span,
2359 ))
2360 }
2361 };
2362 if self.at(&TokenKind::LParen) {
2363 let args = self.paren_args()?;
2364 let mut list = vec![sym_at(name_span, &name)];
2365 list.extend(args);
2366 return Ok(list_at(self.span_since(name_span.start), list));
2367 }
2368 Ok(sym_at(name_span, &name))
2369 }
2370
2371 fn eat_ident(&mut self, name: &str) -> bool {
2373 if self.at_ident(name) {
2374 self.bump();
2375 true
2376 } else {
2377 false
2378 }
2379 }
2380
2381 fn expect_ident(&mut self, name: &str) -> Result<(), Fail> {
2382 if self.at_ident(name) {
2383 self.bump();
2384 Ok(())
2385 } else {
2386 Err(self.error(format!("expected `{name}`, found {}", self.peek())))
2387 }
2388 }
2389
2390 fn body(&mut self, terminators: &[&str]) -> Result<Spanned, Fail> {
2393 let start = self.mark();
2396 let mut forms = Vec::new();
2397 loop {
2398 self.skip_newlines();
2399 if matches!(self.peek(), TokenKind::Eof) {
2400 return Err(self.error(format!(
2401 "unterminated block: expected {} before the end of the file",
2402 one_of(terminators)
2403 )));
2404 }
2405 if terminators.iter().any(|t| self.at_ident(t)) {
2406 break;
2407 }
2408 forms.push(self.statement()?);
2409 self.end_of_statement(terminators)?;
2410 }
2411 Ok(match forms.len() {
2412 0 => Spanned::new(Span::new(start, start), SpannedForm::Nil),
2413 1 => forms.into_iter().next().expect("checked len"),
2414 _ => {
2415 let span = cover(&forms, Span::new(start, start));
2421 let head = Span::new(span.start, span.start);
2427 let mut list = vec![sym_at(head, "begin")];
2428 list.extend(forms);
2429 list_at(span, list)
2430 }
2431 })
2432 }
2433}
2434
2435fn one_of(words: &[&str]) -> String {
2438 let quoted: Vec<String> = words.iter().map(|w| format!("`{w}`")).collect();
2439 match quoted.split_last() {
2440 None => String::new(),
2441 Some((last, [])) => last.clone(),
2442 Some((last, rest)) => format!("{} or {last}", rest.join(", ")),
2443 }
2444}
2445
2446fn let_bound(binder: Spanned, body: Spanned, at: Span) -> Spanned {
2447 let span = at.merge(body.span);
2448 let pair = list_at(binder.span, vec![binder, sym_at(at, RESCUED)]);
2449 list_at(span, vec![sym_at(at, "let"), list_at(pair.span, vec![pair]), body])
2450}
2451
2452fn thunk(body: Spanned, at: Span) -> Spanned {
2453 let span = at.merge(body.span);
2454 list_at(span, vec![sym_at(at, "lambda"), list_at(at, Vec::new()), body])
2455}
2456
2457fn blue_lang_name(name: &str) -> bool {
2458 matches!(
2459 lex(name).as_deref(),
2460 Ok([t, e]) if matches!(&t.kind, TokenKind::Ident(n) if n == name) && e.kind == TokenKind::Eof
2461 ) && !name.ends_with(['?', '!'])
2462}
2463
2464fn mentions(form: &Spanned, name: &str) -> bool {
2465 match &form.form {
2466 SpannedForm::Atom(Atom::Symbol(s)) => s == name,
2467 SpannedForm::List(items) => items.iter().any(|i| mentions(i, name)),
2468 SpannedForm::Quote(i)
2469 | SpannedForm::Quasiquote(i)
2470 | SpannedForm::Unquote(i)
2471 | SpannedForm::UnquoteSplice(i) => mentions(i, name),
2472 _ => false,
2473 }
2474}
2475
2476fn sym_at(span: Span, s: &str) -> Spanned {
2478 Spanned::new(span, SpannedForm::Atom(Atom::Symbol(s.to_string())))
2479}
2480
2481fn atom_at(span: Span, a: Atom) -> Spanned {
2483 Spanned::new(span, SpannedForm::Atom(a))
2484}
2485
2486fn list_at(span: Span, items: Vec<Spanned>) -> Spanned {
2488 Spanned::new(span, SpannedForm::List(items))
2489}
2490
2491fn cover(items: &[Spanned], fallback: Span) -> Span {
2499 let merged = items
2500 .iter()
2501 .fold(Span::synthetic(), |acc, s| acc.merge(s.span));
2502 if merged.is_synthetic() {
2503 fallback
2504 } else {
2505 merged
2506 }
2507}
2508
2509#[cfg(test)]
2510mod tests {
2511 use super::*;
2512
2513 fn q(src: &str) -> String {
2517 parse_expr(src)
2518 .map(|s| s.to_string())
2519 .unwrap_or_else(|e| panic!("{src:?}: {e}"))
2520 }
2521
2522 #[test]
2525 fn arithmetic_respects_precedence() {
2526 assert_eq!(q("1 + 2 * 3"), "(+ 1 (* 2 3))");
2527 assert_eq!(q("(1 + 2) * 3"), "(* (+ 1 2) 3)");
2528 }
2529
2530 #[test]
2531 fn comparison_binds_looser_than_arithmetic() {
2532 assert_eq!(q("a + 1 < b"), "(< (+ a 1) b)");
2533 }
2534
2535 #[test]
2540 fn logical_operators_bind_loosest_and_lower_to_tataras_names() {
2541 assert_eq!(q("a && b || c"), "(or (and a b) c)");
2542 }
2543
2544 #[test]
2545 fn left_associativity() {
2546 assert_eq!(q("1 - 2 - 3"), "(- (- 1 2) 3)");
2547 }
2548
2549 #[test]
2550 fn a_dot_without_parentheses_reads_a_field() {
2551 assert_eq!(q("user.name"), "(blue-field user :name)");
2552 }
2553
2554 #[test]
2555 fn method_call_with_args() {
2556 assert_eq!(q("user.greet(1, 2)"), "(greet user 1 2)");
2557 }
2558
2559 #[test]
2560 fn chained_fields_read_left_to_right() {
2561 assert_eq!(q("a.b.c"), "(blue-field (blue-field a :b) :c)");
2562 assert_eq!(q("a.b(1).c"), "(blue-field (b a 1) :c)");
2563 }
2564
2565 #[test]
2566 fn plain_call() {
2567 assert_eq!(q("f(1, 2)"), "(f 1 2)");
2568 }
2569
2570 #[test]
2573 fn pipeline_threads_into_first_argument() {
2574 assert_eq!(q("x |> f"), "(f x)");
2575 assert_eq!(q("x |> f(1)"), "(f x 1)");
2576 assert_eq!(q("x |> f |> g"), "(g (f x))");
2577 }
2578
2579 #[test]
2580 fn pipeline_binds_looser_than_arithmetic() {
2581 assert_eq!(q("1 + 2 |> f"), "(f (+ 1 2))");
2582 }
2583
2584 #[test]
2590 fn label_and_rocket_produce_the_same_tree_for_a_symbol_key() {
2591 assert_eq!(q("{a: 1}"), q("{:a => 1}"));
2592 assert_eq!(q("{a: 1}"), "(hash-map :a 1)");
2593 }
2594
2595 #[test]
2598 fn a_string_key_has_no_shorthand() {
2599 assert_eq!(q(r#"{"k" => 1}"#), r#"(hash-map "k" 1)"#);
2600 }
2601
2602 #[test]
2603 fn list_literal() {
2604 assert_eq!(q("[1, 2, 3]"), "(list 1 2 3)");
2605 assert_eq!(q("[]"), "(list)");
2606 }
2607
2608 #[test]
2611 fn if_else_end() {
2612 assert_eq!(q("if a\n 1\nelse\n 2\nend"), "(if a 1 2)");
2613 }
2614
2615 #[test]
2616 fn if_without_else() {
2617 assert_eq!(q("if a\n 1\nend"), "(if a 1)");
2618 }
2619
2620 #[test]
2623 fn unless_is_a_negated_if() {
2624 assert_eq!(q("unless a\n 1\nend"), "(if (not a) 1)");
2625 }
2626
2627 #[test]
2628 fn multi_statement_body_becomes_begin() {
2629 assert_eq!(q("if a\n 1\n 2\nend"), "(if a (begin 1 2))");
2630 }
2631
2632 #[test]
2633 fn def_lowers_to_define() {
2634 assert_eq!(
2635 q("def add(a, b)\n a + b\nend"),
2636 "(define (add a b) (+ a b))"
2637 );
2638 }
2639
2640 #[test]
2641 fn def_with_no_params() {
2642 assert_eq!(q("def zero()\n 0\nend"), "(define (zero) 0)");
2643 }
2644
2645 #[test]
2648 fn literals_lower_to_atoms() {
2649 assert_eq!(q("42"), "42");
2650 assert_eq!(q("true"), "#t");
2651 assert_eq!(q(":ok"), ":ok");
2652 assert_eq!(q(r#""hi""#), r#""hi""#);
2653 }
2654
2655 #[test]
2656 fn unary_minus_and_not() {
2657 assert_eq!(q("-x"), "(- 0 x)");
2658 assert_eq!(q("!x"), "(not x)");
2659 }
2660
2661 #[test]
2664 fn a_program_is_a_sequence_of_forms() {
2665 let forms = parse_program("def f()\n 1\nend\nf()").expect("parse");
2666 assert_eq!(forms.len(), 2);
2667 assert_eq!(forms[1].to_string(), "(f)");
2668 }
2669
2670 #[test]
2671 fn unterminated_block_is_an_error_naming_what_was_expected() {
2672 let e = parse_program("if a\n 1").expect_err("must fail");
2673 assert!(e.message.contains("unterminated"), "{}", e.message);
2674 }
2675
2676 #[test]
2677 fn a_parse_error_carries_a_span_into_the_source() {
2678 let src = "1 + )";
2679 let e = parse_program(src).expect_err("must fail");
2680 assert!(e.span.start < src.len(), "span {:?} outside source", e.span);
2681 }
2682
2683 #[test]
2690 fn postfix_indexing_is_refused_naming_nth() {
2691 let e = parse_program("y = xs[0]").expect_err("must not split into two statements");
2692 assert!(e.message.contains("`nth(0, xs)`"), "{}", e.message);
2693 assert_eq!((e.line, e.col), (1, 7), "{e}");
2694 }
2695
2696 #[test]
2698 fn a_call_without_parentheses_is_refused_naming_the_call() {
2699 let e = parse_program("a = f x").expect_err("must not split into two statements");
2700 assert!(e.message.contains("`f(x)`"), "{}", e.message);
2701 assert_eq!((e.line, e.col), (1, 7), "{e}");
2702 }
2703
2704 #[test]
2706 fn two_statements_on_one_line_are_refused_in_a_body() {
2707 for src in [
2708 "def f(xs)\n xs[0]\nend",
2709 "if a\n f x\nend",
2710 "fn(x) x 1 end",
2711 "test \"t\"\n assert a b\nend",
2712 "1 2",
2713 ] {
2714 assert!(parse_program(src).is_err(), "{src:?} must not parse");
2715 }
2716 }
2717
2718 #[test]
2722 fn a_statement_may_end_at_a_newline_the_end_or_a_closing_keyword() {
2723 for src in [
2724 "fn(x) x + 1 end",
2725 "map(fn(x) x end, xs)",
2726 "if a\n 1\nelse\n 2\nend",
2727 "if a 1 else 2 end",
2728 "case x\nwhen 1 :a\nwhen 2 :b\nelse :c\nend",
2729 "x = 1\ny = 2",
2730 "x = 1",
2731 ] {
2732 assert!(
2733 parse_program(src).is_ok(),
2734 "{src:?}: {:?}",
2735 parse_program(src)
2736 );
2737 }
2738 }
2739
2740 #[test]
2742 fn then_is_refused_by_name() {
2743 let e = parse_program("x = if c then 1 else 2 end").expect_err("then is not a keyword");
2744 assert!(e.message.contains("`then`"), "{}", e.message);
2745 }
2746
2747 #[test]
2749 fn a_brace_block_is_refused_naming_fn() {
2750 let e = parse_program("xs.map { |x| x }").expect_err("no brace blocks");
2751 assert!(e.message.contains("fn(x)"), "{}", e.message);
2752 }
2753
2754 #[test]
2761 fn a_qualified_name_is_one_symbol() {
2762 assert_eq!(q("retsu::first(xs)"), "(retsu/first xs)");
2763 assert_eq!(q("retsu::first"), "retsu/first");
2764 assert_eq!(q("xs |> retsu::first"), "(retsu/first xs)");
2765 assert_eq!(q("blue::length(xs)"), "(blue/length xs)");
2766 }
2767
2768 #[test]
2770 fn whitespace_around_the_qualifier_is_accepted() {
2771 assert_eq!(q("retsu :: first(xs)"), "(retsu/first xs)");
2772 }
2773
2774 #[test]
2777 fn a_reserved_word_after_the_qualifier_is_a_name() {
2778 assert_eq!(q("kueri::if(a, b)"), "(kueri/if a b)");
2779 assert_eq!(q("heni::test(x)"), "(heni/test x)");
2780 assert_eq!(q("sabi::fn"), "sabi/fn");
2781 }
2782
2783 #[test]
2784 fn a_qualified_name_has_exactly_one_qualifier() {
2785 for (src, says) in [
2786 ("::first(xs)", "`package::name`"),
2787 ("a::b::c", "one qualifier"),
2788 ("if::x", "reserved word"),
2789 ("retsu::1", "expected a name after `retsu::`"),
2790 ] {
2791 let e = parse_program(src).expect_err(src);
2792 assert!(e.message.contains(says), "{src}: {}", e.message);
2793 }
2794 }
2795
2796 #[test]
2799 fn qualified_reads_only_the_package_name_shape() {
2800 assert_eq!(qualified("retsu/first"), Some(("retsu", "first")));
2801 assert_eq!(qualified("kueri/if"), Some(("kueri", "if")));
2802 assert_eq!(qualified("nisshi/pair?"), Some(("nisshi", "pair?")));
2803 for not in ["/", "a/", "/a", "a/b/c", "if/x", "hash-map", "a"] {
2804 assert_eq!(qualified(not), None, "{not}");
2805 }
2806 assert!(is_callable_name("kueri/if"));
2807 }
2808
2809 #[test]
2814 fn a_symbol_may_end_in_a_question_or_bang() {
2815 assert_eq!(q("[:pair?, :set!]"), "(list :pair? :set!)");
2816 assert_eq!(q("use(\"n\", [:empty?])"), "(use \"n\" (list :empty?))");
2817 assert_eq!(q(":a != :b"), "(not= :a :b)");
2818 }
2819
2820 #[test]
2824 fn a_parse_error_displays_line_col_and_the_source_text() {
2825 let e = parse_program("x = 1\ny = [1, 2 y]").expect_err("must fail");
2826 assert_eq!(e.to_string(), "2:11: expected `,` or `]`, found `y`");
2827 assert_eq!(e.span, Span::new(16, 17), "the span stays, as data");
2828 }
2829
2830 #[test]
2832 fn no_error_message_names_a_rust_variant() {
2833 for src in [
2834 "f(1 2)",
2835 "{a: 1 b}",
2836 "def 1\nend",
2837 "x.1",
2838 "if a\n",
2839 "fn(1) 2 end",
2840 "[1 :b]",
2841 "1 +",
2842 "(1",
2843 ] {
2844 let e = parse_program(src).expect_err(src);
2845 let shown = e.to_string();
2846 for tell in [
2847 "Ident(", "Int(", "Op(", "Label(", "Sym(", "Str(", "LParen", "RBracket", "Eof",
2848 "Newline", "..",
2849 ] {
2850 assert!(!shown.contains(tell), "{src:?} -> {shown}");
2851 }
2852 assert!(e.line >= 1 && e.col >= 1, "{src:?} not located: {shown}");
2853 }
2854 }
2855
2856 #[test]
2858 fn a_lex_error_is_located_too() {
2859 let e = parse_program("x = 1\ny = 2; z = 3").expect_err("no semicolons");
2860 assert_eq!((e.line, e.col), (2, 6), "{e}");
2861 assert!(e.message.contains("new line"), "{e}");
2862 }
2863
2864 #[test]
2867 fn the_parser_rejects_garbage() {
2868 assert!(parse_program("def").is_err());
2869 assert!(parse_program("(1").is_err());
2870 assert!(parse_program("end").is_err());
2871 }
2872}