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]
207pub fn is_index_key(key: &Spanned) -> bool {
208 key.as_keyword().is_some() || key.as_string().is_some()
209}
210
211#[must_use]
216pub fn constant_text(s: &Sexp) -> Option<String> {
217 match s {
218 Sexp::Atom(Atom::Str(x)) => Some(x.to_string()),
219 Sexp::Atom(Atom::Symbol(h)) if h.as_str() == "line_break" => Some("\n".to_string()),
220 Sexp::List(items) => match items.as_slice() {
221 [Sexp::Atom(Atom::Symbol(h)), Sexp::List(list)]
222 if h.as_str() == "join_lines" || h.as_str() == "unlines" =>
223 {
224 let (Sexp::Atom(Atom::Symbol(l)), elems) = list.split_first()? else {
225 return None;
226 };
227 if l.as_str() != "list" {
228 return None;
229 }
230 let texts = elems
231 .iter()
232 .map(constant_text)
233 .collect::<Option<Vec<_>>>()?;
234 Some(if h.as_str() == "join_lines" {
235 texts.join("\n")
236 } else {
237 texts.iter().map(|t| format!("{t}\n")).collect()
238 })
239 }
240 _ => None,
241 },
242 _ => None,
243 }
244}
245
246pub fn spelling_notes(src: &str) -> Result<Vec<Note>, ParseError> {
247 let mut p = Parser::over(
248 src,
249 lex(src).map_err(|e| ParseError::from(e).located(src))?,
250 MAX_EXPR_DEPTH,
251 );
252 p.program().map_err(|e| e.located(src))?;
253 Ok(p.notes)
254}
255
256pub fn parse_program_in(src: &str, pack: &crate::yakugo::Yakugo) -> Result<Vec<Sexp>, ParseError> {
269 Ok(parse_program_tree_in(src, pack)?
270 .iter()
271 .map(Spanned::to_sexp)
272 .collect())
273}
274
275pub fn parse_program_tree_in(
291 src: &str,
292 pack: &crate::yakugo::Yakugo,
293) -> Result<Vec<Spanned>, ParseError> {
294 let toks =
295 crate::yakugo::canonical_tokens(src, pack).map_err(|e| ParseError::from(e).located(src))?;
296 PARSES.with(|n| n.set(n.get() + 1));
297 let mut p = Parser::over(src, toks, MAX_EXPR_DEPTH);
298 p.program()
299 .map(crate::doc::strip)
300 .map_err(|e| e.located(src))
301}
302
303pub fn parse_program_with_depth(src: &str, max_depth: usize) -> Result<Vec<Sexp>, ParseError> {
312 Ok(parse_program_tree_with_depth(src, max_depth)?
313 .iter()
314 .map(Spanned::to_sexp)
315 .collect())
316}
317
318pub fn parse_program_spanned(src: &str) -> Result<Vec<(Sexp, Span)>, ParseError> {
331 parse_program_spanned_with_depth(src, MAX_EXPR_DEPTH)
332}
333
334pub fn parse_program_spanned_with_depth(
336 src: &str,
337 max_depth: usize,
338) -> Result<Vec<(Sexp, Span)>, ParseError> {
339 Ok(parse_program_tree_with_depth(src, max_depth)?
340 .iter()
341 .map(|f| (f.to_sexp(), f.span))
342 .collect())
343}
344
345pub fn comments(src: &str) -> Vec<Comment> {
353 lex(src)
354 .map(|toks| {
355 toks.iter()
356 .filter_map(|t| match &t.kind {
357 TokenKind::Comment(text) => Some(Comment {
358 text: text.clone(),
359 span: t.span,
360 own_line: line_before_is_blank(src, t.span.start),
361 }),
362 _ => None,
363 })
364 .collect()
365 })
366 .unwrap_or_default()
367}
368
369#[derive(Clone, Debug, PartialEq, Eq)]
371pub struct Comment {
372 pub text: String,
374 pub span: Span,
375 pub own_line: bool,
377}
378
379fn line_before_is_blank(src: &str, start: usize) -> bool {
380 src[..start.min(src.len())]
381 .rsplit('\n')
382 .next()
383 .is_some_and(|prefix| {
384 prefix
385 .trim_matches(|c: char| c.is_whitespace() || c == '\u{feff}')
386 .is_empty()
387 })
388}
389
390pub fn parse_expr(src: &str) -> Result<Sexp, ParseError> {
392 let forms = parse_program(src)?;
393 match forms.len() {
394 1 => Ok(forms.into_iter().next().expect("checked len")),
395 n => Err(ParseError::new(
396 format!("expected exactly one expression, found {n}"),
397 Span::new(0, src.len()),
398 )
399 .located(src)),
400 }
401}
402
403#[derive(Clone, Copy, Debug)]
413pub struct Infix {
414 pub op: &'static str,
416 pub power: (u8, u8),
418 pub callee: &'static str,
420 pub doc: &'static str,
424}
425
426pub const INFIX: &[Infix] = &[
430 Infix {
431 op: "or",
432 power: (1, 2),
433 callee: "or",
434 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).",
435 },
436 Infix {
437 op: "and",
438 power: (3, 4),
439 callee: "and",
440 doc: "Logical and: the right value if the left is truthy, otherwise the left; short-circuits. `&&` is read as `and` and written `and` (B0025).",
441 },
442 Infix {
453 op: "==",
454 power: (5, 6),
455 callee: "equal?",
456 doc: "Structural equality over every value (okite D0001): 4 == 4.0, lists and maps by content, nil only equals nil.",
457 },
458 Infix {
459 op: "!=",
460 power: (5, 6),
461 callee: "not=",
462 doc: "Exactly not(a == b) (okite D0002).",
463 },
464 Infix {
465 op: "<",
466 power: (5, 6),
467 callee: "<",
468 doc: "Numeric less-than. A string operand is a type error: order text with compare(a, b).",
469 },
470 Infix {
471 op: "<=",
472 power: (5, 6),
473 callee: "<=",
474 doc: "Numeric less-than-or-equal. Numbers only, like <.",
475 },
476 Infix {
477 op: ">",
478 power: (5, 6),
479 callee: ">",
480 doc: "Numeric greater-than. Numbers only, like <.",
481 },
482 Infix {
483 op: ">=",
484 power: (5, 6),
485 callee: ">=",
486 doc: "Numeric greater-than-or-equal. Numbers only, like <.",
487 },
488 Infix {
489 op: "+",
490 power: (7, 8),
491 callee: "+",
492 doc: "Numeric addition; an Int and a Float give a Float. Not string concatenation: interpolate instead.",
493 },
494 Infix {
495 op: "-",
496 power: (7, 8),
497 callee: "-",
498 doc: "Numeric subtraction. Written before an operand with nothing on its left, it is unary minus: -n.",
499 },
500 Infix {
501 op: "*",
502 power: (9, 10),
503 callee: "*",
504 doc: "Numeric multiplication.",
505 },
506 Infix {
507 op: "/",
508 power: (9, 10),
509 callee: "/",
510 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).",
511 },
512 Infix {
513 op: "%",
514 power: (9, 10),
515 callee: "mod",
516 doc: "Euclidean remainder: never negative, so (0 - 7) % 3 is 2, not -1. A zero divisor is a runtime error.",
517 },
518];
519
520pub const MASTER_VERBS: &[&str] = &[
538 "read", "need", "has?", "put", "update", "delete", "create", "diff", "info", "exists?",
539];
540
541pub const LOWERED_ASSERT: &str = "blue-assert";
548
549pub const LOWERED_MAP: &str = "hash-map";
557
558pub const LOWERED_CONCAT: &str = "concat";
563
564pub const OPTIONAL_MARKER: &str = "&optional";
565
566pub const LOWERED_OPTIONAL_ARITY: &str = "blue-optional-arity";
567
568pub const LOWERED_OPTIONAL_ARG: &str = "blue-optional-arg";
569
570pub const LOWERED_FIELD: &str = "blue-field";
571
572pub const LOWERED_MERGE: &str = "blue-merge";
573
574pub const LOWERED_SPLICE: &str = "blue-splice";
575
576pub const LOWERED_KWARGS: &str = "blue-kwargs";
577
578pub const LOWERED_ERROR_MATCH: &str = "blue-rescues?";
579
580pub const LOWERED_ENSURE: &str = "blue-ensure";
581
582pub const LOWERED_RERAISE: &str = "blue-reraise";
583
584pub const LOWERED_ATTEMPTED: &str = "blue-attempted";
585
586pub const LOWERED_FAILED: &str = "blue-failed";
587
588pub const LOWERED_HEADS: &[&str] = &[
589 "list",
590 "concat",
591 "not",
592 "apply",
593 "equal?",
594 "not=",
595 "and",
596 "or",
597 "mod",
598 "join_lines",
599 "unlines",
600 "line_break",
601];
602
603pub const RESCUED: &str = "rescued-error";
604
605pub const SURFACE_KEYWORDS: &[&str] = &[
606 "if",
607 "begin",
608 "unless",
609 "def",
610 "defmacro",
611 "quote",
612 "unquote",
613 "unquote_splice",
614 "test",
615 "assert",
616 "fn",
617 "case",
618];
619
620pub const BLOCK_KEYWORDS: &[&str] = &[
632 "do", "end", "else", "elsif", "rescue", "ensure", "true", "false", "nil",
633];
634
635pub const OPERATOR_KEYWORDS: &[&str] = &["and", "or", "not"];
636
637pub fn is_reserved_word(name: &str) -> bool {
644 SURFACE_KEYWORDS.contains(&name)
645 || BLOCK_KEYWORDS.contains(&name)
646 || OPERATOR_KEYWORDS.contains(&name)
647}
648
649pub fn is_callable_name(name: &str) -> bool {
658 if qualified(name).is_some() {
660 return true;
661 }
662 !is_reserved_word(name)
663 && matches!(
664 lex(name).as_deref(),
665 Ok([t, e]) if matches!(&t.kind, TokenKind::Ident(n) if n == name)
666 && e.kind == TokenKind::Eof
667 )
668}
669
670pub const QUALIFIER: char = '/';
675
676pub const BUILTIN_QUALIFIER: &str = "blue";
679
680#[must_use]
682pub fn qualify(pkg: &str, name: &str) -> String {
683 format!("{pkg}{QUALIFIER}{name}")
684}
685
686#[must_use]
692pub fn qualified(sym: &str) -> Option<(&str, &str)> {
693 let (pkg, name) = sym.split_once(QUALIFIER)?;
694 let ident = |s: &str| {
695 matches!(
696 lex(s).as_deref(),
697 Ok([t, e]) if matches!(&t.kind, TokenKind::Ident(n) if n == s) && e.kind == TokenKind::Eof
698 )
699 };
700 let named = |s: &str| ident(s) || matches!(s, "true" | "false" | "nil");
701 (ident(pkg) && !is_reserved_word(pkg) && named(name)).then_some((pkg, name))
702}
703
704#[must_use]
712pub fn keyword_doc(word: &str) -> &'static str {
713 match word {
714 "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).",
715 "unless" => "`unless c … end` is read as `if not c` and written that way (B0026).",
716 "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`.",
717 "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 `|>`.",
718 "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.",
719 "and" => "`a and b`: logical and; `&&` is read as `and` and written `and` (B0025).",
720 "or" => "`a or b`: logical or; `||` is read as `or` and written `or` (B0025).",
721 "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).",
722 "def" => "`def name(a, b) … end`: a named function; its value is the last expression. Parameters may be typed: `def f(n: Int) -> Int`.",
723 "defmacro" => "`defmacro name(x) quote … unquote(x) … end end`: a macro, a rewrite of the unevaluated form.",
724 "quote" => "`quote … end`: the form itself as data, not its value. Used inside `defmacro`.",
725 "unquote" => "`unquote(x)`: inside `quote`, splice in the value of `x`.",
726 "unquote_splice" => "`unquote_splice(xs)`: inside `quote`, splice the items of the form `xs` in place, not the form itself.",
727 "test" => "`test \"what must hold\" … end`: a test block, run by `blue test` and ignored by `blue run`.",
728 "assert" => "`assert expr`: inside a test, fail it unless `expr` is truthy; the failure quotes the expression.",
729 "fn" => "`fn(x) x * 2 end`: an anonymous function, a value. Blue has no brace or do blocks.",
730 "case" => "`case v when 1 … when 2 … else … end`: compares with `==`; it does not destructure.",
731 "do" => "Reserved and refused: blue has no do-blocks. Pass `fn(x) … end` instead.",
732 "end" => "Closes `def`, `defmacro`, `if`, `case`, `fn`, `test` and `quote`.",
733 "else" => "The branch of `if` or `case` taken when nothing before it matched.",
734 "elsif" => "A further condition in an `if` chain; an `else` holding only an `if` is written `elsif` (B0027).",
735 "true" => "The boolean true.",
736 "false" => "The boolean false. Only `false` and `nil` are falsy; 0, \"\" and [] are truthy.",
737 "nil" => "The absent value. It is not `[]` (okite D0004), and only `nil == nil`.",
738 _ => "",
739 }
740}
741
742fn infix(op: &str) -> Option<&'static Infix> {
743 INFIX.iter().find(|i| i.op == op)
744}
745
746pub const SYMBOL_SPELLINGS: &[(&str, &str)] = &[("&&", "and"), ("||", "or"), ("!", "not")];
747
748fn word_for(op: &str) -> &str {
749 SYMBOL_SPELLINGS
750 .iter()
751 .find(|(sym, _)| *sym == op)
752 .map_or(op, |(_, word)| word)
753}
754
755fn infix_of(kind: &TokenKind) -> Option<&'static Infix> {
756 match kind {
757 TokenKind::Op(o) => infix(word_for(o)),
758 TokenKind::Ident(w) if w == "and" || w == "or" => infix(w),
759 _ => None,
760 }
761}
762
763const PIPE_POWER: (u8, u8) = (0, 1);
764const RESCUE_POWER: (u8, u8) = (0, 1);
765
766struct Parser {
767 toks: Vec<Token>,
768 pos: usize,
769 depth: usize,
777 max_depth: usize,
784 src: String,
785 comment_spans: Vec<Span>,
786 notes: Vec<Note>,
787 quoting: usize,
790}
791
792pub const MAX_EXPR_DEPTH: usize = 256;
799
800type Fail = Box<ParseError>;
812
813fn fail(message: impl Into<String>, span: Span) -> Fail {
814 Box::new(ParseError::new(message, span))
815}
816
817impl Parser {
818 fn over(src: &str, all: Vec<Token>, max_depth: usize) -> Self {
819 let comment_spans = all
820 .iter()
821 .filter(|t| matches!(t.kind, TokenKind::Comment(_)))
822 .map(|t| t.span)
823 .collect();
824 let toks = all
825 .into_iter()
826 .filter(|t| !matches!(t.kind, TokenKind::Comment(_)))
827 .collect();
828 Parser {
829 toks,
830 pos: 0,
831 depth: 0,
832 max_depth,
833 src: src.to_string(),
834 comment_spans,
835 notes: Vec::new(),
836 quoting: 0,
837 }
838 }
839
840 fn note(&mut self, spelling: Spelling, span: Span, written: &str, canonical: &str) {
841 self.notes.push(Note {
842 spelling,
843 span,
844 written: written.to_string(),
845 canonical: canonical.to_string(),
846 });
847 }
848
849 fn text(&self, span: Span) -> &str {
850 self.src.get(span.start..span.end).unwrap_or("")
851 }
852
853 fn comment_within(&self, lo: usize, hi: usize) -> bool {
854 self.comment_spans
855 .iter()
856 .any(|c| c.start >= lo && c.start < hi)
857 }
858
859 fn touches_previous(&self) -> bool {
860 let here = self.peek_span();
861 self.toks
862 .get(self.pos.wrapping_sub(1))
863 .is_some_and(|prev| prev.span.end == here.start)
864 }
865
866 fn peek(&self) -> &TokenKind {
867 &self.toks[self.pos.min(self.toks.len() - 1)].kind
868 }
869
870 fn peek_span(&self) -> Span {
871 self.toks[self.pos.min(self.toks.len() - 1)].span
872 }
873
874 fn bump(&mut self) -> TokenKind {
875 let k = self.toks[self.pos.min(self.toks.len() - 1)].kind.clone();
876 if self.pos < self.toks.len() {
877 self.pos += 1;
878 }
879 k
880 }
881
882 fn at(&self, k: &TokenKind) -> bool {
883 self.peek() == k
884 }
885
886 fn eat(&mut self, k: &TokenKind) -> bool {
887 if self.at(k) {
888 self.bump();
889 true
890 } else {
891 false
892 }
893 }
894
895 fn expect(&mut self, k: &TokenKind, what: &str) -> Result<(), Fail> {
896 if self.eat(k) {
897 Ok(())
898 } else {
899 Err(self.error(format!("expected {what}, found {}", self.peek())))
900 }
901 }
902
903 fn error(&self, message: impl Into<String>) -> Fail {
904 fail(message, self.peek_span())
905 }
906
907 fn skip_newlines(&mut self) {
909 while matches!(self.peek(), TokenKind::Newline) {
910 self.bump();
911 }
912 }
913
914 fn at_ident(&self, name: &str) -> bool {
915 matches!(self.peek(), TokenKind::Ident(n) if n == name)
916 }
917
918 fn mark(&self) -> usize {
921 self.peek_span().start
922 }
923
924 fn span_since(&self, start: usize) -> Span {
930 let end = self
931 .toks
932 .get(self.pos.saturating_sub(1))
933 .map_or(start, |t| t.span.end);
934 Span::new(start, end.max(start))
935 }
936
937 fn program(&mut self) -> Result<Vec<Spanned>, Fail> {
938 let mut out = Vec::new();
939 loop {
940 self.skip_newlines();
941 if matches!(self.peek(), TokenKind::Eof) {
942 break;
943 }
944 if let TokenKind::Doc { module, text } = self.peek().clone() {
945 out.push(self.documentation(module, text, out.is_empty())?);
946 continue;
947 }
948 out.push(self.statement()?);
949 self.end_of_statement(&[])?;
950 }
951 Ok(out)
952 }
953
954 fn documentation(&mut self, module: bool, text: String, first: bool) -> Result<Spanned, Fail> {
957 let span = self.peek_span();
958 self.bump();
959 let word = if module { "@moduledoc" } else { "@doc" };
960 let head = sym_at(
961 Span::new(span.start, span.start + word.len()),
962 if module {
963 crate::doc::MODULEDOC_HEAD
964 } else {
965 crate::doc::DOC_HEAD
966 },
967 );
968 let text = crate::doc::text_node(span, text);
969 self.end_of_statement(&[])?;
970 if module {
971 if !first {
972 return Err(fail(
973 "`@moduledoc` is the first form of a file: it documents the whole file",
974 span,
975 ));
976 }
977 return Ok(list_at(span, vec![head, text]));
978 }
979 self.skip_newlines();
980 if matches!(self.peek(), TokenKind::Doc { .. } | TokenKind::Eof) {
981 return Err(fail(
982 "`@doc` documents the definition below it, and none follows",
983 span,
984 ));
985 }
986 let def = self.statement()?;
987 self.end_of_statement(&[])?;
988 if !crate::doc::is_definition(&def) {
989 return Err(fail(
990 "`@doc` documents the definition below it: a `def`, a `defmacro` or a \
991 top-level `name = value`",
992 span,
993 ));
994 }
995 Ok(list_at(
996 Span::new(span.start, def.span.end),
997 vec![head, text, def],
998 ))
999 }
1000
1001 fn end_of_statement(&self, terminators: &[&str]) -> Result<(), Fail> {
1019 let next = self.peek();
1020 if matches!(next, TokenKind::Newline | TokenKind::Eof)
1021 || terminators.iter().any(|t| self.at_ident(t))
1022 {
1023 return Ok(());
1024 }
1025 let prev = self.toks.get(self.pos.wrapping_sub(1)).map(|t| &t.kind);
1026 let prev_name = match prev {
1027 Some(TokenKind::Ident(n)) => Some(n.as_str()),
1028 _ => None,
1029 };
1030 let callee = prev_name.unwrap_or("f");
1031 let why = match (prev, next) {
1032 (Some(TokenKind::Ident(t)), _) if t == "then" => {
1033 "blue's `if` has no `then`: put the branch on the next line".to_string()
1034 }
1035 (_, TokenKind::Ident(d)) if d == "do" => "blue has no `do` blocks: pass a function \
1036 as an argument, `f(xs, fn(x) … end)`"
1037 .to_string(),
1038 (_, TokenKind::LBracket) => {
1039 let index = match (self.toks.get(self.pos + 1), self.toks.get(self.pos + 2)) {
1040 (Some(i), Some(close)) if close.kind == TokenKind::RBracket => match &i.kind {
1041 TokenKind::Int(n) => n.to_string(),
1042 TokenKind::Ident(n) => n.clone(),
1043 _ => "i".to_string(),
1044 },
1045 _ => "i".to_string(),
1046 };
1047 let list = prev_name.unwrap_or("xs");
1048 format!(
1049 "an index touches what it indexes: write `{list}[{index}]`, with no space \
1050 before `[`"
1051 )
1052 }
1053 (_, TokenKind::LBrace) => "blue has no brace blocks: pass a function as an \
1054 argument, `map(xs, fn(x) … end)`"
1055 .to_string(),
1056 (
1057 Some(TokenKind::Ident(_)),
1058 TokenKind::Ident(_)
1059 | TokenKind::Int(_)
1060 | TokenKind::Float(_)
1061 | TokenKind::Str(_)
1062 | TokenKind::InterpolatedStr { .. }
1063 | TokenKind::Sym(_)
1064 | TokenKind::True
1065 | TokenKind::False
1066 | TokenKind::Nil,
1067 ) => {
1068 let arg = match next {
1069 TokenKind::Ident(a) => a.clone(),
1070 _ => "x".to_string(),
1071 };
1072 format!("a call needs parentheses: write `{callee}({arg})`")
1073 }
1074 _ => "put each statement on its own line".to_string(),
1075 };
1076 Err(self.error(format!("expected the end of the line, found {next}: {why}")))
1077 }
1078
1079 fn statement(&mut self) -> Result<Spanned, Fail> {
1092 let max = self.max_depth;
1106 if self.depth >= max {
1107 return Err(self.error(format!(
1108 "statement nests deeper than {max}; refusing to \
1109 recurse further (this is a limit, not a syntax error)"
1110 )));
1111 }
1112 self.depth += 1;
1113 let r = self.statement_inner();
1114 self.depth -= 1;
1115 r
1116 }
1117
1118 fn statement_inner(&mut self) -> Result<Spanned, Fail> {
1119 if let TokenKind::Ident(name) = self.peek().clone() {
1120 if self.peek_at(1) == "=" && !is_reserved_word(&name) {
1121 let name_span = self.peek_span();
1122 self.bump(); self.bump(); self.skip_newlines();
1125 let value = self.expr(0)?;
1126 return Ok(list_at(
1130 self.span_since(name_span.start),
1131 vec![sym_at(name_span, "define"), sym_at(name_span, &name), value],
1132 ));
1133 }
1134 }
1135 let e = self.expr(0)?;
1136 if let SpannedForm::List(items) = &e.form {
1137 if items.len() == 3
1138 && items[0].as_symbol() == Some("define")
1139 && items[1].as_symbol().is_some()
1140 && self.text(items[0].span) == "define"
1141 {
1142 self.note(Spelling::OperatorSpelling, e.span, "define(x, v)", "x = v");
1143 }
1144 }
1145 Ok(e)
1146 }
1147
1148 fn peek_at(&self, n: usize) -> String {
1151 match self.toks.get(self.pos + n).map(|t| &t.kind) {
1152 Some(TokenKind::Op(o)) => o.clone(),
1153 _ => String::new(),
1154 }
1155 }
1156
1157 fn expr(&mut self, min_bp: u8) -> Result<Spanned, Fail> {
1159 let max = self.max_depth;
1169 if self.depth >= max {
1170 return Err(self.error(format!(
1171 "expression nests deeper than {max}; refusing to \
1172 recurse further (this is a limit, not a syntax error)"
1173 )));
1174 }
1175 self.depth += 1;
1176 let r = self.expr_inner(min_bp);
1177 self.depth -= 1;
1178 r
1179 }
1180
1181 #[inline(never)]
1182 fn rescue_node(&self, start: usize, op_span: Span, lhs: Spanned, fallback: Spanned) -> Spanned {
1183 list_at(
1184 self.span_since(start),
1185 vec![
1186 sym_at(op_span, "try"),
1187 lhs,
1188 list_at(
1189 op_span.merge(fallback.span),
1190 vec![
1191 sym_at(op_span, "catch"),
1192 list_at(op_span, vec![sym_at(op_span, "_")]),
1193 fallback,
1194 ],
1195 ),
1196 ],
1197 )
1198 }
1199
1200 #[inline(never)]
1201 fn infix_spelling(&mut self, callee: Option<&str>, op_span: Span) {
1202 let written = self.text(op_span).to_string();
1203 match (callee, self.peek().clone()) {
1204 (None, _) => self.note(Spelling::CallForm, op_span, "|>", "a call"),
1205 (Some(c), TokenKind::Op(o)) if word_for(&o) != o => {
1206 self.note(Spelling::LogicalSymbol, op_span, &written, c);
1207 }
1208 (Some(_), TokenKind::Op(o)) if written != o => {
1209 self.note(Spelling::OperatorSpelling, op_span, &written, &o);
1210 }
1211 _ => {}
1212 }
1213 }
1214
1215 #[inline(never)]
1216 fn nil_test(
1217 &mut self,
1218 start: usize,
1219 op_span: Span,
1220 lhs: &Spanned,
1221 rhs: &Spanned,
1222 ) -> Option<Spanned> {
1223 let (subject, written) = match (&lhs.form, &rhs.form) {
1224 (_, SpannedForm::Nil) if !matches!(lhs.form, SpannedForm::Nil) => (lhs, "x == nil"),
1225 (SpannedForm::Nil, _) if !matches!(rhs.form, SpannedForm::Nil) => (rhs, "nil == x"),
1226 _ => return None,
1227 };
1228 let span = self.span_since(start);
1229 self.note(Spelling::NilTest, span, written, "x.nil?");
1230 Some(list_at(
1231 span,
1232 vec![
1233 sym_at(op_span, LOWERED_FIELD),
1234 subject.clone(),
1235 atom_at(op_span, Atom::Keyword("nil?".to_string())),
1236 ],
1237 ))
1238 }
1239
1240 #[inline(never)]
1241 fn infix_node(
1242 &mut self,
1243 start: usize,
1244 callee: Option<&str>,
1245 op_span: Span,
1246 lhs: Spanned,
1247 rhs: Spanned,
1248 ) -> Spanned {
1249 if callee == Some("-") && matches!(lhs.form, SpannedForm::Atom(Atom::Int(0))) {
1250 self.note(
1251 Spelling::OperatorSpelling,
1252 lhs.span.merge(op_span),
1253 "0 - x",
1254 "-x",
1255 );
1256 }
1257
1258 if callee == Some("equal?") {
1259 if let Some(test) = self.nil_test(start, op_span, &lhs, &rhs) {
1260 return test;
1261 }
1262 }
1263
1264 if callee.is_none() {
1265 let rhs_span = rhs.span;
1270 let threaded = match rhs.form {
1271 SpannedForm::List(mut items) if !items.is_empty() => {
1272 items.insert(1, lhs);
1273 list_at(self.span_since(start), items)
1274 }
1275 callee => list_at(
1276 self.span_since(start),
1277 vec![Spanned::new(rhs_span, callee), lhs],
1278 ),
1279 };
1280 match &threaded.form {
1281 SpannedForm::List(items)
1282 if items.len() == 2 && items[0].as_symbol() == Some("not") =>
1283 {
1284 let (head, operand) = (items[0].span, items[1].clone());
1285 self.negation(head, threaded.span, operand)
1286 }
1287 SpannedForm::List(items)
1288 if items.len() == 4
1289 && items[0].as_symbol() == Some("if")
1290 && matches!(items[3].form, SpannedForm::Nil) =>
1291 {
1292 list_at(threaded.span, items[..3].to_vec())
1293 }
1294 SpannedForm::List(items)
1295 if items.len() == 3 && items[0].as_symbol() == Some("equal?") =>
1296 {
1297 let (op, a, b) = (items[0].span, items[1].clone(), items[2].clone());
1298 match self.nil_test(start, op, &a, &b) {
1299 Some(test) => test,
1300 None => threaded,
1301 }
1302 }
1303 _ => threaded,
1304 }
1305 } else {
1306 list_at(
1312 self.span_since(start),
1313 vec![sym_at(op_span, callee.unwrap_or_default()), lhs, rhs],
1314 )
1315 }
1316 }
1317
1318 fn expr_inner(&mut self, min_bp: u8) -> Result<Spanned, Fail> {
1319 let start = self.mark();
1320 let mut lhs = self.prefix()?;
1321
1322 loop {
1323 match self.peek() {
1325 TokenKind::Dot => {
1326 let dot = self.peek_span();
1327 self.bump();
1328 lhs = self.finish_send(start, lhs, dot)?;
1329 continue;
1330 }
1331 TokenKind::LParen | TokenKind::LBracket
1332 if self.at(&TokenKind::LParen) || self.touches_previous() =>
1333 {
1334 lhs = self.applied(start, lhs)?;
1335 continue;
1336 }
1337 _ => {}
1338 }
1339
1340 let op_span = self.peek_span();
1344 if self.at_ident("rescue") {
1345 if RESCUE_POWER.0 < min_bp {
1346 break;
1347 }
1348 self.bump();
1349 self.skip_newlines();
1350 let fallback = self.expr(RESCUE_POWER.1)?;
1351 lhs = self.rescue_node(start, op_span, lhs, fallback);
1352 continue;
1353 }
1354 let (callee, (lbp, rbp)) = match self.peek() {
1355 TokenKind::Pipe => (None, PIPE_POWER),
1356 other => match infix_of(other) {
1357 Some(i) => (Some(i.callee), i.power),
1358 None => break,
1359 },
1360 };
1361 if lbp < min_bp {
1362 break;
1363 }
1364 self.infix_spelling(callee, op_span);
1365 self.bump();
1366 self.skip_newlines();
1367 let rhs = self.expr(rbp)?;
1368 lhs = self.infix_node(start, callee, op_span, lhs, rhs);
1369 }
1370
1371 Ok(lhs)
1372 }
1373
1374 #[inline(never)]
1375 fn interpolation(span: Span, parts: &[String], exprs: &[String]) -> Result<Spanned, Fail> {
1376 let mut acc = atom_at(span, Atom::Str(parts[0].clone()));
1386 for (i, raw) in exprs.iter().enumerate() {
1387 let inner = parse_expr(raw).map_err(|e| {
1388 fail(
1389 format!("in interpolation `#{{{raw}}}`: {}", e.message),
1390 span,
1391 )
1392 })?;
1393 let inner = Spanned::from_sexp_at(&inner, span);
1394 acc = list_at(span, vec![sym_at(span, LOWERED_CONCAT), acc, inner]);
1395 acc = list_at(
1398 span,
1399 vec![
1400 sym_at(span, LOWERED_CONCAT),
1401 acc,
1402 atom_at(span, Atom::Str(parts[i + 1].clone())),
1403 ],
1404 );
1405 }
1406 Ok(acc)
1407 }
1408
1409 #[inline(never)]
1416 fn lines_literal(
1417 &mut self,
1418 span: Span,
1419 parts: Vec<String>,
1420 exprs: Vec<String>,
1421 ) -> Result<Spanned, Fail> {
1422 let mut lines: Vec<(Vec<String>, Vec<String>)> = vec![(vec![String::new()], Vec::new())];
1423 for (i, part) in parts.iter().enumerate() {
1424 let mut pieces = part.split('\n');
1425 if let Some(head) = pieces.next() {
1426 if let Some(last) = lines.last_mut().and_then(|l| l.0.last_mut()) {
1427 last.push_str(head);
1428 }
1429 }
1430 for piece in pieces {
1431 lines.push((vec![piece.to_string()], Vec::new()));
1432 }
1433 if let Some(raw) = exprs.get(i) {
1434 if let Some(line) = lines.last_mut() {
1435 line.1.push(raw.clone());
1436 line.0.push(String::new());
1437 }
1438 }
1439 }
1440 let empty = |l: &(Vec<String>, Vec<String>)| l.1.is_empty() && l.0 == [""];
1441 if lines.len() == 2 && lines.iter().all(empty) {
1442 self.note(Spelling::NewlineEscape, span, "\"\\n\"", "line_break");
1443 return Ok(sym_at(span, "line_break"));
1444 }
1445 let trailing = lines.last().is_some_and(empty);
1446 if trailing {
1447 lines.pop();
1448 }
1449 let head = if trailing { "unlines" } else { "join_lines" };
1450 let mut items = vec![sym_at(span, "list")];
1451 for (lits, raws) in &lines {
1452 let item = if raws.is_empty() {
1453 atom_at(span, Atom::Str(lits[0].clone()))
1454 } else if raws.len() == 1 && lits.iter().all(String::is_empty) {
1455 let inner = parse_expr(&raws[0]).map_err(|e| {
1456 fail(
1457 format!("in interpolation `#{{{}}}`: {}", raws[0], e.message),
1458 span,
1459 )
1460 })?;
1461 Spanned::from_sexp_at(&inner, span)
1462 } else {
1463 Self::interpolation(span, lits, raws)?
1464 };
1465 items.push(item);
1466 }
1467 self.note(
1468 Spelling::NewlineEscape,
1469 span,
1470 "\"…\\n…\"",
1471 &format!("{head}([…])"),
1472 );
1473 Ok(list_at(
1474 span,
1475 vec![sym_at(span, head), list_at(span, items)],
1476 ))
1477 }
1478
1479 #[inline(never)]
1483 fn applied(&mut self, start: usize, lhs: Spanned) -> Result<Spanned, Fail> {
1484 if self.at(&TokenKind::LParen) {
1485 let (args, splat) = self.call_args()?;
1486 let call = self.call_node(self.span_since(start), vec![lhs], args, splat);
1487 Ok(self.call_spelling(call))
1488 } else {
1489 self.index(start, lhs)
1490 }
1491 }
1492
1493 #[inline(never)]
1496 fn index(&mut self, start: usize, object: Spanned) -> Result<Spanned, Fail> {
1497 let open = self.peek_span();
1498 self.bump();
1499 self.skip_newlines();
1500 let key = self.expr(0)?;
1501 self.skip_newlines();
1502 self.expect(&TokenKind::RBracket, "`]` closing the index")?;
1503 let span = self.span_since(start);
1504 Ok(if is_index_key(&key) {
1505 list_at(span, vec![sym_at(open, "get"), object, key])
1506 } else {
1507 list_at(span, vec![sym_at(open, "nth"), key, object])
1508 })
1509 }
1510
1511 #[inline(never)]
1512 fn not_prefix(&mut self, span: Span) -> Result<Spanned, Fail> {
1513 if self.at(&TokenKind::LParen) && self.touches_previous() {
1514 self.note(Spelling::OperatorSpelling, span, "not(x)", "not x");
1515 let (args, splat) = self.call_args()?;
1516 if args.len() != 1 || splat.is_some() {
1517 let call = self.call_node(
1518 self.span_since(span.start),
1519 vec![sym_at(span, "not")],
1520 args,
1521 splat,
1522 );
1523 return Ok(call);
1524 }
1525 let operand = args.into_iter().next().expect("one argument");
1526 return Ok(self.negation(span, self.span_since(span.start), operand));
1527 }
1528 let rhs = self.expr(11)?;
1529 Ok(self.negation(span, self.span_since(span.start), rhs))
1530 }
1531
1532 fn prefix(&mut self) -> Result<Spanned, Fail> {
1533 if self.at(&TokenKind::LParen) {
1534 let span = self.peek_span();
1535 self.bump();
1536 if self.arrow_lambda_ahead() {
1537 return self.arrow_lambda(span);
1538 }
1539 return self.group();
1540 }
1541 self.prefix_token()
1542 }
1543
1544 fn group(&mut self) -> Result<Spanned, Fail> {
1545 self.skip_newlines();
1546 let inner = self.expr(0)?;
1547 self.skip_newlines();
1548 self.expect(&TokenKind::RParen, "`)`")?;
1549 Ok(inner)
1550 }
1551
1552 #[inline(never)]
1553 fn prefix_token(&mut self) -> Result<Spanned, Fail> {
1554 let span = self.peek_span();
1555 match self.bump() {
1556 TokenKind::Int(v) => Ok(atom_at(span, Atom::Int(v))),
1557 TokenKind::Float(v) => Ok(atom_at(span, Atom::Float(v))),
1558 TokenKind::Str(s) if self.quoting == 0 && s.contains('\n') => {
1559 self.lines_literal(span, vec![s], Vec::new())
1560 }
1561 TokenKind::Str(s) => Ok(atom_at(span, Atom::Str(s))),
1562 TokenKind::Doc { module, .. } => Err(fail(
1563 format!(
1564 "`{}` sits at the top level, above the definition it documents, never \
1565 inside a form",
1566 if module { "@moduledoc" } else { "@doc" }
1567 ),
1568 span,
1569 )),
1570
1571 TokenKind::InterpolatedStr { parts, exprs }
1582 if self.quoting == 0 && parts.iter().any(|p| p.contains('\n')) =>
1583 {
1584 self.lines_literal(span, parts, exprs)
1585 }
1586 TokenKind::InterpolatedStr { parts, exprs } => Self::interpolation(span, &parts, &exprs),
1587 TokenKind::Sym(s) => Ok(atom_at(span, Atom::Keyword(s))),
1588 TokenKind::True => Ok(atom_at(span, Atom::Bool(true))),
1589 TokenKind::False => Ok(atom_at(span, Atom::Bool(false))),
1590 TokenKind::Nil => Ok(Spanned::new(span, SpannedForm::Nil)),
1591
1592 TokenKind::Op(o) if o == "-" => {
1593 let rhs = self.expr(11)?; Ok(list_at(
1595 self.span_since(span.start),
1596 vec![sym_at(span, "-"), atom_at(span, Atom::Int(0)), rhs],
1597 ))
1598 }
1599 TokenKind::Op(o) if o == "!" => {
1600 let written = self.text(span).to_string();
1601 self.note(Spelling::LogicalSymbol, span, &written, "not");
1602 let rhs = self.expr(11)?;
1603 Ok(self.negation(span, self.span_since(span.start), rhs))
1604 }
1605 TokenKind::Ident(w) if w == "not" => self.not_prefix(span),
1606 TokenKind::Ident(w) if (w == "and" || w == "or") && self.at(&TokenKind::LParen) => {
1607 Ok(sym_at(span, &w))
1608 }
1609 TokenKind::Ident(w) if w == "and" || w == "or" => Err(fail(
1610 format!("`{w}` joins two expressions: `a {w} b`"),
1611 span,
1612 )),
1613
1614 TokenKind::LParen => {
1615 self.skip_newlines();
1616 let inner = self.expr(0)?;
1617 self.skip_newlines();
1618 self.expect(&TokenKind::RParen, "`)`")?;
1619 Ok(inner)
1623 }
1624
1625 TokenKind::LBracket => self.list_literal(span),
1626 TokenKind::LBrace => self.map_literal(span),
1627
1628 TokenKind::Ident(name) if self.at(&TokenKind::PathSep) => {
1629 self.qualified_name(span, &name)
1630 }
1631 TokenKind::PathSep => Err(fail(
1632 "a qualified name is `package::name`, with the package written before `::`",
1633 span,
1634 )),
1635 TokenKind::Ident(name) => match name.as_str() {
1636 "if" => self.if_form(span, false),
1637 "begin" if !(self.at(&TokenKind::LParen) && self.touches_previous()) => {
1638 self.begin_form(span)
1639 }
1640 "rescue" | "ensure" => Err(fail(
1641 format!("`{name}` belongs to a `begin … end` block or a `def` body, after the statements it protects"),
1642 span,
1643 )),
1644 "unless" => {
1645 self.note(Spelling::Unless, span, "unless", "if not");
1646 self.if_form(span, true)
1647 }
1648 "def" => self.def_form(span),
1649 "defmacro" => self.defmacro_form(span),
1650 "case" => self.case_form(span),
1651 "fn" => self.lambda_form(span),
1652 "test" => self.test_form(span),
1653 "assert" => self.assert_form(span),
1654 "quote" => self.quote_form(span),
1655 "unquote" => self.unquote_form(span, false),
1656 "unquote_splice" => self.unquote_form(span, true),
1657 "do" => Err(fail(
1658 "blue has no `do` blocks: pass a function as an argument, \
1659 `f(xs, fn(x) … end)`",
1660 span,
1661 )),
1662 "end" => Err(fail(
1663 "unexpected `end`: nothing open here for it to close",
1664 span,
1665 )),
1666 _ => Ok(sym_at(span, &name)),
1667 },
1668
1669 other => Err(fail(format!("expected an expression, found {other}"), span)),
1670 }
1671 }
1672
1673 fn qualified_name(&mut self, pkg_span: Span, pkg: &str) -> Result<Spanned, Fail> {
1683 if is_reserved_word(pkg) {
1684 return Err(fail(
1685 format!("`{pkg}` is a reserved word and cannot name a package"),
1686 pkg_span,
1687 ));
1688 }
1689 self.bump(); let name_span = self.peek_span();
1691 let name = match self.bump() {
1692 TokenKind::Ident(n) => n,
1693 TokenKind::True => "true".to_string(),
1695 TokenKind::False => "false".to_string(),
1696 TokenKind::Nil => "nil".to_string(),
1697 other => {
1698 return Err(fail(
1699 format!("expected a name after `{pkg}::`, found {other}"),
1700 name_span,
1701 ))
1702 }
1703 };
1704 if self.at(&TokenKind::PathSep) {
1705 return Err(self.error(format!(
1706 "a qualified name has one qualifier: `package::name`, and `{pkg}::{name}::…` has two"
1707 )));
1708 }
1709 Ok(sym_at(
1710 self.span_since(pkg_span.start),
1711 &qualify(pkg, &name),
1712 ))
1713 }
1714
1715 #[inline(never)]
1722 fn negation(&mut self, head: Span, whole: Span, operand: Spanned) -> Spanned {
1723 if let SpannedForm::List(items) = &operand.form {
1724 let flipped = match items.first().and_then(Spanned::as_symbol) {
1725 Some("equal?") => Some("not="),
1726 Some("not=") => Some("equal?"),
1727 _ => None,
1728 };
1729 if let (Some(to), 3) = (flipped, items.len()) {
1730 let (written, canonical) = if to == "not=" {
1731 ("not (a == b)", "a != b")
1732 } else {
1733 ("not (a != b)", "a == b")
1734 };
1735 self.note(Spelling::NegatedComparison, whole, written, canonical);
1736 let mut out = items.clone();
1737 out[0] = sym_at(items[0].span, to);
1738 if to == "equal?" {
1739 if let Some(test) =
1740 self.nil_test(whole.start, items[0].span, &items[1], &items[2])
1741 {
1742 return test;
1743 }
1744 }
1745 return list_at(whole, out);
1746 }
1747 }
1748 list_at(whole, vec![sym_at(head, "not"), operand])
1749 }
1750
1751 #[inline(never)]
1752 fn call_spelling(&mut self, call: Spanned) -> Spanned {
1753 let SpannedForm::List(items) = &call.form else {
1754 return call;
1755 };
1756 let Some(head) = items.first().and_then(Spanned::as_symbol) else {
1757 return call;
1758 };
1759 let argc = items.len() - 1;
1760 if head == "get" && argc == 2 && is_index_key(&items[2]) {
1761 self.note(Spelling::Index, call.span, "get(m, :k)", "m[:k]");
1762 return call;
1763 }
1764 if head == "nth" && argc == 2 && !is_index_key(&items[1]) {
1765 self.note(Spelling::Index, call.span, "nth(i, xs)", "xs[i]");
1766 return call;
1767 }
1768 if head == "equal?" && argc == 2 {
1769 let (op_span, start) = (items[0].span, call.span.start);
1770 if let Some(test) = self.nil_test(start, op_span, &items[1], &items[2]) {
1771 return test;
1772 }
1773 }
1774 let operator = match (head, argc) {
1775 ("and" | "or" | "mod" | "equal?", 2) => Some(head),
1776 ("list", _) => Some("list"),
1777 _ => None,
1778 };
1779 if let Some(op) = operator {
1780 let canonical = match op {
1781 "list" => "[…]".to_string(),
1782 "mod" => "a % b".to_string(),
1783 "equal?" => "a == b".to_string(),
1784 other => format!("a {other} b"),
1785 };
1786 self.note(
1787 Spelling::OperatorSpelling,
1788 call.span,
1789 &format!("{head}(…)"),
1790 &canonical,
1791 );
1792 return call;
1793 }
1794 if head == "apply"
1795 && self.text(items[0].span) == "apply"
1796 && argc >= 2
1797 && items[1].as_symbol().is_some_and(is_callable_name)
1798 {
1799 self.note(Spelling::Apply, call.span, "apply(f, …, xs)", "f(…, *xs)");
1800 return call;
1801 }
1802 if head == "try" {
1803 return self.try_spelling(call);
1804 }
1805 call
1806 }
1807
1808 fn try_spelling(&mut self, call: Spanned) -> Spanned {
1809 let SpannedForm::List(items) = &call.form else {
1810 return call;
1811 };
1812 let Some((catch, body)) = items[1..].split_last() else {
1813 return call;
1814 };
1815 let Some(clause) = catch.as_list() else {
1816 return call;
1817 };
1818 let binder = match clause {
1819 [head, binding, handler @ ..]
1820 if head.as_symbol() == Some("catch") && !handler.is_empty() =>
1821 {
1822 match binding.as_list() {
1823 Some([b]) => b.as_symbol().map(|n| (n.to_string(), b.span)),
1824 _ => None,
1825 }
1826 }
1827 _ => None,
1828 };
1829 let Some((name, binder_span)) = binder else {
1830 return call;
1831 };
1832 if body.is_empty() {
1833 return call;
1834 }
1835 self.note(Spelling::TryCatch, call.span, "try(…, catch(…))", "rescue");
1836 let handler = &clause[2..];
1837 let rename =
1838 name.starts_with('_') && name != "_" && !handler.iter().any(|h| mentions(h, &name));
1839 let body = if body.len() == 1 {
1840 body[0].clone()
1841 } else {
1842 let span = cover(body, call.span);
1843 let mut list = vec![sym_at(Span::new(span.start, span.start), "begin")];
1844 list.extend(body.iter().cloned());
1845 list_at(span, list)
1846 };
1847 let handler = if handler.len() == 1 {
1848 handler[0].clone()
1849 } else {
1850 let span = cover(handler, catch.span);
1851 let mut list = vec![sym_at(Span::new(span.start, span.start), "begin")];
1852 list.extend(handler.iter().cloned());
1853 list_at(span, list)
1854 };
1855 let binder = sym_at(binder_span, if rename { "_" } else { &name });
1856 list_at(
1857 call.span,
1858 vec![
1859 items[0].clone(),
1860 body,
1861 list_at(
1862 catch.span,
1863 vec![
1864 clause[0].clone(),
1865 list_at(clause[1].span, vec![binder]),
1866 handler,
1867 ],
1868 ),
1869 ],
1870 )
1871 }
1872
1873 fn finish_send(&mut self, start: usize, recv: Spanned, dot: Span) -> Result<Spanned, Fail> {
1874 let name_span = self.peek_span();
1875 let name = match self.bump() {
1876 TokenKind::Ident(n) if is_reserved_word(&n) => {
1890 return Err(self.error(format!(
1891 "`{n}` is a reserved word and cannot be a method name — \
1892 `recv.{n}` would lower onto the `{n}` form itself"
1893 )))
1894 }
1895 TokenKind::Ident(n) => n,
1896 other => {
1897 return Err(fail(
1898 format!("expected a method name after `.`, found {other}"),
1899 name_span,
1900 ))
1901 }
1902 };
1903 if !self.at(&TokenKind::LParen) {
1904 return Ok(list_at(
1905 self.span_since(start),
1906 vec![
1907 sym_at(dot, LOWERED_FIELD),
1908 recv,
1909 atom_at(name_span, Atom::Keyword(name)),
1910 ],
1911 ));
1912 }
1913 self.note(
1914 Spelling::CallForm,
1915 dot.merge(name_span),
1916 &format!("x.{name}(…)"),
1917 &format!("{name}(x, …)"),
1918 );
1919 let (args, splat) = self.call_args()?;
1920 Ok(self.call_node(
1921 self.span_since(start),
1922 vec![sym_at(name_span, &name), recv],
1923 args,
1924 splat,
1925 ))
1926 }
1927
1928 fn call_node(
1929 &self,
1930 span: Span,
1931 head: Vec<Spanned>,
1932 args: Vec<Spanned>,
1933 splat: Option<Span>,
1934 ) -> Spanned {
1935 let mut list = Vec::with_capacity(head.len() + args.len() + 1);
1936 if let Some(at) = splat {
1937 list.push(sym_at(at, "apply"));
1938 }
1939 list.extend(head);
1940 list.extend(args);
1941 list_at(span, list)
1942 }
1943
1944 fn call_args(&mut self) -> Result<(Vec<Spanned>, Option<Span>), Fail> {
1945 self.expect(&TokenKind::LParen, "`(`")?;
1946 let mut args = Vec::new();
1947 let mut splat = None;
1948 self.skip_newlines();
1949 if self.eat(&TokenKind::RParen) {
1950 return Ok((args, splat));
1951 }
1952 loop {
1953 self.skip_newlines();
1954 if splat.is_some() {
1955 return Err(
1956 self.error("a splat `*xs` passes a list as the LAST arguments: write it last")
1957 );
1958 }
1959 if self.at_spread() || matches!(self.peek(), TokenKind::Label(_)) {
1960 let open = self.peek_span();
1961 let (segments, spread) = self.map_entries(open, &TokenKind::RParen)?;
1962 if !spread {
1963 return Err(fail(
1964 "a keyword entry in a call needs a `**` spread: pass a map, f({a: 1})",
1965 open,
1966 ));
1967 }
1968 args.push(self.merged(open, LOWERED_KWARGS, segments));
1969 return Ok((args, splat));
1970 }
1971 if matches!(self.peek(), TokenKind::Op(o) if o == "*") {
1972 splat = Some(self.peek_span());
1973 self.bump();
1974 }
1975 args.push(self.expr(0)?);
1976 self.skip_newlines();
1977 if self.eat(&TokenKind::Comma) {
1978 if self.trailing_comma(&TokenKind::RParen) {
1979 break;
1980 }
1981 continue;
1982 }
1983 self.expect(&TokenKind::RParen, "`,` or `)`")?;
1984 break;
1985 }
1986 Ok((args, splat))
1987 }
1988
1989 fn trailing_comma(&mut self, closer: &TokenKind) -> bool {
1990 let comma = self
1991 .toks
1992 .get(self.pos.wrapping_sub(1))
1993 .map_or(self.peek_span(), |t| t.span);
1994 self.skip_newlines();
1995 if self.at(closer) {
1996 self.note(Spelling::TrailingComma, comma, ",", "");
1997 self.bump();
1998 return true;
1999 }
2000 false
2001 }
2002
2003 fn list_literal(&mut self, open: Span) -> Result<Spanned, Fail> {
2004 let mut items = vec![sym_at(open, "list")];
2005 self.skip_newlines();
2006 if self.eat(&TokenKind::RBracket) {
2007 return Ok(list_at(self.span_since(open.start), items));
2008 }
2009 loop {
2010 self.skip_newlines();
2011 items.push(self.expr(0)?);
2012 self.skip_newlines();
2013 if self.eat(&TokenKind::Comma) {
2014 if self.trailing_comma(&TokenKind::RBracket) {
2015 break;
2016 }
2017 continue;
2018 }
2019 self.expect(&TokenKind::RBracket, "`,` or `]`")?;
2020 break;
2021 }
2022 Ok(list_at(self.span_since(open.start), items))
2023 }
2024
2025 fn map_literal(&mut self, open: Span) -> Result<Spanned, Fail> {
2032 self.skip_newlines();
2033 if self.eat(&TokenKind::RBrace) {
2034 return Ok(list_at(
2035 self.span_since(open.start),
2036 vec![sym_at(open, LOWERED_MAP)],
2037 ));
2038 }
2039 let (mut segments, spread) = self.map_entries(open, &TokenKind::RBrace)?;
2040 if !spread {
2041 return Ok(segments.remove(0));
2042 }
2043 Ok(self.merged(open, LOWERED_MERGE, segments))
2044 }
2045
2046 fn merged(&self, open: Span, head: &str, segments: Vec<Spanned>) -> Spanned {
2047 let mut items = vec![sym_at(open, head)];
2048 items.extend(segments);
2049 list_at(self.span_since(open.start), items)
2050 }
2051
2052 fn at_spread(&self) -> bool {
2053 matches!(self.peek(), TokenKind::Op(o) if o == "**")
2054 }
2055
2056 fn map_entries(
2057 &mut self,
2058 open: Span,
2059 closer: &TokenKind,
2060 ) -> Result<(Vec<Spanned>, bool), Fail> {
2061 let mut segments = Vec::new();
2062 let mut items = vec![sym_at(open, LOWERED_MAP)];
2063 let mut spread = false;
2064 loop {
2065 self.skip_newlines();
2066 if self.at_spread() {
2067 let star = self.peek_span();
2068 self.bump();
2069 let value = self.expr(0)?;
2070 if items.len() > 1 {
2071 let span = items[1].span.merge(items[items.len() - 1].span);
2072 items[0] = sym_at(items[1].span, LOWERED_MAP);
2073 segments.push(list_at(span, std::mem::take(&mut items)));
2074 items = vec![sym_at(open, LOWERED_MAP)];
2075 }
2076 segments.push(list_at(
2077 star.merge(value.span),
2078 vec![sym_at(star, LOWERED_SPLICE), value],
2079 ));
2080 spread = true;
2081 } else {
2082 self.map_entry(&mut items, closer)?;
2083 }
2084 self.skip_newlines();
2085 if self.eat(&TokenKind::Comma) {
2086 if self.trailing_comma(closer) {
2087 break;
2088 }
2089 continue;
2090 }
2091 self.expect(closer, "`,` or the end of the entries")?;
2092 break;
2093 }
2094 if items.len() > 1 || !spread {
2095 let whole = self.span_since(open.start);
2096 segments.push(list_at(whole, items));
2097 }
2098 Ok((segments, spread))
2099 }
2100
2101 fn map_entry(&mut self, items: &mut Vec<Spanned>, closer: &TokenKind) -> Result<(), Fail> {
2102 match self.peek().clone() {
2103 TokenKind::Label(name) => {
2104 let label_span = self.peek_span();
2105 self.bump();
2106 self.skip_newlines();
2107 items.push(atom_at(label_span, Atom::Keyword(name.clone())));
2108 if matches!(self.peek(), TokenKind::Comma) || self.at(closer) {
2109 items.push(sym_at(label_span, &name));
2110 } else {
2111 let value = self.expr(0)?;
2112 if matches!(&value.form, SpannedForm::Atom(Atom::Symbol(s)) if *s == name)
2113 && label_name(&name)
2114 {
2115 let written = format!("{name}: {name}");
2116 let canonical = format!("{name}:");
2117 self.note(
2118 Spelling::MapPun,
2119 label_span.merge(value.span),
2120 &written,
2121 &canonical,
2122 );
2123 }
2124 items.push(value);
2125 }
2126 }
2127 _ => {
2128 let k = self.expr(0)?;
2129 self.skip_newlines();
2130 if let SpannedForm::Atom(Atom::Keyword(name)) = &k.form {
2131 if blue_lang_name(name) {
2132 let written = format!(":{name} =>");
2133 let canonical = format!("{name}:");
2134 self.note(Spelling::KeywordRocket, k.span, &written, &canonical);
2135 }
2136 }
2137 self.expect(&TokenKind::Rocket, "`=>` in a map literal")?;
2138 self.skip_newlines();
2139 items.push(k);
2140 items.push(self.expr(0)?);
2141 }
2142 }
2143 Ok(())
2144 }
2145
2146 fn if_chain(&mut self, head: Span) -> Result<Spanned, Fail> {
2156 let cond = self.expr(0)?;
2157 let then = self.body(&["elsif", "else", "end"])?;
2158 let els = self.else_part()?;
2159 let mut out = vec![sym_at(head, "if"), cond, then];
2160 if let Some(e) = els {
2161 out.push(e);
2162 }
2163 Ok(list_at(self.span_since(head.start), out))
2164 }
2165
2166 fn else_part(&mut self) -> Result<Option<Spanned>, Fail> {
2167 if self.at_ident("elsif") {
2168 let elsif = self.peek_span();
2169 self.bump();
2170 return Ok(Some(self.if_chain(elsif)?));
2171 }
2172 if !self.at_ident("else") {
2173 self.expect_ident("end")?;
2174 return Ok(None);
2175 }
2176 let else_span = self.peek_span();
2177 self.bump();
2178 let save = self.pos;
2179 self.skip_newlines();
2180 let leads_with_if = self.at_ident("if") || self.at_ident("unless");
2181 self.pos = save;
2182 let e = self.body(&["end"])?;
2183 let end_at = self.peek_span().start;
2184 self.expect_ident("end")?;
2185 if matches!(e.form, SpannedForm::Nil) && !self.comment_within(else_span.start, end_at) {
2186 self.note(Spelling::ElseNil, else_span, "else nil", "no else");
2187 return Ok(None);
2188 }
2189 let single_if = matches!(&e.form, SpannedForm::List(items)
2190 if (items.len() == 3 || items.len() == 4) && items[0].as_symbol() == Some("if"));
2191 if leads_with_if && single_if {
2192 self.note(Spelling::ElseIf, else_span, "else if", "elsif");
2193 }
2194 Ok(Some(e))
2195 }
2196
2197 fn if_form(&mut self, head: Span, negate: bool) -> Result<Spanned, Fail> {
2198 let cond = self.expr(0)?;
2199 let cond = if negate {
2200 let inner = cond.span;
2203 self.negation(head, head.merge(inner), cond)
2204 } else {
2205 cond
2206 };
2207 let then = self.body(&["elsif", "else", "end"])?;
2220 let els = self.else_part()?;
2221 let mut out = vec![sym_at(head, "if"), cond, then];
2222 if let Some(e) = els {
2223 out.push(e);
2224 }
2225 Ok(list_at(self.span_since(head.start), out))
2226 }
2227
2228 fn case_form(&mut self, head: Span) -> Result<Spanned, Fail> {
2254 let subject = self.expr(0)?;
2255 self.skip_newlines();
2256
2257 let subject_var = "case-subject";
2260 let mut arms: Vec<Spanned> = Vec::new();
2261 let mut otherwise: Option<Spanned> = None;
2262 let mut else_kw: Option<Span> = None;
2265
2266 loop {
2267 self.skip_newlines();
2268 if self.at_ident("end") {
2269 break;
2270 }
2271 let maybe_else = self.peek_span();
2272 if self.eat_ident("else") {
2273 else_kw = Some(maybe_else);
2274 otherwise = Some(self.body(&["end"])?);
2275 continue;
2276 }
2277 let when = self.peek_span();
2278 if !self.eat_ident("when") {
2279 return Err(self.error(format!(
2280 "expected `when`, `else` or `end` in a case, found {}",
2281 self.peek()
2282 )));
2283 }
2284 self.skip_newlines();
2285 let pattern = self.expr(0)?;
2286 let body = self.body(&["when", "else", "end"])?;
2287 let test = list_at(
2291 when.merge(pattern.span),
2292 vec![sym_at(when, "equal?"), sym_at(when, subject_var), pattern],
2293 );
2294 arms.push(list_at(when.merge(body.span), vec![test, body]));
2295 }
2296 self.expect_ident("end")?;
2297
2298 if arms.is_empty() && otherwise.is_none() {
2299 return Err(self.error("a case needs at least one `when` or an `else`".to_string()));
2300 }
2301
2302 let mut cond = vec![sym_at(head, "cond")];
2307 cond.extend(arms);
2308 let else_span = else_kw.unwrap_or(head);
2315 let else_body = otherwise.unwrap_or_else(|| Spanned::new(else_span, SpannedForm::Nil));
2316 cond.push(list_at(
2317 else_span.merge(else_body.span),
2318 vec![sym_at(else_span, "else"), else_body],
2319 ));
2320
2321 let whole = self.span_since(head.start);
2322 let binding = list_at(
2323 head.merge(subject.span),
2324 vec![sym_at(head, subject_var), subject],
2325 );
2326 Ok(list_at(
2327 whole,
2328 vec![
2329 sym_at(head, "let"),
2330 list_at(binding.span, vec![binding]),
2331 list_at(whole, cond),
2332 ],
2333 ))
2334 }
2335
2336 fn arrow_lambda_ahead(&self) -> bool {
2339 let mut i = self.pos;
2340 let kind = |i: usize| self.toks.get(i).map(|t| &t.kind);
2341 if !matches!(kind(i), Some(TokenKind::RParen)) {
2342 loop {
2343 if matches!(kind(i), Some(TokenKind::Op(o)) if o == "*") {
2344 i += 1;
2345 }
2346 if !matches!(kind(i), Some(TokenKind::Ident(_))) {
2347 return false;
2348 }
2349 i += 1;
2350 match kind(i) {
2351 Some(TokenKind::Comma) => i += 1,
2352 Some(TokenKind::RParen) => break,
2353 _ => return false,
2354 }
2355 }
2356 }
2357 matches!(kind(i + 1), Some(TokenKind::Op(o)) if o == "->")
2358 }
2359
2360 fn arrow_lambda(&mut self, head: Span) -> Result<Spanned, Fail> {
2367 let mut params: Vec<Spanned> = Vec::new();
2368 while !self.at(&TokenKind::RParen) {
2369 let p_span = self.peek_span();
2370 if self.at_star() {
2371 params.extend(self.rest_parameter()?);
2372 break;
2373 }
2374 match self.bump() {
2375 TokenKind::Ident(p) => params.push(sym_at(p_span, &p)),
2376 other => {
2377 return Err(fail(
2378 format!("expected a parameter name, found {other}"),
2379 p_span,
2380 ))
2381 }
2382 }
2383 self.eat(&TokenKind::Comma);
2384 }
2385 self.bump();
2386 self.bump();
2387 self.skip_newlines();
2388 let params_span = cover(¶ms, head);
2389 let body = self.expr(0)?;
2390 Ok(list_at(
2391 self.span_since(head.start),
2392 vec![sym_at(head, "lambda"), list_at(params_span, params), body],
2393 ))
2394 }
2395
2396 fn rest_parameter(&mut self) -> Result<[Spanned; 2], Fail> {
2397 let star = self.peek_span();
2398 self.bump();
2399 let name_span = self.peek_span();
2400 match self.bump() {
2401 TokenKind::Ident(p) => {
2402 let whole = star.merge(name_span);
2403 Ok([sym_at(whole, "&rest"), sym_at(whole, &p)])
2404 }
2405 other => Err(fail(
2406 format!("expected a parameter name after `*`, found {other}"),
2407 name_span,
2408 )),
2409 }
2410 }
2411
2412 fn at_star(&self) -> bool {
2413 matches!(self.peek(), TokenKind::Op(o) if o == "*")
2414 }
2415
2416 fn optional_mark(
2417 &self,
2418 name: String,
2419 span: Span,
2420 optional_from: &mut Option<usize>,
2421 index: usize,
2422 ) -> Result<String, Fail> {
2423 match name.strip_suffix('?').filter(|n| !n.is_empty()) {
2424 Some(bare) => {
2425 optional_from.get_or_insert(index);
2426 Ok(bare.to_string())
2427 }
2428 None if optional_from.is_some() => Err(fail(
2429 format!("the required parameter `{name}` cannot follow an optional one"),
2430 span,
2431 )),
2432 None => Ok(name),
2433 }
2434 }
2435
2436 fn rest_is_last(&mut self) -> Result<(), Fail> {
2437 self.skip_newlines();
2438 if self.at(&TokenKind::Comma) {
2439 return Err(self.error("a rest parameter `*name` comes last".to_string()));
2440 }
2441 Ok(())
2442 }
2443
2444 fn lambda_form(&mut self, head: Span) -> Result<Spanned, Fail> {
2454 let mut params: Vec<Spanned> = Vec::new();
2455 if !self.at(&TokenKind::LParen) {
2456 self.note(Spelling::ParameterList, head, "fn", "fn()");
2457 }
2458 if self.at(&TokenKind::LParen) {
2459 self.bump();
2460 self.skip_newlines();
2461 if !self.eat(&TokenKind::RParen) {
2462 loop {
2463 self.skip_newlines();
2464 let p_span = self.peek_span();
2465 if self.at_star() {
2466 params.extend(self.rest_parameter()?);
2467 self.rest_is_last()?;
2468 self.expect(&TokenKind::RParen, "`)` after the rest parameter")?;
2469 break;
2470 }
2471 match self.bump() {
2472 TokenKind::Ident(p) => params.push(sym_at(p_span, &p)),
2473 other => {
2474 return Err(fail(
2475 format!("expected a parameter name, found {other}"),
2476 p_span,
2477 ))
2478 }
2479 }
2480 self.skip_newlines();
2481 if self.eat(&TokenKind::Comma) {
2482 if self.trailing_comma(&TokenKind::RParen) {
2483 break;
2484 }
2485 continue;
2486 }
2487 self.expect(&TokenKind::RParen, "`,` or `)`")?;
2488 break;
2489 }
2490 }
2491 }
2492 let params_span = cover(¶ms, head);
2493 let body = self.body(&["end"])?;
2494 self.expect_ident("end")?;
2495 Ok(list_at(
2496 self.span_since(head.start),
2497 vec![sym_at(head, "lambda"), list_at(params_span, params), body],
2498 ))
2499 }
2500
2501 fn test_form(&mut self, head: Span) -> Result<Spanned, Fail> {
2507 let name_span = self.peek_span();
2508 let name = match self.bump() {
2509 TokenKind::Str(s) => s,
2510 other => {
2511 return Err(fail(
2512 format!("expected a string name after `test`, found {other}"),
2513 name_span,
2514 ))
2515 }
2516 };
2517 let body = self.body(&["end"])?;
2518 self.expect_ident("end")?;
2519 Ok(list_at(
2520 self.span_since(head.start),
2521 vec![
2522 sym_at(head, "deftest"),
2523 atom_at(name_span, Atom::Str(name)),
2524 body,
2525 ],
2526 ))
2527 }
2528
2529 fn assert_form(&mut self, head: Span) -> Result<Spanned, Fail> {
2553 let e = self.expr(0)?;
2554 let quoted = Spanned::new(e.span, SpannedForm::Quote(Box::new(e.clone())));
2555 Ok(list_at(
2556 self.span_since(head.start),
2557 vec![sym_at(head, LOWERED_ASSERT), quoted, e],
2558 ))
2559 }
2560
2561 fn defmacro_form(&mut self, head: Span) -> Result<Spanned, Fail> {
2570 let name_span = self.peek_span();
2571 let name = match self.bump() {
2572 TokenKind::Ident(n) => n,
2573 other => {
2574 return Err(fail(
2575 format!("expected a name after `defmacro`, found {other}"),
2576 name_span,
2577 ))
2578 }
2579 };
2580 self.not_reserved(&name, name_span, "a `defmacro` name")?;
2581 let mut params: Vec<Spanned> = Vec::new();
2582 if self.at(&TokenKind::LParen) {
2583 self.bump();
2584 self.skip_newlines();
2585 if !self.eat(&TokenKind::RParen) {
2586 loop {
2587 self.skip_newlines();
2588 let p_span = self.peek_span();
2589 match self.bump() {
2590 TokenKind::Ident(p) => params.push(sym_at(p_span, &p)),
2591 TokenKind::Label(p) => {
2592 return Err(self.error(format!(
2593 "macro parameter `{p}` cannot be typed: a macro receives \
2594 source forms, not values"
2595 )))
2596 }
2597 other => {
2598 return Err(fail(
2599 format!("expected a parameter name, found {other}"),
2600 p_span,
2601 ))
2602 }
2603 }
2604 self.skip_newlines();
2605 if self.eat(&TokenKind::Comma) {
2606 if self.trailing_comma(&TokenKind::RParen) {
2607 break;
2608 }
2609 continue;
2610 }
2611 self.expect(&TokenKind::RParen, "`,` or `)`")?;
2612 break;
2613 }
2614 }
2615 }
2616 if matches!(self.peek(), TokenKind::Op(o) if o == "->") {
2617 return Err(self.error(
2618 "a macro has no return type: it produces source forms, not values".to_string(),
2619 ));
2620 }
2621
2622 let params_span = cover(¶ms, name_span);
2623 let body = self.body(&["end"])?;
2624 self.expect_ident("end")?;
2625
2626 Ok(list_at(
2629 self.span_since(head.start),
2630 vec![
2631 sym_at(head, "defmacro"),
2632 sym_at(name_span, &name),
2633 list_at(params_span, params),
2634 body,
2635 ],
2636 ))
2637 }
2638
2639 fn quote_form(&mut self, head: Span) -> Result<Spanned, Fail> {
2645 self.quoting += 1;
2646 let body = self.body(&["end"]);
2647 self.quoting -= 1;
2648 let body = body?;
2649 self.expect_ident("end")?;
2650 Ok(Spanned::new(
2659 self.span_since(head.start),
2660 SpannedForm::Quasiquote(Box::new(body)),
2661 ))
2662 }
2663
2664 fn unquote_form(&mut self, head: Span, splice: bool) -> Result<Spanned, Fail> {
2666 self.expect(&TokenKind::LParen, "`(` after unquote")?;
2667 self.skip_newlines();
2668 let inner = self.expr(0)?;
2669 self.skip_newlines();
2670 self.expect(&TokenKind::RParen, "`)`")?;
2671 let span = self.span_since(head.start);
2672 Ok(Spanned::new(
2673 span,
2674 if splice {
2675 SpannedForm::UnquoteSplice(Box::new(inner))
2676 } else {
2677 SpannedForm::Unquote(Box::new(inner))
2678 },
2679 ))
2680 }
2681
2682 fn protected_body(&mut self, head: Span) -> Result<Spanned, Fail> {
2683 let body = self.body(&["rescue", "ensure", "end"])?;
2684 if self.at_ident("rescue") || self.at_ident("ensure") {
2685 return self.rescue_tail(head, body);
2686 }
2687 Ok(body)
2688 }
2689
2690 fn begin_form(&mut self, head: Span) -> Result<Spanned, Fail> {
2691 let body = self.body(&["rescue", "ensure", "end"])?;
2692 if !(self.at_ident("rescue") || self.at_ident("ensure")) {
2693 return Err(self.error(
2694 "a `begin` block protects its statements with `rescue` or `ensure`; without either, write the statements directly",
2695 ));
2696 }
2697 let tree = self.rescue_tail(head, body)?;
2698 self.expect_ident("end")?;
2699 Ok(Spanned::new(self.span_since(head.start), tree.form))
2700 }
2701
2702 fn rescue_tail(&mut self, head: Span, body: Spanned) -> Result<Spanned, Fail> {
2703 let mut clauses: Vec<Spanned> = Vec::new();
2704 let mut catch_all: Option<(Spanned, Spanned, Span)> = None;
2705 while self.at_ident("rescue") {
2706 let kw = self.peek_span();
2707 self.bump();
2708 if catch_all.is_some() {
2709 return Err(fail(
2710 "a `rescue NAME` catches every error, so it is the last `rescue`; put the kinds it would shadow above it",
2711 kw,
2712 ));
2713 }
2714 match self.peek().clone() {
2715 TokenKind::Sym(_) => {
2716 let mut kinds = Vec::new();
2717 loop {
2718 let k_span = self.peek_span();
2719 match self.bump() {
2720 TokenKind::Sym(k) => kinds.push(atom_at(k_span, Atom::Keyword(k))),
2721 other => {
2722 return Err(fail(
2723 format!("expected an error kind such as `:not_found`, found {other}"),
2724 k_span,
2725 ))
2726 }
2727 }
2728 if self.eat(&TokenKind::Comma) {
2729 self.skip_newlines();
2730 continue;
2731 }
2732 break;
2733 }
2734 let binder = if self.eat_ident("as") {
2735 let b_span = self.peek_span();
2736 match self.bump() {
2737 TokenKind::Ident(b) if !is_reserved_word(&b) => Some(sym_at(b_span, &b)),
2738 other => {
2739 return Err(fail(
2740 format!("expected the name the error is bound to after `as`, found {other}"),
2741 b_span,
2742 ))
2743 }
2744 }
2745 } else {
2746 None
2747 };
2748 let handler = self.body(&["rescue", "ensure", "end"])?;
2749 let mut test = vec![sym_at(kw, LOWERED_ERROR_MATCH), sym_at(kw, RESCUED)];
2750 test.extend(kinds);
2751 let test = list_at(cover(&test, kw), test);
2752 let handler = match binder {
2753 Some(b) => let_bound(b, handler, kw),
2754 None => handler,
2755 };
2756 clauses.push(list_at(kw.merge(handler.span), vec![test, handler]));
2757 }
2758 TokenKind::Ident(b) if !is_reserved_word(&b) => {
2759 let b_span = self.peek_span();
2760 self.bump();
2761 let handler = self.body(&["rescue", "ensure", "end"])?;
2762 catch_all = Some((sym_at(b_span, &b), handler, kw));
2763 }
2764 _ => {
2765 return Err(fail(
2766 "`rescue` names what it catches: `rescue :kind`, `rescue :a, :b as e`, or `rescue e` for every error (`rescue _` discards it)",
2767 kw,
2768 ))
2769 }
2770 }
2771 }
2772 let ensure = if self.at_ident("ensure") {
2773 self.bump();
2774 let cleanup = self.body(&["rescue", "end"])?;
2775 if self.at_ident("rescue") {
2776 return Err(self.error("`ensure` comes after every `rescue` of its block"));
2777 }
2778 Some(cleanup)
2779 } else {
2780 None
2781 };
2782 let whole = self.span_since(head.start);
2783 let protected = if clauses.is_empty() {
2784 match catch_all {
2785 None => body,
2786 Some((binder, handler, kw)) => list_at(
2787 whole,
2788 vec![
2789 sym_at(head, "try"),
2790 body,
2791 list_at(
2792 kw.merge(handler.span),
2793 vec![
2794 sym_at(kw, "catch"),
2795 list_at(binder.span, vec![binder]),
2796 handler,
2797 ],
2798 ),
2799 ],
2800 ),
2801 }
2802 } else {
2803 let first_kw = clauses[0].span;
2804 let otherwise = match catch_all {
2805 Some((binder, handler, kw)) => {
2806 let bound = let_bound(binder, handler, kw);
2807 list_at(kw.merge(bound.span), vec![sym_at(kw, "else"), bound])
2808 }
2809 None => list_at(
2810 head,
2811 vec![
2812 sym_at(head, "else"),
2813 list_at(
2814 head,
2815 vec![sym_at(head, LOWERED_RERAISE), sym_at(head, RESCUED)],
2816 ),
2817 ],
2818 ),
2819 };
2820 let mut cond = vec![sym_at(first_kw, "cond")];
2821 cond.extend(clauses);
2822 cond.push(otherwise);
2823 let cond_span = cover(&cond, first_kw);
2824 list_at(
2825 whole,
2826 vec![
2827 sym_at(head, "try"),
2828 body,
2829 list_at(
2830 cond_span,
2831 vec![
2832 sym_at(first_kw, "catch"),
2833 list_at(first_kw, vec![sym_at(first_kw, RESCUED)]),
2834 list_at(cond_span, cond),
2835 ],
2836 ),
2837 ],
2838 )
2839 };
2840 Ok(match ensure {
2841 None => protected,
2842 Some(cleanup) => {
2843 let attempted = list_at(
2844 protected.span,
2845 vec![sym_at(head, LOWERED_ATTEMPTED), protected],
2846 );
2847 let failed = list_at(
2848 head,
2849 vec![sym_at(head, LOWERED_FAILED), sym_at(head, RESCUED)],
2850 );
2851 let caught = list_at(
2852 whole,
2853 vec![
2854 sym_at(head, "try"),
2855 attempted,
2856 list_at(
2857 head,
2858 vec![
2859 sym_at(head, "catch"),
2860 list_at(head, vec![sym_at(head, RESCUED)]),
2861 failed,
2862 ],
2863 ),
2864 ],
2865 );
2866 list_at(
2867 whole,
2868 vec![sym_at(head, LOWERED_ENSURE), caught, thunk(cleanup, head)],
2869 )
2870 }
2871 })
2872 }
2873
2874 fn def_form(&mut self, head: Span) -> Result<Spanned, Fail> {
2875 let name_span = self.peek_span();
2876 let name = match self.bump() {
2877 TokenKind::Ident(n) => n,
2878 other => {
2879 return Err(fail(
2880 format!("expected a name after `def`, found {other}"),
2881 name_span,
2882 ))
2883 }
2884 };
2885 self.not_reserved(&name, name_span, "a `def` name")?;
2886 let mut params: Vec<(String, Span, Option<Spanned>)> = Vec::new();
2887 let mut optional_from: Option<usize> = None;
2888 let mut rest: Vec<Spanned> = Vec::new();
2889 if !self.at(&TokenKind::LParen) {
2890 self.note(
2891 Spelling::ParameterList,
2892 name_span,
2893 &format!("def {name}"),
2894 &format!("def {name}()"),
2895 );
2896 }
2897 if self.at(&TokenKind::LParen) {
2898 self.bump();
2899 self.skip_newlines();
2900 if !self.eat(&TokenKind::RParen) {
2901 loop {
2902 self.skip_newlines();
2903 let p_span = self.peek_span();
2904 if self.at_star() {
2905 if optional_from.is_some() {
2906 return Err(fail(
2907 "a rest parameter cannot follow an optional one",
2908 p_span,
2909 ));
2910 }
2911 rest = self.rest_parameter()?.to_vec();
2912 self.rest_is_last()?;
2913 self.expect(&TokenKind::RParen, "`)` after the rest parameter")?;
2914 break;
2915 }
2916 match self.bump() {
2917 TokenKind::Ident(p) => {
2919 self.not_reserved(&p, p_span, "a parameter name")?;
2920 let p =
2921 self.optional_mark(p, p_span, &mut optional_from, params.len())?;
2922 params.push((p, p_span, None));
2923 }
2924 TokenKind::Label(p) => {
2926 self.not_reserved(&p, p_span, "a parameter name")?;
2927 let p =
2928 self.optional_mark(p, p_span, &mut optional_from, params.len())?;
2929 self.skip_newlines();
2930 let ty = self.type_expr()?;
2931 params.push((p, p_span, Some(ty)));
2932 }
2933 other => {
2934 return Err(fail(
2935 format!("expected a parameter name, found {other}"),
2936 p_span,
2937 ))
2938 }
2939 }
2940 self.skip_newlines();
2941 if self.eat(&TokenKind::Comma) {
2942 if self.trailing_comma(&TokenKind::RParen) {
2943 break;
2944 }
2945 continue;
2946 }
2947 self.expect(&TokenKind::RParen, "`,` or `)`")?;
2948 break;
2949 }
2950 }
2951 }
2952 let sig_span = self.span_since(name_span.start);
2954
2955 let ret = if matches!(self.peek(), TokenKind::Op(o) if o == "->") {
2957 self.bump();
2958 self.skip_newlines();
2959 Some(self.type_expr()?)
2960 } else {
2961 None
2962 };
2963
2964 let begins = {
2965 let save = self.pos;
2966 self.skip_newlines();
2967 let at = self.at_ident("begin").then(|| self.peek_span());
2968 self.pos = save;
2969 at
2970 };
2971 let body = self.protected_body(head)?;
2972 if let Some(b) = begins {
2973 let protects = matches!(&body.form, SpannedForm::List(items)
2974 if matches!(items.first().and_then(Spanned::as_symbol), Some("try") | Some(LOWERED_ENSURE)));
2975 if protects && body.span.start == b.start {
2976 self.note(
2977 Spelling::TryCatch,
2978 b,
2979 "begin … end as a whole def body",
2980 "the clauses on the def itself",
2981 );
2982 }
2983 }
2984 self.expect_ident("end")?;
2985 let whole = self.span_since(head.start);
2986
2987 let annotated = ret.is_some() || params.iter().any(|(_, _, t)| t.is_some());
2988 if !annotated {
2989 let mut sig = vec![sym_at(name_span, &name)];
2990 for (i, (p, s, _)) in params.into_iter().enumerate() {
2991 if optional_from == Some(i) {
2992 sig.push(sym_at(s, OPTIONAL_MARKER));
2993 }
2994 sig.push(sym_at(s, &p));
2995 }
2996 sig.extend(rest);
2997 return Ok(list_at(
2998 whole,
2999 vec![sym_at(head, "define"), list_at(sig_span, sig), body],
3000 ));
3001 }
3002
3003 let mut sig = vec![sym_at(name_span, &name)];
3011 for (i, (p, s, t)) in params.into_iter().enumerate() {
3012 if optional_from == Some(i) {
3013 sig.push(sym_at(s, OPTIONAL_MARKER));
3014 }
3015 let ty = t.unwrap_or_else(|| sym_at(s, "dyn"));
3016 sig.push(list_at(s.merge(ty.span), vec![sym_at(s, &p), ty]));
3017 }
3018 sig.extend(rest);
3019 Ok(list_at(
3020 whole,
3021 vec![
3022 sym_at(head, "define-typed"),
3023 list_at(sig_span, sig),
3024 ret.unwrap_or_else(|| sym_at(head, "dyn")),
3025 body,
3026 ],
3027 ))
3028 }
3029
3030 fn type_expr(&mut self) -> Result<Spanned, Fail> {
3032 let name_span = self.peek_span();
3033 let name = match self.bump() {
3034 TokenKind::Ident(n) => n,
3035 other => {
3036 return Err(fail(
3037 format!("expected a type name, found {other}"),
3038 name_span,
3039 ))
3040 }
3041 };
3042 self.not_reserved(&name, name_span, "a type name")?;
3043 if self.at(&TokenKind::LParen) {
3044 self.bump();
3045 self.skip_newlines();
3046 let mut list = vec![sym_at(name_span, &name)];
3047 if !self.eat(&TokenKind::RParen) {
3048 loop {
3049 self.skip_newlines();
3050 list.push(self.type_expr()?);
3051 self.skip_newlines();
3052 if self.eat(&TokenKind::Comma) {
3053 if self.trailing_comma(&TokenKind::RParen) {
3054 break;
3055 }
3056 continue;
3057 }
3058 self.expect(&TokenKind::RParen, "`,` or `)`")?;
3059 break;
3060 }
3061 }
3062 return Ok(list_at(self.span_since(name_span.start), list));
3063 }
3064 Ok(sym_at(name_span, &name))
3065 }
3066
3067 fn not_reserved(&self, name: &str, at: Span, what: &str) -> Result<(), Fail> {
3068 if is_reserved_word(name) {
3069 return Err(fail(
3070 format!("`{name}` is a reserved word and cannot be {what}"),
3071 at,
3072 ));
3073 }
3074 Ok(())
3075 }
3076
3077 fn eat_ident(&mut self, name: &str) -> bool {
3079 if self.at_ident(name) {
3080 self.bump();
3081 true
3082 } else {
3083 false
3084 }
3085 }
3086
3087 fn expect_ident(&mut self, name: &str) -> Result<(), Fail> {
3088 if self.at_ident(name) {
3089 self.bump();
3090 Ok(())
3091 } else {
3092 Err(self.error(format!("expected `{name}`, found {}", self.peek())))
3093 }
3094 }
3095
3096 fn body(&mut self, terminators: &[&str]) -> Result<Spanned, Fail> {
3099 let start = self.mark();
3102 let mut forms = Vec::new();
3103 loop {
3104 self.skip_newlines();
3105 if matches!(self.peek(), TokenKind::Eof) {
3106 return Err(self.error(format!(
3107 "unterminated block: expected {} before the end of the file",
3108 one_of(terminators)
3109 )));
3110 }
3111 if terminators.iter().any(|t| self.at_ident(t)) {
3112 break;
3113 }
3114 forms.push(self.statement()?);
3115 self.end_of_statement(terminators)?;
3116 }
3117 Ok(match forms.len() {
3118 0 => Spanned::new(Span::new(start, start), SpannedForm::Nil),
3119 1 => forms.into_iter().next().expect("checked len"),
3120 _ => {
3121 let span = cover(&forms, Span::new(start, start));
3127 let head = Span::new(span.start, span.start);
3133 let mut list = vec![sym_at(head, "begin")];
3134 list.extend(forms);
3135 list_at(span, list)
3136 }
3137 })
3138 }
3139}
3140
3141fn one_of(words: &[&str]) -> String {
3144 let quoted: Vec<String> = words.iter().map(|w| format!("`{w}`")).collect();
3145 match quoted.split_last() {
3146 None => String::new(),
3147 Some((last, [])) => last.clone(),
3148 Some((last, rest)) => format!("{} or {last}", rest.join(", ")),
3149 }
3150}
3151
3152fn let_bound(binder: Spanned, body: Spanned, at: Span) -> Spanned {
3153 let span = at.merge(body.span);
3154 let pair = list_at(binder.span, vec![binder, sym_at(at, RESCUED)]);
3155 list_at(
3156 span,
3157 vec![sym_at(at, "let"), list_at(pair.span, vec![pair]), body],
3158 )
3159}
3160
3161fn thunk(body: Spanned, at: Span) -> Spanned {
3162 let span = at.merge(body.span);
3163 list_at(
3164 span,
3165 vec![sym_at(at, "lambda"), list_at(at, Vec::new()), body],
3166 )
3167}
3168
3169#[must_use]
3174pub fn label_name(name: &str) -> bool {
3175 matches!(
3176 lex(&format!("{name}:")).as_deref(),
3177 Ok([t, e]) if matches!(&t.kind, TokenKind::Label(n) if n == name) && e.kind == TokenKind::Eof
3178 )
3179}
3180
3181fn blue_lang_name(name: &str) -> bool {
3182 matches!(
3183 lex(name).as_deref(),
3184 Ok([t, e]) if matches!(&t.kind, TokenKind::Ident(n) if n == name) && e.kind == TokenKind::Eof
3185 ) && !name.ends_with(['?', '!'])
3186}
3187
3188fn mentions(form: &Spanned, name: &str) -> bool {
3189 match &form.form {
3190 SpannedForm::Atom(Atom::Symbol(s)) => s == name,
3191 SpannedForm::List(items) => items.iter().any(|i| mentions(i, name)),
3192 SpannedForm::Quote(i)
3193 | SpannedForm::Quasiquote(i)
3194 | SpannedForm::Unquote(i)
3195 | SpannedForm::UnquoteSplice(i) => mentions(i, name),
3196 _ => false,
3197 }
3198}
3199
3200fn sym_at(span: Span, s: &str) -> Spanned {
3202 Spanned::new(span, SpannedForm::Atom(Atom::Symbol(s.to_string())))
3203}
3204
3205fn atom_at(span: Span, a: Atom) -> Spanned {
3207 Spanned::new(span, SpannedForm::Atom(a))
3208}
3209
3210fn list_at(span: Span, items: Vec<Spanned>) -> Spanned {
3212 Spanned::new(span, SpannedForm::List(items))
3213}
3214
3215fn cover(items: &[Spanned], fallback: Span) -> Span {
3223 let merged = items
3224 .iter()
3225 .fold(Span::synthetic(), |acc, s| acc.merge(s.span));
3226 if merged.is_synthetic() {
3227 fallback
3228 } else {
3229 merged
3230 }
3231}
3232
3233#[cfg(test)]
3234mod tests {
3235 use super::*;
3236
3237 fn q(src: &str) -> String {
3241 parse_expr(src)
3242 .map(|s| s.to_string())
3243 .unwrap_or_else(|e| panic!("{src:?}: {e}"))
3244 }
3245
3246 #[test]
3247 fn a_reserved_word_is_not_a_def_name_a_parameter_or_a_type() {
3248 for src in [
3249 "def def a(x)\n x\nend",
3250 "def end()\n 1\nend",
3251 "def f(end)\n 1\nend",
3252 "def f(a: not)\n 1\nend",
3253 "def f(a) -> not a\n a\nend",
3254 "def f(a: List(1 + 2))\n a\nend",
3255 "defmacro defmacro m(a)\n quote\n a\n end\nend",
3256 ] {
3257 assert!(parse_program(src).is_err(), "{src:?} parsed");
3258 }
3259 }
3260
3261 #[test]
3262 fn a_type_application_takes_types_of_any_arity() {
3263 assert_eq!(
3264 q("def f(a: Map(Str, List(Int))) -> Unit()\n a\nend"),
3265 "(define-typed (f (a (Map Str (List Int)))) (Unit) a)"
3266 );
3267 }
3268
3269 #[test]
3272 fn arithmetic_respects_precedence() {
3273 assert_eq!(q("1 + 2 * 3"), "(+ 1 (* 2 3))");
3274 assert_eq!(q("(1 + 2) * 3"), "(* (+ 1 2) 3)");
3275 }
3276
3277 #[test]
3278 fn comparison_binds_looser_than_arithmetic() {
3279 assert_eq!(q("a + 1 < b"), "(< (+ a 1) b)");
3280 }
3281
3282 #[test]
3287 fn logical_operators_bind_loosest_and_lower_to_tataras_names() {
3288 assert_eq!(q("a && b || c"), "(or (and a b) c)");
3289 }
3290
3291 #[test]
3292 fn left_associativity() {
3293 assert_eq!(q("1 - 2 - 3"), "(- (- 1 2) 3)");
3294 }
3295
3296 #[test]
3297 fn a_dot_without_parentheses_reads_a_field() {
3298 assert_eq!(q("user.name"), "(blue-field user :name)");
3299 }
3300
3301 #[test]
3302 fn method_call_with_args() {
3303 assert_eq!(q("user.greet(1, 2)"), "(greet user 1 2)");
3304 }
3305
3306 #[test]
3307 fn chained_fields_read_left_to_right() {
3308 assert_eq!(q("a.b.c"), "(blue-field (blue-field a :b) :c)");
3309 assert_eq!(q("a.b(1).c"), "(blue-field (b a 1) :c)");
3310 }
3311
3312 #[test]
3313 fn plain_call() {
3314 assert_eq!(q("f(1, 2)"), "(f 1 2)");
3315 }
3316
3317 #[test]
3320 fn pipeline_threads_into_first_argument() {
3321 assert_eq!(q("x |> f"), "(f x)");
3322 assert_eq!(q("x |> f(1)"), "(f x 1)");
3323 assert_eq!(q("x |> f |> g"), "(g (f x))");
3324 }
3325
3326 #[test]
3327 fn pipeline_binds_looser_than_arithmetic() {
3328 assert_eq!(q("1 + 2 |> f"), "(f (+ 1 2))");
3329 }
3330
3331 #[test]
3337 fn label_and_rocket_produce_the_same_tree_for_a_symbol_key() {
3338 assert_eq!(q("{a: 1}"), q("{:a => 1}"));
3339 assert_eq!(q("{a: 1}"), "(hash-map :a 1)");
3340 }
3341
3342 #[test]
3345 fn a_string_key_has_no_shorthand() {
3346 assert_eq!(q(r#"{"k" => 1}"#), r#"(hash-map "k" 1)"#);
3347 }
3348
3349 #[test]
3350 fn a_newline_escape_and_its_lines_are_one_tree() {
3351 for (escaped, lines) in [
3352 (r#""a\nb""#, r#"join_lines(["a", "b"])"#),
3353 (r##""#{m}\n#{h}""##, "join_lines([m, h])"),
3354 (r##""n=#{n}\ndone\n""##, r##"unlines(["n=#{n}", "done"])"##),
3355 (r#""\n""#, "line_break"),
3356 (r#""a\n\n\tb\n""#, r#"unlines(["a", "", "\tb"])"#),
3357 (r#""\na""#, r#"join_lines(["", "a"])"#),
3358 (r#""\n\n""#, r#"unlines(["", ""])"#),
3359 ] {
3360 assert_eq!(q(escaped), q(lines), "{escaped}");
3361 }
3362 assert_eq!(q(r#""a\tb""#), q(r#""a\tb""#));
3363 assert!(!q(r#""a\tb""#).contains("join_lines"));
3364 }
3365
3366 #[test]
3367 fn a_newline_escape_is_noted_and_a_quoted_one_is_data() {
3368 let notes = spelling_notes(r#""a\nb""#).unwrap();
3369 assert_eq!(notes.len(), 1);
3370 assert_eq!(notes[0].spelling, Spelling::NewlineEscape);
3371 assert!(spelling_notes(r#""a\tb""#).unwrap().is_empty());
3372 let quoted = q("def f()\n quote\n \"x\\ny\"\n end\nend\n");
3373 assert!(!quoted.contains("join_lines"), "{quoted}");
3374 assert!(
3375 spelling_notes("def f()\n quote\n \"x\\ny\"\n end\nend\n")
3376 .unwrap()
3377 .is_empty()
3378 );
3379 }
3380
3381 #[test]
3382 fn list_literal() {
3383 assert_eq!(q("[1, 2, 3]"), "(list 1 2 3)");
3384 assert_eq!(q("[]"), "(list)");
3385 }
3386
3387 #[test]
3390 fn if_else_end() {
3391 assert_eq!(q("if a\n 1\nelse\n 2\nend"), "(if a 1 2)");
3392 }
3393
3394 #[test]
3395 fn if_without_else() {
3396 assert_eq!(q("if a\n 1\nend"), "(if a 1)");
3397 }
3398
3399 #[test]
3402 fn unless_is_a_negated_if() {
3403 assert_eq!(q("unless a\n 1\nend"), "(if (not a) 1)");
3404 }
3405
3406 #[test]
3407 fn multi_statement_body_becomes_begin() {
3408 assert_eq!(q("if a\n 1\n 2\nend"), "(if a (begin 1 2))");
3409 }
3410
3411 #[test]
3412 fn def_lowers_to_define() {
3413 assert_eq!(
3414 q("def add(a, b)\n a + b\nend"),
3415 "(define (add a b) (+ a b))"
3416 );
3417 }
3418
3419 #[test]
3420 fn def_with_no_params() {
3421 assert_eq!(q("def zero()\n 0\nend"), "(define (zero) 0)");
3422 }
3423
3424 fn refused(src: &str) -> String {
3425 match parse_expr(src) {
3426 Ok(s) => panic!("{src:?} should be refused, parsed to {s}"),
3427 Err(e) => e.to_string(),
3428 }
3429 }
3430
3431 #[test]
3432 fn a_question_mark_parameter_is_optional() {
3433 assert_eq!(q("def f(a, b?)\n b\nend"), "(define (f a &optional b) b)");
3434 assert_eq!(q("def f(a?, b?)\n a\nend"), "(define (f &optional a b) a)");
3435 assert_eq!(q("def f(a?)\n a\nend"), "(define (f &optional a) a)");
3436 }
3437
3438 #[test]
3439 fn an_annotated_optional_parameter_keeps_its_type_after_the_marker() {
3440 assert_eq!(
3441 q("def f(a: Int, b?: Str) -> Int\n a\nend"),
3442 "(define-typed (f (a Int) &optional (b Str)) Int a)"
3443 );
3444 assert_eq!(
3445 q("def f(a, b?: Str)\n a\nend"),
3446 "(define-typed (f (a dyn) &optional (b Str)) dyn a)"
3447 );
3448 }
3449
3450 #[test]
3451 fn a_def_without_question_marks_carries_no_marker() {
3452 assert_eq!(q("def f(a, b)\n a\nend"), "(define (f a b) a)");
3453 assert_eq!(q("def f(a, *r)\n a\nend"), "(define (f a &rest r) a)");
3454 }
3455
3456 #[test]
3457 fn a_required_parameter_cannot_follow_an_optional_one() {
3458 assert!(refused("def f(a?, b)\n a\nend").contains("`b` cannot follow an optional"));
3459 assert!(refused("def f(a, b?, c: Int)\n a\nend").contains("`c` cannot follow"));
3460 }
3461
3462 #[test]
3463 fn a_rest_parameter_cannot_follow_an_optional_one() {
3464 assert!(refused("def f(a, b?, *r)\n a\nend").contains("rest parameter cannot follow"));
3465 }
3466
3467 #[test]
3470 fn literals_lower_to_atoms() {
3471 assert_eq!(q("42"), "42");
3472 assert_eq!(q("true"), "#t");
3473 assert_eq!(q(":ok"), ":ok");
3474 assert_eq!(q(r#""hi""#), r#""hi""#);
3475 }
3476
3477 #[test]
3478 fn unary_minus_and_not() {
3479 assert_eq!(q("-x"), "(- 0 x)");
3480 assert_eq!(q("!x"), "(not x)");
3481 }
3482
3483 #[test]
3486 fn a_program_is_a_sequence_of_forms() {
3487 let forms = parse_program("def f()\n 1\nend\nf()").expect("parse");
3488 assert_eq!(forms.len(), 2);
3489 assert_eq!(forms[1].to_string(), "(f)");
3490 }
3491
3492 #[test]
3493 fn unterminated_block_is_an_error_naming_what_was_expected() {
3494 let e = parse_program("if a\n 1").expect_err("must fail");
3495 assert!(e.message.contains("unterminated"), "{}", e.message);
3496 }
3497
3498 #[test]
3499 fn a_parse_error_carries_a_span_into_the_source() {
3500 let src = "1 + )";
3501 let e = parse_program(src).expect_err("must fail");
3502 assert!(e.span.start < src.len(), "span {:?} outside source", e.span);
3503 }
3504
3505 #[test]
3512 fn a_spaced_index_is_refused_naming_the_touching_one() {
3513 let e = parse_program("y = xs [0]").expect_err("must not split into two statements");
3514 assert!(e.message.contains("`xs[0]`"), "{}", e.message);
3515 assert_eq!((e.line, e.col), (1, 8), "{e}");
3516 }
3517
3518 #[test]
3519 fn an_index_is_the_tree_of_get_or_nth() {
3520 for (short, long) in [
3521 ("m[:k]", "get(m, :k)"),
3522 (r#"m["k"]"#, r#"get(m, "k")"#),
3523 ("xs[0]", "nth(0, xs)"),
3524 ("xs[i + 1]", "nth(i + 1, xs)"),
3525 ("f(x)[2]", "nth(2, f(x))"),
3526 ("m[:a][:b]", "get(get(m, :a), :b)"),
3527 ("xs[0][:k]", "get(nth(0, xs), :k)"),
3528 ("[1, 2][1]", "nth(1, [1, 2])"),
3529 ] {
3530 assert_eq!(q(short), q(long), "{short}");
3531 }
3532 assert_eq!(q("get(m, k)"), "(get m k)");
3533 let notes = spelling_notes("get(m, :k)\nnth(0, xs)\nget(m, k)\n").unwrap();
3534 assert_eq!(
3535 notes
3536 .iter()
3537 .filter(|n| n.spelling == Spelling::Index)
3538 .count(),
3539 2
3540 );
3541 }
3542
3543 #[test]
3545 fn a_call_without_parentheses_is_refused_naming_the_call() {
3546 let e = parse_program("a = f x").expect_err("must not split into two statements");
3547 assert!(e.message.contains("`f(x)`"), "{}", e.message);
3548 assert_eq!((e.line, e.col), (1, 7), "{e}");
3549 }
3550
3551 #[test]
3553 fn two_statements_on_one_line_are_refused_in_a_body() {
3554 for src in [
3555 "def f(xs)\n xs [0]\nend",
3556 "if a\n f x\nend",
3557 "fn(x) x 1 end",
3558 "test \"t\"\n assert a b\nend",
3559 "1 2",
3560 ] {
3561 assert!(parse_program(src).is_err(), "{src:?} must not parse");
3562 }
3563 }
3564
3565 #[test]
3567 fn an_arrow_lambda_lowers_to_the_same_tree_as_fn() {
3568 for (arrow, block) in [
3569 ("(x) -> x * 2", "fn(x) x * 2 end"),
3570 ("(a, b) -> a + b", "fn(a, b) a + b end"),
3571 ("() -> 1", "fn() 1 end"),
3572 ("map(xs, (x) -> x + 1)", "map(xs, fn(x) x + 1 end)"),
3573 ] {
3574 let a = parse_program(arrow).expect(arrow);
3575 let b = parse_program(block).expect(block);
3576 assert_eq!(format!("{a:?}"), format!("{b:?}"), "{arrow}");
3577 }
3578 assert_eq!(
3579 parse_program("(x)").map(|p| p.len()).ok(),
3580 Some(1),
3581 "a parenthesised name is still a group"
3582 );
3583 }
3584
3585 #[test]
3589 fn a_statement_may_end_at_a_newline_the_end_or_a_closing_keyword() {
3590 for src in [
3591 "fn(x) x + 1 end",
3592 "map(fn(x) x end, xs)",
3593 "if a\n 1\nelse\n 2\nend",
3594 "if a 1 else 2 end",
3595 "case x\nwhen 1 :a\nwhen 2 :b\nelse :c\nend",
3596 "x = 1\ny = 2",
3597 "x = 1",
3598 ] {
3599 assert!(
3600 parse_program(src).is_ok(),
3601 "{src:?}: {:?}",
3602 parse_program(src)
3603 );
3604 }
3605 }
3606
3607 #[test]
3609 fn then_is_refused_by_name() {
3610 let e = parse_program("x = if c then 1 else 2 end").expect_err("then is not a keyword");
3611 assert!(e.message.contains("`then`"), "{}", e.message);
3612 }
3613
3614 #[test]
3616 fn a_brace_block_is_refused_naming_fn() {
3617 let e = parse_program("xs.map { |x| x }").expect_err("no brace blocks");
3618 assert!(e.message.contains("fn(x)"), "{}", e.message);
3619 }
3620
3621 #[test]
3628 fn a_qualified_name_is_one_symbol() {
3629 assert_eq!(q("retsu::first(xs)"), "(retsu/first xs)");
3630 assert_eq!(q("retsu::first"), "retsu/first");
3631 assert_eq!(q("xs |> retsu::first"), "(retsu/first xs)");
3632 assert_eq!(q("blue::length(xs)"), "(blue/length xs)");
3633 }
3634
3635 #[test]
3637 fn whitespace_around_the_qualifier_is_accepted() {
3638 assert_eq!(q("retsu :: first(xs)"), "(retsu/first xs)");
3639 }
3640
3641 #[test]
3644 fn a_reserved_word_after_the_qualifier_is_a_name() {
3645 assert_eq!(q("kueri::if(a, b)"), "(kueri/if a b)");
3646 assert_eq!(q("heni::test(x)"), "(heni/test x)");
3647 assert_eq!(q("sabi::fn"), "sabi/fn");
3648 }
3649
3650 #[test]
3651 fn a_qualified_name_has_exactly_one_qualifier() {
3652 for (src, says) in [
3653 ("::first(xs)", "`package::name`"),
3654 ("a::b::c", "one qualifier"),
3655 ("if::x", "reserved word"),
3656 ("retsu::1", "expected a name after `retsu::`"),
3657 ] {
3658 let e = parse_program(src).expect_err(src);
3659 assert!(e.message.contains(says), "{src}: {}", e.message);
3660 }
3661 }
3662
3663 #[test]
3666 fn qualified_reads_only_the_package_name_shape() {
3667 assert_eq!(qualified("retsu/first"), Some(("retsu", "first")));
3668 assert_eq!(qualified("kueri/if"), Some(("kueri", "if")));
3669 assert_eq!(qualified("nisshi/pair?"), Some(("nisshi", "pair?")));
3670 for not in ["/", "a/", "/a", "a/b/c", "if/x", "hash-map", "a"] {
3671 assert_eq!(qualified(not), None, "{not}");
3672 }
3673 assert!(is_callable_name("kueri/if"));
3674 }
3675
3676 #[test]
3681 fn a_symbol_may_end_in_a_question_or_bang() {
3682 assert_eq!(q("[:pair?, :set!]"), "(list :pair? :set!)");
3683 assert_eq!(q("use(\"n\", [:empty?])"), "(use \"n\" (list :empty?))");
3684 assert_eq!(q(":a != :b"), "(not= :a :b)");
3685 }
3686
3687 #[test]
3691 fn a_parse_error_displays_line_col_and_the_source_text() {
3692 let e = parse_program("x = 1\ny = [1, 2 y]").expect_err("must fail");
3693 assert_eq!(e.to_string(), "2:11: expected `,` or `]`, found `y`");
3694 assert_eq!(e.span, Span::new(16, 17), "the span stays, as data");
3695 }
3696
3697 #[test]
3699 fn no_error_message_names_a_rust_variant() {
3700 for src in [
3701 "f(1 2)",
3702 "{a: 1 b}",
3703 "def 1\nend",
3704 "x.1",
3705 "if a\n",
3706 "fn(1) 2 end",
3707 "[1 :b]",
3708 "1 +",
3709 "(1",
3710 ] {
3711 let e = parse_program(src).expect_err(src);
3712 let shown = e.to_string();
3713 for tell in [
3714 "Ident(", "Int(", "Op(", "Label(", "Sym(", "Str(", "LParen", "RBracket", "Eof",
3715 "Newline", "..",
3716 ] {
3717 assert!(!shown.contains(tell), "{src:?} -> {shown}");
3718 }
3719 assert!(e.line >= 1 && e.col >= 1, "{src:?} not located: {shown}");
3720 }
3721 }
3722
3723 #[test]
3725 fn a_lex_error_is_located_too() {
3726 let e = parse_program("x = 1\ny = 2; z = 3").expect_err("no semicolons");
3727 assert_eq!((e.line, e.col), (2, 6), "{e}");
3728 assert!(e.message.contains("new line"), "{e}");
3729 }
3730
3731 #[test]
3734 fn the_parser_rejects_garbage() {
3735 assert!(parse_program("def").is_err());
3736 assert!(parse_program("(1").is_err());
3737 assert!(parse_program("end").is_err());
3738 }
3739}