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 pub(crate) 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 pub(crate) 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 parse_program_documented_with_depth(src, max_depth).map(crate::doc::strip)
144}
145
146pub fn parse_program_documented(src: &str) -> Result<Vec<Spanned>, ParseError> {
149 parse_program_documented_with_depth(src, MAX_EXPR_DEPTH)
150}
151
152pub fn parse_program_documented_with_depth(
154 src: &str,
155 max_depth: usize,
156) -> Result<Vec<Spanned>, ParseError> {
157 if let Some(hit) = RECENT.with(|r| {
158 r.borrow()
159 .iter()
160 .find(|(d, s, _)| *d == max_depth && s.as_str() == src)
161 .map(|(_, _, t)| t.clone())
162 }) {
163 return hit;
164 }
165 PARSES.with(|n| n.set(n.get() + 1));
166 let parsed = lex(src)
167 .map_err(|e| ParseError::from(e).located(src))
168 .and_then(|toks| {
169 Parser::over(src, toks, max_depth)
170 .program()
171 .map_err(|e| e.located(src))
172 });
173 RECENT.with(|r| {
174 let mut r = r.borrow_mut();
175 if r.len() == RECENT_TEXTS {
176 drop(r.remove(0));
177 }
178 r.push((max_depth, src.to_string(), parsed.clone()));
179 });
180 parsed
181}
182
183pub(crate) fn parse_snippet(src: &str) -> Result<Vec<Spanned>, ParseError> {
186 let toks = lex(src).map_err(|e| ParseError::from(e).located(src))?;
187 Parser::over(src, toks, MAX_EXPR_DEPTH)
188 .program()
189 .map(crate::doc::strip)
190 .map_err(|e| e.located(src))
191}
192
193const RECENT_TEXTS: usize = 4;
197
198type Recent = Vec<(usize, String, Result<Vec<Spanned>, ParseError>)>;
199
200thread_local! {
201 static RECENT: std::cell::RefCell<Recent> = const { std::cell::RefCell::new(Vec::new()) };
202}
203
204#[must_use]
209pub fn constant_text(s: &Sexp) -> Option<String> {
210 match s {
211 Sexp::Atom(Atom::Str(x)) => Some(x.to_string()),
212 Sexp::Atom(Atom::Symbol(h)) if h.as_str() == "line_break" => Some("\n".to_string()),
213 Sexp::List(items) => match items.as_slice() {
214 [Sexp::Atom(Atom::Symbol(h)), Sexp::List(list)]
215 if h.as_str() == "join_lines" || h.as_str() == "unlines" =>
216 {
217 let (Sexp::Atom(Atom::Symbol(l)), elems) = list.split_first()? else {
218 return None;
219 };
220 if l.as_str() != "list" {
221 return None;
222 }
223 let texts = elems
224 .iter()
225 .map(constant_text)
226 .collect::<Option<Vec<_>>>()?;
227 Some(if h.as_str() == "join_lines" {
228 texts.join("\n")
229 } else {
230 texts.iter().map(|t| format!("{t}\n")).collect()
231 })
232 }
233 _ => None,
234 },
235 _ => None,
236 }
237}
238
239pub fn spelling_notes(src: &str) -> Result<Vec<Note>, ParseError> {
240 let mut p = Parser::over(
241 src,
242 lex(src).map_err(|e| ParseError::from(e).located(src))?,
243 MAX_EXPR_DEPTH,
244 );
245 p.program().map_err(|e| e.located(src))?;
246 Ok(p.notes)
247}
248
249pub fn parse_program_in(src: &str, pack: &crate::yakugo::Yakugo) -> Result<Vec<Sexp>, ParseError> {
262 Ok(parse_program_tree_in(src, pack)?
263 .iter()
264 .map(Spanned::to_sexp)
265 .collect())
266}
267
268pub fn parse_program_tree_in(
284 src: &str,
285 pack: &crate::yakugo::Yakugo,
286) -> Result<Vec<Spanned>, ParseError> {
287 let toks =
288 crate::yakugo::canonical_tokens(src, pack).map_err(|e| ParseError::from(e).located(src))?;
289 PARSES.with(|n| n.set(n.get() + 1));
290 let mut p = Parser::over(src, toks, MAX_EXPR_DEPTH);
291 p.program()
292 .map(crate::doc::strip)
293 .map_err(|e| e.located(src))
294}
295
296pub fn parse_program_with_depth(src: &str, max_depth: usize) -> Result<Vec<Sexp>, ParseError> {
305 Ok(parse_program_tree_with_depth(src, max_depth)?
306 .iter()
307 .map(Spanned::to_sexp)
308 .collect())
309}
310
311pub fn parse_program_spanned(src: &str) -> Result<Vec<(Sexp, Span)>, ParseError> {
324 parse_program_spanned_with_depth(src, MAX_EXPR_DEPTH)
325}
326
327pub fn parse_program_spanned_with_depth(
329 src: &str,
330 max_depth: usize,
331) -> Result<Vec<(Sexp, Span)>, ParseError> {
332 Ok(parse_program_tree_with_depth(src, max_depth)?
333 .iter()
334 .map(|f| (f.to_sexp(), f.span))
335 .collect())
336}
337
338pub fn comments(src: &str) -> Vec<Comment> {
346 lex(src)
347 .map(|toks| {
348 toks.iter()
349 .filter_map(|t| match &t.kind {
350 TokenKind::Comment(text) => Some(Comment {
351 text: text.clone(),
352 span: t.span,
353 own_line: line_before_is_blank(src, t.span.start),
354 }),
355 _ => None,
356 })
357 .collect()
358 })
359 .unwrap_or_default()
360}
361
362#[derive(Clone, Debug, PartialEq, Eq)]
364pub struct Comment {
365 pub text: String,
367 pub span: Span,
368 pub own_line: bool,
370}
371
372fn line_before_is_blank(src: &str, start: usize) -> bool {
373 src[..start.min(src.len())]
374 .rsplit('\n')
375 .next()
376 .is_some_and(|prefix| {
377 prefix
378 .trim_matches(|c: char| c.is_whitespace() || c == '\u{feff}')
379 .is_empty()
380 })
381}
382
383pub fn parse_expr(src: &str) -> Result<Sexp, ParseError> {
385 let forms = parse_program(src)?;
386 match forms.len() {
387 1 => Ok(forms.into_iter().next().expect("checked len")),
388 n => Err(ParseError::new(
389 format!("expected exactly one expression, found {n}"),
390 Span::new(0, src.len()),
391 )
392 .located(src)),
393 }
394}
395
396#[derive(Clone, Copy, Debug)]
406pub struct Infix {
407 pub op: &'static str,
409 pub power: (u8, u8),
411 pub callee: &'static str,
413 pub doc: &'static str,
417}
418
419pub const INFIX: &[Infix] = &[
423 Infix {
424 op: "or",
425 power: (1, 2),
426 callee: "or",
427 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).",
428 },
429 Infix {
430 op: "and",
431 power: (3, 4),
432 callee: "and",
433 doc: "Logical and: the right value if the left is truthy, otherwise the left; short-circuits. `&&` is read as `and` and written `and` (B0025).",
434 },
435 Infix {
446 op: "==",
447 power: (5, 6),
448 callee: "equal?",
449 doc: "Structural equality over every value (okite D0001): 4 == 4.0, lists and maps by content, nil only equals nil.",
450 },
451 Infix {
452 op: "!=",
453 power: (5, 6),
454 callee: "not=",
455 doc: "Exactly not(a == b) (okite D0002).",
456 },
457 Infix {
458 op: "<",
459 power: (5, 6),
460 callee: "<",
461 doc: "Numeric less-than. A string operand is a type error: order text with compare(a, b).",
462 },
463 Infix {
464 op: "<=",
465 power: (5, 6),
466 callee: "<=",
467 doc: "Numeric less-than-or-equal. Numbers only, like <.",
468 },
469 Infix {
470 op: ">",
471 power: (5, 6),
472 callee: ">",
473 doc: "Numeric greater-than. Numbers only, like <.",
474 },
475 Infix {
476 op: ">=",
477 power: (5, 6),
478 callee: ">=",
479 doc: "Numeric greater-than-or-equal. Numbers only, like <.",
480 },
481 Infix {
482 op: "+",
483 power: (7, 8),
484 callee: "+",
485 doc: "Numeric addition; an Int and a Float give a Float. Not string concatenation: interpolate instead.",
486 },
487 Infix {
488 op: "-",
489 power: (7, 8),
490 callee: "-",
491 doc: "Numeric subtraction. Written before an operand with nothing on its left, it is unary minus: -n.",
492 },
493 Infix {
494 op: "*",
495 power: (9, 10),
496 callee: "*",
497 doc: "Numeric multiplication.",
498 },
499 Infix {
500 op: "/",
501 power: (9, 10),
502 callee: "/",
503 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).",
504 },
505 Infix {
506 op: "%",
507 power: (9, 10),
508 callee: "mod",
509 doc: "Euclidean remainder: never negative, so (0 - 7) % 3 is 2, not -1. A zero divisor is a runtime error.",
510 },
511];
512
513pub const MASTER_VERBS: &[&str] = &[
531 "read", "need", "has?", "put", "update", "delete", "create", "diff", "info", "exists?",
532];
533
534pub const LOWERED_ASSERT: &str = "blue-assert";
541
542pub const LOWERED_MAP: &str = "hash-map";
550
551pub const LOWERED_CONCAT: &str = "concat";
556
557pub const OPTIONAL_MARKER: &str = "&optional";
558
559pub const LOWERED_OPTIONAL_ARITY: &str = "blue-optional-arity";
560
561pub const LOWERED_OPTIONAL_ARG: &str = "blue-optional-arg";
562
563pub const LOWERED_FIELD: &str = "blue-field";
564
565pub const LOWERED_MERGE: &str = "blue-merge";
566
567pub const LOWERED_SPLICE: &str = "blue-splice";
568
569pub const LOWERED_KWARGS: &str = "blue-kwargs";
570
571pub const LOWERED_ERROR_MATCH: &str = "blue-rescues?";
572
573pub const LOWERED_ENSURE: &str = "blue-ensure";
574
575pub const LOWERED_RERAISE: &str = "blue-reraise";
576
577pub const LOWERED_ATTEMPTED: &str = "blue-attempted";
578
579pub const LOWERED_FAILED: &str = "blue-failed";
580
581pub const LOWERED_HEADS: &[&str] = &[
582 "list",
583 "concat",
584 "not",
585 "apply",
586 "equal?",
587 "not=",
588 "and",
589 "or",
590 "mod",
591 "join_lines",
592 "unlines",
593 "line_break",
594];
595
596pub const RESCUED: &str = "rescued-error";
597
598pub const SURFACE_KEYWORDS: &[&str] = &[
599 "if",
600 "begin",
601 "unless",
602 "def",
603 "defmacro",
604 "quote",
605 "unquote",
606 "unquote_splice",
607 "test",
608 "assert",
609 "fn",
610 "case",
611];
612
613pub const BLOCK_KEYWORDS: &[&str] = &[
625 "do", "end", "else", "elsif", "rescue", "ensure", "true", "false", "nil",
626];
627
628pub const OPERATOR_KEYWORDS: &[&str] = &["and", "or", "not"];
629
630pub fn is_reserved_word(name: &str) -> bool {
637 SURFACE_KEYWORDS.contains(&name)
638 || BLOCK_KEYWORDS.contains(&name)
639 || OPERATOR_KEYWORDS.contains(&name)
640}
641
642pub fn is_callable_name(name: &str) -> bool {
651 if qualified(name).is_some() {
653 return true;
654 }
655 !is_reserved_word(name)
656 && matches!(
657 lex(name).as_deref(),
658 Ok([t, e]) if matches!(&t.kind, TokenKind::Ident(n) if n == name)
659 && e.kind == TokenKind::Eof
660 )
661}
662
663pub const QUALIFIER: char = '/';
668
669pub const BUILTIN_QUALIFIER: &str = "blue";
672
673#[must_use]
675pub fn qualify(pkg: &str, name: &str) -> String {
676 format!("{pkg}{QUALIFIER}{name}")
677}
678
679#[must_use]
685pub fn qualified(sym: &str) -> Option<(&str, &str)> {
686 let (pkg, name) = sym.split_once(QUALIFIER)?;
687 let ident = |s: &str| {
688 matches!(
689 lex(s).as_deref(),
690 Ok([t, e]) if matches!(&t.kind, TokenKind::Ident(n) if n == s) && e.kind == TokenKind::Eof
691 )
692 };
693 let named = |s: &str| ident(s) || matches!(s, "true" | "false" | "nil");
694 (ident(pkg) && !is_reserved_word(pkg) && named(name)).then_some((pkg, name))
695}
696
697#[must_use]
705pub fn keyword_doc(word: &str) -> &'static str {
706 match word {
707 "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).",
708 "unless" => "`unless c … end` is read as `if not c` and written that way (B0026).",
709 "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`.",
710 "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 `|>`.",
711 "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.",
712 "and" => "`a and b`: logical and; `&&` is read as `and` and written `and` (B0025).",
713 "or" => "`a or b`: logical or; `||` is read as `or` and written `or` (B0025).",
714 "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).",
715 "def" => "`def name(a, b) … end`: a named function; its value is the last expression. Parameters may be typed: `def f(n: Int) -> Int`.",
716 "defmacro" => "`defmacro name(x) quote … unquote(x) … end end`: a macro, a rewrite of the unevaluated form.",
717 "quote" => "`quote … end`: the form itself as data, not its value. Used inside `defmacro`.",
718 "unquote" => "`unquote(x)`: inside `quote`, splice in the value of `x`.",
719 "unquote_splice" => "`unquote_splice(xs)`: inside `quote`, splice the items of the form `xs` in place, not the form itself.",
720 "test" => "`test \"what must hold\" … end`: a test block, run by `blue test` and ignored by `blue run`.",
721 "assert" => "`assert expr`: inside a test, fail it unless `expr` is truthy; the failure quotes the expression.",
722 "fn" => "`fn(x) x * 2 end`: an anonymous function, a value. Blue has no brace or do blocks.",
723 "case" => "`case v when 1 … when 2 … else … end`: compares with `==`; it does not destructure.",
724 "do" => "Reserved and refused: blue has no do-blocks. Pass `fn(x) … end` instead.",
725 "end" => "Closes `def`, `defmacro`, `if`, `case`, `fn`, `test` and `quote`.",
726 "else" => "The branch of `if` or `case` taken when nothing before it matched.",
727 "elsif" => "A further condition in an `if` chain; an `else` holding only an `if` is written `elsif` (B0027).",
728 "true" => "The boolean true.",
729 "false" => "The boolean false. Only `false` and `nil` are falsy; 0, \"\" and [] are truthy.",
730 "nil" => "The absent value. It is not `[]` (okite D0004), and only `nil == nil`.",
731 _ => "",
732 }
733}
734
735fn infix(op: &str) -> Option<&'static Infix> {
736 INFIX.iter().find(|i| i.op == op)
737}
738
739pub const SYMBOL_SPELLINGS: &[(&str, &str)] = &[("&&", "and"), ("||", "or"), ("!", "not")];
740
741fn word_for(op: &str) -> &str {
742 SYMBOL_SPELLINGS
743 .iter()
744 .find(|(sym, _)| *sym == op)
745 .map_or(op, |(_, word)| word)
746}
747
748fn infix_of(kind: &TokenKind) -> Option<&'static Infix> {
749 match kind {
750 TokenKind::Op(o) => infix(word_for(o)),
751 TokenKind::Ident(w) if w == "and" || w == "or" => infix(w),
752 _ => None,
753 }
754}
755
756const PIPE_POWER: (u8, u8) = (0, 1);
757const RESCUE_POWER: (u8, u8) = (0, 1);
758
759struct Parser {
760 toks: Vec<Token>,
761 pos: usize,
762 depth: usize,
770 max_depth: usize,
777 src: String,
778 comment_spans: Vec<Span>,
779 notes: Vec<Note>,
780 quoting: usize,
783}
784
785pub const MAX_EXPR_DEPTH: usize = 256;
792
793type Fail = Box<ParseError>;
805
806fn fail(message: impl Into<String>, span: Span) -> Fail {
807 Box::new(ParseError::new(message, span))
808}
809
810impl Parser {
811 fn over(src: &str, all: Vec<Token>, max_depth: usize) -> Self {
812 let comment_spans = all
813 .iter()
814 .filter(|t| matches!(t.kind, TokenKind::Comment(_)))
815 .map(|t| t.span)
816 .collect();
817 let toks = all
818 .into_iter()
819 .filter(|t| !matches!(t.kind, TokenKind::Comment(_)))
820 .collect();
821 Parser {
822 toks,
823 pos: 0,
824 depth: 0,
825 max_depth,
826 src: src.to_string(),
827 comment_spans,
828 notes: Vec::new(),
829 quoting: 0,
830 }
831 }
832
833 fn note(&mut self, spelling: Spelling, span: Span, written: &str, canonical: &str) {
834 self.notes.push(Note {
835 spelling,
836 span,
837 written: written.to_string(),
838 canonical: canonical.to_string(),
839 });
840 }
841
842 fn text(&self, span: Span) -> &str {
843 self.src.get(span.start..span.end).unwrap_or("")
844 }
845
846 fn comment_within(&self, lo: usize, hi: usize) -> bool {
847 self.comment_spans
848 .iter()
849 .any(|c| c.start >= lo && c.start < hi)
850 }
851
852 fn touches_previous(&self) -> bool {
853 let here = self.peek_span();
854 self.toks
855 .get(self.pos.wrapping_sub(1))
856 .is_some_and(|prev| prev.span.end == here.start)
857 }
858
859 fn peek(&self) -> &TokenKind {
860 &self.toks[self.pos.min(self.toks.len() - 1)].kind
861 }
862
863 fn peek_span(&self) -> Span {
864 self.toks[self.pos.min(self.toks.len() - 1)].span
865 }
866
867 fn bump(&mut self) -> TokenKind {
868 let k = self.toks[self.pos.min(self.toks.len() - 1)].kind.clone();
869 if self.pos < self.toks.len() {
870 self.pos += 1;
871 }
872 k
873 }
874
875 fn at(&self, k: &TokenKind) -> bool {
876 self.peek() == k
877 }
878
879 fn eat(&mut self, k: &TokenKind) -> bool {
880 if self.at(k) {
881 self.bump();
882 true
883 } else {
884 false
885 }
886 }
887
888 fn expect(&mut self, k: &TokenKind, what: &str) -> Result<(), Fail> {
889 if self.eat(k) {
890 Ok(())
891 } else {
892 Err(self.error(format!("expected {what}, found {}", self.peek())))
893 }
894 }
895
896 fn error(&self, message: impl Into<String>) -> Fail {
897 fail(message, self.peek_span())
898 }
899
900 fn skip_newlines(&mut self) {
902 while matches!(self.peek(), TokenKind::Newline) {
903 self.bump();
904 }
905 }
906
907 fn at_ident(&self, name: &str) -> bool {
908 matches!(self.peek(), TokenKind::Ident(n) if n == name)
909 }
910
911 fn mark(&self) -> usize {
914 self.peek_span().start
915 }
916
917 fn span_since(&self, start: usize) -> Span {
923 let end = self
924 .toks
925 .get(self.pos.saturating_sub(1))
926 .map_or(start, |t| t.span.end);
927 Span::new(start, end.max(start))
928 }
929
930 fn program(&mut self) -> Result<Vec<Spanned>, Fail> {
931 let mut out = Vec::new();
932 loop {
933 self.skip_newlines();
934 if matches!(self.peek(), TokenKind::Eof) {
935 break;
936 }
937 if let TokenKind::Doc { module, text } = self.peek().clone() {
938 out.push(self.documentation(module, text, out.is_empty())?);
939 continue;
940 }
941 out.push(self.statement()?);
942 self.end_of_statement(&[])?;
943 }
944 Ok(out)
945 }
946
947 fn documentation(&mut self, module: bool, text: String, first: bool) -> Result<Spanned, Fail> {
950 let span = self.peek_span();
951 self.bump();
952 let word = if module { "@moduledoc" } else { "@doc" };
953 let head = sym_at(
954 Span::new(span.start, span.start + word.len()),
955 if module {
956 crate::doc::MODULEDOC_HEAD
957 } else {
958 crate::doc::DOC_HEAD
959 },
960 );
961 let text = crate::doc::text_node(span, text);
962 self.end_of_statement(&[])?;
963 if module {
964 if !first {
965 return Err(fail(
966 "`@moduledoc` is the first form of a file: it documents the whole file",
967 span,
968 ));
969 }
970 return Ok(list_at(span, vec![head, text]));
971 }
972 self.skip_newlines();
973 if matches!(self.peek(), TokenKind::Doc { .. } | TokenKind::Eof) {
974 return Err(fail(
975 "`@doc` documents the definition below it, and none follows",
976 span,
977 ));
978 }
979 let def = self.statement()?;
980 self.end_of_statement(&[])?;
981 if !crate::doc::is_definition(&def) {
982 return Err(fail(
983 "`@doc` documents the definition below it: a `def`, a `defmacro` or a \
984 top-level `name = value`",
985 span,
986 ));
987 }
988 Ok(list_at(
989 Span::new(span.start, def.span.end),
990 vec![head, text, def],
991 ))
992 }
993
994 fn end_of_statement(&self, terminators: &[&str]) -> Result<(), Fail> {
1012 let next = self.peek();
1013 if matches!(next, TokenKind::Newline | TokenKind::Eof)
1014 || terminators.iter().any(|t| self.at_ident(t))
1015 {
1016 return Ok(());
1017 }
1018 let prev = self.toks.get(self.pos.wrapping_sub(1)).map(|t| &t.kind);
1019 let prev_name = match prev {
1020 Some(TokenKind::Ident(n)) => Some(n.as_str()),
1021 _ => None,
1022 };
1023 let callee = prev_name.unwrap_or("f");
1024 let why = match (prev, next) {
1025 (Some(TokenKind::Ident(t)), _) if t == "then" => {
1026 "blue's `if` has no `then`: put the branch on the next line".to_string()
1027 }
1028 (_, TokenKind::Ident(d)) if d == "do" => "blue has no `do` blocks: pass a function \
1029 as an argument, `f(xs, fn(x) … end)`"
1030 .to_string(),
1031 (_, TokenKind::LBracket) => {
1032 let index = match (self.toks.get(self.pos + 1), self.toks.get(self.pos + 2)) {
1033 (Some(i), Some(close)) if close.kind == TokenKind::RBracket => match &i.kind {
1034 TokenKind::Int(n) => n.to_string(),
1035 TokenKind::Ident(n) => n.clone(),
1036 _ => "i".to_string(),
1037 },
1038 _ => "i".to_string(),
1039 };
1040 let list = prev_name.unwrap_or("xs");
1041 format!(
1042 "blue has no `xs[i]` indexing: write `nth({index}, {list})` \
1043 (or `first({list})`, `last({list})`)"
1044 )
1045 }
1046 (_, TokenKind::LBrace) => "blue has no brace blocks: pass a function as an \
1047 argument, `map(xs, fn(x) … end)`"
1048 .to_string(),
1049 (
1050 Some(TokenKind::Ident(_)),
1051 TokenKind::Ident(_)
1052 | TokenKind::Int(_)
1053 | TokenKind::Float(_)
1054 | TokenKind::Str(_)
1055 | TokenKind::InterpolatedStr { .. }
1056 | TokenKind::Sym(_)
1057 | TokenKind::True
1058 | TokenKind::False
1059 | TokenKind::Nil,
1060 ) => {
1061 let arg = match next {
1062 TokenKind::Ident(a) => a.clone(),
1063 _ => "x".to_string(),
1064 };
1065 format!("a call needs parentheses: write `{callee}({arg})`")
1066 }
1067 _ => "put each statement on its own line".to_string(),
1068 };
1069 Err(self.error(format!("expected the end of the line, found {next}: {why}")))
1070 }
1071
1072 fn statement(&mut self) -> Result<Spanned, Fail> {
1085 let max = self.max_depth;
1099 if self.depth >= max {
1100 return Err(self.error(format!(
1101 "statement nests deeper than {max}; refusing to \
1102 recurse further (this is a limit, not a syntax error)"
1103 )));
1104 }
1105 self.depth += 1;
1106 let r = self.statement_inner();
1107 self.depth -= 1;
1108 r
1109 }
1110
1111 fn statement_inner(&mut self) -> Result<Spanned, Fail> {
1112 if let TokenKind::Ident(name) = self.peek().clone() {
1113 if self.peek_at(1) == "=" && !is_reserved_word(&name) {
1114 let name_span = self.peek_span();
1115 self.bump(); self.bump(); self.skip_newlines();
1118 let value = self.expr(0)?;
1119 return Ok(list_at(
1123 self.span_since(name_span.start),
1124 vec![sym_at(name_span, "define"), sym_at(name_span, &name), value],
1125 ));
1126 }
1127 }
1128 let e = self.expr(0)?;
1129 if let SpannedForm::List(items) = &e.form {
1130 if items.len() == 3
1131 && items[0].as_symbol() == Some("define")
1132 && items[1].as_symbol().is_some()
1133 && self.text(items[0].span) == "define"
1134 {
1135 self.note(Spelling::OperatorSpelling, e.span, "define(x, v)", "x = v");
1136 }
1137 }
1138 Ok(e)
1139 }
1140
1141 fn peek_at(&self, n: usize) -> String {
1144 match self.toks.get(self.pos + n).map(|t| &t.kind) {
1145 Some(TokenKind::Op(o)) => o.clone(),
1146 _ => String::new(),
1147 }
1148 }
1149
1150 fn expr(&mut self, min_bp: u8) -> Result<Spanned, Fail> {
1152 let max = self.max_depth;
1162 if self.depth >= max {
1163 return Err(self.error(format!(
1164 "expression nests deeper than {max}; refusing to \
1165 recurse further (this is a limit, not a syntax error)"
1166 )));
1167 }
1168 self.depth += 1;
1169 let r = self.expr_inner(min_bp);
1170 self.depth -= 1;
1171 r
1172 }
1173
1174 #[inline(never)]
1175 fn rescue_node(&self, start: usize, op_span: Span, lhs: Spanned, fallback: Spanned) -> Spanned {
1176 list_at(
1177 self.span_since(start),
1178 vec![
1179 sym_at(op_span, "try"),
1180 lhs,
1181 list_at(
1182 op_span.merge(fallback.span),
1183 vec![
1184 sym_at(op_span, "catch"),
1185 list_at(op_span, vec![sym_at(op_span, "_")]),
1186 fallback,
1187 ],
1188 ),
1189 ],
1190 )
1191 }
1192
1193 #[inline(never)]
1194 fn infix_spelling(&mut self, callee: Option<&str>, op_span: Span) {
1195 let written = self.text(op_span).to_string();
1196 match (callee, self.peek().clone()) {
1197 (None, _) => self.note(Spelling::CallForm, op_span, "|>", "a call"),
1198 (Some(c), TokenKind::Op(o)) if word_for(&o) != o => {
1199 self.note(Spelling::LogicalSymbol, op_span, &written, c);
1200 }
1201 (Some(_), TokenKind::Op(o)) if written != o => {
1202 self.note(Spelling::OperatorSpelling, op_span, &written, &o);
1203 }
1204 _ => {}
1205 }
1206 }
1207
1208 #[inline(never)]
1209 fn nil_test(
1210 &mut self,
1211 start: usize,
1212 op_span: Span,
1213 lhs: &Spanned,
1214 rhs: &Spanned,
1215 ) -> Option<Spanned> {
1216 let (subject, written) = match (&lhs.form, &rhs.form) {
1217 (_, SpannedForm::Nil) if !matches!(lhs.form, SpannedForm::Nil) => (lhs, "x == nil"),
1218 (SpannedForm::Nil, _) if !matches!(rhs.form, SpannedForm::Nil) => (rhs, "nil == x"),
1219 _ => return None,
1220 };
1221 let span = self.span_since(start);
1222 self.note(Spelling::NilTest, span, written, "x.nil?");
1223 Some(list_at(
1224 span,
1225 vec![
1226 sym_at(op_span, LOWERED_FIELD),
1227 subject.clone(),
1228 atom_at(op_span, Atom::Keyword("nil?".to_string())),
1229 ],
1230 ))
1231 }
1232
1233 #[inline(never)]
1234 fn infix_node(
1235 &mut self,
1236 start: usize,
1237 callee: Option<&str>,
1238 op_span: Span,
1239 lhs: Spanned,
1240 rhs: Spanned,
1241 ) -> Spanned {
1242 if callee == Some("-") && matches!(lhs.form, SpannedForm::Atom(Atom::Int(0))) {
1243 self.note(
1244 Spelling::OperatorSpelling,
1245 lhs.span.merge(op_span),
1246 "0 - x",
1247 "-x",
1248 );
1249 }
1250
1251 if callee == Some("equal?") {
1252 if let Some(test) = self.nil_test(start, op_span, &lhs, &rhs) {
1253 return test;
1254 }
1255 }
1256
1257 if callee.is_none() {
1258 let rhs_span = rhs.span;
1263 let threaded = match rhs.form {
1264 SpannedForm::List(mut items) if !items.is_empty() => {
1265 items.insert(1, lhs);
1266 list_at(self.span_since(start), items)
1267 }
1268 callee => list_at(
1269 self.span_since(start),
1270 vec![Spanned::new(rhs_span, callee), lhs],
1271 ),
1272 };
1273 match &threaded.form {
1274 SpannedForm::List(items)
1275 if items.len() == 2 && items[0].as_symbol() == Some("not") =>
1276 {
1277 let (head, operand) = (items[0].span, items[1].clone());
1278 self.negation(head, threaded.span, operand)
1279 }
1280 SpannedForm::List(items)
1281 if items.len() == 4
1282 && items[0].as_symbol() == Some("if")
1283 && matches!(items[3].form, SpannedForm::Nil) =>
1284 {
1285 list_at(threaded.span, items[..3].to_vec())
1286 }
1287 SpannedForm::List(items)
1288 if items.len() == 3 && items[0].as_symbol() == Some("equal?") =>
1289 {
1290 let (op, a, b) = (items[0].span, items[1].clone(), items[2].clone());
1291 match self.nil_test(start, op, &a, &b) {
1292 Some(test) => test,
1293 None => threaded,
1294 }
1295 }
1296 _ => threaded,
1297 }
1298 } else {
1299 list_at(
1305 self.span_since(start),
1306 vec![sym_at(op_span, callee.unwrap_or_default()), lhs, rhs],
1307 )
1308 }
1309 }
1310
1311 fn expr_inner(&mut self, min_bp: u8) -> Result<Spanned, Fail> {
1312 let start = self.mark();
1313 let mut lhs = self.prefix()?;
1314
1315 loop {
1316 match self.peek() {
1318 TokenKind::Dot => {
1319 let dot = self.peek_span();
1320 self.bump();
1321 lhs = self.finish_send(start, lhs, dot)?;
1322 continue;
1323 }
1324 TokenKind::LParen => {
1325 let (args, splat) = self.call_args()?;
1326 let call = self.call_node(self.span_since(start), vec![lhs], args, splat);
1327 lhs = self.call_spelling(call);
1328 continue;
1329 }
1330 _ => {}
1331 }
1332
1333 let op_span = self.peek_span();
1337 if self.at_ident("rescue") {
1338 if RESCUE_POWER.0 < min_bp {
1339 break;
1340 }
1341 self.bump();
1342 self.skip_newlines();
1343 let fallback = self.expr(RESCUE_POWER.1)?;
1344 lhs = self.rescue_node(start, op_span, lhs, fallback);
1345 continue;
1346 }
1347 let (callee, (lbp, rbp)) = match self.peek() {
1348 TokenKind::Pipe => (None, PIPE_POWER),
1349 other => match infix_of(other) {
1350 Some(i) => (Some(i.callee), i.power),
1351 None => break,
1352 },
1353 };
1354 if lbp < min_bp {
1355 break;
1356 }
1357 self.infix_spelling(callee, op_span);
1358 self.bump();
1359 self.skip_newlines();
1360 let rhs = self.expr(rbp)?;
1361 lhs = self.infix_node(start, callee, op_span, lhs, rhs);
1362 }
1363
1364 Ok(lhs)
1365 }
1366
1367 #[inline(never)]
1368 fn interpolation(span: Span, parts: &[String], exprs: &[String]) -> Result<Spanned, Fail> {
1369 let mut acc = atom_at(span, Atom::Str(parts[0].clone()));
1379 for (i, raw) in exprs.iter().enumerate() {
1380 let inner = parse_expr(raw).map_err(|e| {
1381 fail(
1382 format!("in interpolation `#{{{raw}}}`: {}", e.message),
1383 span,
1384 )
1385 })?;
1386 let inner = Spanned::from_sexp_at(&inner, span);
1387 acc = list_at(span, vec![sym_at(span, LOWERED_CONCAT), acc, inner]);
1388 acc = list_at(
1391 span,
1392 vec![
1393 sym_at(span, LOWERED_CONCAT),
1394 acc,
1395 atom_at(span, Atom::Str(parts[i + 1].clone())),
1396 ],
1397 );
1398 }
1399 Ok(acc)
1400 }
1401
1402 fn lines_literal(
1409 &mut self,
1410 span: Span,
1411 parts: Vec<String>,
1412 exprs: Vec<String>,
1413 ) -> Result<Spanned, Fail> {
1414 let mut lines: Vec<(Vec<String>, Vec<String>)> = vec![(vec![String::new()], Vec::new())];
1415 for (i, part) in parts.iter().enumerate() {
1416 let mut pieces = part.split('\n');
1417 if let Some(head) = pieces.next() {
1418 if let Some(last) = lines.last_mut().and_then(|l| l.0.last_mut()) {
1419 last.push_str(head);
1420 }
1421 }
1422 for piece in pieces {
1423 lines.push((vec![piece.to_string()], Vec::new()));
1424 }
1425 if let Some(raw) = exprs.get(i) {
1426 if let Some(line) = lines.last_mut() {
1427 line.1.push(raw.clone());
1428 line.0.push(String::new());
1429 }
1430 }
1431 }
1432 let empty = |l: &(Vec<String>, Vec<String>)| l.1.is_empty() && l.0 == [""];
1433 if lines.len() == 2 && lines.iter().all(empty) {
1434 self.note(Spelling::NewlineEscape, span, "\"\\n\"", "line_break");
1435 return Ok(sym_at(span, "line_break"));
1436 }
1437 let trailing = lines.last().is_some_and(empty);
1438 if trailing {
1439 lines.pop();
1440 }
1441 let head = if trailing { "unlines" } else { "join_lines" };
1442 let mut items = vec![sym_at(span, "list")];
1443 for (lits, raws) in &lines {
1444 let item = if raws.is_empty() {
1445 atom_at(span, Atom::Str(lits[0].clone()))
1446 } else if raws.len() == 1 && lits.iter().all(String::is_empty) {
1447 let inner = parse_expr(&raws[0]).map_err(|e| {
1448 fail(
1449 format!("in interpolation `#{{{}}}`: {}", raws[0], e.message),
1450 span,
1451 )
1452 })?;
1453 Spanned::from_sexp_at(&inner, span)
1454 } else {
1455 Self::interpolation(span, lits, raws)?
1456 };
1457 items.push(item);
1458 }
1459 self.note(
1460 Spelling::NewlineEscape,
1461 span,
1462 "\"…\\n…\"",
1463 &format!("{head}([…])"),
1464 );
1465 Ok(list_at(
1466 span,
1467 vec![sym_at(span, head), list_at(span, items)],
1468 ))
1469 }
1470
1471 #[inline(never)]
1472 fn not_prefix(&mut self, span: Span) -> Result<Spanned, Fail> {
1473 if self.at(&TokenKind::LParen) && self.touches_previous() {
1474 self.note(Spelling::OperatorSpelling, span, "not(x)", "not x");
1475 let (args, splat) = self.call_args()?;
1476 if args.len() != 1 || splat.is_some() {
1477 let call = self.call_node(
1478 self.span_since(span.start),
1479 vec![sym_at(span, "not")],
1480 args,
1481 splat,
1482 );
1483 return Ok(call);
1484 }
1485 let operand = args.into_iter().next().expect("one argument");
1486 return Ok(self.negation(span, self.span_since(span.start), operand));
1487 }
1488 let rhs = self.expr(11)?;
1489 Ok(self.negation(span, self.span_since(span.start), rhs))
1490 }
1491
1492 fn prefix(&mut self) -> Result<Spanned, Fail> {
1493 if self.at(&TokenKind::LParen) {
1494 let span = self.peek_span();
1495 self.bump();
1496 if self.arrow_lambda_ahead() {
1497 return self.arrow_lambda(span);
1498 }
1499 return self.group();
1500 }
1501 self.prefix_token()
1502 }
1503
1504 fn group(&mut self) -> Result<Spanned, Fail> {
1505 self.skip_newlines();
1506 let inner = self.expr(0)?;
1507 self.skip_newlines();
1508 self.expect(&TokenKind::RParen, "`)`")?;
1509 Ok(inner)
1510 }
1511
1512 #[inline(never)]
1513 fn prefix_token(&mut self) -> Result<Spanned, Fail> {
1514 let span = self.peek_span();
1515 match self.bump() {
1516 TokenKind::Int(v) => Ok(atom_at(span, Atom::Int(v))),
1517 TokenKind::Float(v) => Ok(atom_at(span, Atom::Float(v))),
1518 TokenKind::Str(s) if self.quoting == 0 && s.contains('\n') => {
1519 self.lines_literal(span, vec![s], Vec::new())
1520 }
1521 TokenKind::Str(s) => Ok(atom_at(span, Atom::Str(s))),
1522 TokenKind::Doc { module, .. } => Err(fail(
1523 format!(
1524 "`{}` sits at the top level, above the definition it documents, never \
1525 inside a form",
1526 if module { "@moduledoc" } else { "@doc" }
1527 ),
1528 span,
1529 )),
1530
1531 TokenKind::InterpolatedStr { parts, exprs }
1542 if self.quoting == 0 && parts.iter().any(|p| p.contains('\n')) =>
1543 {
1544 self.lines_literal(span, parts, exprs)
1545 }
1546 TokenKind::InterpolatedStr { parts, exprs } => Self::interpolation(span, &parts, &exprs),
1547 TokenKind::Sym(s) => Ok(atom_at(span, Atom::Keyword(s))),
1548 TokenKind::True => Ok(atom_at(span, Atom::Bool(true))),
1549 TokenKind::False => Ok(atom_at(span, Atom::Bool(false))),
1550 TokenKind::Nil => Ok(Spanned::new(span, SpannedForm::Nil)),
1551
1552 TokenKind::Op(o) if o == "-" => {
1553 let rhs = self.expr(11)?; Ok(list_at(
1555 self.span_since(span.start),
1556 vec![sym_at(span, "-"), atom_at(span, Atom::Int(0)), rhs],
1557 ))
1558 }
1559 TokenKind::Op(o) if o == "!" => {
1560 let written = self.text(span).to_string();
1561 self.note(Spelling::LogicalSymbol, span, &written, "not");
1562 let rhs = self.expr(11)?;
1563 Ok(self.negation(span, self.span_since(span.start), rhs))
1564 }
1565 TokenKind::Ident(w) if w == "not" => self.not_prefix(span),
1566 TokenKind::Ident(w) if (w == "and" || w == "or") && self.at(&TokenKind::LParen) => {
1567 Ok(sym_at(span, &w))
1568 }
1569 TokenKind::Ident(w) if w == "and" || w == "or" => Err(fail(
1570 format!("`{w}` joins two expressions: `a {w} b`"),
1571 span,
1572 )),
1573
1574 TokenKind::LParen => {
1575 self.skip_newlines();
1576 let inner = self.expr(0)?;
1577 self.skip_newlines();
1578 self.expect(&TokenKind::RParen, "`)`")?;
1579 Ok(inner)
1583 }
1584
1585 TokenKind::LBracket => self.list_literal(span),
1586 TokenKind::LBrace => self.map_literal(span),
1587
1588 TokenKind::Ident(name) if self.at(&TokenKind::PathSep) => {
1589 self.qualified_name(span, &name)
1590 }
1591 TokenKind::PathSep => Err(fail(
1592 "a qualified name is `package::name`, with the package written before `::`",
1593 span,
1594 )),
1595 TokenKind::Ident(name) => match name.as_str() {
1596 "if" => self.if_form(span, false),
1597 "begin" if !(self.at(&TokenKind::LParen) && self.touches_previous()) => {
1598 self.begin_form(span)
1599 }
1600 "rescue" | "ensure" => Err(fail(
1601 format!("`{name}` belongs to a `begin … end` block or a `def` body, after the statements it protects"),
1602 span,
1603 )),
1604 "unless" => {
1605 self.note(Spelling::Unless, span, "unless", "if not");
1606 self.if_form(span, true)
1607 }
1608 "def" => self.def_form(span),
1609 "defmacro" => self.defmacro_form(span),
1610 "case" => self.case_form(span),
1611 "fn" => self.lambda_form(span),
1612 "test" => self.test_form(span),
1613 "assert" => self.assert_form(span),
1614 "quote" => self.quote_form(span),
1615 "unquote" => self.unquote_form(span, false),
1616 "unquote_splice" => self.unquote_form(span, true),
1617 "do" => Err(fail(
1618 "blue has no `do` blocks: pass a function as an argument, \
1619 `f(xs, fn(x) … end)`",
1620 span,
1621 )),
1622 "end" => Err(fail(
1623 "unexpected `end`: nothing open here for it to close",
1624 span,
1625 )),
1626 _ => Ok(sym_at(span, &name)),
1627 },
1628
1629 other => Err(fail(format!("expected an expression, found {other}"), span)),
1630 }
1631 }
1632
1633 fn qualified_name(&mut self, pkg_span: Span, pkg: &str) -> Result<Spanned, Fail> {
1643 if is_reserved_word(pkg) {
1644 return Err(fail(
1645 format!("`{pkg}` is a reserved word and cannot name a package"),
1646 pkg_span,
1647 ));
1648 }
1649 self.bump(); let name_span = self.peek_span();
1651 let name = match self.bump() {
1652 TokenKind::Ident(n) => n,
1653 TokenKind::True => "true".to_string(),
1655 TokenKind::False => "false".to_string(),
1656 TokenKind::Nil => "nil".to_string(),
1657 other => {
1658 return Err(fail(
1659 format!("expected a name after `{pkg}::`, found {other}"),
1660 name_span,
1661 ))
1662 }
1663 };
1664 if self.at(&TokenKind::PathSep) {
1665 return Err(self.error(format!(
1666 "a qualified name has one qualifier: `package::name`, and `{pkg}::{name}::…` has two"
1667 )));
1668 }
1669 Ok(sym_at(
1670 self.span_since(pkg_span.start),
1671 &qualify(pkg, &name),
1672 ))
1673 }
1674
1675 #[inline(never)]
1682 fn negation(&mut self, head: Span, whole: Span, operand: Spanned) -> Spanned {
1683 if let SpannedForm::List(items) = &operand.form {
1684 let flipped = match items.first().and_then(Spanned::as_symbol) {
1685 Some("equal?") => Some("not="),
1686 Some("not=") => Some("equal?"),
1687 _ => None,
1688 };
1689 if let (Some(to), 3) = (flipped, items.len()) {
1690 let (written, canonical) = if to == "not=" {
1691 ("not (a == b)", "a != b")
1692 } else {
1693 ("not (a != b)", "a == b")
1694 };
1695 self.note(Spelling::NegatedComparison, whole, written, canonical);
1696 let mut out = items.clone();
1697 out[0] = sym_at(items[0].span, to);
1698 if to == "equal?" {
1699 if let Some(test) =
1700 self.nil_test(whole.start, items[0].span, &items[1], &items[2])
1701 {
1702 return test;
1703 }
1704 }
1705 return list_at(whole, out);
1706 }
1707 }
1708 list_at(whole, vec![sym_at(head, "not"), operand])
1709 }
1710
1711 #[inline(never)]
1712 fn call_spelling(&mut self, call: Spanned) -> Spanned {
1713 let SpannedForm::List(items) = &call.form else {
1714 return call;
1715 };
1716 let Some(head) = items.first().and_then(Spanned::as_symbol) else {
1717 return call;
1718 };
1719 let argc = items.len() - 1;
1720 if head == "equal?" && argc == 2 {
1721 let (op_span, start) = (items[0].span, call.span.start);
1722 if let Some(test) = self.nil_test(start, op_span, &items[1], &items[2]) {
1723 return test;
1724 }
1725 }
1726 let operator = match (head, argc) {
1727 ("and" | "or" | "mod" | "equal?", 2) => Some(head),
1728 ("list", _) => Some("list"),
1729 _ => None,
1730 };
1731 if let Some(op) = operator {
1732 let canonical = match op {
1733 "list" => "[…]".to_string(),
1734 "mod" => "a % b".to_string(),
1735 "equal?" => "a == b".to_string(),
1736 other => format!("a {other} b"),
1737 };
1738 self.note(
1739 Spelling::OperatorSpelling,
1740 call.span,
1741 &format!("{head}(…)"),
1742 &canonical,
1743 );
1744 return call;
1745 }
1746 if head == "apply"
1747 && self.text(items[0].span) == "apply"
1748 && argc >= 2
1749 && items[1].as_symbol().is_some_and(is_callable_name)
1750 {
1751 self.note(Spelling::Apply, call.span, "apply(f, …, xs)", "f(…, *xs)");
1752 return call;
1753 }
1754 if head == "try" {
1755 return self.try_spelling(call);
1756 }
1757 call
1758 }
1759
1760 fn try_spelling(&mut self, call: Spanned) -> Spanned {
1761 let SpannedForm::List(items) = &call.form else {
1762 return call;
1763 };
1764 let Some((catch, body)) = items[1..].split_last() else {
1765 return call;
1766 };
1767 let Some(clause) = catch.as_list() else {
1768 return call;
1769 };
1770 let binder = match clause {
1771 [head, binding, handler @ ..]
1772 if head.as_symbol() == Some("catch") && !handler.is_empty() =>
1773 {
1774 match binding.as_list() {
1775 Some([b]) => b.as_symbol().map(|n| (n.to_string(), b.span)),
1776 _ => None,
1777 }
1778 }
1779 _ => None,
1780 };
1781 let Some((name, binder_span)) = binder else {
1782 return call;
1783 };
1784 if body.is_empty() {
1785 return call;
1786 }
1787 self.note(Spelling::TryCatch, call.span, "try(…, catch(…))", "rescue");
1788 let handler = &clause[2..];
1789 let rename =
1790 name.starts_with('_') && name != "_" && !handler.iter().any(|h| mentions(h, &name));
1791 let body = if body.len() == 1 {
1792 body[0].clone()
1793 } else {
1794 let span = cover(body, call.span);
1795 let mut list = vec![sym_at(Span::new(span.start, span.start), "begin")];
1796 list.extend(body.iter().cloned());
1797 list_at(span, list)
1798 };
1799 let handler = if handler.len() == 1 {
1800 handler[0].clone()
1801 } else {
1802 let span = cover(handler, catch.span);
1803 let mut list = vec![sym_at(Span::new(span.start, span.start), "begin")];
1804 list.extend(handler.iter().cloned());
1805 list_at(span, list)
1806 };
1807 let binder = sym_at(binder_span, if rename { "_" } else { &name });
1808 list_at(
1809 call.span,
1810 vec![
1811 items[0].clone(),
1812 body,
1813 list_at(
1814 catch.span,
1815 vec![
1816 clause[0].clone(),
1817 list_at(clause[1].span, vec![binder]),
1818 handler,
1819 ],
1820 ),
1821 ],
1822 )
1823 }
1824
1825 fn finish_send(&mut self, start: usize, recv: Spanned, dot: Span) -> Result<Spanned, Fail> {
1826 let name_span = self.peek_span();
1827 let name = match self.bump() {
1828 TokenKind::Ident(n) if is_reserved_word(&n) => {
1842 return Err(self.error(format!(
1843 "`{n}` is a reserved word and cannot be a method name — \
1844 `recv.{n}` would lower onto the `{n}` form itself"
1845 )))
1846 }
1847 TokenKind::Ident(n) => n,
1848 other => {
1849 return Err(fail(
1850 format!("expected a method name after `.`, found {other}"),
1851 name_span,
1852 ))
1853 }
1854 };
1855 if !self.at(&TokenKind::LParen) {
1856 return Ok(list_at(
1857 self.span_since(start),
1858 vec![
1859 sym_at(dot, LOWERED_FIELD),
1860 recv,
1861 atom_at(name_span, Atom::Keyword(name)),
1862 ],
1863 ));
1864 }
1865 self.note(
1866 Spelling::CallForm,
1867 dot.merge(name_span),
1868 &format!("x.{name}(…)"),
1869 &format!("{name}(x, …)"),
1870 );
1871 let (args, splat) = self.call_args()?;
1872 Ok(self.call_node(
1873 self.span_since(start),
1874 vec![sym_at(name_span, &name), recv],
1875 args,
1876 splat,
1877 ))
1878 }
1879
1880 fn call_node(
1881 &self,
1882 span: Span,
1883 head: Vec<Spanned>,
1884 args: Vec<Spanned>,
1885 splat: Option<Span>,
1886 ) -> Spanned {
1887 let mut list = Vec::with_capacity(head.len() + args.len() + 1);
1888 if let Some(at) = splat {
1889 list.push(sym_at(at, "apply"));
1890 }
1891 list.extend(head);
1892 list.extend(args);
1893 list_at(span, list)
1894 }
1895
1896 fn call_args(&mut self) -> Result<(Vec<Spanned>, Option<Span>), Fail> {
1897 self.expect(&TokenKind::LParen, "`(`")?;
1898 let mut args = Vec::new();
1899 let mut splat = None;
1900 self.skip_newlines();
1901 if self.eat(&TokenKind::RParen) {
1902 return Ok((args, splat));
1903 }
1904 loop {
1905 self.skip_newlines();
1906 if splat.is_some() {
1907 return Err(
1908 self.error("a splat `*xs` passes a list as the LAST arguments: write it last")
1909 );
1910 }
1911 if self.at_spread() || matches!(self.peek(), TokenKind::Label(_)) {
1912 let open = self.peek_span();
1913 let (segments, spread) = self.map_entries(open, &TokenKind::RParen)?;
1914 if !spread {
1915 return Err(fail(
1916 "a keyword entry in a call needs a `**` spread: pass a map, f({a: 1})",
1917 open,
1918 ));
1919 }
1920 args.push(self.merged(open, LOWERED_KWARGS, segments));
1921 return Ok((args, splat));
1922 }
1923 if matches!(self.peek(), TokenKind::Op(o) if o == "*") {
1924 splat = Some(self.peek_span());
1925 self.bump();
1926 }
1927 args.push(self.expr(0)?);
1928 self.skip_newlines();
1929 if self.eat(&TokenKind::Comma) {
1930 if self.trailing_comma(&TokenKind::RParen) {
1931 break;
1932 }
1933 continue;
1934 }
1935 self.expect(&TokenKind::RParen, "`,` or `)`")?;
1936 break;
1937 }
1938 Ok((args, splat))
1939 }
1940
1941 fn trailing_comma(&mut self, closer: &TokenKind) -> bool {
1942 let comma = self
1943 .toks
1944 .get(self.pos.wrapping_sub(1))
1945 .map_or(self.peek_span(), |t| t.span);
1946 self.skip_newlines();
1947 if self.at(closer) {
1948 self.note(Spelling::TrailingComma, comma, ",", "");
1949 self.bump();
1950 return true;
1951 }
1952 false
1953 }
1954
1955 fn list_literal(&mut self, open: Span) -> Result<Spanned, Fail> {
1956 let mut items = vec![sym_at(open, "list")];
1957 self.skip_newlines();
1958 if self.eat(&TokenKind::RBracket) {
1959 return Ok(list_at(self.span_since(open.start), items));
1960 }
1961 loop {
1962 self.skip_newlines();
1963 items.push(self.expr(0)?);
1964 self.skip_newlines();
1965 if self.eat(&TokenKind::Comma) {
1966 if self.trailing_comma(&TokenKind::RBracket) {
1967 break;
1968 }
1969 continue;
1970 }
1971 self.expect(&TokenKind::RBracket, "`,` or `]`")?;
1972 break;
1973 }
1974 Ok(list_at(self.span_since(open.start), items))
1975 }
1976
1977 fn map_literal(&mut self, open: Span) -> Result<Spanned, Fail> {
1984 self.skip_newlines();
1985 if self.eat(&TokenKind::RBrace) {
1986 return Ok(list_at(
1987 self.span_since(open.start),
1988 vec![sym_at(open, LOWERED_MAP)],
1989 ));
1990 }
1991 let (mut segments, spread) = self.map_entries(open, &TokenKind::RBrace)?;
1992 if !spread {
1993 return Ok(segments.remove(0));
1994 }
1995 Ok(self.merged(open, LOWERED_MERGE, segments))
1996 }
1997
1998 fn merged(&self, open: Span, head: &str, segments: Vec<Spanned>) -> Spanned {
1999 let mut items = vec![sym_at(open, head)];
2000 items.extend(segments);
2001 list_at(self.span_since(open.start), items)
2002 }
2003
2004 fn at_spread(&self) -> bool {
2005 matches!(self.peek(), TokenKind::Op(o) if o == "**")
2006 }
2007
2008 fn map_entries(
2009 &mut self,
2010 open: Span,
2011 closer: &TokenKind,
2012 ) -> Result<(Vec<Spanned>, bool), Fail> {
2013 let mut segments = Vec::new();
2014 let mut items = vec![sym_at(open, LOWERED_MAP)];
2015 let mut spread = false;
2016 loop {
2017 self.skip_newlines();
2018 if self.at_spread() {
2019 let star = self.peek_span();
2020 self.bump();
2021 let value = self.expr(0)?;
2022 if items.len() > 1 {
2023 let span = items[1].span.merge(items[items.len() - 1].span);
2024 items[0] = sym_at(items[1].span, LOWERED_MAP);
2025 segments.push(list_at(span, std::mem::take(&mut items)));
2026 items = vec![sym_at(open, LOWERED_MAP)];
2027 }
2028 segments.push(list_at(
2029 star.merge(value.span),
2030 vec![sym_at(star, LOWERED_SPLICE), value],
2031 ));
2032 spread = true;
2033 } else {
2034 self.map_entry(&mut items, closer)?;
2035 }
2036 self.skip_newlines();
2037 if self.eat(&TokenKind::Comma) {
2038 if self.trailing_comma(closer) {
2039 break;
2040 }
2041 continue;
2042 }
2043 self.expect(closer, "`,` or the end of the entries")?;
2044 break;
2045 }
2046 if items.len() > 1 || !spread {
2047 let whole = self.span_since(open.start);
2048 segments.push(list_at(whole, items));
2049 }
2050 Ok((segments, spread))
2051 }
2052
2053 fn map_entry(&mut self, items: &mut Vec<Spanned>, closer: &TokenKind) -> Result<(), Fail> {
2054 match self.peek().clone() {
2055 TokenKind::Label(name) => {
2056 let label_span = self.peek_span();
2057 self.bump();
2058 self.skip_newlines();
2059 items.push(atom_at(label_span, Atom::Keyword(name.clone())));
2060 if matches!(self.peek(), TokenKind::Comma) || self.at(closer) {
2061 items.push(sym_at(label_span, &name));
2062 } else {
2063 let value = self.expr(0)?;
2064 if matches!(&value.form, SpannedForm::Atom(Atom::Symbol(s)) if *s == name)
2065 && label_name(&name)
2066 {
2067 let written = format!("{name}: {name}");
2068 let canonical = format!("{name}:");
2069 self.note(
2070 Spelling::MapPun,
2071 label_span.merge(value.span),
2072 &written,
2073 &canonical,
2074 );
2075 }
2076 items.push(value);
2077 }
2078 }
2079 _ => {
2080 let k = self.expr(0)?;
2081 self.skip_newlines();
2082 if let SpannedForm::Atom(Atom::Keyword(name)) = &k.form {
2083 if blue_lang_name(name) {
2084 let written = format!(":{name} =>");
2085 let canonical = format!("{name}:");
2086 self.note(Spelling::KeywordRocket, k.span, &written, &canonical);
2087 }
2088 }
2089 self.expect(&TokenKind::Rocket, "`=>` in a map literal")?;
2090 self.skip_newlines();
2091 items.push(k);
2092 items.push(self.expr(0)?);
2093 }
2094 }
2095 Ok(())
2096 }
2097
2098 fn if_chain(&mut self, head: Span) -> Result<Spanned, Fail> {
2108 let cond = self.expr(0)?;
2109 let then = self.body(&["elsif", "else", "end"])?;
2110 let els = self.else_part()?;
2111 let mut out = vec![sym_at(head, "if"), cond, then];
2112 if let Some(e) = els {
2113 out.push(e);
2114 }
2115 Ok(list_at(self.span_since(head.start), out))
2116 }
2117
2118 fn else_part(&mut self) -> Result<Option<Spanned>, Fail> {
2119 if self.at_ident("elsif") {
2120 let elsif = self.peek_span();
2121 self.bump();
2122 return Ok(Some(self.if_chain(elsif)?));
2123 }
2124 if !self.at_ident("else") {
2125 self.expect_ident("end")?;
2126 return Ok(None);
2127 }
2128 let else_span = self.peek_span();
2129 self.bump();
2130 let save = self.pos;
2131 self.skip_newlines();
2132 let leads_with_if = self.at_ident("if") || self.at_ident("unless");
2133 self.pos = save;
2134 let e = self.body(&["end"])?;
2135 let end_at = self.peek_span().start;
2136 self.expect_ident("end")?;
2137 if matches!(e.form, SpannedForm::Nil) && !self.comment_within(else_span.start, end_at) {
2138 self.note(Spelling::ElseNil, else_span, "else nil", "no else");
2139 return Ok(None);
2140 }
2141 let single_if = matches!(&e.form, SpannedForm::List(items)
2142 if (items.len() == 3 || items.len() == 4) && items[0].as_symbol() == Some("if"));
2143 if leads_with_if && single_if {
2144 self.note(Spelling::ElseIf, else_span, "else if", "elsif");
2145 }
2146 Ok(Some(e))
2147 }
2148
2149 fn if_form(&mut self, head: Span, negate: bool) -> Result<Spanned, Fail> {
2150 let cond = self.expr(0)?;
2151 let cond = if negate {
2152 let inner = cond.span;
2155 self.negation(head, head.merge(inner), cond)
2156 } else {
2157 cond
2158 };
2159 let then = self.body(&["elsif", "else", "end"])?;
2172 let els = self.else_part()?;
2173 let mut out = vec![sym_at(head, "if"), cond, then];
2174 if let Some(e) = els {
2175 out.push(e);
2176 }
2177 Ok(list_at(self.span_since(head.start), out))
2178 }
2179
2180 fn case_form(&mut self, head: Span) -> Result<Spanned, Fail> {
2206 let subject = self.expr(0)?;
2207 self.skip_newlines();
2208
2209 let subject_var = "case-subject";
2212 let mut arms: Vec<Spanned> = Vec::new();
2213 let mut otherwise: Option<Spanned> = None;
2214 let mut else_kw: Option<Span> = None;
2217
2218 loop {
2219 self.skip_newlines();
2220 if self.at_ident("end") {
2221 break;
2222 }
2223 let maybe_else = self.peek_span();
2224 if self.eat_ident("else") {
2225 else_kw = Some(maybe_else);
2226 otherwise = Some(self.body(&["end"])?);
2227 continue;
2228 }
2229 let when = self.peek_span();
2230 if !self.eat_ident("when") {
2231 return Err(self.error(format!(
2232 "expected `when`, `else` or `end` in a case, found {}",
2233 self.peek()
2234 )));
2235 }
2236 self.skip_newlines();
2237 let pattern = self.expr(0)?;
2238 let body = self.body(&["when", "else", "end"])?;
2239 let test = list_at(
2243 when.merge(pattern.span),
2244 vec![sym_at(when, "equal?"), sym_at(when, subject_var), pattern],
2245 );
2246 arms.push(list_at(when.merge(body.span), vec![test, body]));
2247 }
2248 self.expect_ident("end")?;
2249
2250 if arms.is_empty() && otherwise.is_none() {
2251 return Err(self.error("a case needs at least one `when` or an `else`".to_string()));
2252 }
2253
2254 let mut cond = vec![sym_at(head, "cond")];
2259 cond.extend(arms);
2260 let else_span = else_kw.unwrap_or(head);
2267 let else_body = otherwise.unwrap_or_else(|| Spanned::new(else_span, SpannedForm::Nil));
2268 cond.push(list_at(
2269 else_span.merge(else_body.span),
2270 vec![sym_at(else_span, "else"), else_body],
2271 ));
2272
2273 let whole = self.span_since(head.start);
2274 let binding = list_at(
2275 head.merge(subject.span),
2276 vec![sym_at(head, subject_var), subject],
2277 );
2278 Ok(list_at(
2279 whole,
2280 vec![
2281 sym_at(head, "let"),
2282 list_at(binding.span, vec![binding]),
2283 list_at(whole, cond),
2284 ],
2285 ))
2286 }
2287
2288 fn arrow_lambda_ahead(&self) -> bool {
2291 let mut i = self.pos;
2292 let kind = |i: usize| self.toks.get(i).map(|t| &t.kind);
2293 if !matches!(kind(i), Some(TokenKind::RParen)) {
2294 loop {
2295 if matches!(kind(i), Some(TokenKind::Op(o)) if o == "*") {
2296 i += 1;
2297 }
2298 if !matches!(kind(i), Some(TokenKind::Ident(_))) {
2299 return false;
2300 }
2301 i += 1;
2302 match kind(i) {
2303 Some(TokenKind::Comma) => i += 1,
2304 Some(TokenKind::RParen) => break,
2305 _ => return false,
2306 }
2307 }
2308 }
2309 matches!(kind(i + 1), Some(TokenKind::Op(o)) if o == "->")
2310 }
2311
2312 fn arrow_lambda(&mut self, head: Span) -> Result<Spanned, Fail> {
2319 let mut params: Vec<Spanned> = Vec::new();
2320 while !self.at(&TokenKind::RParen) {
2321 let p_span = self.peek_span();
2322 if self.at_star() {
2323 params.extend(self.rest_parameter()?);
2324 break;
2325 }
2326 match self.bump() {
2327 TokenKind::Ident(p) => params.push(sym_at(p_span, &p)),
2328 other => {
2329 return Err(fail(
2330 format!("expected a parameter name, found {other}"),
2331 p_span,
2332 ))
2333 }
2334 }
2335 self.eat(&TokenKind::Comma);
2336 }
2337 self.bump();
2338 self.bump();
2339 self.skip_newlines();
2340 let params_span = cover(¶ms, head);
2341 let body = self.expr(0)?;
2342 Ok(list_at(
2343 self.span_since(head.start),
2344 vec![sym_at(head, "lambda"), list_at(params_span, params), body],
2345 ))
2346 }
2347
2348 fn rest_parameter(&mut self) -> Result<[Spanned; 2], Fail> {
2349 let star = self.peek_span();
2350 self.bump();
2351 let name_span = self.peek_span();
2352 match self.bump() {
2353 TokenKind::Ident(p) => {
2354 let whole = star.merge(name_span);
2355 Ok([sym_at(whole, "&rest"), sym_at(whole, &p)])
2356 }
2357 other => Err(fail(
2358 format!("expected a parameter name after `*`, found {other}"),
2359 name_span,
2360 )),
2361 }
2362 }
2363
2364 fn at_star(&self) -> bool {
2365 matches!(self.peek(), TokenKind::Op(o) if o == "*")
2366 }
2367
2368 fn optional_mark(
2369 &self,
2370 name: String,
2371 span: Span,
2372 optional_from: &mut Option<usize>,
2373 index: usize,
2374 ) -> Result<String, Fail> {
2375 match name.strip_suffix('?').filter(|n| !n.is_empty()) {
2376 Some(bare) => {
2377 optional_from.get_or_insert(index);
2378 Ok(bare.to_string())
2379 }
2380 None if optional_from.is_some() => Err(fail(
2381 format!("the required parameter `{name}` cannot follow an optional one"),
2382 span,
2383 )),
2384 None => Ok(name),
2385 }
2386 }
2387
2388 fn rest_is_last(&mut self) -> Result<(), Fail> {
2389 self.skip_newlines();
2390 if self.at(&TokenKind::Comma) {
2391 return Err(self.error("a rest parameter `*name` comes last".to_string()));
2392 }
2393 Ok(())
2394 }
2395
2396 fn lambda_form(&mut self, head: Span) -> Result<Spanned, Fail> {
2406 let mut params: Vec<Spanned> = Vec::new();
2407 if !self.at(&TokenKind::LParen) {
2408 self.note(Spelling::ParameterList, head, "fn", "fn()");
2409 }
2410 if self.at(&TokenKind::LParen) {
2411 self.bump();
2412 self.skip_newlines();
2413 if !self.eat(&TokenKind::RParen) {
2414 loop {
2415 self.skip_newlines();
2416 let p_span = self.peek_span();
2417 if self.at_star() {
2418 params.extend(self.rest_parameter()?);
2419 self.rest_is_last()?;
2420 self.expect(&TokenKind::RParen, "`)` after the rest parameter")?;
2421 break;
2422 }
2423 match self.bump() {
2424 TokenKind::Ident(p) => params.push(sym_at(p_span, &p)),
2425 other => {
2426 return Err(fail(
2427 format!("expected a parameter name, found {other}"),
2428 p_span,
2429 ))
2430 }
2431 }
2432 self.skip_newlines();
2433 if self.eat(&TokenKind::Comma) {
2434 if self.trailing_comma(&TokenKind::RParen) {
2435 break;
2436 }
2437 continue;
2438 }
2439 self.expect(&TokenKind::RParen, "`,` or `)`")?;
2440 break;
2441 }
2442 }
2443 }
2444 let params_span = cover(¶ms, head);
2445 let body = self.body(&["end"])?;
2446 self.expect_ident("end")?;
2447 Ok(list_at(
2448 self.span_since(head.start),
2449 vec![sym_at(head, "lambda"), list_at(params_span, params), body],
2450 ))
2451 }
2452
2453 fn test_form(&mut self, head: Span) -> Result<Spanned, Fail> {
2459 let name_span = self.peek_span();
2460 let name = match self.bump() {
2461 TokenKind::Str(s) => s,
2462 other => {
2463 return Err(fail(
2464 format!("expected a string name after `test`, found {other}"),
2465 name_span,
2466 ))
2467 }
2468 };
2469 let body = self.body(&["end"])?;
2470 self.expect_ident("end")?;
2471 Ok(list_at(
2472 self.span_since(head.start),
2473 vec![
2474 sym_at(head, "deftest"),
2475 atom_at(name_span, Atom::Str(name)),
2476 body,
2477 ],
2478 ))
2479 }
2480
2481 fn assert_form(&mut self, head: Span) -> Result<Spanned, Fail> {
2505 let e = self.expr(0)?;
2506 let quoted = Spanned::new(e.span, SpannedForm::Quote(Box::new(e.clone())));
2507 Ok(list_at(
2508 self.span_since(head.start),
2509 vec![sym_at(head, LOWERED_ASSERT), quoted, e],
2510 ))
2511 }
2512
2513 fn defmacro_form(&mut self, head: Span) -> Result<Spanned, Fail> {
2522 let name_span = self.peek_span();
2523 let name = match self.bump() {
2524 TokenKind::Ident(n) => n,
2525 other => {
2526 return Err(fail(
2527 format!("expected a name after `defmacro`, found {other}"),
2528 name_span,
2529 ))
2530 }
2531 };
2532 self.not_reserved(&name, name_span, "a `defmacro` name")?;
2533 let mut params: Vec<Spanned> = Vec::new();
2534 if self.at(&TokenKind::LParen) {
2535 self.bump();
2536 self.skip_newlines();
2537 if !self.eat(&TokenKind::RParen) {
2538 loop {
2539 self.skip_newlines();
2540 let p_span = self.peek_span();
2541 match self.bump() {
2542 TokenKind::Ident(p) => params.push(sym_at(p_span, &p)),
2543 TokenKind::Label(p) => {
2544 return Err(self.error(format!(
2545 "macro parameter `{p}` cannot be typed: a macro receives \
2546 source forms, not values"
2547 )))
2548 }
2549 other => {
2550 return Err(fail(
2551 format!("expected a parameter name, found {other}"),
2552 p_span,
2553 ))
2554 }
2555 }
2556 self.skip_newlines();
2557 if self.eat(&TokenKind::Comma) {
2558 if self.trailing_comma(&TokenKind::RParen) {
2559 break;
2560 }
2561 continue;
2562 }
2563 self.expect(&TokenKind::RParen, "`,` or `)`")?;
2564 break;
2565 }
2566 }
2567 }
2568 if matches!(self.peek(), TokenKind::Op(o) if o == "->") {
2569 return Err(self.error(
2570 "a macro has no return type: it produces source forms, not values".to_string(),
2571 ));
2572 }
2573
2574 let params_span = cover(¶ms, name_span);
2575 let body = self.body(&["end"])?;
2576 self.expect_ident("end")?;
2577
2578 Ok(list_at(
2581 self.span_since(head.start),
2582 vec![
2583 sym_at(head, "defmacro"),
2584 sym_at(name_span, &name),
2585 list_at(params_span, params),
2586 body,
2587 ],
2588 ))
2589 }
2590
2591 fn quote_form(&mut self, head: Span) -> Result<Spanned, Fail> {
2597 self.quoting += 1;
2598 let body = self.body(&["end"]);
2599 self.quoting -= 1;
2600 let body = body?;
2601 self.expect_ident("end")?;
2602 Ok(Spanned::new(
2611 self.span_since(head.start),
2612 SpannedForm::Quasiquote(Box::new(body)),
2613 ))
2614 }
2615
2616 fn unquote_form(&mut self, head: Span, splice: bool) -> Result<Spanned, Fail> {
2618 self.expect(&TokenKind::LParen, "`(` after unquote")?;
2619 self.skip_newlines();
2620 let inner = self.expr(0)?;
2621 self.skip_newlines();
2622 self.expect(&TokenKind::RParen, "`)`")?;
2623 let span = self.span_since(head.start);
2624 Ok(Spanned::new(
2625 span,
2626 if splice {
2627 SpannedForm::UnquoteSplice(Box::new(inner))
2628 } else {
2629 SpannedForm::Unquote(Box::new(inner))
2630 },
2631 ))
2632 }
2633
2634 fn protected_body(&mut self, head: Span) -> Result<Spanned, Fail> {
2635 let body = self.body(&["rescue", "ensure", "end"])?;
2636 if self.at_ident("rescue") || self.at_ident("ensure") {
2637 return self.rescue_tail(head, body);
2638 }
2639 Ok(body)
2640 }
2641
2642 fn begin_form(&mut self, head: Span) -> Result<Spanned, Fail> {
2643 let body = self.body(&["rescue", "ensure", "end"])?;
2644 if !(self.at_ident("rescue") || self.at_ident("ensure")) {
2645 return Err(self.error(
2646 "a `begin` block protects its statements with `rescue` or `ensure`; without either, write the statements directly",
2647 ));
2648 }
2649 let tree = self.rescue_tail(head, body)?;
2650 self.expect_ident("end")?;
2651 Ok(Spanned::new(self.span_since(head.start), tree.form))
2652 }
2653
2654 fn rescue_tail(&mut self, head: Span, body: Spanned) -> Result<Spanned, Fail> {
2655 let mut clauses: Vec<Spanned> = Vec::new();
2656 let mut catch_all: Option<(Spanned, Spanned, Span)> = None;
2657 while self.at_ident("rescue") {
2658 let kw = self.peek_span();
2659 self.bump();
2660 if catch_all.is_some() {
2661 return Err(fail(
2662 "a `rescue NAME` catches every error, so it is the last `rescue`; put the kinds it would shadow above it",
2663 kw,
2664 ));
2665 }
2666 match self.peek().clone() {
2667 TokenKind::Sym(_) => {
2668 let mut kinds = Vec::new();
2669 loop {
2670 let k_span = self.peek_span();
2671 match self.bump() {
2672 TokenKind::Sym(k) => kinds.push(atom_at(k_span, Atom::Keyword(k))),
2673 other => {
2674 return Err(fail(
2675 format!("expected an error kind such as `:not_found`, found {other}"),
2676 k_span,
2677 ))
2678 }
2679 }
2680 if self.eat(&TokenKind::Comma) {
2681 self.skip_newlines();
2682 continue;
2683 }
2684 break;
2685 }
2686 let binder = if self.eat_ident("as") {
2687 let b_span = self.peek_span();
2688 match self.bump() {
2689 TokenKind::Ident(b) if !is_reserved_word(&b) => Some(sym_at(b_span, &b)),
2690 other => {
2691 return Err(fail(
2692 format!("expected the name the error is bound to after `as`, found {other}"),
2693 b_span,
2694 ))
2695 }
2696 }
2697 } else {
2698 None
2699 };
2700 let handler = self.body(&["rescue", "ensure", "end"])?;
2701 let mut test = vec![sym_at(kw, LOWERED_ERROR_MATCH), sym_at(kw, RESCUED)];
2702 test.extend(kinds);
2703 let test = list_at(cover(&test, kw), test);
2704 let handler = match binder {
2705 Some(b) => let_bound(b, handler, kw),
2706 None => handler,
2707 };
2708 clauses.push(list_at(kw.merge(handler.span), vec![test, handler]));
2709 }
2710 TokenKind::Ident(b) if !is_reserved_word(&b) => {
2711 let b_span = self.peek_span();
2712 self.bump();
2713 let handler = self.body(&["rescue", "ensure", "end"])?;
2714 catch_all = Some((sym_at(b_span, &b), handler, kw));
2715 }
2716 _ => {
2717 return Err(fail(
2718 "`rescue` names what it catches: `rescue :kind`, `rescue :a, :b as e`, or `rescue e` for every error (`rescue _` discards it)",
2719 kw,
2720 ))
2721 }
2722 }
2723 }
2724 let ensure = if self.at_ident("ensure") {
2725 self.bump();
2726 let cleanup = self.body(&["rescue", "end"])?;
2727 if self.at_ident("rescue") {
2728 return Err(self.error("`ensure` comes after every `rescue` of its block"));
2729 }
2730 Some(cleanup)
2731 } else {
2732 None
2733 };
2734 let whole = self.span_since(head.start);
2735 let protected = if clauses.is_empty() {
2736 match catch_all {
2737 None => body,
2738 Some((binder, handler, kw)) => list_at(
2739 whole,
2740 vec![
2741 sym_at(head, "try"),
2742 body,
2743 list_at(
2744 kw.merge(handler.span),
2745 vec![
2746 sym_at(kw, "catch"),
2747 list_at(binder.span, vec![binder]),
2748 handler,
2749 ],
2750 ),
2751 ],
2752 ),
2753 }
2754 } else {
2755 let first_kw = clauses[0].span;
2756 let otherwise = match catch_all {
2757 Some((binder, handler, kw)) => {
2758 let bound = let_bound(binder, handler, kw);
2759 list_at(kw.merge(bound.span), vec![sym_at(kw, "else"), bound])
2760 }
2761 None => list_at(
2762 head,
2763 vec![
2764 sym_at(head, "else"),
2765 list_at(
2766 head,
2767 vec![sym_at(head, LOWERED_RERAISE), sym_at(head, RESCUED)],
2768 ),
2769 ],
2770 ),
2771 };
2772 let mut cond = vec![sym_at(first_kw, "cond")];
2773 cond.extend(clauses);
2774 cond.push(otherwise);
2775 let cond_span = cover(&cond, first_kw);
2776 list_at(
2777 whole,
2778 vec![
2779 sym_at(head, "try"),
2780 body,
2781 list_at(
2782 cond_span,
2783 vec![
2784 sym_at(first_kw, "catch"),
2785 list_at(first_kw, vec![sym_at(first_kw, RESCUED)]),
2786 list_at(cond_span, cond),
2787 ],
2788 ),
2789 ],
2790 )
2791 };
2792 Ok(match ensure {
2793 None => protected,
2794 Some(cleanup) => {
2795 let attempted = list_at(
2796 protected.span,
2797 vec![sym_at(head, LOWERED_ATTEMPTED), protected],
2798 );
2799 let failed = list_at(
2800 head,
2801 vec![sym_at(head, LOWERED_FAILED), sym_at(head, RESCUED)],
2802 );
2803 let caught = list_at(
2804 whole,
2805 vec![
2806 sym_at(head, "try"),
2807 attempted,
2808 list_at(
2809 head,
2810 vec![
2811 sym_at(head, "catch"),
2812 list_at(head, vec![sym_at(head, RESCUED)]),
2813 failed,
2814 ],
2815 ),
2816 ],
2817 );
2818 list_at(
2819 whole,
2820 vec![sym_at(head, LOWERED_ENSURE), caught, thunk(cleanup, head)],
2821 )
2822 }
2823 })
2824 }
2825
2826 fn def_form(&mut self, head: Span) -> Result<Spanned, Fail> {
2827 let name_span = self.peek_span();
2828 let name = match self.bump() {
2829 TokenKind::Ident(n) => n,
2830 other => {
2831 return Err(fail(
2832 format!("expected a name after `def`, found {other}"),
2833 name_span,
2834 ))
2835 }
2836 };
2837 self.not_reserved(&name, name_span, "a `def` name")?;
2838 let mut params: Vec<(String, Span, Option<Spanned>)> = Vec::new();
2839 let mut optional_from: Option<usize> = None;
2840 let mut rest: Vec<Spanned> = Vec::new();
2841 if !self.at(&TokenKind::LParen) {
2842 self.note(
2843 Spelling::ParameterList,
2844 name_span,
2845 &format!("def {name}"),
2846 &format!("def {name}()"),
2847 );
2848 }
2849 if self.at(&TokenKind::LParen) {
2850 self.bump();
2851 self.skip_newlines();
2852 if !self.eat(&TokenKind::RParen) {
2853 loop {
2854 self.skip_newlines();
2855 let p_span = self.peek_span();
2856 if self.at_star() {
2857 if optional_from.is_some() {
2858 return Err(fail(
2859 "a rest parameter cannot follow an optional one",
2860 p_span,
2861 ));
2862 }
2863 rest = self.rest_parameter()?.to_vec();
2864 self.rest_is_last()?;
2865 self.expect(&TokenKind::RParen, "`)` after the rest parameter")?;
2866 break;
2867 }
2868 match self.bump() {
2869 TokenKind::Ident(p) => {
2871 self.not_reserved(&p, p_span, "a parameter name")?;
2872 let p =
2873 self.optional_mark(p, p_span, &mut optional_from, params.len())?;
2874 params.push((p, p_span, None));
2875 }
2876 TokenKind::Label(p) => {
2878 self.not_reserved(&p, p_span, "a parameter name")?;
2879 let p =
2880 self.optional_mark(p, p_span, &mut optional_from, params.len())?;
2881 self.skip_newlines();
2882 let ty = self.type_expr()?;
2883 params.push((p, p_span, Some(ty)));
2884 }
2885 other => {
2886 return Err(fail(
2887 format!("expected a parameter name, found {other}"),
2888 p_span,
2889 ))
2890 }
2891 }
2892 self.skip_newlines();
2893 if self.eat(&TokenKind::Comma) {
2894 if self.trailing_comma(&TokenKind::RParen) {
2895 break;
2896 }
2897 continue;
2898 }
2899 self.expect(&TokenKind::RParen, "`,` or `)`")?;
2900 break;
2901 }
2902 }
2903 }
2904 let sig_span = self.span_since(name_span.start);
2906
2907 let ret = if matches!(self.peek(), TokenKind::Op(o) if o == "->") {
2909 self.bump();
2910 self.skip_newlines();
2911 Some(self.type_expr()?)
2912 } else {
2913 None
2914 };
2915
2916 let begins = {
2917 let save = self.pos;
2918 self.skip_newlines();
2919 let at = self.at_ident("begin").then(|| self.peek_span());
2920 self.pos = save;
2921 at
2922 };
2923 let body = self.protected_body(head)?;
2924 if let Some(b) = begins {
2925 let protects = matches!(&body.form, SpannedForm::List(items)
2926 if matches!(items.first().and_then(Spanned::as_symbol), Some("try") | Some(LOWERED_ENSURE)));
2927 if protects && body.span.start == b.start {
2928 self.note(
2929 Spelling::TryCatch,
2930 b,
2931 "begin … end as a whole def body",
2932 "the clauses on the def itself",
2933 );
2934 }
2935 }
2936 self.expect_ident("end")?;
2937 let whole = self.span_since(head.start);
2938
2939 let annotated = ret.is_some() || params.iter().any(|(_, _, t)| t.is_some());
2940 if !annotated {
2941 let mut sig = vec![sym_at(name_span, &name)];
2942 for (i, (p, s, _)) in params.into_iter().enumerate() {
2943 if optional_from == Some(i) {
2944 sig.push(sym_at(s, OPTIONAL_MARKER));
2945 }
2946 sig.push(sym_at(s, &p));
2947 }
2948 sig.extend(rest);
2949 return Ok(list_at(
2950 whole,
2951 vec![sym_at(head, "define"), list_at(sig_span, sig), body],
2952 ));
2953 }
2954
2955 let mut sig = vec![sym_at(name_span, &name)];
2963 for (i, (p, s, t)) in params.into_iter().enumerate() {
2964 if optional_from == Some(i) {
2965 sig.push(sym_at(s, OPTIONAL_MARKER));
2966 }
2967 let ty = t.unwrap_or_else(|| sym_at(s, "dyn"));
2968 sig.push(list_at(s.merge(ty.span), vec![sym_at(s, &p), ty]));
2969 }
2970 sig.extend(rest);
2971 Ok(list_at(
2972 whole,
2973 vec![
2974 sym_at(head, "define-typed"),
2975 list_at(sig_span, sig),
2976 ret.unwrap_or_else(|| sym_at(head, "dyn")),
2977 body,
2978 ],
2979 ))
2980 }
2981
2982 fn type_expr(&mut self) -> Result<Spanned, Fail> {
2984 let name_span = self.peek_span();
2985 let name = match self.bump() {
2986 TokenKind::Ident(n) => n,
2987 other => {
2988 return Err(fail(
2989 format!("expected a type name, found {other}"),
2990 name_span,
2991 ))
2992 }
2993 };
2994 self.not_reserved(&name, name_span, "a type name")?;
2995 if self.at(&TokenKind::LParen) {
2996 self.bump();
2997 self.skip_newlines();
2998 let mut list = vec![sym_at(name_span, &name)];
2999 if !self.eat(&TokenKind::RParen) {
3000 loop {
3001 self.skip_newlines();
3002 list.push(self.type_expr()?);
3003 self.skip_newlines();
3004 if self.eat(&TokenKind::Comma) {
3005 if self.trailing_comma(&TokenKind::RParen) {
3006 break;
3007 }
3008 continue;
3009 }
3010 self.expect(&TokenKind::RParen, "`,` or `)`")?;
3011 break;
3012 }
3013 }
3014 return Ok(list_at(self.span_since(name_span.start), list));
3015 }
3016 Ok(sym_at(name_span, &name))
3017 }
3018
3019 fn not_reserved(&self, name: &str, at: Span, what: &str) -> Result<(), Fail> {
3020 if is_reserved_word(name) {
3021 return Err(fail(
3022 format!("`{name}` is a reserved word and cannot be {what}"),
3023 at,
3024 ));
3025 }
3026 Ok(())
3027 }
3028
3029 fn eat_ident(&mut self, name: &str) -> bool {
3031 if self.at_ident(name) {
3032 self.bump();
3033 true
3034 } else {
3035 false
3036 }
3037 }
3038
3039 fn expect_ident(&mut self, name: &str) -> Result<(), Fail> {
3040 if self.at_ident(name) {
3041 self.bump();
3042 Ok(())
3043 } else {
3044 Err(self.error(format!("expected `{name}`, found {}", self.peek())))
3045 }
3046 }
3047
3048 fn body(&mut self, terminators: &[&str]) -> Result<Spanned, Fail> {
3051 let start = self.mark();
3054 let mut forms = Vec::new();
3055 loop {
3056 self.skip_newlines();
3057 if matches!(self.peek(), TokenKind::Eof) {
3058 return Err(self.error(format!(
3059 "unterminated block: expected {} before the end of the file",
3060 one_of(terminators)
3061 )));
3062 }
3063 if terminators.iter().any(|t| self.at_ident(t)) {
3064 break;
3065 }
3066 forms.push(self.statement()?);
3067 self.end_of_statement(terminators)?;
3068 }
3069 Ok(match forms.len() {
3070 0 => Spanned::new(Span::new(start, start), SpannedForm::Nil),
3071 1 => forms.into_iter().next().expect("checked len"),
3072 _ => {
3073 let span = cover(&forms, Span::new(start, start));
3079 let head = Span::new(span.start, span.start);
3085 let mut list = vec![sym_at(head, "begin")];
3086 list.extend(forms);
3087 list_at(span, list)
3088 }
3089 })
3090 }
3091}
3092
3093fn one_of(words: &[&str]) -> String {
3096 let quoted: Vec<String> = words.iter().map(|w| format!("`{w}`")).collect();
3097 match quoted.split_last() {
3098 None => String::new(),
3099 Some((last, [])) => last.clone(),
3100 Some((last, rest)) => format!("{} or {last}", rest.join(", ")),
3101 }
3102}
3103
3104fn let_bound(binder: Spanned, body: Spanned, at: Span) -> Spanned {
3105 let span = at.merge(body.span);
3106 let pair = list_at(binder.span, vec![binder, sym_at(at, RESCUED)]);
3107 list_at(
3108 span,
3109 vec![sym_at(at, "let"), list_at(pair.span, vec![pair]), body],
3110 )
3111}
3112
3113fn thunk(body: Spanned, at: Span) -> Spanned {
3114 let span = at.merge(body.span);
3115 list_at(
3116 span,
3117 vec![sym_at(at, "lambda"), list_at(at, Vec::new()), body],
3118 )
3119}
3120
3121#[must_use]
3126pub fn label_name(name: &str) -> bool {
3127 matches!(
3128 lex(&format!("{name}:")).as_deref(),
3129 Ok([t, e]) if matches!(&t.kind, TokenKind::Label(n) if n == name) && e.kind == TokenKind::Eof
3130 )
3131}
3132
3133fn blue_lang_name(name: &str) -> bool {
3134 matches!(
3135 lex(name).as_deref(),
3136 Ok([t, e]) if matches!(&t.kind, TokenKind::Ident(n) if n == name) && e.kind == TokenKind::Eof
3137 ) && !name.ends_with(['?', '!'])
3138}
3139
3140fn mentions(form: &Spanned, name: &str) -> bool {
3141 match &form.form {
3142 SpannedForm::Atom(Atom::Symbol(s)) => s == name,
3143 SpannedForm::List(items) => items.iter().any(|i| mentions(i, name)),
3144 SpannedForm::Quote(i)
3145 | SpannedForm::Quasiquote(i)
3146 | SpannedForm::Unquote(i)
3147 | SpannedForm::UnquoteSplice(i) => mentions(i, name),
3148 _ => false,
3149 }
3150}
3151
3152fn sym_at(span: Span, s: &str) -> Spanned {
3154 Spanned::new(span, SpannedForm::Atom(Atom::Symbol(s.to_string())))
3155}
3156
3157fn atom_at(span: Span, a: Atom) -> Spanned {
3159 Spanned::new(span, SpannedForm::Atom(a))
3160}
3161
3162fn list_at(span: Span, items: Vec<Spanned>) -> Spanned {
3164 Spanned::new(span, SpannedForm::List(items))
3165}
3166
3167fn cover(items: &[Spanned], fallback: Span) -> Span {
3175 let merged = items
3176 .iter()
3177 .fold(Span::synthetic(), |acc, s| acc.merge(s.span));
3178 if merged.is_synthetic() {
3179 fallback
3180 } else {
3181 merged
3182 }
3183}
3184
3185#[cfg(test)]
3186mod tests {
3187 use super::*;
3188
3189 fn q(src: &str) -> String {
3193 parse_expr(src)
3194 .map(|s| s.to_string())
3195 .unwrap_or_else(|e| panic!("{src:?}: {e}"))
3196 }
3197
3198 #[test]
3199 fn a_reserved_word_is_not_a_def_name_a_parameter_or_a_type() {
3200 for src in [
3201 "def def a(x)\n x\nend",
3202 "def end()\n 1\nend",
3203 "def f(end)\n 1\nend",
3204 "def f(a: not)\n 1\nend",
3205 "def f(a) -> not a\n a\nend",
3206 "def f(a: List(1 + 2))\n a\nend",
3207 "defmacro defmacro m(a)\n quote\n a\n end\nend",
3208 ] {
3209 assert!(parse_program(src).is_err(), "{src:?} parsed");
3210 }
3211 }
3212
3213 #[test]
3214 fn a_type_application_takes_types_of_any_arity() {
3215 assert_eq!(
3216 q("def f(a: Map(Str, List(Int))) -> Unit()\n a\nend"),
3217 "(define-typed (f (a (Map Str (List Int)))) (Unit) a)"
3218 );
3219 }
3220
3221 #[test]
3224 fn arithmetic_respects_precedence() {
3225 assert_eq!(q("1 + 2 * 3"), "(+ 1 (* 2 3))");
3226 assert_eq!(q("(1 + 2) * 3"), "(* (+ 1 2) 3)");
3227 }
3228
3229 #[test]
3230 fn comparison_binds_looser_than_arithmetic() {
3231 assert_eq!(q("a + 1 < b"), "(< (+ a 1) b)");
3232 }
3233
3234 #[test]
3239 fn logical_operators_bind_loosest_and_lower_to_tataras_names() {
3240 assert_eq!(q("a && b || c"), "(or (and a b) c)");
3241 }
3242
3243 #[test]
3244 fn left_associativity() {
3245 assert_eq!(q("1 - 2 - 3"), "(- (- 1 2) 3)");
3246 }
3247
3248 #[test]
3249 fn a_dot_without_parentheses_reads_a_field() {
3250 assert_eq!(q("user.name"), "(blue-field user :name)");
3251 }
3252
3253 #[test]
3254 fn method_call_with_args() {
3255 assert_eq!(q("user.greet(1, 2)"), "(greet user 1 2)");
3256 }
3257
3258 #[test]
3259 fn chained_fields_read_left_to_right() {
3260 assert_eq!(q("a.b.c"), "(blue-field (blue-field a :b) :c)");
3261 assert_eq!(q("a.b(1).c"), "(blue-field (b a 1) :c)");
3262 }
3263
3264 #[test]
3265 fn plain_call() {
3266 assert_eq!(q("f(1, 2)"), "(f 1 2)");
3267 }
3268
3269 #[test]
3272 fn pipeline_threads_into_first_argument() {
3273 assert_eq!(q("x |> f"), "(f x)");
3274 assert_eq!(q("x |> f(1)"), "(f x 1)");
3275 assert_eq!(q("x |> f |> g"), "(g (f x))");
3276 }
3277
3278 #[test]
3279 fn pipeline_binds_looser_than_arithmetic() {
3280 assert_eq!(q("1 + 2 |> f"), "(f (+ 1 2))");
3281 }
3282
3283 #[test]
3289 fn label_and_rocket_produce_the_same_tree_for_a_symbol_key() {
3290 assert_eq!(q("{a: 1}"), q("{:a => 1}"));
3291 assert_eq!(q("{a: 1}"), "(hash-map :a 1)");
3292 }
3293
3294 #[test]
3297 fn a_string_key_has_no_shorthand() {
3298 assert_eq!(q(r#"{"k" => 1}"#), r#"(hash-map "k" 1)"#);
3299 }
3300
3301 #[test]
3302 fn a_newline_escape_and_its_lines_are_one_tree() {
3303 for (escaped, lines) in [
3304 (r#""a\nb""#, r#"join_lines(["a", "b"])"#),
3305 (r##""#{m}\n#{h}""##, "join_lines([m, h])"),
3306 (r##""n=#{n}\ndone\n""##, r##"unlines(["n=#{n}", "done"])"##),
3307 (r#""\n""#, "line_break"),
3308 (r#""a\n\n\tb\n""#, r#"unlines(["a", "", "\tb"])"#),
3309 (r#""\na""#, r#"join_lines(["", "a"])"#),
3310 (r#""\n\n""#, r#"unlines(["", ""])"#),
3311 ] {
3312 assert_eq!(q(escaped), q(lines), "{escaped}");
3313 }
3314 assert_eq!(q(r#""a\tb""#), q(r#""a\tb""#));
3315 assert!(!q(r#""a\tb""#).contains("join_lines"));
3316 }
3317
3318 #[test]
3319 fn a_newline_escape_is_noted_and_a_quoted_one_is_data() {
3320 let notes = spelling_notes(r#""a\nb""#).unwrap();
3321 assert_eq!(notes.len(), 1);
3322 assert_eq!(notes[0].spelling, Spelling::NewlineEscape);
3323 assert!(spelling_notes(r#""a\tb""#).unwrap().is_empty());
3324 let quoted = q("def f()\n quote\n \"x\\ny\"\n end\nend\n");
3325 assert!(!quoted.contains("join_lines"), "{quoted}");
3326 assert!(
3327 spelling_notes("def f()\n quote\n \"x\\ny\"\n end\nend\n")
3328 .unwrap()
3329 .is_empty()
3330 );
3331 }
3332
3333 #[test]
3334 fn list_literal() {
3335 assert_eq!(q("[1, 2, 3]"), "(list 1 2 3)");
3336 assert_eq!(q("[]"), "(list)");
3337 }
3338
3339 #[test]
3342 fn if_else_end() {
3343 assert_eq!(q("if a\n 1\nelse\n 2\nend"), "(if a 1 2)");
3344 }
3345
3346 #[test]
3347 fn if_without_else() {
3348 assert_eq!(q("if a\n 1\nend"), "(if a 1)");
3349 }
3350
3351 #[test]
3354 fn unless_is_a_negated_if() {
3355 assert_eq!(q("unless a\n 1\nend"), "(if (not a) 1)");
3356 }
3357
3358 #[test]
3359 fn multi_statement_body_becomes_begin() {
3360 assert_eq!(q("if a\n 1\n 2\nend"), "(if a (begin 1 2))");
3361 }
3362
3363 #[test]
3364 fn def_lowers_to_define() {
3365 assert_eq!(
3366 q("def add(a, b)\n a + b\nend"),
3367 "(define (add a b) (+ a b))"
3368 );
3369 }
3370
3371 #[test]
3372 fn def_with_no_params() {
3373 assert_eq!(q("def zero()\n 0\nend"), "(define (zero) 0)");
3374 }
3375
3376 fn refused(src: &str) -> String {
3377 match parse_expr(src) {
3378 Ok(s) => panic!("{src:?} should be refused, parsed to {s}"),
3379 Err(e) => e.to_string(),
3380 }
3381 }
3382
3383 #[test]
3384 fn a_question_mark_parameter_is_optional() {
3385 assert_eq!(q("def f(a, b?)\n b\nend"), "(define (f a &optional b) b)");
3386 assert_eq!(q("def f(a?, b?)\n a\nend"), "(define (f &optional a b) a)");
3387 assert_eq!(q("def f(a?)\n a\nend"), "(define (f &optional a) a)");
3388 }
3389
3390 #[test]
3391 fn an_annotated_optional_parameter_keeps_its_type_after_the_marker() {
3392 assert_eq!(
3393 q("def f(a: Int, b?: Str) -> Int\n a\nend"),
3394 "(define-typed (f (a Int) &optional (b Str)) Int a)"
3395 );
3396 assert_eq!(
3397 q("def f(a, b?: Str)\n a\nend"),
3398 "(define-typed (f (a dyn) &optional (b Str)) dyn a)"
3399 );
3400 }
3401
3402 #[test]
3403 fn a_def_without_question_marks_carries_no_marker() {
3404 assert_eq!(q("def f(a, b)\n a\nend"), "(define (f a b) a)");
3405 assert_eq!(q("def f(a, *r)\n a\nend"), "(define (f a &rest r) a)");
3406 }
3407
3408 #[test]
3409 fn a_required_parameter_cannot_follow_an_optional_one() {
3410 assert!(refused("def f(a?, b)\n a\nend").contains("`b` cannot follow an optional"));
3411 assert!(refused("def f(a, b?, c: Int)\n a\nend").contains("`c` cannot follow"));
3412 }
3413
3414 #[test]
3415 fn a_rest_parameter_cannot_follow_an_optional_one() {
3416 assert!(refused("def f(a, b?, *r)\n a\nend").contains("rest parameter cannot follow"));
3417 }
3418
3419 #[test]
3422 fn literals_lower_to_atoms() {
3423 assert_eq!(q("42"), "42");
3424 assert_eq!(q("true"), "#t");
3425 assert_eq!(q(":ok"), ":ok");
3426 assert_eq!(q(r#""hi""#), r#""hi""#);
3427 }
3428
3429 #[test]
3430 fn unary_minus_and_not() {
3431 assert_eq!(q("-x"), "(- 0 x)");
3432 assert_eq!(q("!x"), "(not x)");
3433 }
3434
3435 #[test]
3438 fn a_program_is_a_sequence_of_forms() {
3439 let forms = parse_program("def f()\n 1\nend\nf()").expect("parse");
3440 assert_eq!(forms.len(), 2);
3441 assert_eq!(forms[1].to_string(), "(f)");
3442 }
3443
3444 #[test]
3445 fn unterminated_block_is_an_error_naming_what_was_expected() {
3446 let e = parse_program("if a\n 1").expect_err("must fail");
3447 assert!(e.message.contains("unterminated"), "{}", e.message);
3448 }
3449
3450 #[test]
3451 fn a_parse_error_carries_a_span_into_the_source() {
3452 let src = "1 + )";
3453 let e = parse_program(src).expect_err("must fail");
3454 assert!(e.span.start < src.len(), "span {:?} outside source", e.span);
3455 }
3456
3457 #[test]
3464 fn postfix_indexing_is_refused_naming_nth() {
3465 let e = parse_program("y = xs[0]").expect_err("must not split into two statements");
3466 assert!(e.message.contains("`nth(0, xs)`"), "{}", e.message);
3467 assert_eq!((e.line, e.col), (1, 7), "{e}");
3468 }
3469
3470 #[test]
3472 fn a_call_without_parentheses_is_refused_naming_the_call() {
3473 let e = parse_program("a = f x").expect_err("must not split into two statements");
3474 assert!(e.message.contains("`f(x)`"), "{}", e.message);
3475 assert_eq!((e.line, e.col), (1, 7), "{e}");
3476 }
3477
3478 #[test]
3480 fn two_statements_on_one_line_are_refused_in_a_body() {
3481 for src in [
3482 "def f(xs)\n xs[0]\nend",
3483 "if a\n f x\nend",
3484 "fn(x) x 1 end",
3485 "test \"t\"\n assert a b\nend",
3486 "1 2",
3487 ] {
3488 assert!(parse_program(src).is_err(), "{src:?} must not parse");
3489 }
3490 }
3491
3492 #[test]
3494 fn an_arrow_lambda_lowers_to_the_same_tree_as_fn() {
3495 for (arrow, block) in [
3496 ("(x) -> x * 2", "fn(x) x * 2 end"),
3497 ("(a, b) -> a + b", "fn(a, b) a + b end"),
3498 ("() -> 1", "fn() 1 end"),
3499 ("map(xs, (x) -> x + 1)", "map(xs, fn(x) x + 1 end)"),
3500 ] {
3501 let a = parse_program(arrow).expect(arrow);
3502 let b = parse_program(block).expect(block);
3503 assert_eq!(format!("{a:?}"), format!("{b:?}"), "{arrow}");
3504 }
3505 assert_eq!(
3506 parse_program("(x)").map(|p| p.len()).ok(),
3507 Some(1),
3508 "a parenthesised name is still a group"
3509 );
3510 }
3511
3512 #[test]
3516 fn a_statement_may_end_at_a_newline_the_end_or_a_closing_keyword() {
3517 for src in [
3518 "fn(x) x + 1 end",
3519 "map(fn(x) x end, xs)",
3520 "if a\n 1\nelse\n 2\nend",
3521 "if a 1 else 2 end",
3522 "case x\nwhen 1 :a\nwhen 2 :b\nelse :c\nend",
3523 "x = 1\ny = 2",
3524 "x = 1",
3525 ] {
3526 assert!(
3527 parse_program(src).is_ok(),
3528 "{src:?}: {:?}",
3529 parse_program(src)
3530 );
3531 }
3532 }
3533
3534 #[test]
3536 fn then_is_refused_by_name() {
3537 let e = parse_program("x = if c then 1 else 2 end").expect_err("then is not a keyword");
3538 assert!(e.message.contains("`then`"), "{}", e.message);
3539 }
3540
3541 #[test]
3543 fn a_brace_block_is_refused_naming_fn() {
3544 let e = parse_program("xs.map { |x| x }").expect_err("no brace blocks");
3545 assert!(e.message.contains("fn(x)"), "{}", e.message);
3546 }
3547
3548 #[test]
3555 fn a_qualified_name_is_one_symbol() {
3556 assert_eq!(q("retsu::first(xs)"), "(retsu/first xs)");
3557 assert_eq!(q("retsu::first"), "retsu/first");
3558 assert_eq!(q("xs |> retsu::first"), "(retsu/first xs)");
3559 assert_eq!(q("blue::length(xs)"), "(blue/length xs)");
3560 }
3561
3562 #[test]
3564 fn whitespace_around_the_qualifier_is_accepted() {
3565 assert_eq!(q("retsu :: first(xs)"), "(retsu/first xs)");
3566 }
3567
3568 #[test]
3571 fn a_reserved_word_after_the_qualifier_is_a_name() {
3572 assert_eq!(q("kueri::if(a, b)"), "(kueri/if a b)");
3573 assert_eq!(q("heni::test(x)"), "(heni/test x)");
3574 assert_eq!(q("sabi::fn"), "sabi/fn");
3575 }
3576
3577 #[test]
3578 fn a_qualified_name_has_exactly_one_qualifier() {
3579 for (src, says) in [
3580 ("::first(xs)", "`package::name`"),
3581 ("a::b::c", "one qualifier"),
3582 ("if::x", "reserved word"),
3583 ("retsu::1", "expected a name after `retsu::`"),
3584 ] {
3585 let e = parse_program(src).expect_err(src);
3586 assert!(e.message.contains(says), "{src}: {}", e.message);
3587 }
3588 }
3589
3590 #[test]
3593 fn qualified_reads_only_the_package_name_shape() {
3594 assert_eq!(qualified("retsu/first"), Some(("retsu", "first")));
3595 assert_eq!(qualified("kueri/if"), Some(("kueri", "if")));
3596 assert_eq!(qualified("nisshi/pair?"), Some(("nisshi", "pair?")));
3597 for not in ["/", "a/", "/a", "a/b/c", "if/x", "hash-map", "a"] {
3598 assert_eq!(qualified(not), None, "{not}");
3599 }
3600 assert!(is_callable_name("kueri/if"));
3601 }
3602
3603 #[test]
3608 fn a_symbol_may_end_in_a_question_or_bang() {
3609 assert_eq!(q("[:pair?, :set!]"), "(list :pair? :set!)");
3610 assert_eq!(q("use(\"n\", [:empty?])"), "(use \"n\" (list :empty?))");
3611 assert_eq!(q(":a != :b"), "(not= :a :b)");
3612 }
3613
3614 #[test]
3618 fn a_parse_error_displays_line_col_and_the_source_text() {
3619 let e = parse_program("x = 1\ny = [1, 2 y]").expect_err("must fail");
3620 assert_eq!(e.to_string(), "2:11: expected `,` or `]`, found `y`");
3621 assert_eq!(e.span, Span::new(16, 17), "the span stays, as data");
3622 }
3623
3624 #[test]
3626 fn no_error_message_names_a_rust_variant() {
3627 for src in [
3628 "f(1 2)",
3629 "{a: 1 b}",
3630 "def 1\nend",
3631 "x.1",
3632 "if a\n",
3633 "fn(1) 2 end",
3634 "[1 :b]",
3635 "1 +",
3636 "(1",
3637 ] {
3638 let e = parse_program(src).expect_err(src);
3639 let shown = e.to_string();
3640 for tell in [
3641 "Ident(", "Int(", "Op(", "Label(", "Sym(", "Str(", "LParen", "RBracket", "Eof",
3642 "Newline", "..",
3643 ] {
3644 assert!(!shown.contains(tell), "{src:?} -> {shown}");
3645 }
3646 assert!(e.line >= 1 && e.col >= 1, "{src:?} not located: {shown}");
3647 }
3648 }
3649
3650 #[test]
3652 fn a_lex_error_is_located_too() {
3653 let e = parse_program("x = 1\ny = 2; z = 3").expect_err("no semicolons");
3654 assert_eq!((e.line, e.col), (2, 6), "{e}");
3655 assert!(e.message.contains("new line"), "{e}");
3656 }
3657
3658 #[test]
3661 fn the_parser_rejects_garbage() {
3662 assert!(parse_program("def").is_err());
3663 assert!(parse_program("(1").is_err());
3664 assert!(parse_program("end").is_err());
3665 }
3666}