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::Words { .. }
1065 | TokenKind::True
1066 | TokenKind::False
1067 | TokenKind::Nil,
1068 ) => {
1069 let arg = match next {
1070 TokenKind::Ident(a) => a.clone(),
1071 _ => "x".to_string(),
1072 };
1073 format!("a call needs parentheses: write `{callee}({arg})`")
1074 }
1075 _ => "put each statement on its own line".to_string(),
1076 };
1077 Err(self.error(format!("expected the end of the line, found {next}: {why}")))
1078 }
1079
1080 fn statement(&mut self) -> Result<Spanned, Fail> {
1093 let max = self.max_depth;
1107 if self.depth >= max {
1108 return Err(self.error(format!(
1109 "statement nests deeper than {max}; refusing to \
1110 recurse further (this is a limit, not a syntax error)"
1111 )));
1112 }
1113 self.depth += 1;
1114 let r = self.statement_inner();
1115 self.depth -= 1;
1116 r
1117 }
1118
1119 fn statement_inner(&mut self) -> Result<Spanned, Fail> {
1120 if let TokenKind::Ident(name) = self.peek().clone() {
1121 if self.peek_at(1) == "=" && !is_reserved_word(&name) {
1122 let name_span = self.peek_span();
1123 self.bump(); self.bump(); self.skip_newlines();
1126 let value = self.expr(0)?;
1127 return Ok(list_at(
1131 self.span_since(name_span.start),
1132 vec![sym_at(name_span, "define"), sym_at(name_span, &name), value],
1133 ));
1134 }
1135 }
1136 let e = self.expr(0)?;
1137 if let SpannedForm::List(items) = &e.form {
1138 if items.len() == 3
1139 && items[0].as_symbol() == Some("define")
1140 && items[1].as_symbol().is_some()
1141 && self.text(items[0].span) == "define"
1142 {
1143 self.note(Spelling::OperatorSpelling, e.span, "define(x, v)", "x = v");
1144 }
1145 }
1146 Ok(e)
1147 }
1148
1149 fn peek_at(&self, n: usize) -> String {
1152 match self.toks.get(self.pos + n).map(|t| &t.kind) {
1153 Some(TokenKind::Op(o)) => o.clone(),
1154 _ => String::new(),
1155 }
1156 }
1157
1158 fn expr(&mut self, min_bp: u8) -> Result<Spanned, Fail> {
1160 let max = self.max_depth;
1170 if self.depth >= max {
1171 return Err(self.error(format!(
1172 "expression nests deeper than {max}; refusing to \
1173 recurse further (this is a limit, not a syntax error)"
1174 )));
1175 }
1176 self.depth += 1;
1177 let r = self.expr_inner(min_bp);
1178 self.depth -= 1;
1179 r
1180 }
1181
1182 #[inline(never)]
1183 fn rescue_node(&self, start: usize, op_span: Span, lhs: Spanned, fallback: Spanned) -> Spanned {
1184 list_at(
1185 self.span_since(start),
1186 vec![
1187 sym_at(op_span, "try"),
1188 lhs,
1189 list_at(
1190 op_span.merge(fallback.span),
1191 vec![
1192 sym_at(op_span, "catch"),
1193 list_at(op_span, vec![sym_at(op_span, "_")]),
1194 fallback,
1195 ],
1196 ),
1197 ],
1198 )
1199 }
1200
1201 #[inline(never)]
1202 fn infix_spelling(&mut self, callee: Option<&str>, op_span: Span) {
1203 let written = self.text(op_span).to_string();
1204 match (callee, self.peek().clone()) {
1205 (None, _) => self.note(Spelling::CallForm, op_span, "|>", "a call"),
1206 (Some(c), TokenKind::Op(o)) if word_for(&o) != o => {
1207 self.note(Spelling::LogicalSymbol, op_span, &written, c);
1208 }
1209 (Some(_), TokenKind::Op(o)) if written != o => {
1210 self.note(Spelling::OperatorSpelling, op_span, &written, &o);
1211 }
1212 _ => {}
1213 }
1214 }
1215
1216 #[inline(never)]
1217 fn nil_test(
1218 &mut self,
1219 start: usize,
1220 op_span: Span,
1221 lhs: &Spanned,
1222 rhs: &Spanned,
1223 ) -> Option<Spanned> {
1224 let (subject, written) = match (&lhs.form, &rhs.form) {
1225 (_, SpannedForm::Nil) if !matches!(lhs.form, SpannedForm::Nil) => (lhs, "x == nil"),
1226 (SpannedForm::Nil, _) if !matches!(rhs.form, SpannedForm::Nil) => (rhs, "nil == x"),
1227 _ => return None,
1228 };
1229 let span = self.span_since(start);
1230 self.note(Spelling::NilTest, span, written, "x.nil?");
1231 Some(list_at(
1232 span,
1233 vec![
1234 sym_at(op_span, LOWERED_FIELD),
1235 subject.clone(),
1236 atom_at(op_span, Atom::Keyword("nil?".to_string())),
1237 ],
1238 ))
1239 }
1240
1241 #[inline(never)]
1242 fn infix_node(
1243 &mut self,
1244 start: usize,
1245 callee: Option<&str>,
1246 op_span: Span,
1247 lhs: Spanned,
1248 rhs: Spanned,
1249 ) -> Spanned {
1250 if callee == Some("-") && matches!(lhs.form, SpannedForm::Atom(Atom::Int(0))) {
1251 self.note(
1252 Spelling::OperatorSpelling,
1253 lhs.span.merge(op_span),
1254 "0 - x",
1255 "-x",
1256 );
1257 }
1258
1259 if callee == Some("equal?") {
1260 if let Some(test) = self.nil_test(start, op_span, &lhs, &rhs) {
1261 return test;
1262 }
1263 }
1264
1265 if callee.is_none() {
1266 let rhs_span = rhs.span;
1271 let threaded = match rhs.form {
1272 SpannedForm::List(mut items) if !items.is_empty() => {
1273 items.insert(1, lhs);
1274 list_at(self.span_since(start), items)
1275 }
1276 callee => list_at(
1277 self.span_since(start),
1278 vec![Spanned::new(rhs_span, callee), lhs],
1279 ),
1280 };
1281 match &threaded.form {
1282 SpannedForm::List(items)
1283 if items.len() == 2 && items[0].as_symbol() == Some("not") =>
1284 {
1285 let (head, operand) = (items[0].span, items[1].clone());
1286 self.negation(head, threaded.span, operand)
1287 }
1288 SpannedForm::List(items)
1289 if items.len() == 4
1290 && items[0].as_symbol() == Some("if")
1291 && matches!(items[3].form, SpannedForm::Nil) =>
1292 {
1293 list_at(threaded.span, items[..3].to_vec())
1294 }
1295 SpannedForm::List(items)
1296 if items.len() == 3 && items[0].as_symbol() == Some("equal?") =>
1297 {
1298 let (op, a, b) = (items[0].span, items[1].clone(), items[2].clone());
1299 match self.nil_test(start, op, &a, &b) {
1300 Some(test) => test,
1301 None => threaded,
1302 }
1303 }
1304 _ => threaded,
1305 }
1306 } else {
1307 list_at(
1313 self.span_since(start),
1314 vec![sym_at(op_span, callee.unwrap_or_default()), lhs, rhs],
1315 )
1316 }
1317 }
1318
1319 fn expr_inner(&mut self, min_bp: u8) -> Result<Spanned, Fail> {
1320 let start = self.mark();
1321 let mut lhs = self.prefix()?;
1322
1323 loop {
1324 match self.peek() {
1326 TokenKind::Dot => {
1327 let dot = self.peek_span();
1328 self.bump();
1329 lhs = self.finish_send(start, lhs, dot)?;
1330 continue;
1331 }
1332 TokenKind::LParen | TokenKind::LBracket
1333 if self.at(&TokenKind::LParen) || self.touches_previous() =>
1334 {
1335 lhs = self.applied(start, lhs)?;
1336 continue;
1337 }
1338 _ => {}
1339 }
1340
1341 let op_span = self.peek_span();
1345 if self.at_ident("rescue") {
1346 if RESCUE_POWER.0 < min_bp {
1347 break;
1348 }
1349 self.bump();
1350 self.skip_newlines();
1351 let fallback = self.expr(RESCUE_POWER.1)?;
1352 lhs = self.rescue_node(start, op_span, lhs, fallback);
1353 continue;
1354 }
1355 let (callee, (lbp, rbp)) = match self.peek() {
1356 TokenKind::Pipe => (None, PIPE_POWER),
1357 other => match infix_of(other) {
1358 Some(i) => (Some(i.callee), i.power),
1359 None => break,
1360 },
1361 };
1362 if lbp < min_bp {
1363 break;
1364 }
1365 self.infix_spelling(callee, op_span);
1366 self.bump();
1367 self.skip_newlines();
1368 let rhs = self.expr(rbp)?;
1369 lhs = self.infix_node(start, callee, op_span, lhs, rhs);
1370 }
1371
1372 Ok(lhs)
1373 }
1374
1375 #[inline(never)]
1376 fn interpolation(span: Span, parts: &[String], exprs: &[String]) -> Result<Spanned, Fail> {
1377 let mut acc = atom_at(span, Atom::Str(parts[0].clone()));
1387 for (i, raw) in exprs.iter().enumerate() {
1388 let inner = parse_expr(raw).map_err(|e| {
1389 fail(
1390 format!("in interpolation `#{{{raw}}}`: {}", e.message),
1391 span,
1392 )
1393 })?;
1394 let inner = Spanned::from_sexp_at(&inner, span);
1395 acc = list_at(span, vec![sym_at(span, LOWERED_CONCAT), acc, inner]);
1396 acc = list_at(
1399 span,
1400 vec![
1401 sym_at(span, LOWERED_CONCAT),
1402 acc,
1403 atom_at(span, Atom::Str(parts[i + 1].clone())),
1404 ],
1405 );
1406 }
1407 Ok(acc)
1408 }
1409
1410 #[inline(never)]
1417 fn lines_literal(
1418 &mut self,
1419 span: Span,
1420 parts: Vec<String>,
1421 exprs: Vec<String>,
1422 ) -> Result<Spanned, Fail> {
1423 let mut lines: Vec<(Vec<String>, Vec<String>)> = vec![(vec![String::new()], Vec::new())];
1424 for (i, part) in parts.iter().enumerate() {
1425 let mut pieces = part.split('\n');
1426 if let Some(head) = pieces.next() {
1427 if let Some(last) = lines.last_mut().and_then(|l| l.0.last_mut()) {
1428 last.push_str(head);
1429 }
1430 }
1431 for piece in pieces {
1432 lines.push((vec![piece.to_string()], Vec::new()));
1433 }
1434 if let Some(raw) = exprs.get(i) {
1435 if let Some(line) = lines.last_mut() {
1436 line.1.push(raw.clone());
1437 line.0.push(String::new());
1438 }
1439 }
1440 }
1441 let empty = |l: &(Vec<String>, Vec<String>)| l.1.is_empty() && l.0 == [""];
1442 if lines.len() == 2 && lines.iter().all(empty) {
1443 self.note(Spelling::NewlineEscape, span, "\"\\n\"", "line_break");
1444 return Ok(sym_at(span, "line_break"));
1445 }
1446 let trailing = lines.last().is_some_and(empty);
1447 if trailing {
1448 lines.pop();
1449 }
1450 let head = if trailing { "unlines" } else { "join_lines" };
1451 let mut items = vec![sym_at(span, "list")];
1452 for (lits, raws) in &lines {
1453 let item = if raws.is_empty() {
1454 atom_at(span, Atom::Str(lits[0].clone()))
1455 } else if raws.len() == 1 && lits.iter().all(String::is_empty) {
1456 let inner = parse_expr(&raws[0]).map_err(|e| {
1457 fail(
1458 format!("in interpolation `#{{{}}}`: {}", raws[0], e.message),
1459 span,
1460 )
1461 })?;
1462 Spanned::from_sexp_at(&inner, span)
1463 } else {
1464 Self::interpolation(span, lits, raws)?
1465 };
1466 items.push(item);
1467 }
1468 self.note(
1469 Spelling::NewlineEscape,
1470 span,
1471 "\"…\\n…\"",
1472 &format!("{head}([…])"),
1473 );
1474 Ok(list_at(
1475 span,
1476 vec![sym_at(span, head), list_at(span, items)],
1477 ))
1478 }
1479
1480 #[inline(never)]
1484 fn applied(&mut self, start: usize, lhs: Spanned) -> Result<Spanned, Fail> {
1485 if self.at(&TokenKind::LParen) {
1486 let (args, splat) = self.call_args()?;
1487 let call = self.call_node(self.span_since(start), vec![lhs], args, splat);
1488 Ok(self.call_spelling(call))
1489 } else {
1490 self.index(start, lhs)
1491 }
1492 }
1493
1494 #[inline(never)]
1497 fn index(&mut self, start: usize, object: Spanned) -> Result<Spanned, Fail> {
1498 let open = self.peek_span();
1499 self.bump();
1500 self.skip_newlines();
1501 let key = self.expr(0)?;
1502 self.skip_newlines();
1503 self.expect(&TokenKind::RBracket, "`]` closing the index")?;
1504 let span = self.span_since(start);
1505 Ok(if is_index_key(&key) {
1506 list_at(span, vec![sym_at(open, "get"), object, key])
1507 } else {
1508 list_at(span, vec![sym_at(open, "nth"), key, object])
1509 })
1510 }
1511
1512 #[inline(never)]
1513 fn not_prefix(&mut self, span: Span) -> Result<Spanned, Fail> {
1514 if self.at(&TokenKind::LParen) && self.touches_previous() {
1515 self.note(Spelling::OperatorSpelling, span, "not(x)", "not x");
1516 let (args, splat) = self.call_args()?;
1517 if args.len() != 1 || splat.is_some() {
1518 let call = self.call_node(
1519 self.span_since(span.start),
1520 vec![sym_at(span, "not")],
1521 args,
1522 splat,
1523 );
1524 return Ok(call);
1525 }
1526 let operand = args.into_iter().next().expect("one argument");
1527 return Ok(self.negation(span, self.span_since(span.start), operand));
1528 }
1529 let rhs = self.expr(11)?;
1530 Ok(self.negation(span, self.span_since(span.start), rhs))
1531 }
1532
1533 fn prefix(&mut self) -> Result<Spanned, Fail> {
1534 if self.at(&TokenKind::LParen) {
1535 let span = self.peek_span();
1536 self.bump();
1537 if self.arrow_lambda_ahead() {
1538 return self.arrow_lambda(span);
1539 }
1540 return self.group();
1541 }
1542 self.prefix_token()
1543 }
1544
1545 fn group(&mut self) -> Result<Spanned, Fail> {
1546 self.skip_newlines();
1547 let inner = self.expr(0)?;
1548 self.skip_newlines();
1549 self.expect(&TokenKind::RParen, "`)`")?;
1550 Ok(inner)
1551 }
1552
1553 #[inline(never)]
1554 fn prefix_token(&mut self) -> Result<Spanned, Fail> {
1555 let span = self.peek_span();
1556 match self.bump() {
1557 TokenKind::Int(v) => Ok(atom_at(span, Atom::Int(v))),
1558 TokenKind::Float(v) => Ok(atom_at(span, Atom::Float(v))),
1559 TokenKind::Str(s) if self.quoting == 0 && s.contains('\n') => {
1560 self.lines_literal(span, vec![s], Vec::new())
1561 }
1562 TokenKind::Str(s) => Ok(atom_at(span, Atom::Str(s))),
1563 TokenKind::Doc { module, .. } => Err(fail(
1564 format!(
1565 "`{}` sits at the top level, above the definition it documents, never \
1566 inside a form",
1567 if module { "@moduledoc" } else { "@doc" }
1568 ),
1569 span,
1570 )),
1571
1572 TokenKind::InterpolatedStr { parts, exprs }
1583 if self.quoting == 0 && parts.iter().any(|p| p.contains('\n')) =>
1584 {
1585 self.lines_literal(span, parts, exprs)
1586 }
1587 TokenKind::InterpolatedStr { parts, exprs } => Self::interpolation(span, &parts, &exprs),
1588 TokenKind::Sym(s) => Ok(atom_at(span, Atom::Keyword(s))),
1589 TokenKind::Words { flavor, words } => {
1590 let open = Span::new(span.start, span.start + 3);
1591 let mut items = vec![sym_at(open, "list")];
1592 items.extend(words.into_iter().map(|(w, at)| {
1593 atom_at(
1594 at,
1595 match flavor {
1596 crate::words::Flavor::Strings => Atom::Str(w),
1597 crate::words::Flavor::Keywords => Atom::Keyword(w),
1598 },
1599 )
1600 }));
1601 Ok(list_at(span, items))
1602 }
1603 TokenKind::True => Ok(atom_at(span, Atom::Bool(true))),
1604 TokenKind::False => Ok(atom_at(span, Atom::Bool(false))),
1605 TokenKind::Nil => Ok(Spanned::new(span, SpannedForm::Nil)),
1606
1607 TokenKind::Op(o) if o == "-" => {
1608 let rhs = self.expr(11)?; Ok(list_at(
1610 self.span_since(span.start),
1611 vec![sym_at(span, "-"), atom_at(span, Atom::Int(0)), rhs],
1612 ))
1613 }
1614 TokenKind::Op(o) if o == "!" => {
1615 let written = self.text(span).to_string();
1616 self.note(Spelling::LogicalSymbol, span, &written, "not");
1617 let rhs = self.expr(11)?;
1618 Ok(self.negation(span, self.span_since(span.start), rhs))
1619 }
1620 TokenKind::Ident(w) if w == "not" => self.not_prefix(span),
1621 TokenKind::Ident(w) if (w == "and" || w == "or") && self.at(&TokenKind::LParen) => {
1622 Ok(sym_at(span, &w))
1623 }
1624 TokenKind::Ident(w) if w == "and" || w == "or" => Err(fail(
1625 format!("`{w}` joins two expressions: `a {w} b`"),
1626 span,
1627 )),
1628
1629 TokenKind::LParen => {
1630 self.skip_newlines();
1631 let inner = self.expr(0)?;
1632 self.skip_newlines();
1633 self.expect(&TokenKind::RParen, "`)`")?;
1634 Ok(inner)
1638 }
1639
1640 TokenKind::LBracket => self.list_literal(span),
1641 TokenKind::LBrace => self.map_literal(span),
1642
1643 TokenKind::Ident(name) if self.at(&TokenKind::PathSep) => {
1644 self.qualified_name(span, &name)
1645 }
1646 TokenKind::PathSep => Err(fail(
1647 "a qualified name is `package::name`, with the package written before `::`",
1648 span,
1649 )),
1650 TokenKind::Ident(name) => match name.as_str() {
1651 "if" => self.if_form(span, false),
1652 "begin" if !(self.at(&TokenKind::LParen) && self.touches_previous()) => {
1653 self.begin_form(span)
1654 }
1655 "rescue" | "ensure" => Err(fail(
1656 format!("`{name}` belongs to a `begin … end` block or a `def` body, after the statements it protects"),
1657 span,
1658 )),
1659 "unless" => {
1660 self.note(Spelling::Unless, span, "unless", "if not");
1661 self.if_form(span, true)
1662 }
1663 "def" => self.def_form(span),
1664 "defmacro" => self.defmacro_form(span),
1665 "case" => self.case_form(span),
1666 "fn" => self.lambda_form(span),
1667 "test" => self.test_form(span),
1668 "assert" => self.assert_form(span),
1669 "quote" => self.quote_form(span),
1670 "unquote" => self.unquote_form(span, false),
1671 "unquote_splice" => self.unquote_form(span, true),
1672 "do" => Err(fail(
1673 "blue has no `do` blocks: pass a function as an argument, \
1674 `f(xs, fn(x) … end)`",
1675 span,
1676 )),
1677 "end" => Err(fail(
1678 "unexpected `end`: nothing open here for it to close",
1679 span,
1680 )),
1681 _ => Ok(sym_at(span, &name)),
1682 },
1683
1684 other => Err(fail(format!("expected an expression, found {other}"), span)),
1685 }
1686 }
1687
1688 fn qualified_name(&mut self, pkg_span: Span, pkg: &str) -> Result<Spanned, Fail> {
1698 if is_reserved_word(pkg) {
1699 return Err(fail(
1700 format!("`{pkg}` is a reserved word and cannot name a package"),
1701 pkg_span,
1702 ));
1703 }
1704 self.bump(); let name_span = self.peek_span();
1706 let name = match self.bump() {
1707 TokenKind::Ident(n) => n,
1708 TokenKind::True => "true".to_string(),
1710 TokenKind::False => "false".to_string(),
1711 TokenKind::Nil => "nil".to_string(),
1712 other => {
1713 return Err(fail(
1714 format!("expected a name after `{pkg}::`, found {other}"),
1715 name_span,
1716 ))
1717 }
1718 };
1719 if self.at(&TokenKind::PathSep) {
1720 return Err(self.error(format!(
1721 "a qualified name has one qualifier: `package::name`, and `{pkg}::{name}::…` has two"
1722 )));
1723 }
1724 Ok(sym_at(
1725 self.span_since(pkg_span.start),
1726 &qualify(pkg, &name),
1727 ))
1728 }
1729
1730 #[inline(never)]
1737 fn negation(&mut self, head: Span, whole: Span, operand: Spanned) -> Spanned {
1738 if let SpannedForm::List(items) = &operand.form {
1739 let flipped = match items.first().and_then(Spanned::as_symbol) {
1740 Some("equal?") => Some("not="),
1741 Some("not=") => Some("equal?"),
1742 _ => None,
1743 };
1744 if let (Some(to), 3) = (flipped, items.len()) {
1745 let (written, canonical) = if to == "not=" {
1746 ("not (a == b)", "a != b")
1747 } else {
1748 ("not (a != b)", "a == b")
1749 };
1750 self.note(Spelling::NegatedComparison, whole, written, canonical);
1751 let mut out = items.clone();
1752 out[0] = sym_at(items[0].span, to);
1753 if to == "equal?" {
1754 if let Some(test) =
1755 self.nil_test(whole.start, items[0].span, &items[1], &items[2])
1756 {
1757 return test;
1758 }
1759 }
1760 return list_at(whole, out);
1761 }
1762 }
1763 list_at(whole, vec![sym_at(head, "not"), operand])
1764 }
1765
1766 #[inline(never)]
1767 fn call_spelling(&mut self, call: Spanned) -> Spanned {
1768 let SpannedForm::List(items) = &call.form else {
1769 return call;
1770 };
1771 let Some(head) = items.first().and_then(Spanned::as_symbol) else {
1772 return call;
1773 };
1774 let argc = items.len() - 1;
1775 if head == "get" && argc == 2 && is_index_key(&items[2]) {
1776 self.note(Spelling::Index, call.span, "get(m, :k)", "m[:k]");
1777 return call;
1778 }
1779 if head == "nth" && argc == 2 && !is_index_key(&items[1]) {
1780 self.note(Spelling::Index, call.span, "nth(i, xs)", "xs[i]");
1781 return call;
1782 }
1783 if head == "equal?" && argc == 2 {
1784 let (op_span, start) = (items[0].span, call.span.start);
1785 if let Some(test) = self.nil_test(start, op_span, &items[1], &items[2]) {
1786 return test;
1787 }
1788 }
1789 let operator = match (head, argc) {
1790 ("and" | "or" | "mod" | "equal?", 2) => Some(head),
1791 ("list", _) => Some("list"),
1792 _ => None,
1793 };
1794 if let Some(op) = operator {
1795 let canonical = match op {
1796 "list" => "[…]".to_string(),
1797 "mod" => "a % b".to_string(),
1798 "equal?" => "a == b".to_string(),
1799 other => format!("a {other} b"),
1800 };
1801 self.note(
1802 Spelling::OperatorSpelling,
1803 call.span,
1804 &format!("{head}(…)"),
1805 &canonical,
1806 );
1807 return call;
1808 }
1809 if head == "apply"
1810 && self.text(items[0].span) == "apply"
1811 && argc >= 2
1812 && items[1].as_symbol().is_some_and(is_callable_name)
1813 {
1814 self.note(Spelling::Apply, call.span, "apply(f, …, xs)", "f(…, *xs)");
1815 return call;
1816 }
1817 if head == "try" {
1818 return self.try_spelling(call);
1819 }
1820 call
1821 }
1822
1823 fn try_spelling(&mut self, call: Spanned) -> Spanned {
1824 let SpannedForm::List(items) = &call.form else {
1825 return call;
1826 };
1827 let Some((catch, body)) = items[1..].split_last() else {
1828 return call;
1829 };
1830 let Some(clause) = catch.as_list() else {
1831 return call;
1832 };
1833 let binder = match clause {
1834 [head, binding, handler @ ..]
1835 if head.as_symbol() == Some("catch") && !handler.is_empty() =>
1836 {
1837 match binding.as_list() {
1838 Some([b]) => b.as_symbol().map(|n| (n.to_string(), b.span)),
1839 _ => None,
1840 }
1841 }
1842 _ => None,
1843 };
1844 let Some((name, binder_span)) = binder else {
1845 return call;
1846 };
1847 if body.is_empty() {
1848 return call;
1849 }
1850 self.note(Spelling::TryCatch, call.span, "try(…, catch(…))", "rescue");
1851 let handler = &clause[2..];
1852 let rename =
1853 name.starts_with('_') && name != "_" && !handler.iter().any(|h| mentions(h, &name));
1854 let body = if body.len() == 1 {
1855 body[0].clone()
1856 } else {
1857 let span = cover(body, call.span);
1858 let mut list = vec![sym_at(Span::new(span.start, span.start), "begin")];
1859 list.extend(body.iter().cloned());
1860 list_at(span, list)
1861 };
1862 let handler = if handler.len() == 1 {
1863 handler[0].clone()
1864 } else {
1865 let span = cover(handler, catch.span);
1866 let mut list = vec![sym_at(Span::new(span.start, span.start), "begin")];
1867 list.extend(handler.iter().cloned());
1868 list_at(span, list)
1869 };
1870 let binder = sym_at(binder_span, if rename { "_" } else { &name });
1871 list_at(
1872 call.span,
1873 vec![
1874 items[0].clone(),
1875 body,
1876 list_at(
1877 catch.span,
1878 vec![
1879 clause[0].clone(),
1880 list_at(clause[1].span, vec![binder]),
1881 handler,
1882 ],
1883 ),
1884 ],
1885 )
1886 }
1887
1888 fn finish_send(&mut self, start: usize, recv: Spanned, dot: Span) -> Result<Spanned, Fail> {
1889 let name_span = self.peek_span();
1890 let name = match self.bump() {
1891 TokenKind::Ident(n) if is_reserved_word(&n) => {
1905 return Err(self.error(format!(
1906 "`{n}` is a reserved word and cannot be a method name — \
1907 `recv.{n}` would lower onto the `{n}` form itself"
1908 )))
1909 }
1910 TokenKind::Ident(n) => n,
1911 other => {
1912 return Err(fail(
1913 format!("expected a method name after `.`, found {other}"),
1914 name_span,
1915 ))
1916 }
1917 };
1918 if !self.at(&TokenKind::LParen) {
1919 return Ok(list_at(
1920 self.span_since(start),
1921 vec![
1922 sym_at(dot, LOWERED_FIELD),
1923 recv,
1924 atom_at(name_span, Atom::Keyword(name)),
1925 ],
1926 ));
1927 }
1928 self.note(
1929 Spelling::CallForm,
1930 dot.merge(name_span),
1931 &format!("x.{name}(…)"),
1932 &format!("{name}(x, …)"),
1933 );
1934 let (args, splat) = self.call_args()?;
1935 Ok(self.call_node(
1936 self.span_since(start),
1937 vec![sym_at(name_span, &name), recv],
1938 args,
1939 splat,
1940 ))
1941 }
1942
1943 fn call_node(
1944 &self,
1945 span: Span,
1946 head: Vec<Spanned>,
1947 args: Vec<Spanned>,
1948 splat: Option<Span>,
1949 ) -> Spanned {
1950 let mut list = Vec::with_capacity(head.len() + args.len() + 1);
1951 if let Some(at) = splat {
1952 list.push(sym_at(at, "apply"));
1953 }
1954 list.extend(head);
1955 list.extend(args);
1956 list_at(span, list)
1957 }
1958
1959 fn call_args(&mut self) -> Result<(Vec<Spanned>, Option<Span>), Fail> {
1960 self.expect(&TokenKind::LParen, "`(`")?;
1961 let mut args = Vec::new();
1962 let mut splat = None;
1963 self.skip_newlines();
1964 if self.eat(&TokenKind::RParen) {
1965 return Ok((args, splat));
1966 }
1967 loop {
1968 self.skip_newlines();
1969 if splat.is_some() {
1970 return Err(
1971 self.error("a splat `*xs` passes a list as the LAST arguments: write it last")
1972 );
1973 }
1974 if self.at_spread() || matches!(self.peek(), TokenKind::Label(_)) {
1975 let open = self.peek_span();
1976 let (segments, spread) = self.map_entries(open, &TokenKind::RParen)?;
1977 if !spread {
1978 return Err(fail(
1979 "a keyword entry in a call needs a `**` spread: pass a map, f({a: 1})",
1980 open,
1981 ));
1982 }
1983 args.push(self.merged(open, LOWERED_KWARGS, segments));
1984 return Ok((args, splat));
1985 }
1986 if matches!(self.peek(), TokenKind::Op(o) if o == "*") {
1987 splat = Some(self.peek_span());
1988 self.bump();
1989 }
1990 args.push(self.expr(0)?);
1991 self.skip_newlines();
1992 if self.eat(&TokenKind::Comma) {
1993 if self.trailing_comma(&TokenKind::RParen) {
1994 break;
1995 }
1996 continue;
1997 }
1998 self.expect(&TokenKind::RParen, "`,` or `)`")?;
1999 break;
2000 }
2001 Ok((args, splat))
2002 }
2003
2004 fn trailing_comma(&mut self, closer: &TokenKind) -> bool {
2005 let comma = self
2006 .toks
2007 .get(self.pos.wrapping_sub(1))
2008 .map_or(self.peek_span(), |t| t.span);
2009 self.skip_newlines();
2010 if self.at(closer) {
2011 self.note(Spelling::TrailingComma, comma, ",", "");
2012 self.bump();
2013 return true;
2014 }
2015 false
2016 }
2017
2018 fn list_literal(&mut self, open: Span) -> Result<Spanned, Fail> {
2019 let mut items = vec![sym_at(open, "list")];
2020 self.skip_newlines();
2021 if self.eat(&TokenKind::RBracket) {
2022 return Ok(list_at(self.span_since(open.start), items));
2023 }
2024 loop {
2025 self.skip_newlines();
2026 items.push(self.expr(0)?);
2027 self.skip_newlines();
2028 if self.eat(&TokenKind::Comma) {
2029 if self.trailing_comma(&TokenKind::RBracket) {
2030 break;
2031 }
2032 continue;
2033 }
2034 self.expect(&TokenKind::RBracket, "`,` or `]`")?;
2035 break;
2036 }
2037 Ok(list_at(self.span_since(open.start), items))
2038 }
2039
2040 fn map_literal(&mut self, open: Span) -> Result<Spanned, Fail> {
2047 self.skip_newlines();
2048 if self.eat(&TokenKind::RBrace) {
2049 return Ok(list_at(
2050 self.span_since(open.start),
2051 vec![sym_at(open, LOWERED_MAP)],
2052 ));
2053 }
2054 let (mut segments, spread) = self.map_entries(open, &TokenKind::RBrace)?;
2055 if !spread {
2056 return Ok(segments.remove(0));
2057 }
2058 Ok(self.merged(open, LOWERED_MERGE, segments))
2059 }
2060
2061 fn merged(&self, open: Span, head: &str, segments: Vec<Spanned>) -> Spanned {
2062 let mut items = vec![sym_at(open, head)];
2063 items.extend(segments);
2064 list_at(self.span_since(open.start), items)
2065 }
2066
2067 fn at_spread(&self) -> bool {
2068 matches!(self.peek(), TokenKind::Op(o) if o == "**")
2069 }
2070
2071 fn map_entries(
2072 &mut self,
2073 open: Span,
2074 closer: &TokenKind,
2075 ) -> Result<(Vec<Spanned>, bool), Fail> {
2076 let mut segments = Vec::new();
2077 let mut items = vec![sym_at(open, LOWERED_MAP)];
2078 let mut spread = false;
2079 loop {
2080 self.skip_newlines();
2081 if self.at_spread() {
2082 let star = self.peek_span();
2083 self.bump();
2084 let value = self.expr(0)?;
2085 if items.len() > 1 {
2086 let span = items[1].span.merge(items[items.len() - 1].span);
2087 items[0] = sym_at(items[1].span, LOWERED_MAP);
2088 segments.push(list_at(span, std::mem::take(&mut items)));
2089 items = vec![sym_at(open, LOWERED_MAP)];
2090 }
2091 segments.push(list_at(
2092 star.merge(value.span),
2093 vec![sym_at(star, LOWERED_SPLICE), value],
2094 ));
2095 spread = true;
2096 } else {
2097 self.map_entry(&mut items, closer)?;
2098 }
2099 self.skip_newlines();
2100 if self.eat(&TokenKind::Comma) {
2101 if self.trailing_comma(closer) {
2102 break;
2103 }
2104 continue;
2105 }
2106 self.expect(closer, "`,` or the end of the entries")?;
2107 break;
2108 }
2109 if items.len() > 1 || !spread {
2110 let whole = self.span_since(open.start);
2111 segments.push(list_at(whole, items));
2112 }
2113 Ok((segments, spread))
2114 }
2115
2116 fn map_entry(&mut self, items: &mut Vec<Spanned>, closer: &TokenKind) -> Result<(), Fail> {
2117 match self.peek().clone() {
2118 TokenKind::Label(name) => {
2119 let label_span = self.peek_span();
2120 self.bump();
2121 self.skip_newlines();
2122 items.push(atom_at(label_span, Atom::Keyword(name.clone())));
2123 if matches!(self.peek(), TokenKind::Comma) || self.at(closer) {
2124 items.push(sym_at(label_span, &name));
2125 } else {
2126 let value = self.expr(0)?;
2127 if matches!(&value.form, SpannedForm::Atom(Atom::Symbol(s)) if *s == name)
2128 && label_name(&name)
2129 {
2130 let written = format!("{name}: {name}");
2131 let canonical = format!("{name}:");
2132 self.note(
2133 Spelling::MapPun,
2134 label_span.merge(value.span),
2135 &written,
2136 &canonical,
2137 );
2138 }
2139 items.push(value);
2140 }
2141 }
2142 _ => {
2143 let k = self.expr(0)?;
2144 self.skip_newlines();
2145 if let SpannedForm::Atom(Atom::Keyword(name)) = &k.form {
2146 if blue_lang_name(name) {
2147 let written = format!(":{name} =>");
2148 let canonical = format!("{name}:");
2149 self.note(Spelling::KeywordRocket, k.span, &written, &canonical);
2150 }
2151 }
2152 self.expect(&TokenKind::Rocket, "`=>` in a map literal")?;
2153 self.skip_newlines();
2154 items.push(k);
2155 items.push(self.expr(0)?);
2156 }
2157 }
2158 Ok(())
2159 }
2160
2161 fn if_chain(&mut self, head: Span) -> Result<Spanned, Fail> {
2171 let cond = self.expr(0)?;
2172 let then = self.body(&["elsif", "else", "end"])?;
2173 let els = self.else_part()?;
2174 let mut out = vec![sym_at(head, "if"), cond, then];
2175 if let Some(e) = els {
2176 out.push(e);
2177 }
2178 Ok(list_at(self.span_since(head.start), out))
2179 }
2180
2181 fn else_part(&mut self) -> Result<Option<Spanned>, Fail> {
2182 if self.at_ident("elsif") {
2183 let elsif = self.peek_span();
2184 self.bump();
2185 return Ok(Some(self.if_chain(elsif)?));
2186 }
2187 if !self.at_ident("else") {
2188 self.expect_ident("end")?;
2189 return Ok(None);
2190 }
2191 let else_span = self.peek_span();
2192 self.bump();
2193 let save = self.pos;
2194 self.skip_newlines();
2195 let leads_with_if = self.at_ident("if") || self.at_ident("unless");
2196 self.pos = save;
2197 let e = self.body(&["end"])?;
2198 let end_at = self.peek_span().start;
2199 self.expect_ident("end")?;
2200 if matches!(e.form, SpannedForm::Nil) && !self.comment_within(else_span.start, end_at) {
2201 self.note(Spelling::ElseNil, else_span, "else nil", "no else");
2202 return Ok(None);
2203 }
2204 let single_if = matches!(&e.form, SpannedForm::List(items)
2205 if (items.len() == 3 || items.len() == 4) && items[0].as_symbol() == Some("if"));
2206 if leads_with_if && single_if {
2207 self.note(Spelling::ElseIf, else_span, "else if", "elsif");
2208 }
2209 Ok(Some(e))
2210 }
2211
2212 fn if_form(&mut self, head: Span, negate: bool) -> Result<Spanned, Fail> {
2213 let cond = self.expr(0)?;
2214 let cond = if negate {
2215 let inner = cond.span;
2218 self.negation(head, head.merge(inner), cond)
2219 } else {
2220 cond
2221 };
2222 let then = self.body(&["elsif", "else", "end"])?;
2235 let els = self.else_part()?;
2236 let mut out = vec![sym_at(head, "if"), cond, then];
2237 if let Some(e) = els {
2238 out.push(e);
2239 }
2240 Ok(list_at(self.span_since(head.start), out))
2241 }
2242
2243 fn case_form(&mut self, head: Span) -> Result<Spanned, Fail> {
2269 let subject = self.expr(0)?;
2270 self.skip_newlines();
2271
2272 let subject_var = "case-subject";
2275 let mut arms: Vec<Spanned> = Vec::new();
2276 let mut otherwise: Option<Spanned> = None;
2277 let mut else_kw: Option<Span> = None;
2280
2281 loop {
2282 self.skip_newlines();
2283 if self.at_ident("end") {
2284 break;
2285 }
2286 let maybe_else = self.peek_span();
2287 if self.eat_ident("else") {
2288 else_kw = Some(maybe_else);
2289 otherwise = Some(self.body(&["end"])?);
2290 continue;
2291 }
2292 let when = self.peek_span();
2293 if !self.eat_ident("when") {
2294 return Err(self.error(format!(
2295 "expected `when`, `else` or `end` in a case, found {}",
2296 self.peek()
2297 )));
2298 }
2299 self.skip_newlines();
2300 let pattern = self.expr(0)?;
2301 let body = self.body(&["when", "else", "end"])?;
2302 let test = list_at(
2306 when.merge(pattern.span),
2307 vec![sym_at(when, "equal?"), sym_at(when, subject_var), pattern],
2308 );
2309 arms.push(list_at(when.merge(body.span), vec![test, body]));
2310 }
2311 self.expect_ident("end")?;
2312
2313 if arms.is_empty() && otherwise.is_none() {
2314 return Err(self.error("a case needs at least one `when` or an `else`".to_string()));
2315 }
2316
2317 let mut cond = vec![sym_at(head, "cond")];
2322 cond.extend(arms);
2323 let else_span = else_kw.unwrap_or(head);
2330 let else_body = otherwise.unwrap_or_else(|| Spanned::new(else_span, SpannedForm::Nil));
2331 cond.push(list_at(
2332 else_span.merge(else_body.span),
2333 vec![sym_at(else_span, "else"), else_body],
2334 ));
2335
2336 let whole = self.span_since(head.start);
2337 let binding = list_at(
2338 head.merge(subject.span),
2339 vec![sym_at(head, subject_var), subject],
2340 );
2341 Ok(list_at(
2342 whole,
2343 vec![
2344 sym_at(head, "let"),
2345 list_at(binding.span, vec![binding]),
2346 list_at(whole, cond),
2347 ],
2348 ))
2349 }
2350
2351 fn arrow_lambda_ahead(&self) -> bool {
2354 let mut i = self.pos;
2355 let kind = |i: usize| self.toks.get(i).map(|t| &t.kind);
2356 if !matches!(kind(i), Some(TokenKind::RParen)) {
2357 loop {
2358 if matches!(kind(i), Some(TokenKind::Op(o)) if o == "*") {
2359 i += 1;
2360 }
2361 if !matches!(kind(i), Some(TokenKind::Ident(_))) {
2362 return false;
2363 }
2364 i += 1;
2365 match kind(i) {
2366 Some(TokenKind::Comma) => i += 1,
2367 Some(TokenKind::RParen) => break,
2368 _ => return false,
2369 }
2370 }
2371 }
2372 matches!(kind(i + 1), Some(TokenKind::Op(o)) if o == "->")
2373 }
2374
2375 fn arrow_lambda(&mut self, head: Span) -> Result<Spanned, Fail> {
2382 let mut params: Vec<Spanned> = Vec::new();
2383 while !self.at(&TokenKind::RParen) {
2384 let p_span = self.peek_span();
2385 if self.at_star() {
2386 params.extend(self.rest_parameter()?);
2387 break;
2388 }
2389 match self.bump() {
2390 TokenKind::Ident(p) => params.push(sym_at(p_span, &p)),
2391 other => {
2392 return Err(fail(
2393 format!("expected a parameter name, found {other}"),
2394 p_span,
2395 ))
2396 }
2397 }
2398 self.eat(&TokenKind::Comma);
2399 }
2400 self.bump();
2401 self.bump();
2402 self.skip_newlines();
2403 let params_span = cover(¶ms, head);
2404 let body = self.expr(0)?;
2405 Ok(list_at(
2406 self.span_since(head.start),
2407 vec![sym_at(head, "lambda"), list_at(params_span, params), body],
2408 ))
2409 }
2410
2411 fn rest_parameter(&mut self) -> Result<[Spanned; 2], Fail> {
2412 let star = self.peek_span();
2413 self.bump();
2414 let name_span = self.peek_span();
2415 match self.bump() {
2416 TokenKind::Ident(p) => {
2417 let whole = star.merge(name_span);
2418 Ok([sym_at(whole, "&rest"), sym_at(whole, &p)])
2419 }
2420 other => Err(fail(
2421 format!("expected a parameter name after `*`, found {other}"),
2422 name_span,
2423 )),
2424 }
2425 }
2426
2427 fn at_star(&self) -> bool {
2428 matches!(self.peek(), TokenKind::Op(o) if o == "*")
2429 }
2430
2431 fn optional_mark(
2432 &self,
2433 name: String,
2434 span: Span,
2435 optional_from: &mut Option<usize>,
2436 index: usize,
2437 ) -> Result<String, Fail> {
2438 match name.strip_suffix('?').filter(|n| !n.is_empty()) {
2439 Some(bare) => {
2440 optional_from.get_or_insert(index);
2441 Ok(bare.to_string())
2442 }
2443 None if optional_from.is_some() => Err(fail(
2444 format!("the required parameter `{name}` cannot follow an optional one"),
2445 span,
2446 )),
2447 None => Ok(name),
2448 }
2449 }
2450
2451 fn rest_is_last(&mut self) -> Result<(), Fail> {
2452 self.skip_newlines();
2453 if self.at(&TokenKind::Comma) {
2454 return Err(self.error("a rest parameter `*name` comes last".to_string()));
2455 }
2456 Ok(())
2457 }
2458
2459 fn lambda_form(&mut self, head: Span) -> Result<Spanned, Fail> {
2469 let mut params: Vec<Spanned> = Vec::new();
2470 if !self.at(&TokenKind::LParen) {
2471 self.note(Spelling::ParameterList, head, "fn", "fn()");
2472 }
2473 if self.at(&TokenKind::LParen) {
2474 self.bump();
2475 self.skip_newlines();
2476 if !self.eat(&TokenKind::RParen) {
2477 loop {
2478 self.skip_newlines();
2479 let p_span = self.peek_span();
2480 if self.at_star() {
2481 params.extend(self.rest_parameter()?);
2482 self.rest_is_last()?;
2483 self.expect(&TokenKind::RParen, "`)` after the rest parameter")?;
2484 break;
2485 }
2486 match self.bump() {
2487 TokenKind::Ident(p) => params.push(sym_at(p_span, &p)),
2488 other => {
2489 return Err(fail(
2490 format!("expected a parameter name, found {other}"),
2491 p_span,
2492 ))
2493 }
2494 }
2495 self.skip_newlines();
2496 if self.eat(&TokenKind::Comma) {
2497 if self.trailing_comma(&TokenKind::RParen) {
2498 break;
2499 }
2500 continue;
2501 }
2502 self.expect(&TokenKind::RParen, "`,` or `)`")?;
2503 break;
2504 }
2505 }
2506 }
2507 let params_span = cover(¶ms, head);
2508 let body = self.body(&["end"])?;
2509 self.expect_ident("end")?;
2510 Ok(list_at(
2511 self.span_since(head.start),
2512 vec![sym_at(head, "lambda"), list_at(params_span, params), body],
2513 ))
2514 }
2515
2516 fn test_form(&mut self, head: Span) -> Result<Spanned, Fail> {
2522 let name_span = self.peek_span();
2523 let name = match self.bump() {
2524 TokenKind::Str(s) => s,
2525 other => {
2526 return Err(fail(
2527 format!("expected a string name after `test`, found {other}"),
2528 name_span,
2529 ))
2530 }
2531 };
2532 let body = self.body(&["end"])?;
2533 self.expect_ident("end")?;
2534 Ok(list_at(
2535 self.span_since(head.start),
2536 vec![
2537 sym_at(head, "deftest"),
2538 atom_at(name_span, Atom::Str(name)),
2539 body,
2540 ],
2541 ))
2542 }
2543
2544 fn assert_form(&mut self, head: Span) -> Result<Spanned, Fail> {
2568 let e = self.expr(0)?;
2569 let quoted = Spanned::new(e.span, SpannedForm::Quote(Box::new(e.clone())));
2570 Ok(list_at(
2571 self.span_since(head.start),
2572 vec![sym_at(head, LOWERED_ASSERT), quoted, e],
2573 ))
2574 }
2575
2576 fn defmacro_form(&mut self, head: Span) -> Result<Spanned, Fail> {
2585 let name_span = self.peek_span();
2586 let name = match self.bump() {
2587 TokenKind::Ident(n) => n,
2588 other => {
2589 return Err(fail(
2590 format!("expected a name after `defmacro`, found {other}"),
2591 name_span,
2592 ))
2593 }
2594 };
2595 self.not_reserved(&name, name_span, "a `defmacro` name")?;
2596 let mut params: Vec<Spanned> = Vec::new();
2597 if self.at(&TokenKind::LParen) {
2598 self.bump();
2599 self.skip_newlines();
2600 if !self.eat(&TokenKind::RParen) {
2601 loop {
2602 self.skip_newlines();
2603 let p_span = self.peek_span();
2604 match self.bump() {
2605 TokenKind::Ident(p) => params.push(sym_at(p_span, &p)),
2606 TokenKind::Label(p) => {
2607 return Err(self.error(format!(
2608 "macro parameter `{p}` cannot be typed: a macro receives \
2609 source forms, not values"
2610 )))
2611 }
2612 other => {
2613 return Err(fail(
2614 format!("expected a parameter name, found {other}"),
2615 p_span,
2616 ))
2617 }
2618 }
2619 self.skip_newlines();
2620 if self.eat(&TokenKind::Comma) {
2621 if self.trailing_comma(&TokenKind::RParen) {
2622 break;
2623 }
2624 continue;
2625 }
2626 self.expect(&TokenKind::RParen, "`,` or `)`")?;
2627 break;
2628 }
2629 }
2630 }
2631 if matches!(self.peek(), TokenKind::Op(o) if o == "->") {
2632 return Err(self.error(
2633 "a macro has no return type: it produces source forms, not values".to_string(),
2634 ));
2635 }
2636
2637 let params_span = cover(¶ms, name_span);
2638 let body = self.body(&["end"])?;
2639 self.expect_ident("end")?;
2640
2641 Ok(list_at(
2644 self.span_since(head.start),
2645 vec![
2646 sym_at(head, "defmacro"),
2647 sym_at(name_span, &name),
2648 list_at(params_span, params),
2649 body,
2650 ],
2651 ))
2652 }
2653
2654 fn quote_form(&mut self, head: Span) -> Result<Spanned, Fail> {
2660 self.quoting += 1;
2661 let body = self.body(&["end"]);
2662 self.quoting -= 1;
2663 let body = body?;
2664 self.expect_ident("end")?;
2665 Ok(Spanned::new(
2674 self.span_since(head.start),
2675 SpannedForm::Quasiquote(Box::new(body)),
2676 ))
2677 }
2678
2679 fn unquote_form(&mut self, head: Span, splice: bool) -> Result<Spanned, Fail> {
2681 self.expect(&TokenKind::LParen, "`(` after unquote")?;
2682 self.skip_newlines();
2683 let inner = self.expr(0)?;
2684 self.skip_newlines();
2685 self.expect(&TokenKind::RParen, "`)`")?;
2686 let span = self.span_since(head.start);
2687 Ok(Spanned::new(
2688 span,
2689 if splice {
2690 SpannedForm::UnquoteSplice(Box::new(inner))
2691 } else {
2692 SpannedForm::Unquote(Box::new(inner))
2693 },
2694 ))
2695 }
2696
2697 fn protected_body(&mut self, head: Span) -> Result<Spanned, Fail> {
2698 let body = self.body(&["rescue", "ensure", "end"])?;
2699 if self.at_ident("rescue") || self.at_ident("ensure") {
2700 return self.rescue_tail(head, body);
2701 }
2702 Ok(body)
2703 }
2704
2705 fn begin_form(&mut self, head: Span) -> Result<Spanned, Fail> {
2706 let body = self.body(&["rescue", "ensure", "end"])?;
2707 if !(self.at_ident("rescue") || self.at_ident("ensure")) {
2708 return Err(self.error(
2709 "a `begin` block protects its statements with `rescue` or `ensure`; without either, write the statements directly",
2710 ));
2711 }
2712 let tree = self.rescue_tail(head, body)?;
2713 self.expect_ident("end")?;
2714 Ok(Spanned::new(self.span_since(head.start), tree.form))
2715 }
2716
2717 fn rescue_tail(&mut self, head: Span, body: Spanned) -> Result<Spanned, Fail> {
2718 let mut clauses: Vec<Spanned> = Vec::new();
2719 let mut catch_all: Option<(Spanned, Spanned, Span)> = None;
2720 while self.at_ident("rescue") {
2721 let kw = self.peek_span();
2722 self.bump();
2723 if catch_all.is_some() {
2724 return Err(fail(
2725 "a `rescue NAME` catches every error, so it is the last `rescue`; put the kinds it would shadow above it",
2726 kw,
2727 ));
2728 }
2729 match self.peek().clone() {
2730 TokenKind::Sym(_) => {
2731 let mut kinds = Vec::new();
2732 loop {
2733 let k_span = self.peek_span();
2734 match self.bump() {
2735 TokenKind::Sym(k) => kinds.push(atom_at(k_span, Atom::Keyword(k))),
2736 other => {
2737 return Err(fail(
2738 format!("expected an error kind such as `:not_found`, found {other}"),
2739 k_span,
2740 ))
2741 }
2742 }
2743 if self.eat(&TokenKind::Comma) {
2744 self.skip_newlines();
2745 continue;
2746 }
2747 break;
2748 }
2749 let binder = if self.eat_ident("as") {
2750 let b_span = self.peek_span();
2751 match self.bump() {
2752 TokenKind::Ident(b) if !is_reserved_word(&b) => Some(sym_at(b_span, &b)),
2753 other => {
2754 return Err(fail(
2755 format!("expected the name the error is bound to after `as`, found {other}"),
2756 b_span,
2757 ))
2758 }
2759 }
2760 } else {
2761 None
2762 };
2763 let handler = self.body(&["rescue", "ensure", "end"])?;
2764 let mut test = vec![sym_at(kw, LOWERED_ERROR_MATCH), sym_at(kw, RESCUED)];
2765 test.extend(kinds);
2766 let test = list_at(cover(&test, kw), test);
2767 let handler = match binder {
2768 Some(b) => let_bound(b, handler, kw),
2769 None => handler,
2770 };
2771 clauses.push(list_at(kw.merge(handler.span), vec![test, handler]));
2772 }
2773 TokenKind::Ident(b) if !is_reserved_word(&b) => {
2774 let b_span = self.peek_span();
2775 self.bump();
2776 let handler = self.body(&["rescue", "ensure", "end"])?;
2777 catch_all = Some((sym_at(b_span, &b), handler, kw));
2778 }
2779 _ => {
2780 return Err(fail(
2781 "`rescue` names what it catches: `rescue :kind`, `rescue :a, :b as e`, or `rescue e` for every error (`rescue _` discards it)",
2782 kw,
2783 ))
2784 }
2785 }
2786 }
2787 let ensure = if self.at_ident("ensure") {
2788 self.bump();
2789 let cleanup = self.body(&["rescue", "end"])?;
2790 if self.at_ident("rescue") {
2791 return Err(self.error("`ensure` comes after every `rescue` of its block"));
2792 }
2793 Some(cleanup)
2794 } else {
2795 None
2796 };
2797 let whole = self.span_since(head.start);
2798 let protected = if clauses.is_empty() {
2799 match catch_all {
2800 None => body,
2801 Some((binder, handler, kw)) => list_at(
2802 whole,
2803 vec![
2804 sym_at(head, "try"),
2805 body,
2806 list_at(
2807 kw.merge(handler.span),
2808 vec![
2809 sym_at(kw, "catch"),
2810 list_at(binder.span, vec![binder]),
2811 handler,
2812 ],
2813 ),
2814 ],
2815 ),
2816 }
2817 } else {
2818 let first_kw = clauses[0].span;
2819 let otherwise = match catch_all {
2820 Some((binder, handler, kw)) => {
2821 let bound = let_bound(binder, handler, kw);
2822 list_at(kw.merge(bound.span), vec![sym_at(kw, "else"), bound])
2823 }
2824 None => list_at(
2825 head,
2826 vec![
2827 sym_at(head, "else"),
2828 list_at(
2829 head,
2830 vec![sym_at(head, LOWERED_RERAISE), sym_at(head, RESCUED)],
2831 ),
2832 ],
2833 ),
2834 };
2835 let mut cond = vec![sym_at(first_kw, "cond")];
2836 cond.extend(clauses);
2837 cond.push(otherwise);
2838 let cond_span = cover(&cond, first_kw);
2839 list_at(
2840 whole,
2841 vec![
2842 sym_at(head, "try"),
2843 body,
2844 list_at(
2845 cond_span,
2846 vec![
2847 sym_at(first_kw, "catch"),
2848 list_at(first_kw, vec![sym_at(first_kw, RESCUED)]),
2849 list_at(cond_span, cond),
2850 ],
2851 ),
2852 ],
2853 )
2854 };
2855 Ok(match ensure {
2856 None => protected,
2857 Some(cleanup) => {
2858 let attempted = list_at(
2859 protected.span,
2860 vec![sym_at(head, LOWERED_ATTEMPTED), protected],
2861 );
2862 let failed = list_at(
2863 head,
2864 vec![sym_at(head, LOWERED_FAILED), sym_at(head, RESCUED)],
2865 );
2866 let caught = list_at(
2867 whole,
2868 vec![
2869 sym_at(head, "try"),
2870 attempted,
2871 list_at(
2872 head,
2873 vec![
2874 sym_at(head, "catch"),
2875 list_at(head, vec![sym_at(head, RESCUED)]),
2876 failed,
2877 ],
2878 ),
2879 ],
2880 );
2881 list_at(
2882 whole,
2883 vec![sym_at(head, LOWERED_ENSURE), caught, thunk(cleanup, head)],
2884 )
2885 }
2886 })
2887 }
2888
2889 fn def_form(&mut self, head: Span) -> Result<Spanned, Fail> {
2890 let name_span = self.peek_span();
2891 let name = match self.bump() {
2892 TokenKind::Ident(n) => n,
2893 other => {
2894 return Err(fail(
2895 format!("expected a name after `def`, found {other}"),
2896 name_span,
2897 ))
2898 }
2899 };
2900 self.not_reserved(&name, name_span, "a `def` name")?;
2901 let mut params: Vec<(String, Span, Option<Spanned>)> = Vec::new();
2902 let mut optional_from: Option<usize> = None;
2903 let mut rest: Vec<Spanned> = Vec::new();
2904 if !self.at(&TokenKind::LParen) {
2905 self.note(
2906 Spelling::ParameterList,
2907 name_span,
2908 &format!("def {name}"),
2909 &format!("def {name}()"),
2910 );
2911 }
2912 if self.at(&TokenKind::LParen) {
2913 self.bump();
2914 self.skip_newlines();
2915 if !self.eat(&TokenKind::RParen) {
2916 loop {
2917 self.skip_newlines();
2918 let p_span = self.peek_span();
2919 if self.at_star() {
2920 if optional_from.is_some() {
2921 return Err(fail(
2922 "a rest parameter cannot follow an optional one",
2923 p_span,
2924 ));
2925 }
2926 rest = self.rest_parameter()?.to_vec();
2927 self.rest_is_last()?;
2928 self.expect(&TokenKind::RParen, "`)` after the rest parameter")?;
2929 break;
2930 }
2931 match self.bump() {
2932 TokenKind::Ident(p) => {
2934 self.not_reserved(&p, p_span, "a parameter name")?;
2935 let p =
2936 self.optional_mark(p, p_span, &mut optional_from, params.len())?;
2937 params.push((p, p_span, None));
2938 }
2939 TokenKind::Label(p) => {
2941 self.not_reserved(&p, p_span, "a parameter name")?;
2942 let p =
2943 self.optional_mark(p, p_span, &mut optional_from, params.len())?;
2944 self.skip_newlines();
2945 let ty = self.type_expr()?;
2946 params.push((p, p_span, Some(ty)));
2947 }
2948 other => {
2949 return Err(fail(
2950 format!("expected a parameter name, found {other}"),
2951 p_span,
2952 ))
2953 }
2954 }
2955 self.skip_newlines();
2956 if self.eat(&TokenKind::Comma) {
2957 if self.trailing_comma(&TokenKind::RParen) {
2958 break;
2959 }
2960 continue;
2961 }
2962 self.expect(&TokenKind::RParen, "`,` or `)`")?;
2963 break;
2964 }
2965 }
2966 }
2967 let sig_span = self.span_since(name_span.start);
2969
2970 let ret = if matches!(self.peek(), TokenKind::Op(o) if o == "->") {
2972 self.bump();
2973 self.skip_newlines();
2974 Some(self.type_expr()?)
2975 } else {
2976 None
2977 };
2978
2979 let begins = {
2980 let save = self.pos;
2981 self.skip_newlines();
2982 let at = self.at_ident("begin").then(|| self.peek_span());
2983 self.pos = save;
2984 at
2985 };
2986 let body = self.protected_body(head)?;
2987 if let Some(b) = begins {
2988 let protects = matches!(&body.form, SpannedForm::List(items)
2989 if matches!(items.first().and_then(Spanned::as_symbol), Some("try") | Some(LOWERED_ENSURE)));
2990 if protects && body.span.start == b.start {
2991 self.note(
2992 Spelling::TryCatch,
2993 b,
2994 "begin … end as a whole def body",
2995 "the clauses on the def itself",
2996 );
2997 }
2998 }
2999 self.expect_ident("end")?;
3000 let whole = self.span_since(head.start);
3001
3002 let annotated = ret.is_some() || params.iter().any(|(_, _, t)| t.is_some());
3003 if !annotated {
3004 let mut sig = vec![sym_at(name_span, &name)];
3005 for (i, (p, s, _)) in params.into_iter().enumerate() {
3006 if optional_from == Some(i) {
3007 sig.push(sym_at(s, OPTIONAL_MARKER));
3008 }
3009 sig.push(sym_at(s, &p));
3010 }
3011 sig.extend(rest);
3012 return Ok(list_at(
3013 whole,
3014 vec![sym_at(head, "define"), list_at(sig_span, sig), body],
3015 ));
3016 }
3017
3018 let mut sig = vec![sym_at(name_span, &name)];
3026 for (i, (p, s, t)) in params.into_iter().enumerate() {
3027 if optional_from == Some(i) {
3028 sig.push(sym_at(s, OPTIONAL_MARKER));
3029 }
3030 let ty = t.unwrap_or_else(|| sym_at(s, "dyn"));
3031 sig.push(list_at(s.merge(ty.span), vec![sym_at(s, &p), ty]));
3032 }
3033 sig.extend(rest);
3034 Ok(list_at(
3035 whole,
3036 vec![
3037 sym_at(head, "define-typed"),
3038 list_at(sig_span, sig),
3039 ret.unwrap_or_else(|| sym_at(head, "dyn")),
3040 body,
3041 ],
3042 ))
3043 }
3044
3045 fn type_expr(&mut self) -> Result<Spanned, Fail> {
3047 let name_span = self.peek_span();
3048 let name = match self.bump() {
3049 TokenKind::Ident(n) => n,
3050 other => {
3051 return Err(fail(
3052 format!("expected a type name, found {other}"),
3053 name_span,
3054 ))
3055 }
3056 };
3057 self.not_reserved(&name, name_span, "a type name")?;
3058 if self.at(&TokenKind::LParen) {
3059 self.bump();
3060 self.skip_newlines();
3061 let mut list = vec![sym_at(name_span, &name)];
3062 if !self.eat(&TokenKind::RParen) {
3063 loop {
3064 self.skip_newlines();
3065 list.push(self.type_expr()?);
3066 self.skip_newlines();
3067 if self.eat(&TokenKind::Comma) {
3068 if self.trailing_comma(&TokenKind::RParen) {
3069 break;
3070 }
3071 continue;
3072 }
3073 self.expect(&TokenKind::RParen, "`,` or `)`")?;
3074 break;
3075 }
3076 }
3077 return Ok(list_at(self.span_since(name_span.start), list));
3078 }
3079 Ok(sym_at(name_span, &name))
3080 }
3081
3082 fn not_reserved(&self, name: &str, at: Span, what: &str) -> Result<(), Fail> {
3083 if is_reserved_word(name) {
3084 return Err(fail(
3085 format!("`{name}` is a reserved word and cannot be {what}"),
3086 at,
3087 ));
3088 }
3089 Ok(())
3090 }
3091
3092 fn eat_ident(&mut self, name: &str) -> bool {
3094 if self.at_ident(name) {
3095 self.bump();
3096 true
3097 } else {
3098 false
3099 }
3100 }
3101
3102 fn expect_ident(&mut self, name: &str) -> Result<(), Fail> {
3103 if self.at_ident(name) {
3104 self.bump();
3105 Ok(())
3106 } else {
3107 Err(self.error(format!("expected `{name}`, found {}", self.peek())))
3108 }
3109 }
3110
3111 fn body(&mut self, terminators: &[&str]) -> Result<Spanned, Fail> {
3114 let start = self.mark();
3117 let mut forms = Vec::new();
3118 loop {
3119 self.skip_newlines();
3120 if matches!(self.peek(), TokenKind::Eof) {
3121 return Err(self.error(format!(
3122 "unterminated block: expected {} before the end of the file",
3123 one_of(terminators)
3124 )));
3125 }
3126 if terminators.iter().any(|t| self.at_ident(t)) {
3127 break;
3128 }
3129 forms.push(self.statement()?);
3130 self.end_of_statement(terminators)?;
3131 }
3132 Ok(match forms.len() {
3133 0 => Spanned::new(Span::new(start, start), SpannedForm::Nil),
3134 1 => forms.into_iter().next().expect("checked len"),
3135 _ => {
3136 let span = cover(&forms, Span::new(start, start));
3142 let head = Span::new(span.start, span.start);
3148 let mut list = vec![sym_at(head, "begin")];
3149 list.extend(forms);
3150 list_at(span, list)
3151 }
3152 })
3153 }
3154}
3155
3156fn one_of(words: &[&str]) -> String {
3159 let quoted: Vec<String> = words.iter().map(|w| format!("`{w}`")).collect();
3160 match quoted.split_last() {
3161 None => String::new(),
3162 Some((last, [])) => last.clone(),
3163 Some((last, rest)) => format!("{} or {last}", rest.join(", ")),
3164 }
3165}
3166
3167fn let_bound(binder: Spanned, body: Spanned, at: Span) -> Spanned {
3168 let span = at.merge(body.span);
3169 let pair = list_at(binder.span, vec![binder, sym_at(at, RESCUED)]);
3170 list_at(
3171 span,
3172 vec![sym_at(at, "let"), list_at(pair.span, vec![pair]), body],
3173 )
3174}
3175
3176fn thunk(body: Spanned, at: Span) -> Spanned {
3177 let span = at.merge(body.span);
3178 list_at(
3179 span,
3180 vec![sym_at(at, "lambda"), list_at(at, Vec::new()), body],
3181 )
3182}
3183
3184#[must_use]
3189pub fn label_name(name: &str) -> bool {
3190 matches!(
3191 lex(&format!("{name}:")).as_deref(),
3192 Ok([t, e]) if matches!(&t.kind, TokenKind::Label(n) if n == name) && e.kind == TokenKind::Eof
3193 )
3194}
3195
3196fn blue_lang_name(name: &str) -> bool {
3197 matches!(
3198 lex(name).as_deref(),
3199 Ok([t, e]) if matches!(&t.kind, TokenKind::Ident(n) if n == name) && e.kind == TokenKind::Eof
3200 ) && !name.ends_with(['?', '!'])
3201}
3202
3203fn mentions(form: &Spanned, name: &str) -> bool {
3204 match &form.form {
3205 SpannedForm::Atom(Atom::Symbol(s)) => s == name,
3206 SpannedForm::List(items) => items.iter().any(|i| mentions(i, name)),
3207 SpannedForm::Quote(i)
3208 | SpannedForm::Quasiquote(i)
3209 | SpannedForm::Unquote(i)
3210 | SpannedForm::UnquoteSplice(i) => mentions(i, name),
3211 _ => false,
3212 }
3213}
3214
3215fn sym_at(span: Span, s: &str) -> Spanned {
3217 Spanned::new(span, SpannedForm::Atom(Atom::Symbol(s.to_string())))
3218}
3219
3220fn atom_at(span: Span, a: Atom) -> Spanned {
3222 Spanned::new(span, SpannedForm::Atom(a))
3223}
3224
3225fn list_at(span: Span, items: Vec<Spanned>) -> Spanned {
3227 Spanned::new(span, SpannedForm::List(items))
3228}
3229
3230fn cover(items: &[Spanned], fallback: Span) -> Span {
3238 let merged = items
3239 .iter()
3240 .fold(Span::synthetic(), |acc, s| acc.merge(s.span));
3241 if merged.is_synthetic() {
3242 fallback
3243 } else {
3244 merged
3245 }
3246}
3247
3248#[cfg(test)]
3249mod tests {
3250 use super::*;
3251
3252 fn q(src: &str) -> String {
3256 parse_expr(src)
3257 .map(|s| s.to_string())
3258 .unwrap_or_else(|e| panic!("{src:?}: {e}"))
3259 }
3260
3261 #[test]
3262 fn a_reserved_word_is_not_a_def_name_a_parameter_or_a_type() {
3263 for src in [
3264 "def def a(x)\n x\nend",
3265 "def end()\n 1\nend",
3266 "def f(end)\n 1\nend",
3267 "def f(a: not)\n 1\nend",
3268 "def f(a) -> not a\n a\nend",
3269 "def f(a: List(1 + 2))\n a\nend",
3270 "defmacro defmacro m(a)\n quote\n a\n end\nend",
3271 ] {
3272 assert!(parse_program(src).is_err(), "{src:?} parsed");
3273 }
3274 }
3275
3276 #[test]
3277 fn a_type_application_takes_types_of_any_arity() {
3278 assert_eq!(
3279 q("def f(a: Map(Str, List(Int))) -> Unit()\n a\nend"),
3280 "(define-typed (f (a (Map Str (List Int)))) (Unit) a)"
3281 );
3282 }
3283
3284 #[test]
3287 fn arithmetic_respects_precedence() {
3288 assert_eq!(q("1 + 2 * 3"), "(+ 1 (* 2 3))");
3289 assert_eq!(q("(1 + 2) * 3"), "(* (+ 1 2) 3)");
3290 }
3291
3292 #[test]
3293 fn comparison_binds_looser_than_arithmetic() {
3294 assert_eq!(q("a + 1 < b"), "(< (+ a 1) b)");
3295 }
3296
3297 #[test]
3302 fn logical_operators_bind_loosest_and_lower_to_tataras_names() {
3303 assert_eq!(q("a && b || c"), "(or (and a b) c)");
3304 }
3305
3306 #[test]
3307 fn left_associativity() {
3308 assert_eq!(q("1 - 2 - 3"), "(- (- 1 2) 3)");
3309 }
3310
3311 #[test]
3312 fn a_dot_without_parentheses_reads_a_field() {
3313 assert_eq!(q("user.name"), "(blue-field user :name)");
3314 }
3315
3316 #[test]
3317 fn method_call_with_args() {
3318 assert_eq!(q("user.greet(1, 2)"), "(greet user 1 2)");
3319 }
3320
3321 #[test]
3322 fn chained_fields_read_left_to_right() {
3323 assert_eq!(q("a.b.c"), "(blue-field (blue-field a :b) :c)");
3324 assert_eq!(q("a.b(1).c"), "(blue-field (b a 1) :c)");
3325 }
3326
3327 #[test]
3328 fn plain_call() {
3329 assert_eq!(q("f(1, 2)"), "(f 1 2)");
3330 }
3331
3332 #[test]
3335 fn pipeline_threads_into_first_argument() {
3336 assert_eq!(q("x |> f"), "(f x)");
3337 assert_eq!(q("x |> f(1)"), "(f x 1)");
3338 assert_eq!(q("x |> f |> g"), "(g (f x))");
3339 }
3340
3341 #[test]
3342 fn pipeline_binds_looser_than_arithmetic() {
3343 assert_eq!(q("1 + 2 |> f"), "(f (+ 1 2))");
3344 }
3345
3346 #[test]
3352 fn label_and_rocket_produce_the_same_tree_for_a_symbol_key() {
3353 assert_eq!(q("{a: 1}"), q("{:a => 1}"));
3354 assert_eq!(q("{a: 1}"), "(hash-map :a 1)");
3355 }
3356
3357 #[test]
3360 fn a_string_key_has_no_shorthand() {
3361 assert_eq!(q(r#"{"k" => 1}"#), r#"(hash-map "k" 1)"#);
3362 }
3363
3364 #[test]
3365 fn a_newline_escape_and_its_lines_are_one_tree() {
3366 for (escaped, lines) in [
3367 (r#""a\nb""#, r#"join_lines(["a", "b"])"#),
3368 (r##""#{m}\n#{h}""##, "join_lines([m, h])"),
3369 (r##""n=#{n}\ndone\n""##, r##"unlines(["n=#{n}", "done"])"##),
3370 (r#""\n""#, "line_break"),
3371 (r#""a\n\n\tb\n""#, r#"unlines(["a", "", "\tb"])"#),
3372 (r#""\na""#, r#"join_lines(["", "a"])"#),
3373 (r#""\n\n""#, r#"unlines(["", ""])"#),
3374 ] {
3375 assert_eq!(q(escaped), q(lines), "{escaped}");
3376 }
3377 assert_eq!(q(r#""a\tb""#), q(r#""a\tb""#));
3378 assert!(!q(r#""a\tb""#).contains("join_lines"));
3379 }
3380
3381 #[test]
3382 fn a_newline_escape_is_noted_and_a_quoted_one_is_data() {
3383 let notes = spelling_notes(r#""a\nb""#).unwrap();
3384 assert_eq!(notes.len(), 1);
3385 assert_eq!(notes[0].spelling, Spelling::NewlineEscape);
3386 assert!(spelling_notes(r#""a\tb""#).unwrap().is_empty());
3387 let quoted = q("def f()\n quote\n \"x\\ny\"\n end\nend\n");
3388 assert!(!quoted.contains("join_lines"), "{quoted}");
3389 assert!(
3390 spelling_notes("def f()\n quote\n \"x\\ny\"\n end\nend\n")
3391 .unwrap()
3392 .is_empty()
3393 );
3394 }
3395
3396 #[test]
3397 fn list_literal() {
3398 assert_eq!(q("[1, 2, 3]"), "(list 1 2 3)");
3399 assert_eq!(q("[]"), "(list)");
3400 }
3401
3402 #[test]
3405 fn if_else_end() {
3406 assert_eq!(q("if a\n 1\nelse\n 2\nend"), "(if a 1 2)");
3407 }
3408
3409 #[test]
3410 fn if_without_else() {
3411 assert_eq!(q("if a\n 1\nend"), "(if a 1)");
3412 }
3413
3414 #[test]
3417 fn unless_is_a_negated_if() {
3418 assert_eq!(q("unless a\n 1\nend"), "(if (not a) 1)");
3419 }
3420
3421 #[test]
3422 fn multi_statement_body_becomes_begin() {
3423 assert_eq!(q("if a\n 1\n 2\nend"), "(if a (begin 1 2))");
3424 }
3425
3426 #[test]
3427 fn def_lowers_to_define() {
3428 assert_eq!(
3429 q("def add(a, b)\n a + b\nend"),
3430 "(define (add a b) (+ a b))"
3431 );
3432 }
3433
3434 #[test]
3435 fn def_with_no_params() {
3436 assert_eq!(q("def zero()\n 0\nend"), "(define (zero) 0)");
3437 }
3438
3439 fn refused(src: &str) -> String {
3440 match parse_expr(src) {
3441 Ok(s) => panic!("{src:?} should be refused, parsed to {s}"),
3442 Err(e) => e.to_string(),
3443 }
3444 }
3445
3446 #[test]
3447 fn a_question_mark_parameter_is_optional() {
3448 assert_eq!(q("def f(a, b?)\n b\nend"), "(define (f a &optional b) b)");
3449 assert_eq!(q("def f(a?, b?)\n a\nend"), "(define (f &optional a b) a)");
3450 assert_eq!(q("def f(a?)\n a\nend"), "(define (f &optional a) a)");
3451 }
3452
3453 #[test]
3454 fn an_annotated_optional_parameter_keeps_its_type_after_the_marker() {
3455 assert_eq!(
3456 q("def f(a: Int, b?: Str) -> Int\n a\nend"),
3457 "(define-typed (f (a Int) &optional (b Str)) Int a)"
3458 );
3459 assert_eq!(
3460 q("def f(a, b?: Str)\n a\nend"),
3461 "(define-typed (f (a dyn) &optional (b Str)) dyn a)"
3462 );
3463 }
3464
3465 #[test]
3466 fn a_def_without_question_marks_carries_no_marker() {
3467 assert_eq!(q("def f(a, b)\n a\nend"), "(define (f a b) a)");
3468 assert_eq!(q("def f(a, *r)\n a\nend"), "(define (f a &rest r) a)");
3469 }
3470
3471 #[test]
3472 fn a_required_parameter_cannot_follow_an_optional_one() {
3473 assert!(refused("def f(a?, b)\n a\nend").contains("`b` cannot follow an optional"));
3474 assert!(refused("def f(a, b?, c: Int)\n a\nend").contains("`c` cannot follow"));
3475 }
3476
3477 #[test]
3478 fn a_rest_parameter_cannot_follow_an_optional_one() {
3479 assert!(refused("def f(a, b?, *r)\n a\nend").contains("rest parameter cannot follow"));
3480 }
3481
3482 #[test]
3485 fn literals_lower_to_atoms() {
3486 assert_eq!(q("42"), "42");
3487 assert_eq!(q("true"), "#t");
3488 assert_eq!(q(":ok"), ":ok");
3489 assert_eq!(q(r#""hi""#), r#""hi""#);
3490 }
3491
3492 #[test]
3493 fn unary_minus_and_not() {
3494 assert_eq!(q("-x"), "(- 0 x)");
3495 assert_eq!(q("!x"), "(not x)");
3496 }
3497
3498 #[test]
3501 fn a_program_is_a_sequence_of_forms() {
3502 let forms = parse_program("def f()\n 1\nend\nf()").expect("parse");
3503 assert_eq!(forms.len(), 2);
3504 assert_eq!(forms[1].to_string(), "(f)");
3505 }
3506
3507 #[test]
3508 fn unterminated_block_is_an_error_naming_what_was_expected() {
3509 let e = parse_program("if a\n 1").expect_err("must fail");
3510 assert!(e.message.contains("unterminated"), "{}", e.message);
3511 }
3512
3513 #[test]
3514 fn a_parse_error_carries_a_span_into_the_source() {
3515 let src = "1 + )";
3516 let e = parse_program(src).expect_err("must fail");
3517 assert!(e.span.start < src.len(), "span {:?} outside source", e.span);
3518 }
3519
3520 #[test]
3527 fn a_spaced_index_is_refused_naming_the_touching_one() {
3528 let e = parse_program("y = xs [0]").expect_err("must not split into two statements");
3529 assert!(e.message.contains("`xs[0]`"), "{}", e.message);
3530 assert_eq!((e.line, e.col), (1, 8), "{e}");
3531 }
3532
3533 #[test]
3534 fn an_index_is_the_tree_of_get_or_nth() {
3535 for (short, long) in [
3536 ("m[:k]", "get(m, :k)"),
3537 (r#"m["k"]"#, r#"get(m, "k")"#),
3538 ("xs[0]", "nth(0, xs)"),
3539 ("xs[i + 1]", "nth(i + 1, xs)"),
3540 ("f(x)[2]", "nth(2, f(x))"),
3541 ("m[:a][:b]", "get(get(m, :a), :b)"),
3542 ("xs[0][:k]", "get(nth(0, xs), :k)"),
3543 ("[1, 2][1]", "nth(1, [1, 2])"),
3544 ] {
3545 assert_eq!(q(short), q(long), "{short}");
3546 }
3547 assert_eq!(q("get(m, k)"), "(get m k)");
3548 let notes = spelling_notes("get(m, :k)\nnth(0, xs)\nget(m, k)\n").unwrap();
3549 assert_eq!(
3550 notes
3551 .iter()
3552 .filter(|n| n.spelling == Spelling::Index)
3553 .count(),
3554 2
3555 );
3556 }
3557
3558 #[test]
3560 fn a_call_without_parentheses_is_refused_naming_the_call() {
3561 let e = parse_program("a = f x").expect_err("must not split into two statements");
3562 assert!(e.message.contains("`f(x)`"), "{}", e.message);
3563 assert_eq!((e.line, e.col), (1, 7), "{e}");
3564 }
3565
3566 #[test]
3568 fn two_statements_on_one_line_are_refused_in_a_body() {
3569 for src in [
3570 "def f(xs)\n xs [0]\nend",
3571 "if a\n f x\nend",
3572 "fn(x) x 1 end",
3573 "test \"t\"\n assert a b\nend",
3574 "1 2",
3575 ] {
3576 assert!(parse_program(src).is_err(), "{src:?} must not parse");
3577 }
3578 }
3579
3580 #[test]
3582 fn an_arrow_lambda_lowers_to_the_same_tree_as_fn() {
3583 for (arrow, block) in [
3584 ("(x) -> x * 2", "fn(x) x * 2 end"),
3585 ("(a, b) -> a + b", "fn(a, b) a + b end"),
3586 ("() -> 1", "fn() 1 end"),
3587 ("map(xs, (x) -> x + 1)", "map(xs, fn(x) x + 1 end)"),
3588 ] {
3589 let a = parse_program(arrow).expect(arrow);
3590 let b = parse_program(block).expect(block);
3591 assert_eq!(format!("{a:?}"), format!("{b:?}"), "{arrow}");
3592 }
3593 assert_eq!(
3594 parse_program("(x)").map(|p| p.len()).ok(),
3595 Some(1),
3596 "a parenthesised name is still a group"
3597 );
3598 }
3599
3600 #[test]
3604 fn a_statement_may_end_at_a_newline_the_end_or_a_closing_keyword() {
3605 for src in [
3606 "fn(x) x + 1 end",
3607 "map(fn(x) x end, xs)",
3608 "if a\n 1\nelse\n 2\nend",
3609 "if a 1 else 2 end",
3610 "case x\nwhen 1 :a\nwhen 2 :b\nelse :c\nend",
3611 "x = 1\ny = 2",
3612 "x = 1",
3613 ] {
3614 assert!(
3615 parse_program(src).is_ok(),
3616 "{src:?}: {:?}",
3617 parse_program(src)
3618 );
3619 }
3620 }
3621
3622 #[test]
3624 fn then_is_refused_by_name() {
3625 let e = parse_program("x = if c then 1 else 2 end").expect_err("then is not a keyword");
3626 assert!(e.message.contains("`then`"), "{}", e.message);
3627 }
3628
3629 #[test]
3631 fn a_brace_block_is_refused_naming_fn() {
3632 let e = parse_program("xs.map { |x| x }").expect_err("no brace blocks");
3633 assert!(e.message.contains("fn(x)"), "{}", e.message);
3634 }
3635
3636 #[test]
3643 fn a_qualified_name_is_one_symbol() {
3644 assert_eq!(q("retsu::first(xs)"), "(retsu/first xs)");
3645 assert_eq!(q("retsu::first"), "retsu/first");
3646 assert_eq!(q("xs |> retsu::first"), "(retsu/first xs)");
3647 assert_eq!(q("blue::length(xs)"), "(blue/length xs)");
3648 }
3649
3650 #[test]
3652 fn whitespace_around_the_qualifier_is_accepted() {
3653 assert_eq!(q("retsu :: first(xs)"), "(retsu/first xs)");
3654 }
3655
3656 #[test]
3659 fn a_reserved_word_after_the_qualifier_is_a_name() {
3660 assert_eq!(q("kueri::if(a, b)"), "(kueri/if a b)");
3661 assert_eq!(q("heni::test(x)"), "(heni/test x)");
3662 assert_eq!(q("sabi::fn"), "sabi/fn");
3663 }
3664
3665 #[test]
3666 fn a_qualified_name_has_exactly_one_qualifier() {
3667 for (src, says) in [
3668 ("::first(xs)", "`package::name`"),
3669 ("a::b::c", "one qualifier"),
3670 ("if::x", "reserved word"),
3671 ("retsu::1", "expected a name after `retsu::`"),
3672 ] {
3673 let e = parse_program(src).expect_err(src);
3674 assert!(e.message.contains(says), "{src}: {}", e.message);
3675 }
3676 }
3677
3678 #[test]
3681 fn qualified_reads_only_the_package_name_shape() {
3682 assert_eq!(qualified("retsu/first"), Some(("retsu", "first")));
3683 assert_eq!(qualified("kueri/if"), Some(("kueri", "if")));
3684 assert_eq!(qualified("nisshi/pair?"), Some(("nisshi", "pair?")));
3685 for not in ["/", "a/", "/a", "a/b/c", "if/x", "hash-map", "a"] {
3686 assert_eq!(qualified(not), None, "{not}");
3687 }
3688 assert!(is_callable_name("kueri/if"));
3689 }
3690
3691 #[test]
3696 fn a_symbol_may_end_in_a_question_or_bang() {
3697 assert_eq!(q("[:pair?, :set!]"), "(list :pair? :set!)");
3698 assert_eq!(q("use(\"n\", [:empty?])"), "(use \"n\" (list :empty?))");
3699 assert_eq!(q(":a != :b"), "(not= :a :b)");
3700 }
3701
3702 #[test]
3706 fn a_parse_error_displays_line_col_and_the_source_text() {
3707 let e = parse_program("x = 1\ny = [1, 2 y]").expect_err("must fail");
3708 assert_eq!(e.to_string(), "2:11: expected `,` or `]`, found `y`");
3709 assert_eq!(e.span, Span::new(16, 17), "the span stays, as data");
3710 }
3711
3712 #[test]
3714 fn no_error_message_names_a_rust_variant() {
3715 for src in [
3716 "f(1 2)",
3717 "{a: 1 b}",
3718 "def 1\nend",
3719 "x.1",
3720 "if a\n",
3721 "fn(1) 2 end",
3722 "[1 :b]",
3723 "1 +",
3724 "(1",
3725 ] {
3726 let e = parse_program(src).expect_err(src);
3727 let shown = e.to_string();
3728 for tell in [
3729 "Ident(", "Int(", "Op(", "Label(", "Sym(", "Str(", "LParen", "RBracket", "Eof",
3730 "Newline", "..",
3731 ] {
3732 assert!(!shown.contains(tell), "{src:?} -> {shown}");
3733 }
3734 assert!(e.line >= 1 && e.col >= 1, "{src:?} not located: {shown}");
3735 }
3736 }
3737
3738 #[test]
3740 fn a_lex_error_is_located_too() {
3741 let e = parse_program("x = 1\ny = 2; z = 3").expect_err("no semicolons");
3742 assert_eq!((e.line, e.col), (2, 6), "{e}");
3743 assert!(e.message.contains("new line"), "{e}");
3744 }
3745
3746 #[test]
3749 fn the_parser_rejects_garbage() {
3750 assert!(parse_program("def").is_err());
3751 assert!(parse_program("(1").is_err());
3752 assert!(parse_program("end").is_err());
3753 }
3754}