1use tatara_lisp::{Atom, Sexp, Spanned, SpannedForm};
46
47use crate::lex::{lex, Span, Token, TokenKind};
48use crate::spelling::{Note, Spelling};
49
50#[derive(Clone, Debug, PartialEq)]
65pub struct ParseError {
66 pub message: String,
67 pub span: Span,
68 pub line: usize,
70 pub col: usize,
72}
73
74impl ParseError {
75 pub(crate) fn new(message: impl Into<String>, span: Span) -> Self {
79 Self {
80 message: message.into(),
81 span,
82 line: 0,
83 col: 0,
84 }
85 }
86
87 pub(crate) fn located(mut self, src: &str) -> Self {
89 let (line, col) = Span::line_col(src, self.span.start.min(src.len()));
90 self.line = line;
91 self.col = col;
92 self
93 }
94}
95
96impl std::fmt::Display for ParseError {
97 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
98 write!(f, "{}:{}: {}", self.line, self.col, self.message)
99 }
100}
101
102impl std::error::Error for ParseError {}
103
104impl From<crate::lex::LexError> for ParseError {
105 fn from(e: crate::lex::LexError) -> Self {
106 Self::new(e.message, e.span)
107 }
108}
109
110thread_local! {
111 static PARSES: std::cell::Cell<u64> = const { std::cell::Cell::new(0) };
112}
113
114#[must_use]
118pub fn parses_on_this_thread() -> u64 {
119 PARSES.with(std::cell::Cell::get)
120}
121
122pub fn parse_program(src: &str) -> Result<Vec<Sexp>, ParseError> {
124 parse_program_with_depth(src, MAX_EXPR_DEPTH)
125}
126
127pub fn parse_program_tree(src: &str) -> Result<Vec<Spanned>, ParseError> {
135 parse_program_tree_with_depth(src, MAX_EXPR_DEPTH)
136}
137
138pub fn parse_program_tree_with_depth(
140 src: &str,
141 max_depth: usize,
142) -> Result<Vec<Spanned>, ParseError> {
143 parse_program_documented_with_depth(src, max_depth).map(crate::doc::strip)
144}
145
146pub fn parse_program_documented(src: &str) -> Result<Vec<Spanned>, ParseError> {
149 parse_program_documented_with_depth(src, MAX_EXPR_DEPTH)
150}
151
152pub fn parse_program_documented_with_depth(
154 src: &str,
155 max_depth: usize,
156) -> Result<Vec<Spanned>, ParseError> {
157 if let Some(hit) = RECENT.with(|r| {
158 r.borrow()
159 .iter()
160 .find(|(d, s, _)| *d == max_depth && s.as_str() == src)
161 .map(|(_, _, t)| t.clone())
162 }) {
163 return hit;
164 }
165 PARSES.with(|n| n.set(n.get() + 1));
166 let parsed = lex(src)
167 .map_err(|e| ParseError::from(e).located(src))
168 .and_then(|toks| {
169 Parser::over(src, toks, max_depth)
170 .program()
171 .map_err(|e| e.located(src))
172 });
173 RECENT.with(|r| {
174 let mut r = r.borrow_mut();
175 if r.len() == RECENT_TEXTS {
176 drop(r.remove(0));
177 }
178 r.push((max_depth, src.to_string(), parsed.clone()));
179 });
180 parsed
181}
182
183pub(crate) fn parse_snippet(src: &str) -> Result<Vec<Spanned>, ParseError> {
186 let toks = lex(src).map_err(|e| ParseError::from(e).located(src))?;
187 Parser::over(src, toks, MAX_EXPR_DEPTH)
188 .program()
189 .map(crate::doc::strip)
190 .map_err(|e| e.located(src))
191}
192
193const RECENT_TEXTS: usize = 4;
197
198type Recent = Vec<(usize, String, Result<Vec<Spanned>, ParseError>)>;
199
200thread_local! {
201 static RECENT: std::cell::RefCell<Recent> = const { std::cell::RefCell::new(Vec::new()) };
202}
203
204#[must_use]
209pub fn constant_text(s: &Sexp) -> Option<String> {
210 match s {
211 Sexp::Atom(Atom::Str(x)) => Some(x.to_string()),
212 Sexp::Atom(Atom::Symbol(h)) if h.as_str() == "line_break" => Some("\n".to_string()),
213 Sexp::List(items) => match items.as_slice() {
214 [Sexp::Atom(Atom::Symbol(h)), Sexp::List(list)]
215 if h.as_str() == "join_lines" || h.as_str() == "unlines" =>
216 {
217 let (Sexp::Atom(Atom::Symbol(l)), elems) = list.split_first()? else {
218 return None;
219 };
220 if l.as_str() != "list" {
221 return None;
222 }
223 let texts = elems.iter().map(constant_text).collect::<Option<Vec<_>>>()?;
224 Some(if h.as_str() == "join_lines" {
225 texts.join("\n")
226 } else {
227 texts.iter().map(|t| format!("{t}\n")).collect()
228 })
229 }
230 _ => None,
231 },
232 _ => None,
233 }
234}
235
236pub fn spelling_notes(src: &str) -> Result<Vec<Note>, ParseError> {
237 let mut p = Parser::over(
238 src,
239 lex(src).map_err(|e| ParseError::from(e).located(src))?,
240 MAX_EXPR_DEPTH,
241 );
242 p.program().map_err(|e| e.located(src))?;
243 Ok(p.notes)
244}
245
246pub fn parse_program_in(src: &str, pack: &crate::yakugo::Yakugo) -> Result<Vec<Sexp>, ParseError> {
259 Ok(parse_program_tree_in(src, pack)?
260 .iter()
261 .map(Spanned::to_sexp)
262 .collect())
263}
264
265pub fn parse_program_tree_in(
281 src: &str,
282 pack: &crate::yakugo::Yakugo,
283) -> Result<Vec<Spanned>, ParseError> {
284 let toks =
285 crate::yakugo::canonical_tokens(src, pack).map_err(|e| ParseError::from(e).located(src))?;
286 PARSES.with(|n| n.set(n.get() + 1));
287 let mut p = Parser::over(src, toks, MAX_EXPR_DEPTH);
288 p.program()
289 .map(crate::doc::strip)
290 .map_err(|e| e.located(src))
291}
292
293pub fn parse_program_with_depth(src: &str, max_depth: usize) -> Result<Vec<Sexp>, ParseError> {
302 Ok(parse_program_tree_with_depth(src, max_depth)?
303 .iter()
304 .map(Spanned::to_sexp)
305 .collect())
306}
307
308pub fn parse_program_spanned(src: &str) -> Result<Vec<(Sexp, Span)>, ParseError> {
321 parse_program_spanned_with_depth(src, MAX_EXPR_DEPTH)
322}
323
324pub fn parse_program_spanned_with_depth(
326 src: &str,
327 max_depth: usize,
328) -> Result<Vec<(Sexp, Span)>, ParseError> {
329 Ok(parse_program_tree_with_depth(src, max_depth)?
330 .iter()
331 .map(|f| (f.to_sexp(), f.span))
332 .collect())
333}
334
335pub fn comments(src: &str) -> Vec<Comment> {
343 lex(src)
344 .map(|toks| {
345 toks.iter()
346 .filter_map(|t| match &t.kind {
347 TokenKind::Comment(text) => Some(Comment {
348 text: text.clone(),
349 span: t.span,
350 own_line: line_before_is_blank(src, t.span.start),
351 }),
352 _ => None,
353 })
354 .collect()
355 })
356 .unwrap_or_default()
357}
358
359#[derive(Clone, Debug, PartialEq, Eq)]
361pub struct Comment {
362 pub text: String,
364 pub span: Span,
365 pub own_line: bool,
367}
368
369fn line_before_is_blank(src: &str, start: usize) -> bool {
370 src[..start.min(src.len())]
371 .rsplit('\n')
372 .next()
373 .is_some_and(|prefix| {
374 prefix
375 .trim_matches(|c: char| c.is_whitespace() || c == '\u{feff}')
376 .is_empty()
377 })
378}
379
380pub fn parse_expr(src: &str) -> Result<Sexp, ParseError> {
382 let forms = parse_program(src)?;
383 match forms.len() {
384 1 => Ok(forms.into_iter().next().expect("checked len")),
385 n => Err(ParseError::new(
386 format!("expected exactly one expression, found {n}"),
387 Span::new(0, src.len()),
388 )
389 .located(src)),
390 }
391}
392
393#[derive(Clone, Copy, Debug)]
403pub struct Infix {
404 pub op: &'static str,
406 pub power: (u8, u8),
408 pub callee: &'static str,
410 pub doc: &'static str,
414}
415
416pub const INFIX: &[Infix] = &[
420 Infix {
421 op: "or",
422 power: (1, 2),
423 callee: "or",
424 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).",
425 },
426 Infix {
427 op: "and",
428 power: (3, 4),
429 callee: "and",
430 doc: "Logical and: the right value if the left is truthy, otherwise the left; short-circuits. `&&` is read as `and` and written `and` (B0025).",
431 },
432 Infix {
443 op: "==",
444 power: (5, 6),
445 callee: "equal?",
446 doc: "Structural equality over every value (okite D0001): 4 == 4.0, lists and maps by content, nil only equals nil.",
447 },
448 Infix {
449 op: "!=",
450 power: (5, 6),
451 callee: "not=",
452 doc: "Exactly not(a == b) (okite D0002).",
453 },
454 Infix {
455 op: "<",
456 power: (5, 6),
457 callee: "<",
458 doc: "Numeric less-than. A string operand is a type error: order text with compare(a, b).",
459 },
460 Infix {
461 op: "<=",
462 power: (5, 6),
463 callee: "<=",
464 doc: "Numeric less-than-or-equal. Numbers only, like <.",
465 },
466 Infix {
467 op: ">",
468 power: (5, 6),
469 callee: ">",
470 doc: "Numeric greater-than. Numbers only, like <.",
471 },
472 Infix {
473 op: ">=",
474 power: (5, 6),
475 callee: ">=",
476 doc: "Numeric greater-than-or-equal. Numbers only, like <.",
477 },
478 Infix {
479 op: "+",
480 power: (7, 8),
481 callee: "+",
482 doc: "Numeric addition; an Int and a Float give a Float. Not string concatenation: interpolate instead.",
483 },
484 Infix {
485 op: "-",
486 power: (7, 8),
487 callee: "-",
488 doc: "Numeric subtraction. Written before an operand with nothing on its left, it is unary minus: -n.",
489 },
490 Infix {
491 op: "*",
492 power: (9, 10),
493 callee: "*",
494 doc: "Numeric multiplication.",
495 },
496 Infix {
497 op: "/",
498 power: (9, 10),
499 callee: "/",
500 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).",
501 },
502 Infix {
503 op: "%",
504 power: (9, 10),
505 callee: "mod",
506 doc: "Euclidean remainder: never negative, so (0 - 7) % 3 is 2, not -1. A zero divisor is a runtime error.",
507 },
508];
509
510pub const MASTER_VERBS: &[&str] = &[
528 "read", "need", "has?", "put", "update", "delete", "create", "diff", "info", "exists?",
529];
530
531pub const LOWERED_ASSERT: &str = "blue-assert";
538
539pub const LOWERED_MAP: &str = "hash-map";
547
548pub const LOWERED_CONCAT: &str = "concat";
553
554pub const OPTIONAL_MARKER: &str = "&optional";
555
556pub const LOWERED_OPTIONAL_ARITY: &str = "blue-optional-arity";
557
558pub const LOWERED_OPTIONAL_ARG: &str = "blue-optional-arg";
559
560pub const LOWERED_FIELD: &str = "blue-field";
561
562pub const LOWERED_MERGE: &str = "blue-merge";
563
564pub const LOWERED_SPLICE: &str = "blue-splice";
565
566pub const LOWERED_KWARGS: &str = "blue-kwargs";
567
568pub const LOWERED_ERROR_MATCH: &str = "blue-rescues?";
569
570pub const LOWERED_ENSURE: &str = "blue-ensure";
571
572pub const LOWERED_RERAISE: &str = "blue-reraise";
573
574pub const LOWERED_ATTEMPTED: &str = "blue-attempted";
575
576pub const LOWERED_FAILED: &str = "blue-failed";
577
578pub const LOWERED_HEADS: &[&str] = &[
579 "list", "concat", "not", "apply", "equal?", "not=", "and", "or", "mod", "join_lines", "unlines",
580 "line_break",
581];
582
583pub const RESCUED: &str = "rescued-error";
584
585pub const SURFACE_KEYWORDS: &[&str] = &[
586 "if",
587 "begin",
588 "unless",
589 "def",
590 "defmacro",
591 "quote",
592 "unquote",
593 "unquote_splice",
594 "test",
595 "assert",
596 "fn",
597 "case",
598];
599
600pub const BLOCK_KEYWORDS: &[&str] = &[
612 "do", "end", "else", "elsif", "rescue", "ensure", "true", "false", "nil",
613];
614
615pub const OPERATOR_KEYWORDS: &[&str] = &["and", "or", "not"];
616
617pub fn is_reserved_word(name: &str) -> bool {
624 SURFACE_KEYWORDS.contains(&name)
625 || BLOCK_KEYWORDS.contains(&name)
626 || OPERATOR_KEYWORDS.contains(&name)
627}
628
629pub fn is_callable_name(name: &str) -> bool {
638 if qualified(name).is_some() {
640 return true;
641 }
642 !is_reserved_word(name)
643 && matches!(
644 lex(name).as_deref(),
645 Ok([t, e]) if matches!(&t.kind, TokenKind::Ident(n) if n == name)
646 && e.kind == TokenKind::Eof
647 )
648}
649
650pub const QUALIFIER: char = '/';
655
656pub const BUILTIN_QUALIFIER: &str = "blue";
659
660#[must_use]
662pub fn qualify(pkg: &str, name: &str) -> String {
663 format!("{pkg}{QUALIFIER}{name}")
664}
665
666#[must_use]
672pub fn qualified(sym: &str) -> Option<(&str, &str)> {
673 let (pkg, name) = sym.split_once(QUALIFIER)?;
674 let ident = |s: &str| {
675 matches!(
676 lex(s).as_deref(),
677 Ok([t, e]) if matches!(&t.kind, TokenKind::Ident(n) if n == s) && e.kind == TokenKind::Eof
678 )
679 };
680 let named = |s: &str| ident(s) || matches!(s, "true" | "false" | "nil");
681 (ident(pkg) && !is_reserved_word(pkg) && named(name)).then_some((pkg, name))
682}
683
684#[must_use]
692pub fn keyword_doc(word: &str) -> &'static str {
693 match word {
694 "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).",
695 "unless" => "`unless c … end` is read as `if not c` and written that way (B0026).",
696 "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`.",
697 "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 `|>`.",
698 "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.",
699 "and" => "`a and b`: logical and; `&&` is read as `and` and written `and` (B0025).",
700 "or" => "`a or b`: logical or; `||` is read as `or` and written `or` (B0025).",
701 "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).",
702 "def" => "`def name(a, b) … end`: a named function; its value is the last expression. Parameters may be typed: `def f(n: Int) -> Int`.",
703 "defmacro" => "`defmacro name(x) quote … unquote(x) … end end`: a macro, a rewrite of the unevaluated form.",
704 "quote" => "`quote … end`: the form itself as data, not its value. Used inside `defmacro`.",
705 "unquote" => "`unquote(x)`: inside `quote`, splice in the value of `x`.",
706 "unquote_splice" => "`unquote_splice(xs)`: inside `quote`, splice the items of the form `xs` in place, not the form itself.",
707 "test" => "`test \"what must hold\" … end`: a test block, run by `blue test` and ignored by `blue run`.",
708 "assert" => "`assert expr`: inside a test, fail it unless `expr` is truthy; the failure quotes the expression.",
709 "fn" => "`fn(x) x * 2 end`: an anonymous function, a value. Blue has no brace or do blocks.",
710 "case" => "`case v when 1 … when 2 … else … end`: compares with `==`; it does not destructure.",
711 "do" => "Reserved and refused: blue has no do-blocks. Pass `fn(x) … end` instead.",
712 "end" => "Closes `def`, `defmacro`, `if`, `case`, `fn`, `test` and `quote`.",
713 "else" => "The branch of `if` or `case` taken when nothing before it matched.",
714 "elsif" => "A further condition in an `if` chain; an `else` holding only an `if` is written `elsif` (B0027).",
715 "true" => "The boolean true.",
716 "false" => "The boolean false. Only `false` and `nil` are falsy; 0, \"\" and [] are truthy.",
717 "nil" => "The absent value. It is not `[]` (okite D0004), and only `nil == nil`.",
718 _ => "",
719 }
720}
721
722fn infix(op: &str) -> Option<&'static Infix> {
723 INFIX.iter().find(|i| i.op == op)
724}
725
726pub const SYMBOL_SPELLINGS: &[(&str, &str)] = &[("&&", "and"), ("||", "or"), ("!", "not")];
727
728fn word_for(op: &str) -> &str {
729 SYMBOL_SPELLINGS
730 .iter()
731 .find(|(sym, _)| *sym == op)
732 .map_or(op, |(_, word)| word)
733}
734
735fn infix_of(kind: &TokenKind) -> Option<&'static Infix> {
736 match kind {
737 TokenKind::Op(o) => infix(word_for(o)),
738 TokenKind::Ident(w) if w == "and" || w == "or" => infix(w),
739 _ => None,
740 }
741}
742
743const PIPE_POWER: (u8, u8) = (0, 1);
744const RESCUE_POWER: (u8, u8) = (0, 1);
745
746struct Parser {
747 toks: Vec<Token>,
748 pos: usize,
749 depth: usize,
757 max_depth: usize,
764 src: String,
765 comment_spans: Vec<Span>,
766 notes: Vec<Note>,
767 quoting: usize,
770}
771
772pub const MAX_EXPR_DEPTH: usize = 256;
779
780type Fail = Box<ParseError>;
792
793fn fail(message: impl Into<String>, span: Span) -> Fail {
794 Box::new(ParseError::new(message, span))
795}
796
797impl Parser {
798 fn over(src: &str, all: Vec<Token>, max_depth: usize) -> Self {
799 let comment_spans = all
800 .iter()
801 .filter(|t| matches!(t.kind, TokenKind::Comment(_)))
802 .map(|t| t.span)
803 .collect();
804 let toks = all
805 .into_iter()
806 .filter(|t| !matches!(t.kind, TokenKind::Comment(_)))
807 .collect();
808 Parser {
809 toks,
810 pos: 0,
811 depth: 0,
812 max_depth,
813 src: src.to_string(),
814 comment_spans,
815 notes: Vec::new(),
816 quoting: 0,
817 }
818 }
819
820 fn note(&mut self, spelling: Spelling, span: Span, written: &str, canonical: &str) {
821 self.notes.push(Note {
822 spelling,
823 span,
824 written: written.to_string(),
825 canonical: canonical.to_string(),
826 });
827 }
828
829 fn text(&self, span: Span) -> &str {
830 self.src.get(span.start..span.end).unwrap_or("")
831 }
832
833 fn comment_within(&self, lo: usize, hi: usize) -> bool {
834 self.comment_spans
835 .iter()
836 .any(|c| c.start >= lo && c.start < hi)
837 }
838
839 fn touches_previous(&self) -> bool {
840 let here = self.peek_span();
841 self.toks
842 .get(self.pos.wrapping_sub(1))
843 .is_some_and(|prev| prev.span.end == here.start)
844 }
845
846 fn peek(&self) -> &TokenKind {
847 &self.toks[self.pos.min(self.toks.len() - 1)].kind
848 }
849
850 fn peek_span(&self) -> Span {
851 self.toks[self.pos.min(self.toks.len() - 1)].span
852 }
853
854 fn bump(&mut self) -> TokenKind {
855 let k = self.toks[self.pos.min(self.toks.len() - 1)].kind.clone();
856 if self.pos < self.toks.len() {
857 self.pos += 1;
858 }
859 k
860 }
861
862 fn at(&self, k: &TokenKind) -> bool {
863 self.peek() == k
864 }
865
866 fn eat(&mut self, k: &TokenKind) -> bool {
867 if self.at(k) {
868 self.bump();
869 true
870 } else {
871 false
872 }
873 }
874
875 fn expect(&mut self, k: &TokenKind, what: &str) -> Result<(), Fail> {
876 if self.eat(k) {
877 Ok(())
878 } else {
879 Err(self.error(format!("expected {what}, found {}", self.peek())))
880 }
881 }
882
883 fn error(&self, message: impl Into<String>) -> Fail {
884 fail(message, self.peek_span())
885 }
886
887 fn skip_newlines(&mut self) {
889 while matches!(self.peek(), TokenKind::Newline) {
890 self.bump();
891 }
892 }
893
894 fn at_ident(&self, name: &str) -> bool {
895 matches!(self.peek(), TokenKind::Ident(n) if n == name)
896 }
897
898 fn mark(&self) -> usize {
901 self.peek_span().start
902 }
903
904 fn span_since(&self, start: usize) -> Span {
910 let end = self
911 .toks
912 .get(self.pos.saturating_sub(1))
913 .map_or(start, |t| t.span.end);
914 Span::new(start, end.max(start))
915 }
916
917 fn program(&mut self) -> Result<Vec<Spanned>, Fail> {
918 let mut out = Vec::new();
919 loop {
920 self.skip_newlines();
921 if matches!(self.peek(), TokenKind::Eof) {
922 break;
923 }
924 if let TokenKind::Doc { module, text } = self.peek().clone() {
925 out.push(self.documentation(module, text, out.is_empty())?);
926 continue;
927 }
928 out.push(self.statement()?);
929 self.end_of_statement(&[])?;
930 }
931 Ok(out)
932 }
933
934 fn documentation(&mut self, module: bool, text: String, first: bool) -> Result<Spanned, Fail> {
937 let span = self.peek_span();
938 self.bump();
939 let word = if module { "@moduledoc" } else { "@doc" };
940 let head = sym_at(
941 Span::new(span.start, span.start + word.len()),
942 if module {
943 crate::doc::MODULEDOC_HEAD
944 } else {
945 crate::doc::DOC_HEAD
946 },
947 );
948 let text = crate::doc::text_node(span, text);
949 self.end_of_statement(&[])?;
950 if module {
951 if !first {
952 return Err(fail(
953 "`@moduledoc` is the first form of a file: it documents the whole file",
954 span,
955 ));
956 }
957 return Ok(list_at(span, vec![head, text]));
958 }
959 self.skip_newlines();
960 if matches!(self.peek(), TokenKind::Doc { .. } | TokenKind::Eof) {
961 return Err(fail(
962 "`@doc` documents the definition below it, and none follows",
963 span,
964 ));
965 }
966 let def = self.statement()?;
967 self.end_of_statement(&[])?;
968 if !crate::doc::is_definition(&def) {
969 return Err(fail(
970 "`@doc` documents the definition below it: a `def`, a `defmacro` or a \
971 top-level `name = value`",
972 span,
973 ));
974 }
975 Ok(list_at(
976 Span::new(span.start, def.span.end),
977 vec![head, text, def],
978 ))
979 }
980
981 fn end_of_statement(&self, terminators: &[&str]) -> Result<(), Fail> {
999 let next = self.peek();
1000 if matches!(next, TokenKind::Newline | TokenKind::Eof)
1001 || terminators.iter().any(|t| self.at_ident(t))
1002 {
1003 return Ok(());
1004 }
1005 let prev = self.toks.get(self.pos.wrapping_sub(1)).map(|t| &t.kind);
1006 let prev_name = match prev {
1007 Some(TokenKind::Ident(n)) => Some(n.as_str()),
1008 _ => None,
1009 };
1010 let callee = prev_name.unwrap_or("f");
1011 let why = match (prev, next) {
1012 (Some(TokenKind::Ident(t)), _) if t == "then" => {
1013 "blue's `if` has no `then`: put the branch on the next line".to_string()
1014 }
1015 (_, TokenKind::Ident(d)) if d == "do" => "blue has no `do` blocks: pass a function \
1016 as an argument, `f(xs, fn(x) … end)`"
1017 .to_string(),
1018 (_, TokenKind::LBracket) => {
1019 let index = match (self.toks.get(self.pos + 1), self.toks.get(self.pos + 2)) {
1020 (Some(i), Some(close)) if close.kind == TokenKind::RBracket => match &i.kind {
1021 TokenKind::Int(n) => n.to_string(),
1022 TokenKind::Ident(n) => n.clone(),
1023 _ => "i".to_string(),
1024 },
1025 _ => "i".to_string(),
1026 };
1027 let list = prev_name.unwrap_or("xs");
1028 format!(
1029 "blue has no `xs[i]` indexing: write `nth({index}, {list})` \
1030 (or `first({list})`, `last({list})`)"
1031 )
1032 }
1033 (_, TokenKind::LBrace) => "blue has no brace blocks: pass a function as an \
1034 argument, `map(xs, fn(x) … end)`"
1035 .to_string(),
1036 (
1037 Some(TokenKind::Ident(_)),
1038 TokenKind::Ident(_)
1039 | TokenKind::Int(_)
1040 | TokenKind::Float(_)
1041 | TokenKind::Str(_)
1042 | TokenKind::InterpolatedStr { .. }
1043 | TokenKind::Sym(_)
1044 | TokenKind::True
1045 | TokenKind::False
1046 | TokenKind::Nil,
1047 ) => {
1048 let arg = match next {
1049 TokenKind::Ident(a) => a.clone(),
1050 _ => "x".to_string(),
1051 };
1052 format!("a call needs parentheses: write `{callee}({arg})`")
1053 }
1054 _ => "put each statement on its own line".to_string(),
1055 };
1056 Err(self.error(format!("expected the end of the line, found {next}: {why}")))
1057 }
1058
1059 fn statement(&mut self) -> Result<Spanned, Fail> {
1072 let max = self.max_depth;
1086 if self.depth >= max {
1087 return Err(self.error(format!(
1088 "statement nests deeper than {max}; refusing to \
1089 recurse further (this is a limit, not a syntax error)"
1090 )));
1091 }
1092 self.depth += 1;
1093 let r = self.statement_inner();
1094 self.depth -= 1;
1095 r
1096 }
1097
1098 fn statement_inner(&mut self) -> Result<Spanned, Fail> {
1099 if let TokenKind::Ident(name) = self.peek().clone() {
1100 if self.peek_at(1) == "=" && !is_reserved_word(&name) {
1101 let name_span = self.peek_span();
1102 self.bump(); self.bump(); self.skip_newlines();
1105 let value = self.expr(0)?;
1106 return Ok(list_at(
1110 self.span_since(name_span.start),
1111 vec![sym_at(name_span, "define"), sym_at(name_span, &name), value],
1112 ));
1113 }
1114 }
1115 let e = self.expr(0)?;
1116 if let SpannedForm::List(items) = &e.form {
1117 if items.len() == 3
1118 && items[0].as_symbol() == Some("define")
1119 && items[1].as_symbol().is_some()
1120 && self.text(items[0].span) == "define"
1121 {
1122 self.note(Spelling::OperatorSpelling, e.span, "define(x, v)", "x = v");
1123 }
1124 }
1125 Ok(e)
1126 }
1127
1128 fn peek_at(&self, n: usize) -> String {
1131 match self.toks.get(self.pos + n).map(|t| &t.kind) {
1132 Some(TokenKind::Op(o)) => o.clone(),
1133 _ => String::new(),
1134 }
1135 }
1136
1137 fn expr(&mut self, min_bp: u8) -> Result<Spanned, Fail> {
1139 let max = self.max_depth;
1149 if self.depth >= max {
1150 return Err(self.error(format!(
1151 "expression nests deeper than {max}; refusing to \
1152 recurse further (this is a limit, not a syntax error)"
1153 )));
1154 }
1155 self.depth += 1;
1156 let r = self.expr_inner(min_bp);
1157 self.depth -= 1;
1158 r
1159 }
1160
1161 #[inline(never)]
1162 fn rescue_node(&self, start: usize, op_span: Span, lhs: Spanned, fallback: Spanned) -> Spanned {
1163 list_at(
1164 self.span_since(start),
1165 vec![
1166 sym_at(op_span, "try"),
1167 lhs,
1168 list_at(
1169 op_span.merge(fallback.span),
1170 vec![
1171 sym_at(op_span, "catch"),
1172 list_at(op_span, vec![sym_at(op_span, "_")]),
1173 fallback,
1174 ],
1175 ),
1176 ],
1177 )
1178 }
1179
1180 #[inline(never)]
1181 fn infix_spelling(&mut self, callee: Option<&str>, op_span: Span) {
1182 let written = self.text(op_span).to_string();
1183 match (callee, self.peek().clone()) {
1184 (None, _) => self.note(Spelling::CallForm, op_span, "|>", "a call"),
1185 (Some(c), TokenKind::Op(o)) if word_for(&o) != o => {
1186 self.note(Spelling::LogicalSymbol, op_span, &written, c);
1187 }
1188 (Some(_), TokenKind::Op(o)) if written != o => {
1189 self.note(Spelling::OperatorSpelling, op_span, &written, &o);
1190 }
1191 _ => {}
1192 }
1193 }
1194
1195 #[inline(never)]
1196 fn nil_test(
1197 &mut self,
1198 start: usize,
1199 op_span: Span,
1200 lhs: &Spanned,
1201 rhs: &Spanned,
1202 ) -> Option<Spanned> {
1203 let (subject, written) = match (&lhs.form, &rhs.form) {
1204 (_, SpannedForm::Nil) if !matches!(lhs.form, SpannedForm::Nil) => (lhs, "x == nil"),
1205 (SpannedForm::Nil, _) if !matches!(rhs.form, SpannedForm::Nil) => (rhs, "nil == x"),
1206 _ => return None,
1207 };
1208 let span = self.span_since(start);
1209 self.note(Spelling::NilTest, span, written, "x.nil?");
1210 Some(list_at(
1211 span,
1212 vec![
1213 sym_at(op_span, LOWERED_FIELD),
1214 subject.clone(),
1215 atom_at(op_span, Atom::Keyword("nil?".to_string())),
1216 ],
1217 ))
1218 }
1219
1220 #[inline(never)]
1221 fn infix_node(
1222 &mut self,
1223 start: usize,
1224 callee: Option<&str>,
1225 op_span: Span,
1226 lhs: Spanned,
1227 rhs: Spanned,
1228 ) -> Spanned {
1229 if callee == Some("-") && matches!(lhs.form, SpannedForm::Atom(Atom::Int(0))) {
1230 self.note(
1231 Spelling::OperatorSpelling,
1232 lhs.span.merge(op_span),
1233 "0 - x",
1234 "-x",
1235 );
1236 }
1237
1238 if callee == Some("equal?") {
1239 if let Some(test) = self.nil_test(start, op_span, &lhs, &rhs) {
1240 return test;
1241 }
1242 }
1243
1244 if callee.is_none() {
1245 let rhs_span = rhs.span;
1250 let threaded = match rhs.form {
1251 SpannedForm::List(mut items) if !items.is_empty() => {
1252 items.insert(1, lhs);
1253 list_at(self.span_since(start), items)
1254 }
1255 callee => list_at(
1256 self.span_since(start),
1257 vec![Spanned::new(rhs_span, callee), lhs],
1258 ),
1259 };
1260 match &threaded.form {
1261 SpannedForm::List(items)
1262 if items.len() == 2 && items[0].as_symbol() == Some("not") =>
1263 {
1264 let (head, operand) = (items[0].span, items[1].clone());
1265 self.negation(head, threaded.span, operand)
1266 }
1267 SpannedForm::List(items)
1268 if items.len() == 4
1269 && items[0].as_symbol() == Some("if")
1270 && matches!(items[3].form, SpannedForm::Nil) =>
1271 {
1272 list_at(threaded.span, items[..3].to_vec())
1273 }
1274 SpannedForm::List(items)
1275 if items.len() == 3 && items[0].as_symbol() == Some("equal?") =>
1276 {
1277 let (op, a, b) = (items[0].span, items[1].clone(), items[2].clone());
1278 match self.nil_test(start, op, &a, &b) {
1279 Some(test) => test,
1280 None => threaded,
1281 }
1282 }
1283 _ => threaded,
1284 }
1285 } else {
1286 list_at(
1292 self.span_since(start),
1293 vec![sym_at(op_span, callee.unwrap_or_default()), lhs, rhs],
1294 )
1295 }
1296 }
1297
1298 fn expr_inner(&mut self, min_bp: u8) -> Result<Spanned, Fail> {
1299 let start = self.mark();
1300 let mut lhs = self.prefix()?;
1301
1302 loop {
1303 match self.peek() {
1305 TokenKind::Dot => {
1306 let dot = self.peek_span();
1307 self.bump();
1308 lhs = self.finish_send(start, lhs, dot)?;
1309 continue;
1310 }
1311 TokenKind::LParen => {
1312 let (args, splat) = self.call_args()?;
1313 let call = self.call_node(self.span_since(start), vec![lhs], args, splat);
1314 lhs = self.call_spelling(call);
1315 continue;
1316 }
1317 _ => {}
1318 }
1319
1320 let op_span = self.peek_span();
1324 if self.at_ident("rescue") {
1325 if RESCUE_POWER.0 < min_bp {
1326 break;
1327 }
1328 self.bump();
1329 self.skip_newlines();
1330 let fallback = self.expr(RESCUE_POWER.1)?;
1331 lhs = self.rescue_node(start, op_span, lhs, fallback);
1332 continue;
1333 }
1334 let (callee, (lbp, rbp)) = match self.peek() {
1335 TokenKind::Pipe => (None, PIPE_POWER),
1336 other => match infix_of(other) {
1337 Some(i) => (Some(i.callee), i.power),
1338 None => break,
1339 },
1340 };
1341 if lbp < min_bp {
1342 break;
1343 }
1344 self.infix_spelling(callee, op_span);
1345 self.bump();
1346 self.skip_newlines();
1347 let rhs = self.expr(rbp)?;
1348 lhs = self.infix_node(start, callee, op_span, lhs, rhs);
1349 }
1350
1351 Ok(lhs)
1352 }
1353
1354 #[inline(never)]
1355 fn interpolation(span: Span, parts: &[String], exprs: &[String]) -> Result<Spanned, Fail> {
1356 let mut acc = atom_at(span, Atom::Str(parts[0].clone()));
1366 for (i, raw) in exprs.iter().enumerate() {
1367 let inner = parse_expr(raw).map_err(|e| {
1368 fail(
1369 format!("in interpolation `#{{{raw}}}`: {}", e.message),
1370 span,
1371 )
1372 })?;
1373 let inner = Spanned::from_sexp_at(&inner, span);
1374 acc = list_at(span, vec![sym_at(span, LOWERED_CONCAT), acc, inner]);
1375 acc = list_at(
1378 span,
1379 vec![
1380 sym_at(span, LOWERED_CONCAT),
1381 acc,
1382 atom_at(span, Atom::Str(parts[i + 1].clone())),
1383 ],
1384 );
1385 }
1386 Ok(acc)
1387 }
1388
1389 fn lines_literal(
1396 &mut self,
1397 span: Span,
1398 parts: Vec<String>,
1399 exprs: Vec<String>,
1400 ) -> Result<Spanned, Fail> {
1401 let mut lines: Vec<(Vec<String>, Vec<String>)> = vec![(vec![String::new()], Vec::new())];
1402 for (i, part) in parts.iter().enumerate() {
1403 let mut pieces = part.split('\n');
1404 if let Some(head) = pieces.next() {
1405 if let Some(last) = lines.last_mut().and_then(|l| l.0.last_mut()) {
1406 last.push_str(head);
1407 }
1408 }
1409 for piece in pieces {
1410 lines.push((vec![piece.to_string()], Vec::new()));
1411 }
1412 if let Some(raw) = exprs.get(i) {
1413 if let Some(line) = lines.last_mut() {
1414 line.1.push(raw.clone());
1415 line.0.push(String::new());
1416 }
1417 }
1418 }
1419 let empty = |l: &(Vec<String>, Vec<String>)| l.1.is_empty() && l.0 == [""];
1420 if lines.len() == 2 && lines.iter().all(empty) {
1421 self.note(Spelling::NewlineEscape, span, "\"\\n\"", "line_break");
1422 return Ok(sym_at(span, "line_break"));
1423 }
1424 let trailing = lines.last().is_some_and(empty);
1425 if trailing {
1426 lines.pop();
1427 }
1428 let head = if trailing { "unlines" } else { "join_lines" };
1429 let mut items = vec![sym_at(span, "list")];
1430 for (lits, raws) in &lines {
1431 let item = if raws.is_empty() {
1432 atom_at(span, Atom::Str(lits[0].clone()))
1433 } else if raws.len() == 1 && lits.iter().all(String::is_empty) {
1434 let inner = parse_expr(&raws[0]).map_err(|e| {
1435 fail(
1436 format!("in interpolation `#{{{}}}`: {}", raws[0], e.message),
1437 span,
1438 )
1439 })?;
1440 Spanned::from_sexp_at(&inner, span)
1441 } else {
1442 Self::interpolation(span, lits, raws)?
1443 };
1444 items.push(item);
1445 }
1446 self.note(
1447 Spelling::NewlineEscape,
1448 span,
1449 "\"…\\n…\"",
1450 &format!("{head}([…])"),
1451 );
1452 Ok(list_at(span, vec![sym_at(span, head), list_at(span, items)]))
1453 }
1454
1455 #[inline(never)]
1456 fn not_prefix(&mut self, span: Span) -> Result<Spanned, Fail> {
1457 if self.at(&TokenKind::LParen) && self.touches_previous() {
1458 self.note(Spelling::OperatorSpelling, span, "not(x)", "not x");
1459 let (args, splat) = self.call_args()?;
1460 if args.len() != 1 || splat.is_some() {
1461 let call = self.call_node(
1462 self.span_since(span.start),
1463 vec![sym_at(span, "not")],
1464 args,
1465 splat,
1466 );
1467 return Ok(call);
1468 }
1469 let operand = args.into_iter().next().expect("one argument");
1470 return Ok(self.negation(span, self.span_since(span.start), operand));
1471 }
1472 let rhs = self.expr(11)?;
1473 Ok(self.negation(span, self.span_since(span.start), rhs))
1474 }
1475
1476 fn prefix(&mut self) -> Result<Spanned, Fail> {
1477 if self.at(&TokenKind::LParen) {
1478 let span = self.peek_span();
1479 self.bump();
1480 if self.arrow_lambda_ahead() {
1481 return self.arrow_lambda(span);
1482 }
1483 return self.group();
1484 }
1485 self.prefix_token()
1486 }
1487
1488 fn group(&mut self) -> Result<Spanned, Fail> {
1489 self.skip_newlines();
1490 let inner = self.expr(0)?;
1491 self.skip_newlines();
1492 self.expect(&TokenKind::RParen, "`)`")?;
1493 Ok(inner)
1494 }
1495
1496 #[inline(never)]
1497 fn prefix_token(&mut self) -> Result<Spanned, Fail> {
1498 let span = self.peek_span();
1499 match self.bump() {
1500 TokenKind::Int(v) => Ok(atom_at(span, Atom::Int(v))),
1501 TokenKind::Float(v) => Ok(atom_at(span, Atom::Float(v))),
1502 TokenKind::Str(s) if self.quoting == 0 && s.contains('\n') => {
1503 self.lines_literal(span, vec![s], Vec::new())
1504 }
1505 TokenKind::Str(s) => Ok(atom_at(span, Atom::Str(s))),
1506 TokenKind::Doc { module, .. } => Err(fail(
1507 format!(
1508 "`{}` sits at the top level, above the definition it documents, never \
1509 inside a form",
1510 if module { "@moduledoc" } else { "@doc" }
1511 ),
1512 span,
1513 )),
1514
1515 TokenKind::InterpolatedStr { parts, exprs }
1526 if self.quoting == 0 && parts.iter().any(|p| p.contains('\n')) =>
1527 {
1528 self.lines_literal(span, parts, exprs)
1529 }
1530 TokenKind::InterpolatedStr { parts, exprs } => Self::interpolation(span, &parts, &exprs),
1531 TokenKind::Sym(s) => Ok(atom_at(span, Atom::Keyword(s))),
1532 TokenKind::True => Ok(atom_at(span, Atom::Bool(true))),
1533 TokenKind::False => Ok(atom_at(span, Atom::Bool(false))),
1534 TokenKind::Nil => Ok(Spanned::new(span, SpannedForm::Nil)),
1535
1536 TokenKind::Op(o) if o == "-" => {
1537 let rhs = self.expr(11)?; Ok(list_at(
1539 self.span_since(span.start),
1540 vec![sym_at(span, "-"), atom_at(span, Atom::Int(0)), rhs],
1541 ))
1542 }
1543 TokenKind::Op(o) if o == "!" => {
1544 let written = self.text(span).to_string();
1545 self.note(Spelling::LogicalSymbol, span, &written, "not");
1546 let rhs = self.expr(11)?;
1547 Ok(self.negation(span, self.span_since(span.start), rhs))
1548 }
1549 TokenKind::Ident(w) if w == "not" => self.not_prefix(span),
1550 TokenKind::Ident(w) if (w == "and" || w == "or") && self.at(&TokenKind::LParen) => {
1551 Ok(sym_at(span, &w))
1552 }
1553 TokenKind::Ident(w) if w == "and" || w == "or" => Err(fail(
1554 format!("`{w}` joins two expressions: `a {w} b`"),
1555 span,
1556 )),
1557
1558 TokenKind::LParen => {
1559 self.skip_newlines();
1560 let inner = self.expr(0)?;
1561 self.skip_newlines();
1562 self.expect(&TokenKind::RParen, "`)`")?;
1563 Ok(inner)
1567 }
1568
1569 TokenKind::LBracket => self.list_literal(span),
1570 TokenKind::LBrace => self.map_literal(span),
1571
1572 TokenKind::Ident(name) if self.at(&TokenKind::PathSep) => {
1573 self.qualified_name(span, &name)
1574 }
1575 TokenKind::PathSep => Err(fail(
1576 "a qualified name is `package::name`, with the package written before `::`",
1577 span,
1578 )),
1579 TokenKind::Ident(name) => match name.as_str() {
1580 "if" => self.if_form(span, false),
1581 "begin" if !(self.at(&TokenKind::LParen) && self.touches_previous()) => {
1582 self.begin_form(span)
1583 }
1584 "rescue" | "ensure" => Err(fail(
1585 format!("`{name}` belongs to a `begin … end` block or a `def` body, after the statements it protects"),
1586 span,
1587 )),
1588 "unless" => {
1589 self.note(Spelling::Unless, span, "unless", "if not");
1590 self.if_form(span, true)
1591 }
1592 "def" => self.def_form(span),
1593 "defmacro" => self.defmacro_form(span),
1594 "case" => self.case_form(span),
1595 "fn" => self.lambda_form(span),
1596 "test" => self.test_form(span),
1597 "assert" => self.assert_form(span),
1598 "quote" => self.quote_form(span),
1599 "unquote" => self.unquote_form(span, false),
1600 "unquote_splice" => self.unquote_form(span, true),
1601 "do" => Err(fail(
1602 "blue has no `do` blocks: pass a function as an argument, \
1603 `f(xs, fn(x) … end)`",
1604 span,
1605 )),
1606 "end" => Err(fail(
1607 "unexpected `end`: nothing open here for it to close",
1608 span,
1609 )),
1610 _ => Ok(sym_at(span, &name)),
1611 },
1612
1613 other => Err(fail(format!("expected an expression, found {other}"), span)),
1614 }
1615 }
1616
1617 fn qualified_name(&mut self, pkg_span: Span, pkg: &str) -> Result<Spanned, Fail> {
1627 if is_reserved_word(pkg) {
1628 return Err(fail(
1629 format!("`{pkg}` is a reserved word and cannot name a package"),
1630 pkg_span,
1631 ));
1632 }
1633 self.bump(); let name_span = self.peek_span();
1635 let name = match self.bump() {
1636 TokenKind::Ident(n) => n,
1637 TokenKind::True => "true".to_string(),
1639 TokenKind::False => "false".to_string(),
1640 TokenKind::Nil => "nil".to_string(),
1641 other => {
1642 return Err(fail(
1643 format!("expected a name after `{pkg}::`, found {other}"),
1644 name_span,
1645 ))
1646 }
1647 };
1648 if self.at(&TokenKind::PathSep) {
1649 return Err(self.error(format!(
1650 "a qualified name has one qualifier: `package::name`, and `{pkg}::{name}::…` has two"
1651 )));
1652 }
1653 Ok(sym_at(
1654 self.span_since(pkg_span.start),
1655 &qualify(pkg, &name),
1656 ))
1657 }
1658
1659 #[inline(never)]
1666 fn negation(&mut self, head: Span, whole: Span, operand: Spanned) -> Spanned {
1667 if let SpannedForm::List(items) = &operand.form {
1668 let flipped = match items.first().and_then(Spanned::as_symbol) {
1669 Some("equal?") => Some("not="),
1670 Some("not=") => Some("equal?"),
1671 _ => None,
1672 };
1673 if let (Some(to), 3) = (flipped, items.len()) {
1674 let (written, canonical) = if to == "not=" {
1675 ("not (a == b)", "a != b")
1676 } else {
1677 ("not (a != b)", "a == b")
1678 };
1679 self.note(Spelling::NegatedComparison, whole, written, canonical);
1680 let mut out = items.clone();
1681 out[0] = sym_at(items[0].span, to);
1682 if to == "equal?" {
1683 if let Some(test) =
1684 self.nil_test(whole.start, items[0].span, &items[1], &items[2])
1685 {
1686 return test;
1687 }
1688 }
1689 return list_at(whole, out);
1690 }
1691 }
1692 list_at(whole, vec![sym_at(head, "not"), operand])
1693 }
1694
1695 #[inline(never)]
1696 fn call_spelling(&mut self, call: Spanned) -> Spanned {
1697 let SpannedForm::List(items) = &call.form else {
1698 return call;
1699 };
1700 let Some(head) = items.first().and_then(Spanned::as_symbol) else {
1701 return call;
1702 };
1703 let argc = items.len() - 1;
1704 if head == "equal?" && argc == 2 {
1705 let (op_span, start) = (items[0].span, call.span.start);
1706 if let Some(test) = self.nil_test(start, op_span, &items[1], &items[2]) {
1707 return test;
1708 }
1709 }
1710 let operator = match (head, argc) {
1711 ("and" | "or" | "mod" | "equal?", 2) => Some(head),
1712 ("list", _) => Some("list"),
1713 _ => None,
1714 };
1715 if let Some(op) = operator {
1716 let canonical = match op {
1717 "list" => "[…]".to_string(),
1718 "mod" => "a % b".to_string(),
1719 "equal?" => "a == b".to_string(),
1720 other => format!("a {other} b"),
1721 };
1722 self.note(
1723 Spelling::OperatorSpelling,
1724 call.span,
1725 &format!("{head}(…)"),
1726 &canonical,
1727 );
1728 return call;
1729 }
1730 if head == "apply"
1731 && self.text(items[0].span) == "apply"
1732 && argc >= 2
1733 && items[1].as_symbol().is_some_and(is_callable_name)
1734 {
1735 self.note(Spelling::Apply, call.span, "apply(f, …, xs)", "f(…, *xs)");
1736 return call;
1737 }
1738 if head == "try" {
1739 return self.try_spelling(call);
1740 }
1741 call
1742 }
1743
1744 fn try_spelling(&mut self, call: Spanned) -> Spanned {
1745 let SpannedForm::List(items) = &call.form else {
1746 return call;
1747 };
1748 let Some((catch, body)) = items[1..].split_last() else {
1749 return call;
1750 };
1751 let Some(clause) = catch.as_list() else {
1752 return call;
1753 };
1754 let binder = match clause {
1755 [head, binding, handler @ ..]
1756 if head.as_symbol() == Some("catch") && !handler.is_empty() =>
1757 {
1758 match binding.as_list() {
1759 Some([b]) => b.as_symbol().map(|n| (n.to_string(), b.span)),
1760 _ => None,
1761 }
1762 }
1763 _ => None,
1764 };
1765 let Some((name, binder_span)) = binder else {
1766 return call;
1767 };
1768 if body.is_empty() {
1769 return call;
1770 }
1771 self.note(Spelling::TryCatch, call.span, "try(…, catch(…))", "rescue");
1772 let handler = &clause[2..];
1773 let rename =
1774 name.starts_with('_') && name != "_" && !handler.iter().any(|h| mentions(h, &name));
1775 let body = if body.len() == 1 {
1776 body[0].clone()
1777 } else {
1778 let span = cover(body, call.span);
1779 let mut list = vec![sym_at(Span::new(span.start, span.start), "begin")];
1780 list.extend(body.iter().cloned());
1781 list_at(span, list)
1782 };
1783 let handler = if handler.len() == 1 {
1784 handler[0].clone()
1785 } else {
1786 let span = cover(handler, catch.span);
1787 let mut list = vec![sym_at(Span::new(span.start, span.start), "begin")];
1788 list.extend(handler.iter().cloned());
1789 list_at(span, list)
1790 };
1791 let binder = sym_at(binder_span, if rename { "_" } else { &name });
1792 list_at(
1793 call.span,
1794 vec![
1795 items[0].clone(),
1796 body,
1797 list_at(
1798 catch.span,
1799 vec![
1800 clause[0].clone(),
1801 list_at(clause[1].span, vec![binder]),
1802 handler,
1803 ],
1804 ),
1805 ],
1806 )
1807 }
1808
1809 fn finish_send(&mut self, start: usize, recv: Spanned, dot: Span) -> Result<Spanned, Fail> {
1810 let name_span = self.peek_span();
1811 let name = match self.bump() {
1812 TokenKind::Ident(n) if is_reserved_word(&n) => {
1826 return Err(self.error(format!(
1827 "`{n}` is a reserved word and cannot be a method name — \
1828 `recv.{n}` would lower onto the `{n}` form itself"
1829 )))
1830 }
1831 TokenKind::Ident(n) => n,
1832 other => {
1833 return Err(fail(
1834 format!("expected a method name after `.`, found {other}"),
1835 name_span,
1836 ))
1837 }
1838 };
1839 if !self.at(&TokenKind::LParen) {
1840 return Ok(list_at(
1841 self.span_since(start),
1842 vec![
1843 sym_at(dot, LOWERED_FIELD),
1844 recv,
1845 atom_at(name_span, Atom::Keyword(name)),
1846 ],
1847 ));
1848 }
1849 self.note(
1850 Spelling::CallForm,
1851 dot.merge(name_span),
1852 &format!("x.{name}(…)"),
1853 &format!("{name}(x, …)"),
1854 );
1855 let (args, splat) = self.call_args()?;
1856 Ok(self.call_node(
1857 self.span_since(start),
1858 vec![sym_at(name_span, &name), recv],
1859 args,
1860 splat,
1861 ))
1862 }
1863
1864 fn call_node(
1865 &self,
1866 span: Span,
1867 head: Vec<Spanned>,
1868 args: Vec<Spanned>,
1869 splat: Option<Span>,
1870 ) -> Spanned {
1871 let mut list = Vec::with_capacity(head.len() + args.len() + 1);
1872 if let Some(at) = splat {
1873 list.push(sym_at(at, "apply"));
1874 }
1875 list.extend(head);
1876 list.extend(args);
1877 list_at(span, list)
1878 }
1879
1880 fn call_args(&mut self) -> Result<(Vec<Spanned>, Option<Span>), Fail> {
1881 self.expect(&TokenKind::LParen, "`(`")?;
1882 let mut args = Vec::new();
1883 let mut splat = None;
1884 self.skip_newlines();
1885 if self.eat(&TokenKind::RParen) {
1886 return Ok((args, splat));
1887 }
1888 loop {
1889 self.skip_newlines();
1890 if splat.is_some() {
1891 return Err(
1892 self.error("a splat `*xs` passes a list as the LAST arguments: write it last")
1893 );
1894 }
1895 if self.at_spread() || matches!(self.peek(), TokenKind::Label(_)) {
1896 let open = self.peek_span();
1897 let (segments, spread) = self.map_entries(open, &TokenKind::RParen)?;
1898 if !spread {
1899 return Err(fail(
1900 "a keyword entry in a call needs a `**` spread: pass a map, f({a: 1})",
1901 open,
1902 ));
1903 }
1904 args.push(self.merged(open, LOWERED_KWARGS, segments));
1905 return Ok((args, splat));
1906 }
1907 if matches!(self.peek(), TokenKind::Op(o) if o == "*") {
1908 splat = Some(self.peek_span());
1909 self.bump();
1910 }
1911 args.push(self.expr(0)?);
1912 self.skip_newlines();
1913 if self.eat(&TokenKind::Comma) {
1914 if self.trailing_comma(&TokenKind::RParen) {
1915 break;
1916 }
1917 continue;
1918 }
1919 self.expect(&TokenKind::RParen, "`,` or `)`")?;
1920 break;
1921 }
1922 Ok((args, splat))
1923 }
1924
1925 fn trailing_comma(&mut self, closer: &TokenKind) -> bool {
1926 let comma = self
1927 .toks
1928 .get(self.pos.wrapping_sub(1))
1929 .map_or(self.peek_span(), |t| t.span);
1930 self.skip_newlines();
1931 if self.at(closer) {
1932 self.note(Spelling::TrailingComma, comma, ",", "");
1933 self.bump();
1934 return true;
1935 }
1936 false
1937 }
1938
1939 fn list_literal(&mut self, open: Span) -> Result<Spanned, Fail> {
1940 let mut items = vec![sym_at(open, "list")];
1941 self.skip_newlines();
1942 if self.eat(&TokenKind::RBracket) {
1943 return Ok(list_at(self.span_since(open.start), items));
1944 }
1945 loop {
1946 self.skip_newlines();
1947 items.push(self.expr(0)?);
1948 self.skip_newlines();
1949 if self.eat(&TokenKind::Comma) {
1950 if self.trailing_comma(&TokenKind::RBracket) {
1951 break;
1952 }
1953 continue;
1954 }
1955 self.expect(&TokenKind::RBracket, "`,` or `]`")?;
1956 break;
1957 }
1958 Ok(list_at(self.span_since(open.start), items))
1959 }
1960
1961 fn map_literal(&mut self, open: Span) -> Result<Spanned, Fail> {
1968 self.skip_newlines();
1969 if self.eat(&TokenKind::RBrace) {
1970 return Ok(list_at(
1971 self.span_since(open.start),
1972 vec![sym_at(open, LOWERED_MAP)],
1973 ));
1974 }
1975 let (mut segments, spread) = self.map_entries(open, &TokenKind::RBrace)?;
1976 if !spread {
1977 return Ok(segments.remove(0));
1978 }
1979 Ok(self.merged(open, LOWERED_MERGE, segments))
1980 }
1981
1982 fn merged(&self, open: Span, head: &str, segments: Vec<Spanned>) -> Spanned {
1983 let mut items = vec![sym_at(open, head)];
1984 items.extend(segments);
1985 list_at(self.span_since(open.start), items)
1986 }
1987
1988 fn at_spread(&self) -> bool {
1989 matches!(self.peek(), TokenKind::Op(o) if o == "**")
1990 }
1991
1992 fn map_entries(
1993 &mut self,
1994 open: Span,
1995 closer: &TokenKind,
1996 ) -> Result<(Vec<Spanned>, bool), Fail> {
1997 let mut segments = Vec::new();
1998 let mut items = vec![sym_at(open, LOWERED_MAP)];
1999 let mut spread = false;
2000 loop {
2001 self.skip_newlines();
2002 if self.at_spread() {
2003 let star = self.peek_span();
2004 self.bump();
2005 let value = self.expr(0)?;
2006 if items.len() > 1 {
2007 let span = items[1].span.merge(items[items.len() - 1].span);
2008 items[0] = sym_at(items[1].span, LOWERED_MAP);
2009 segments.push(list_at(span, std::mem::take(&mut items)));
2010 items = vec![sym_at(open, LOWERED_MAP)];
2011 }
2012 segments.push(list_at(
2013 star.merge(value.span),
2014 vec![sym_at(star, LOWERED_SPLICE), value],
2015 ));
2016 spread = true;
2017 } else {
2018 self.map_entry(&mut items, closer)?;
2019 }
2020 self.skip_newlines();
2021 if self.eat(&TokenKind::Comma) {
2022 if self.trailing_comma(closer) {
2023 break;
2024 }
2025 continue;
2026 }
2027 self.expect(closer, "`,` or the end of the entries")?;
2028 break;
2029 }
2030 if items.len() > 1 || !spread {
2031 let whole = self.span_since(open.start);
2032 segments.push(list_at(whole, items));
2033 }
2034 Ok((segments, spread))
2035 }
2036
2037 fn map_entry(&mut self, items: &mut Vec<Spanned>, closer: &TokenKind) -> Result<(), Fail> {
2038 match self.peek().clone() {
2039 TokenKind::Label(name) => {
2040 let label_span = self.peek_span();
2041 self.bump();
2042 self.skip_newlines();
2043 items.push(atom_at(label_span, Atom::Keyword(name.clone())));
2044 if matches!(self.peek(), TokenKind::Comma) || self.at(closer) {
2045 items.push(sym_at(label_span, &name));
2046 } else {
2047 let value = self.expr(0)?;
2048 if matches!(&value.form, SpannedForm::Atom(Atom::Symbol(s)) if *s == name)
2049 && label_name(&name)
2050 {
2051 let written = format!("{name}: {name}");
2052 let canonical = format!("{name}:");
2053 self.note(
2054 Spelling::MapPun,
2055 label_span.merge(value.span),
2056 &written,
2057 &canonical,
2058 );
2059 }
2060 items.push(value);
2061 }
2062 }
2063 _ => {
2064 let k = self.expr(0)?;
2065 self.skip_newlines();
2066 if let SpannedForm::Atom(Atom::Keyword(name)) = &k.form {
2067 if blue_lang_name(name) {
2068 let written = format!(":{name} =>");
2069 let canonical = format!("{name}:");
2070 self.note(Spelling::KeywordRocket, k.span, &written, &canonical);
2071 }
2072 }
2073 self.expect(&TokenKind::Rocket, "`=>` in a map literal")?;
2074 self.skip_newlines();
2075 items.push(k);
2076 items.push(self.expr(0)?);
2077 }
2078 }
2079 Ok(())
2080 }
2081
2082 fn if_chain(&mut self, head: Span) -> Result<Spanned, Fail> {
2092 let cond = self.expr(0)?;
2093 let then = self.body(&["elsif", "else", "end"])?;
2094 let els = self.else_part()?;
2095 let mut out = vec![sym_at(head, "if"), cond, then];
2096 if let Some(e) = els {
2097 out.push(e);
2098 }
2099 Ok(list_at(self.span_since(head.start), out))
2100 }
2101
2102 fn else_part(&mut self) -> Result<Option<Spanned>, Fail> {
2103 if self.at_ident("elsif") {
2104 let elsif = self.peek_span();
2105 self.bump();
2106 return Ok(Some(self.if_chain(elsif)?));
2107 }
2108 if !self.at_ident("else") {
2109 self.expect_ident("end")?;
2110 return Ok(None);
2111 }
2112 let else_span = self.peek_span();
2113 self.bump();
2114 let save = self.pos;
2115 self.skip_newlines();
2116 let leads_with_if = self.at_ident("if") || self.at_ident("unless");
2117 self.pos = save;
2118 let e = self.body(&["end"])?;
2119 let end_at = self.peek_span().start;
2120 self.expect_ident("end")?;
2121 if matches!(e.form, SpannedForm::Nil) && !self.comment_within(else_span.start, end_at) {
2122 self.note(Spelling::ElseNil, else_span, "else nil", "no else");
2123 return Ok(None);
2124 }
2125 let single_if = matches!(&e.form, SpannedForm::List(items)
2126 if (items.len() == 3 || items.len() == 4) && items[0].as_symbol() == Some("if"));
2127 if leads_with_if && single_if {
2128 self.note(Spelling::ElseIf, else_span, "else if", "elsif");
2129 }
2130 Ok(Some(e))
2131 }
2132
2133 fn if_form(&mut self, head: Span, negate: bool) -> Result<Spanned, Fail> {
2134 let cond = self.expr(0)?;
2135 let cond = if negate {
2136 let inner = cond.span;
2139 self.negation(head, head.merge(inner), cond)
2140 } else {
2141 cond
2142 };
2143 let then = self.body(&["elsif", "else", "end"])?;
2156 let els = self.else_part()?;
2157 let mut out = vec![sym_at(head, "if"), cond, then];
2158 if let Some(e) = els {
2159 out.push(e);
2160 }
2161 Ok(list_at(self.span_since(head.start), out))
2162 }
2163
2164 fn case_form(&mut self, head: Span) -> Result<Spanned, Fail> {
2190 let subject = self.expr(0)?;
2191 self.skip_newlines();
2192
2193 let subject_var = "case-subject";
2196 let mut arms: Vec<Spanned> = Vec::new();
2197 let mut otherwise: Option<Spanned> = None;
2198 let mut else_kw: Option<Span> = None;
2201
2202 loop {
2203 self.skip_newlines();
2204 if self.at_ident("end") {
2205 break;
2206 }
2207 let maybe_else = self.peek_span();
2208 if self.eat_ident("else") {
2209 else_kw = Some(maybe_else);
2210 otherwise = Some(self.body(&["end"])?);
2211 continue;
2212 }
2213 let when = self.peek_span();
2214 if !self.eat_ident("when") {
2215 return Err(self.error(format!(
2216 "expected `when`, `else` or `end` in a case, found {}",
2217 self.peek()
2218 )));
2219 }
2220 self.skip_newlines();
2221 let pattern = self.expr(0)?;
2222 let body = self.body(&["when", "else", "end"])?;
2223 let test = list_at(
2227 when.merge(pattern.span),
2228 vec![sym_at(when, "equal?"), sym_at(when, subject_var), pattern],
2229 );
2230 arms.push(list_at(when.merge(body.span), vec![test, body]));
2231 }
2232 self.expect_ident("end")?;
2233
2234 if arms.is_empty() && otherwise.is_none() {
2235 return Err(self.error("a case needs at least one `when` or an `else`".to_string()));
2236 }
2237
2238 let mut cond = vec![sym_at(head, "cond")];
2243 cond.extend(arms);
2244 let else_span = else_kw.unwrap_or(head);
2251 let else_body = otherwise.unwrap_or_else(|| Spanned::new(else_span, SpannedForm::Nil));
2252 cond.push(list_at(
2253 else_span.merge(else_body.span),
2254 vec![sym_at(else_span, "else"), else_body],
2255 ));
2256
2257 let whole = self.span_since(head.start);
2258 let binding = list_at(
2259 head.merge(subject.span),
2260 vec![sym_at(head, subject_var), subject],
2261 );
2262 Ok(list_at(
2263 whole,
2264 vec![
2265 sym_at(head, "let"),
2266 list_at(binding.span, vec![binding]),
2267 list_at(whole, cond),
2268 ],
2269 ))
2270 }
2271
2272 fn arrow_lambda_ahead(&self) -> bool {
2275 let mut i = self.pos;
2276 let kind = |i: usize| self.toks.get(i).map(|t| &t.kind);
2277 if !matches!(kind(i), Some(TokenKind::RParen)) {
2278 loop {
2279 if matches!(kind(i), Some(TokenKind::Op(o)) if o == "*") {
2280 i += 1;
2281 }
2282 if !matches!(kind(i), Some(TokenKind::Ident(_))) {
2283 return false;
2284 }
2285 i += 1;
2286 match kind(i) {
2287 Some(TokenKind::Comma) => i += 1,
2288 Some(TokenKind::RParen) => break,
2289 _ => return false,
2290 }
2291 }
2292 }
2293 matches!(kind(i + 1), Some(TokenKind::Op(o)) if o == "->")
2294 }
2295
2296 fn arrow_lambda(&mut self, head: Span) -> Result<Spanned, Fail> {
2303 let mut params: Vec<Spanned> = Vec::new();
2304 while !self.at(&TokenKind::RParen) {
2305 let p_span = self.peek_span();
2306 if self.at_star() {
2307 params.extend(self.rest_parameter()?);
2308 break;
2309 }
2310 match self.bump() {
2311 TokenKind::Ident(p) => params.push(sym_at(p_span, &p)),
2312 other => {
2313 return Err(fail(
2314 format!("expected a parameter name, found {other}"),
2315 p_span,
2316 ))
2317 }
2318 }
2319 self.eat(&TokenKind::Comma);
2320 }
2321 self.bump();
2322 self.bump();
2323 self.skip_newlines();
2324 let params_span = cover(¶ms, head);
2325 let body = self.expr(0)?;
2326 Ok(list_at(
2327 self.span_since(head.start),
2328 vec![sym_at(head, "lambda"), list_at(params_span, params), body],
2329 ))
2330 }
2331
2332 fn rest_parameter(&mut self) -> Result<[Spanned; 2], Fail> {
2333 let star = self.peek_span();
2334 self.bump();
2335 let name_span = self.peek_span();
2336 match self.bump() {
2337 TokenKind::Ident(p) => {
2338 let whole = star.merge(name_span);
2339 Ok([sym_at(whole, "&rest"), sym_at(whole, &p)])
2340 }
2341 other => Err(fail(
2342 format!("expected a parameter name after `*`, found {other}"),
2343 name_span,
2344 )),
2345 }
2346 }
2347
2348 fn at_star(&self) -> bool {
2349 matches!(self.peek(), TokenKind::Op(o) if o == "*")
2350 }
2351
2352 fn optional_mark(
2353 &self,
2354 name: String,
2355 span: Span,
2356 optional_from: &mut Option<usize>,
2357 index: usize,
2358 ) -> Result<String, Fail> {
2359 match name.strip_suffix('?').filter(|n| !n.is_empty()) {
2360 Some(bare) => {
2361 optional_from.get_or_insert(index);
2362 Ok(bare.to_string())
2363 }
2364 None if optional_from.is_some() => Err(fail(
2365 format!("the required parameter `{name}` cannot follow an optional one"),
2366 span,
2367 )),
2368 None => Ok(name),
2369 }
2370 }
2371
2372 fn rest_is_last(&mut self) -> Result<(), Fail> {
2373 self.skip_newlines();
2374 if self.at(&TokenKind::Comma) {
2375 return Err(self.error("a rest parameter `*name` comes last".to_string()));
2376 }
2377 Ok(())
2378 }
2379
2380 fn lambda_form(&mut self, head: Span) -> Result<Spanned, Fail> {
2390 let mut params: Vec<Spanned> = Vec::new();
2391 if !self.at(&TokenKind::LParen) {
2392 self.note(Spelling::ParameterList, head, "fn", "fn()");
2393 }
2394 if self.at(&TokenKind::LParen) {
2395 self.bump();
2396 self.skip_newlines();
2397 if !self.eat(&TokenKind::RParen) {
2398 loop {
2399 self.skip_newlines();
2400 let p_span = self.peek_span();
2401 if self.at_star() {
2402 params.extend(self.rest_parameter()?);
2403 self.rest_is_last()?;
2404 self.expect(&TokenKind::RParen, "`)` after the rest parameter")?;
2405 break;
2406 }
2407 match self.bump() {
2408 TokenKind::Ident(p) => params.push(sym_at(p_span, &p)),
2409 other => {
2410 return Err(fail(
2411 format!("expected a parameter name, found {other}"),
2412 p_span,
2413 ))
2414 }
2415 }
2416 self.skip_newlines();
2417 if self.eat(&TokenKind::Comma) {
2418 if self.trailing_comma(&TokenKind::RParen) {
2419 break;
2420 }
2421 continue;
2422 }
2423 self.expect(&TokenKind::RParen, "`,` or `)`")?;
2424 break;
2425 }
2426 }
2427 }
2428 let params_span = cover(¶ms, head);
2429 let body = self.body(&["end"])?;
2430 self.expect_ident("end")?;
2431 Ok(list_at(
2432 self.span_since(head.start),
2433 vec![sym_at(head, "lambda"), list_at(params_span, params), body],
2434 ))
2435 }
2436
2437 fn test_form(&mut self, head: Span) -> Result<Spanned, Fail> {
2443 let name_span = self.peek_span();
2444 let name = match self.bump() {
2445 TokenKind::Str(s) => s,
2446 other => {
2447 return Err(fail(
2448 format!("expected a string name after `test`, found {other}"),
2449 name_span,
2450 ))
2451 }
2452 };
2453 let body = self.body(&["end"])?;
2454 self.expect_ident("end")?;
2455 Ok(list_at(
2456 self.span_since(head.start),
2457 vec![
2458 sym_at(head, "deftest"),
2459 atom_at(name_span, Atom::Str(name)),
2460 body,
2461 ],
2462 ))
2463 }
2464
2465 fn assert_form(&mut self, head: Span) -> Result<Spanned, Fail> {
2489 let e = self.expr(0)?;
2490 let quoted = Spanned::new(e.span, SpannedForm::Quote(Box::new(e.clone())));
2491 Ok(list_at(
2492 self.span_since(head.start),
2493 vec![sym_at(head, LOWERED_ASSERT), quoted, e],
2494 ))
2495 }
2496
2497 fn defmacro_form(&mut self, head: Span) -> Result<Spanned, Fail> {
2506 let name_span = self.peek_span();
2507 let name = match self.bump() {
2508 TokenKind::Ident(n) => n,
2509 other => {
2510 return Err(fail(
2511 format!("expected a name after `defmacro`, found {other}"),
2512 name_span,
2513 ))
2514 }
2515 };
2516 self.not_reserved(&name, name_span, "a `defmacro` name")?;
2517 let mut params: Vec<Spanned> = Vec::new();
2518 if self.at(&TokenKind::LParen) {
2519 self.bump();
2520 self.skip_newlines();
2521 if !self.eat(&TokenKind::RParen) {
2522 loop {
2523 self.skip_newlines();
2524 let p_span = self.peek_span();
2525 match self.bump() {
2526 TokenKind::Ident(p) => params.push(sym_at(p_span, &p)),
2527 TokenKind::Label(p) => {
2528 return Err(self.error(format!(
2529 "macro parameter `{p}` cannot be typed: a macro receives \
2530 source forms, not values"
2531 )))
2532 }
2533 other => {
2534 return Err(fail(
2535 format!("expected a parameter name, found {other}"),
2536 p_span,
2537 ))
2538 }
2539 }
2540 self.skip_newlines();
2541 if self.eat(&TokenKind::Comma) {
2542 if self.trailing_comma(&TokenKind::RParen) {
2543 break;
2544 }
2545 continue;
2546 }
2547 self.expect(&TokenKind::RParen, "`,` or `)`")?;
2548 break;
2549 }
2550 }
2551 }
2552 if matches!(self.peek(), TokenKind::Op(o) if o == "->") {
2553 return Err(self.error(
2554 "a macro has no return type: it produces source forms, not values".to_string(),
2555 ));
2556 }
2557
2558 let params_span = cover(¶ms, name_span);
2559 let body = self.body(&["end"])?;
2560 self.expect_ident("end")?;
2561
2562 Ok(list_at(
2565 self.span_since(head.start),
2566 vec![
2567 sym_at(head, "defmacro"),
2568 sym_at(name_span, &name),
2569 list_at(params_span, params),
2570 body,
2571 ],
2572 ))
2573 }
2574
2575 fn quote_form(&mut self, head: Span) -> Result<Spanned, Fail> {
2581 self.quoting += 1;
2582 let body = self.body(&["end"]);
2583 self.quoting -= 1;
2584 let body = body?;
2585 self.expect_ident("end")?;
2586 Ok(Spanned::new(
2595 self.span_since(head.start),
2596 SpannedForm::Quasiquote(Box::new(body)),
2597 ))
2598 }
2599
2600 fn unquote_form(&mut self, head: Span, splice: bool) -> Result<Spanned, Fail> {
2602 self.expect(&TokenKind::LParen, "`(` after unquote")?;
2603 self.skip_newlines();
2604 let inner = self.expr(0)?;
2605 self.skip_newlines();
2606 self.expect(&TokenKind::RParen, "`)`")?;
2607 let span = self.span_since(head.start);
2608 Ok(Spanned::new(
2609 span,
2610 if splice {
2611 SpannedForm::UnquoteSplice(Box::new(inner))
2612 } else {
2613 SpannedForm::Unquote(Box::new(inner))
2614 },
2615 ))
2616 }
2617
2618 fn protected_body(&mut self, head: Span) -> Result<Spanned, Fail> {
2619 let body = self.body(&["rescue", "ensure", "end"])?;
2620 if self.at_ident("rescue") || self.at_ident("ensure") {
2621 return self.rescue_tail(head, body);
2622 }
2623 Ok(body)
2624 }
2625
2626 fn begin_form(&mut self, head: Span) -> Result<Spanned, Fail> {
2627 let body = self.body(&["rescue", "ensure", "end"])?;
2628 if !(self.at_ident("rescue") || self.at_ident("ensure")) {
2629 return Err(self.error(
2630 "a `begin` block protects its statements with `rescue` or `ensure`; without either, write the statements directly",
2631 ));
2632 }
2633 let tree = self.rescue_tail(head, body)?;
2634 self.expect_ident("end")?;
2635 Ok(Spanned::new(self.span_since(head.start), tree.form))
2636 }
2637
2638 fn rescue_tail(&mut self, head: Span, body: Spanned) -> Result<Spanned, Fail> {
2639 let mut clauses: Vec<Spanned> = Vec::new();
2640 let mut catch_all: Option<(Spanned, Spanned, Span)> = None;
2641 while self.at_ident("rescue") {
2642 let kw = self.peek_span();
2643 self.bump();
2644 if catch_all.is_some() {
2645 return Err(fail(
2646 "a `rescue NAME` catches every error, so it is the last `rescue`; put the kinds it would shadow above it",
2647 kw,
2648 ));
2649 }
2650 match self.peek().clone() {
2651 TokenKind::Sym(_) => {
2652 let mut kinds = Vec::new();
2653 loop {
2654 let k_span = self.peek_span();
2655 match self.bump() {
2656 TokenKind::Sym(k) => kinds.push(atom_at(k_span, Atom::Keyword(k))),
2657 other => {
2658 return Err(fail(
2659 format!("expected an error kind such as `:not_found`, found {other}"),
2660 k_span,
2661 ))
2662 }
2663 }
2664 if self.eat(&TokenKind::Comma) {
2665 self.skip_newlines();
2666 continue;
2667 }
2668 break;
2669 }
2670 let binder = if self.eat_ident("as") {
2671 let b_span = self.peek_span();
2672 match self.bump() {
2673 TokenKind::Ident(b) if !is_reserved_word(&b) => Some(sym_at(b_span, &b)),
2674 other => {
2675 return Err(fail(
2676 format!("expected the name the error is bound to after `as`, found {other}"),
2677 b_span,
2678 ))
2679 }
2680 }
2681 } else {
2682 None
2683 };
2684 let handler = self.body(&["rescue", "ensure", "end"])?;
2685 let mut test = vec![sym_at(kw, LOWERED_ERROR_MATCH), sym_at(kw, RESCUED)];
2686 test.extend(kinds);
2687 let test = list_at(cover(&test, kw), test);
2688 let handler = match binder {
2689 Some(b) => let_bound(b, handler, kw),
2690 None => handler,
2691 };
2692 clauses.push(list_at(kw.merge(handler.span), vec![test, handler]));
2693 }
2694 TokenKind::Ident(b) if !is_reserved_word(&b) => {
2695 let b_span = self.peek_span();
2696 self.bump();
2697 let handler = self.body(&["rescue", "ensure", "end"])?;
2698 catch_all = Some((sym_at(b_span, &b), handler, kw));
2699 }
2700 _ => {
2701 return Err(fail(
2702 "`rescue` names what it catches: `rescue :kind`, `rescue :a, :b as e`, or `rescue e` for every error (`rescue _` discards it)",
2703 kw,
2704 ))
2705 }
2706 }
2707 }
2708 let ensure = if self.at_ident("ensure") {
2709 self.bump();
2710 let cleanup = self.body(&["rescue", "end"])?;
2711 if self.at_ident("rescue") {
2712 return Err(self.error("`ensure` comes after every `rescue` of its block"));
2713 }
2714 Some(cleanup)
2715 } else {
2716 None
2717 };
2718 let whole = self.span_since(head.start);
2719 let protected = if clauses.is_empty() {
2720 match catch_all {
2721 None => body,
2722 Some((binder, handler, kw)) => list_at(
2723 whole,
2724 vec![
2725 sym_at(head, "try"),
2726 body,
2727 list_at(
2728 kw.merge(handler.span),
2729 vec![
2730 sym_at(kw, "catch"),
2731 list_at(binder.span, vec![binder]),
2732 handler,
2733 ],
2734 ),
2735 ],
2736 ),
2737 }
2738 } else {
2739 let first_kw = clauses[0].span;
2740 let otherwise = match catch_all {
2741 Some((binder, handler, kw)) => {
2742 let bound = let_bound(binder, handler, kw);
2743 list_at(kw.merge(bound.span), vec![sym_at(kw, "else"), bound])
2744 }
2745 None => list_at(
2746 head,
2747 vec![
2748 sym_at(head, "else"),
2749 list_at(
2750 head,
2751 vec![sym_at(head, LOWERED_RERAISE), sym_at(head, RESCUED)],
2752 ),
2753 ],
2754 ),
2755 };
2756 let mut cond = vec![sym_at(first_kw, "cond")];
2757 cond.extend(clauses);
2758 cond.push(otherwise);
2759 let cond_span = cover(&cond, first_kw);
2760 list_at(
2761 whole,
2762 vec![
2763 sym_at(head, "try"),
2764 body,
2765 list_at(
2766 cond_span,
2767 vec![
2768 sym_at(first_kw, "catch"),
2769 list_at(first_kw, vec![sym_at(first_kw, RESCUED)]),
2770 list_at(cond_span, cond),
2771 ],
2772 ),
2773 ],
2774 )
2775 };
2776 Ok(match ensure {
2777 None => protected,
2778 Some(cleanup) => {
2779 let attempted = list_at(
2780 protected.span,
2781 vec![sym_at(head, LOWERED_ATTEMPTED), protected],
2782 );
2783 let failed = list_at(
2784 head,
2785 vec![sym_at(head, LOWERED_FAILED), sym_at(head, RESCUED)],
2786 );
2787 let caught = list_at(
2788 whole,
2789 vec![
2790 sym_at(head, "try"),
2791 attempted,
2792 list_at(
2793 head,
2794 vec![
2795 sym_at(head, "catch"),
2796 list_at(head, vec![sym_at(head, RESCUED)]),
2797 failed,
2798 ],
2799 ),
2800 ],
2801 );
2802 list_at(
2803 whole,
2804 vec![sym_at(head, LOWERED_ENSURE), caught, thunk(cleanup, head)],
2805 )
2806 }
2807 })
2808 }
2809
2810 fn def_form(&mut self, head: Span) -> Result<Spanned, Fail> {
2811 let name_span = self.peek_span();
2812 let name = match self.bump() {
2813 TokenKind::Ident(n) => n,
2814 other => {
2815 return Err(fail(
2816 format!("expected a name after `def`, found {other}"),
2817 name_span,
2818 ))
2819 }
2820 };
2821 self.not_reserved(&name, name_span, "a `def` name")?;
2822 let mut params: Vec<(String, Span, Option<Spanned>)> = Vec::new();
2823 let mut optional_from: Option<usize> = None;
2824 let mut rest: Vec<Spanned> = Vec::new();
2825 if !self.at(&TokenKind::LParen) {
2826 self.note(
2827 Spelling::ParameterList,
2828 name_span,
2829 &format!("def {name}"),
2830 &format!("def {name}()"),
2831 );
2832 }
2833 if self.at(&TokenKind::LParen) {
2834 self.bump();
2835 self.skip_newlines();
2836 if !self.eat(&TokenKind::RParen) {
2837 loop {
2838 self.skip_newlines();
2839 let p_span = self.peek_span();
2840 if self.at_star() {
2841 if optional_from.is_some() {
2842 return Err(fail(
2843 "a rest parameter cannot follow an optional one",
2844 p_span,
2845 ));
2846 }
2847 rest = self.rest_parameter()?.to_vec();
2848 self.rest_is_last()?;
2849 self.expect(&TokenKind::RParen, "`)` after the rest parameter")?;
2850 break;
2851 }
2852 match self.bump() {
2853 TokenKind::Ident(p) => {
2855 self.not_reserved(&p, p_span, "a parameter name")?;
2856 let p =
2857 self.optional_mark(p, p_span, &mut optional_from, params.len())?;
2858 params.push((p, p_span, None));
2859 }
2860 TokenKind::Label(p) => {
2862 self.not_reserved(&p, p_span, "a parameter name")?;
2863 let p =
2864 self.optional_mark(p, p_span, &mut optional_from, params.len())?;
2865 self.skip_newlines();
2866 let ty = self.type_expr()?;
2867 params.push((p, p_span, Some(ty)));
2868 }
2869 other => {
2870 return Err(fail(
2871 format!("expected a parameter name, found {other}"),
2872 p_span,
2873 ))
2874 }
2875 }
2876 self.skip_newlines();
2877 if self.eat(&TokenKind::Comma) {
2878 if self.trailing_comma(&TokenKind::RParen) {
2879 break;
2880 }
2881 continue;
2882 }
2883 self.expect(&TokenKind::RParen, "`,` or `)`")?;
2884 break;
2885 }
2886 }
2887 }
2888 let sig_span = self.span_since(name_span.start);
2890
2891 let ret = if matches!(self.peek(), TokenKind::Op(o) if o == "->") {
2893 self.bump();
2894 self.skip_newlines();
2895 Some(self.type_expr()?)
2896 } else {
2897 None
2898 };
2899
2900 let begins = {
2901 let save = self.pos;
2902 self.skip_newlines();
2903 let at = self.at_ident("begin").then(|| self.peek_span());
2904 self.pos = save;
2905 at
2906 };
2907 let body = self.protected_body(head)?;
2908 if let Some(b) = begins {
2909 let protects = matches!(&body.form, SpannedForm::List(items)
2910 if matches!(items.first().and_then(Spanned::as_symbol), Some("try") | Some(LOWERED_ENSURE)));
2911 if protects && body.span.start == b.start {
2912 self.note(
2913 Spelling::TryCatch,
2914 b,
2915 "begin … end as a whole def body",
2916 "the clauses on the def itself",
2917 );
2918 }
2919 }
2920 self.expect_ident("end")?;
2921 let whole = self.span_since(head.start);
2922
2923 let annotated = ret.is_some() || params.iter().any(|(_, _, t)| t.is_some());
2924 if !annotated {
2925 let mut sig = vec![sym_at(name_span, &name)];
2926 for (i, (p, s, _)) in params.into_iter().enumerate() {
2927 if optional_from == Some(i) {
2928 sig.push(sym_at(s, OPTIONAL_MARKER));
2929 }
2930 sig.push(sym_at(s, &p));
2931 }
2932 sig.extend(rest);
2933 return Ok(list_at(
2934 whole,
2935 vec![sym_at(head, "define"), list_at(sig_span, sig), body],
2936 ));
2937 }
2938
2939 let mut sig = vec![sym_at(name_span, &name)];
2947 for (i, (p, s, t)) in params.into_iter().enumerate() {
2948 if optional_from == Some(i) {
2949 sig.push(sym_at(s, OPTIONAL_MARKER));
2950 }
2951 let ty = t.unwrap_or_else(|| sym_at(s, "dyn"));
2952 sig.push(list_at(s.merge(ty.span), vec![sym_at(s, &p), ty]));
2953 }
2954 sig.extend(rest);
2955 Ok(list_at(
2956 whole,
2957 vec![
2958 sym_at(head, "define-typed"),
2959 list_at(sig_span, sig),
2960 ret.unwrap_or_else(|| sym_at(head, "dyn")),
2961 body,
2962 ],
2963 ))
2964 }
2965
2966 fn type_expr(&mut self) -> Result<Spanned, Fail> {
2968 let name_span = self.peek_span();
2969 let name = match self.bump() {
2970 TokenKind::Ident(n) => n,
2971 other => {
2972 return Err(fail(
2973 format!("expected a type name, found {other}"),
2974 name_span,
2975 ))
2976 }
2977 };
2978 self.not_reserved(&name, name_span, "a type name")?;
2979 if self.at(&TokenKind::LParen) {
2980 self.bump();
2981 self.skip_newlines();
2982 let mut list = vec![sym_at(name_span, &name)];
2983 if !self.eat(&TokenKind::RParen) {
2984 loop {
2985 self.skip_newlines();
2986 list.push(self.type_expr()?);
2987 self.skip_newlines();
2988 if self.eat(&TokenKind::Comma) {
2989 if self.trailing_comma(&TokenKind::RParen) {
2990 break;
2991 }
2992 continue;
2993 }
2994 self.expect(&TokenKind::RParen, "`,` or `)`")?;
2995 break;
2996 }
2997 }
2998 return Ok(list_at(self.span_since(name_span.start), list));
2999 }
3000 Ok(sym_at(name_span, &name))
3001 }
3002
3003 fn not_reserved(&self, name: &str, at: Span, what: &str) -> Result<(), Fail> {
3004 if is_reserved_word(name) {
3005 return Err(fail(
3006 format!("`{name}` is a reserved word and cannot be {what}"),
3007 at,
3008 ));
3009 }
3010 Ok(())
3011 }
3012
3013 fn eat_ident(&mut self, name: &str) -> bool {
3015 if self.at_ident(name) {
3016 self.bump();
3017 true
3018 } else {
3019 false
3020 }
3021 }
3022
3023 fn expect_ident(&mut self, name: &str) -> Result<(), Fail> {
3024 if self.at_ident(name) {
3025 self.bump();
3026 Ok(())
3027 } else {
3028 Err(self.error(format!("expected `{name}`, found {}", self.peek())))
3029 }
3030 }
3031
3032 fn body(&mut self, terminators: &[&str]) -> Result<Spanned, Fail> {
3035 let start = self.mark();
3038 let mut forms = Vec::new();
3039 loop {
3040 self.skip_newlines();
3041 if matches!(self.peek(), TokenKind::Eof) {
3042 return Err(self.error(format!(
3043 "unterminated block: expected {} before the end of the file",
3044 one_of(terminators)
3045 )));
3046 }
3047 if terminators.iter().any(|t| self.at_ident(t)) {
3048 break;
3049 }
3050 forms.push(self.statement()?);
3051 self.end_of_statement(terminators)?;
3052 }
3053 Ok(match forms.len() {
3054 0 => Spanned::new(Span::new(start, start), SpannedForm::Nil),
3055 1 => forms.into_iter().next().expect("checked len"),
3056 _ => {
3057 let span = cover(&forms, Span::new(start, start));
3063 let head = Span::new(span.start, span.start);
3069 let mut list = vec![sym_at(head, "begin")];
3070 list.extend(forms);
3071 list_at(span, list)
3072 }
3073 })
3074 }
3075}
3076
3077fn one_of(words: &[&str]) -> String {
3080 let quoted: Vec<String> = words.iter().map(|w| format!("`{w}`")).collect();
3081 match quoted.split_last() {
3082 None => String::new(),
3083 Some((last, [])) => last.clone(),
3084 Some((last, rest)) => format!("{} or {last}", rest.join(", ")),
3085 }
3086}
3087
3088fn let_bound(binder: Spanned, body: Spanned, at: Span) -> Spanned {
3089 let span = at.merge(body.span);
3090 let pair = list_at(binder.span, vec![binder, sym_at(at, RESCUED)]);
3091 list_at(
3092 span,
3093 vec![sym_at(at, "let"), list_at(pair.span, vec![pair]), body],
3094 )
3095}
3096
3097fn thunk(body: Spanned, at: Span) -> Spanned {
3098 let span = at.merge(body.span);
3099 list_at(
3100 span,
3101 vec![sym_at(at, "lambda"), list_at(at, Vec::new()), body],
3102 )
3103}
3104
3105#[must_use]
3110pub fn label_name(name: &str) -> bool {
3111 matches!(
3112 lex(&format!("{name}:")).as_deref(),
3113 Ok([t, e]) if matches!(&t.kind, TokenKind::Label(n) if n == name) && e.kind == TokenKind::Eof
3114 )
3115}
3116
3117fn blue_lang_name(name: &str) -> bool {
3118 matches!(
3119 lex(name).as_deref(),
3120 Ok([t, e]) if matches!(&t.kind, TokenKind::Ident(n) if n == name) && e.kind == TokenKind::Eof
3121 ) && !name.ends_with(['?', '!'])
3122}
3123
3124fn mentions(form: &Spanned, name: &str) -> bool {
3125 match &form.form {
3126 SpannedForm::Atom(Atom::Symbol(s)) => s == name,
3127 SpannedForm::List(items) => items.iter().any(|i| mentions(i, name)),
3128 SpannedForm::Quote(i)
3129 | SpannedForm::Quasiquote(i)
3130 | SpannedForm::Unquote(i)
3131 | SpannedForm::UnquoteSplice(i) => mentions(i, name),
3132 _ => false,
3133 }
3134}
3135
3136fn sym_at(span: Span, s: &str) -> Spanned {
3138 Spanned::new(span, SpannedForm::Atom(Atom::Symbol(s.to_string())))
3139}
3140
3141fn atom_at(span: Span, a: Atom) -> Spanned {
3143 Spanned::new(span, SpannedForm::Atom(a))
3144}
3145
3146fn list_at(span: Span, items: Vec<Spanned>) -> Spanned {
3148 Spanned::new(span, SpannedForm::List(items))
3149}
3150
3151fn cover(items: &[Spanned], fallback: Span) -> Span {
3159 let merged = items
3160 .iter()
3161 .fold(Span::synthetic(), |acc, s| acc.merge(s.span));
3162 if merged.is_synthetic() {
3163 fallback
3164 } else {
3165 merged
3166 }
3167}
3168
3169#[cfg(test)]
3170mod tests {
3171 use super::*;
3172
3173 fn q(src: &str) -> String {
3177 parse_expr(src)
3178 .map(|s| s.to_string())
3179 .unwrap_or_else(|e| panic!("{src:?}: {e}"))
3180 }
3181
3182 #[test]
3183 fn a_reserved_word_is_not_a_def_name_a_parameter_or_a_type() {
3184 for src in [
3185 "def def a(x)\n x\nend",
3186 "def end()\n 1\nend",
3187 "def f(end)\n 1\nend",
3188 "def f(a: not)\n 1\nend",
3189 "def f(a) -> not a\n a\nend",
3190 "def f(a: List(1 + 2))\n a\nend",
3191 "defmacro defmacro m(a)\n quote\n a\n end\nend",
3192 ] {
3193 assert!(parse_program(src).is_err(), "{src:?} parsed");
3194 }
3195 }
3196
3197 #[test]
3198 fn a_type_application_takes_types_of_any_arity() {
3199 assert_eq!(
3200 q("def f(a: Map(Str, List(Int))) -> Unit()\n a\nend"),
3201 "(define-typed (f (a (Map Str (List Int)))) (Unit) a)"
3202 );
3203 }
3204
3205 #[test]
3208 fn arithmetic_respects_precedence() {
3209 assert_eq!(q("1 + 2 * 3"), "(+ 1 (* 2 3))");
3210 assert_eq!(q("(1 + 2) * 3"), "(* (+ 1 2) 3)");
3211 }
3212
3213 #[test]
3214 fn comparison_binds_looser_than_arithmetic() {
3215 assert_eq!(q("a + 1 < b"), "(< (+ a 1) b)");
3216 }
3217
3218 #[test]
3223 fn logical_operators_bind_loosest_and_lower_to_tataras_names() {
3224 assert_eq!(q("a && b || c"), "(or (and a b) c)");
3225 }
3226
3227 #[test]
3228 fn left_associativity() {
3229 assert_eq!(q("1 - 2 - 3"), "(- (- 1 2) 3)");
3230 }
3231
3232 #[test]
3233 fn a_dot_without_parentheses_reads_a_field() {
3234 assert_eq!(q("user.name"), "(blue-field user :name)");
3235 }
3236
3237 #[test]
3238 fn method_call_with_args() {
3239 assert_eq!(q("user.greet(1, 2)"), "(greet user 1 2)");
3240 }
3241
3242 #[test]
3243 fn chained_fields_read_left_to_right() {
3244 assert_eq!(q("a.b.c"), "(blue-field (blue-field a :b) :c)");
3245 assert_eq!(q("a.b(1).c"), "(blue-field (b a 1) :c)");
3246 }
3247
3248 #[test]
3249 fn plain_call() {
3250 assert_eq!(q("f(1, 2)"), "(f 1 2)");
3251 }
3252
3253 #[test]
3256 fn pipeline_threads_into_first_argument() {
3257 assert_eq!(q("x |> f"), "(f x)");
3258 assert_eq!(q("x |> f(1)"), "(f x 1)");
3259 assert_eq!(q("x |> f |> g"), "(g (f x))");
3260 }
3261
3262 #[test]
3263 fn pipeline_binds_looser_than_arithmetic() {
3264 assert_eq!(q("1 + 2 |> f"), "(f (+ 1 2))");
3265 }
3266
3267 #[test]
3273 fn label_and_rocket_produce_the_same_tree_for_a_symbol_key() {
3274 assert_eq!(q("{a: 1}"), q("{:a => 1}"));
3275 assert_eq!(q("{a: 1}"), "(hash-map :a 1)");
3276 }
3277
3278 #[test]
3281 fn a_string_key_has_no_shorthand() {
3282 assert_eq!(q(r#"{"k" => 1}"#), r#"(hash-map "k" 1)"#);
3283 }
3284
3285 #[test]
3286 fn a_newline_escape_and_its_lines_are_one_tree() {
3287 for (escaped, lines) in [
3288 (r#""a\nb""#, r#"join_lines(["a", "b"])"#),
3289 (r##""#{m}\n#{h}""##, "join_lines([m, h])"),
3290 (r##""n=#{n}\ndone\n""##, r##"unlines(["n=#{n}", "done"])"##),
3291 (r#""\n""#, "line_break"),
3292 (r#""a\n\n\tb\n""#, r#"unlines(["a", "", "\tb"])"#),
3293 (r#""\na""#, r#"join_lines(["", "a"])"#),
3294 (r#""\n\n""#, r#"unlines(["", ""])"#),
3295 ] {
3296 assert_eq!(q(escaped), q(lines), "{escaped}");
3297 }
3298 assert_eq!(q(r#""a\tb""#), q(r#""a\tb""#));
3299 assert!(!q(r#""a\tb""#).contains("join_lines"));
3300 }
3301
3302 #[test]
3303 fn a_newline_escape_is_noted_and_a_quoted_one_is_data() {
3304 let notes = spelling_notes(r#""a\nb""#).unwrap();
3305 assert_eq!(notes.len(), 1);
3306 assert_eq!(notes[0].spelling, Spelling::NewlineEscape);
3307 assert!(spelling_notes(r#""a\tb""#).unwrap().is_empty());
3308 let quoted = q("def f()\n quote\n \"x\\ny\"\n end\nend\n");
3309 assert!(!quoted.contains("join_lines"), "{quoted}");
3310 assert!(spelling_notes("def f()\n quote\n \"x\\ny\"\n end\nend\n")
3311 .unwrap()
3312 .is_empty());
3313 }
3314
3315 #[test]
3316 fn list_literal() {
3317 assert_eq!(q("[1, 2, 3]"), "(list 1 2 3)");
3318 assert_eq!(q("[]"), "(list)");
3319 }
3320
3321 #[test]
3324 fn if_else_end() {
3325 assert_eq!(q("if a\n 1\nelse\n 2\nend"), "(if a 1 2)");
3326 }
3327
3328 #[test]
3329 fn if_without_else() {
3330 assert_eq!(q("if a\n 1\nend"), "(if a 1)");
3331 }
3332
3333 #[test]
3336 fn unless_is_a_negated_if() {
3337 assert_eq!(q("unless a\n 1\nend"), "(if (not a) 1)");
3338 }
3339
3340 #[test]
3341 fn multi_statement_body_becomes_begin() {
3342 assert_eq!(q("if a\n 1\n 2\nend"), "(if a (begin 1 2))");
3343 }
3344
3345 #[test]
3346 fn def_lowers_to_define() {
3347 assert_eq!(
3348 q("def add(a, b)\n a + b\nend"),
3349 "(define (add a b) (+ a b))"
3350 );
3351 }
3352
3353 #[test]
3354 fn def_with_no_params() {
3355 assert_eq!(q("def zero()\n 0\nend"), "(define (zero) 0)");
3356 }
3357
3358 fn refused(src: &str) -> String {
3359 match parse_expr(src) {
3360 Ok(s) => panic!("{src:?} should be refused, parsed to {s}"),
3361 Err(e) => e.to_string(),
3362 }
3363 }
3364
3365 #[test]
3366 fn a_question_mark_parameter_is_optional() {
3367 assert_eq!(q("def f(a, b?)\n b\nend"), "(define (f a &optional b) b)");
3368 assert_eq!(q("def f(a?, b?)\n a\nend"), "(define (f &optional a b) a)");
3369 assert_eq!(q("def f(a?)\n a\nend"), "(define (f &optional a) a)");
3370 }
3371
3372 #[test]
3373 fn an_annotated_optional_parameter_keeps_its_type_after_the_marker() {
3374 assert_eq!(
3375 q("def f(a: Int, b?: Str) -> Int\n a\nend"),
3376 "(define-typed (f (a Int) &optional (b Str)) Int a)"
3377 );
3378 assert_eq!(
3379 q("def f(a, b?: Str)\n a\nend"),
3380 "(define-typed (f (a dyn) &optional (b Str)) dyn a)"
3381 );
3382 }
3383
3384 #[test]
3385 fn a_def_without_question_marks_carries_no_marker() {
3386 assert_eq!(q("def f(a, b)\n a\nend"), "(define (f a b) a)");
3387 assert_eq!(q("def f(a, *r)\n a\nend"), "(define (f a &rest r) a)");
3388 }
3389
3390 #[test]
3391 fn a_required_parameter_cannot_follow_an_optional_one() {
3392 assert!(refused("def f(a?, b)\n a\nend").contains("`b` cannot follow an optional"));
3393 assert!(refused("def f(a, b?, c: Int)\n a\nend").contains("`c` cannot follow"));
3394 }
3395
3396 #[test]
3397 fn a_rest_parameter_cannot_follow_an_optional_one() {
3398 assert!(refused("def f(a, b?, *r)\n a\nend").contains("rest parameter cannot follow"));
3399 }
3400
3401 #[test]
3404 fn literals_lower_to_atoms() {
3405 assert_eq!(q("42"), "42");
3406 assert_eq!(q("true"), "#t");
3407 assert_eq!(q(":ok"), ":ok");
3408 assert_eq!(q(r#""hi""#), r#""hi""#);
3409 }
3410
3411 #[test]
3412 fn unary_minus_and_not() {
3413 assert_eq!(q("-x"), "(- 0 x)");
3414 assert_eq!(q("!x"), "(not x)");
3415 }
3416
3417 #[test]
3420 fn a_program_is_a_sequence_of_forms() {
3421 let forms = parse_program("def f()\n 1\nend\nf()").expect("parse");
3422 assert_eq!(forms.len(), 2);
3423 assert_eq!(forms[1].to_string(), "(f)");
3424 }
3425
3426 #[test]
3427 fn unterminated_block_is_an_error_naming_what_was_expected() {
3428 let e = parse_program("if a\n 1").expect_err("must fail");
3429 assert!(e.message.contains("unterminated"), "{}", e.message);
3430 }
3431
3432 #[test]
3433 fn a_parse_error_carries_a_span_into_the_source() {
3434 let src = "1 + )";
3435 let e = parse_program(src).expect_err("must fail");
3436 assert!(e.span.start < src.len(), "span {:?} outside source", e.span);
3437 }
3438
3439 #[test]
3446 fn postfix_indexing_is_refused_naming_nth() {
3447 let e = parse_program("y = xs[0]").expect_err("must not split into two statements");
3448 assert!(e.message.contains("`nth(0, xs)`"), "{}", e.message);
3449 assert_eq!((e.line, e.col), (1, 7), "{e}");
3450 }
3451
3452 #[test]
3454 fn a_call_without_parentheses_is_refused_naming_the_call() {
3455 let e = parse_program("a = f x").expect_err("must not split into two statements");
3456 assert!(e.message.contains("`f(x)`"), "{}", e.message);
3457 assert_eq!((e.line, e.col), (1, 7), "{e}");
3458 }
3459
3460 #[test]
3462 fn two_statements_on_one_line_are_refused_in_a_body() {
3463 for src in [
3464 "def f(xs)\n xs[0]\nend",
3465 "if a\n f x\nend",
3466 "fn(x) x 1 end",
3467 "test \"t\"\n assert a b\nend",
3468 "1 2",
3469 ] {
3470 assert!(parse_program(src).is_err(), "{src:?} must not parse");
3471 }
3472 }
3473
3474 #[test]
3476 fn an_arrow_lambda_lowers_to_the_same_tree_as_fn() {
3477 for (arrow, block) in [
3478 ("(x) -> x * 2", "fn(x) x * 2 end"),
3479 ("(a, b) -> a + b", "fn(a, b) a + b end"),
3480 ("() -> 1", "fn() 1 end"),
3481 ("map(xs, (x) -> x + 1)", "map(xs, fn(x) x + 1 end)"),
3482 ] {
3483 let a = parse_program(arrow).expect(arrow);
3484 let b = parse_program(block).expect(block);
3485 assert_eq!(format!("{a:?}"), format!("{b:?}"), "{arrow}");
3486 }
3487 assert_eq!(
3488 parse_program("(x)").map(|p| p.len()).ok(),
3489 Some(1),
3490 "a parenthesised name is still a group"
3491 );
3492 }
3493
3494 #[test]
3498 fn a_statement_may_end_at_a_newline_the_end_or_a_closing_keyword() {
3499 for src in [
3500 "fn(x) x + 1 end",
3501 "map(fn(x) x end, xs)",
3502 "if a\n 1\nelse\n 2\nend",
3503 "if a 1 else 2 end",
3504 "case x\nwhen 1 :a\nwhen 2 :b\nelse :c\nend",
3505 "x = 1\ny = 2",
3506 "x = 1",
3507 ] {
3508 assert!(
3509 parse_program(src).is_ok(),
3510 "{src:?}: {:?}",
3511 parse_program(src)
3512 );
3513 }
3514 }
3515
3516 #[test]
3518 fn then_is_refused_by_name() {
3519 let e = parse_program("x = if c then 1 else 2 end").expect_err("then is not a keyword");
3520 assert!(e.message.contains("`then`"), "{}", e.message);
3521 }
3522
3523 #[test]
3525 fn a_brace_block_is_refused_naming_fn() {
3526 let e = parse_program("xs.map { |x| x }").expect_err("no brace blocks");
3527 assert!(e.message.contains("fn(x)"), "{}", e.message);
3528 }
3529
3530 #[test]
3537 fn a_qualified_name_is_one_symbol() {
3538 assert_eq!(q("retsu::first(xs)"), "(retsu/first xs)");
3539 assert_eq!(q("retsu::first"), "retsu/first");
3540 assert_eq!(q("xs |> retsu::first"), "(retsu/first xs)");
3541 assert_eq!(q("blue::length(xs)"), "(blue/length xs)");
3542 }
3543
3544 #[test]
3546 fn whitespace_around_the_qualifier_is_accepted() {
3547 assert_eq!(q("retsu :: first(xs)"), "(retsu/first xs)");
3548 }
3549
3550 #[test]
3553 fn a_reserved_word_after_the_qualifier_is_a_name() {
3554 assert_eq!(q("kueri::if(a, b)"), "(kueri/if a b)");
3555 assert_eq!(q("heni::test(x)"), "(heni/test x)");
3556 assert_eq!(q("sabi::fn"), "sabi/fn");
3557 }
3558
3559 #[test]
3560 fn a_qualified_name_has_exactly_one_qualifier() {
3561 for (src, says) in [
3562 ("::first(xs)", "`package::name`"),
3563 ("a::b::c", "one qualifier"),
3564 ("if::x", "reserved word"),
3565 ("retsu::1", "expected a name after `retsu::`"),
3566 ] {
3567 let e = parse_program(src).expect_err(src);
3568 assert!(e.message.contains(says), "{src}: {}", e.message);
3569 }
3570 }
3571
3572 #[test]
3575 fn qualified_reads_only_the_package_name_shape() {
3576 assert_eq!(qualified("retsu/first"), Some(("retsu", "first")));
3577 assert_eq!(qualified("kueri/if"), Some(("kueri", "if")));
3578 assert_eq!(qualified("nisshi/pair?"), Some(("nisshi", "pair?")));
3579 for not in ["/", "a/", "/a", "a/b/c", "if/x", "hash-map", "a"] {
3580 assert_eq!(qualified(not), None, "{not}");
3581 }
3582 assert!(is_callable_name("kueri/if"));
3583 }
3584
3585 #[test]
3590 fn a_symbol_may_end_in_a_question_or_bang() {
3591 assert_eq!(q("[:pair?, :set!]"), "(list :pair? :set!)");
3592 assert_eq!(q("use(\"n\", [:empty?])"), "(use \"n\" (list :empty?))");
3593 assert_eq!(q(":a != :b"), "(not= :a :b)");
3594 }
3595
3596 #[test]
3600 fn a_parse_error_displays_line_col_and_the_source_text() {
3601 let e = parse_program("x = 1\ny = [1, 2 y]").expect_err("must fail");
3602 assert_eq!(e.to_string(), "2:11: expected `,` or `]`, found `y`");
3603 assert_eq!(e.span, Span::new(16, 17), "the span stays, as data");
3604 }
3605
3606 #[test]
3608 fn no_error_message_names_a_rust_variant() {
3609 for src in [
3610 "f(1 2)",
3611 "{a: 1 b}",
3612 "def 1\nend",
3613 "x.1",
3614 "if a\n",
3615 "fn(1) 2 end",
3616 "[1 :b]",
3617 "1 +",
3618 "(1",
3619 ] {
3620 let e = parse_program(src).expect_err(src);
3621 let shown = e.to_string();
3622 for tell in [
3623 "Ident(", "Int(", "Op(", "Label(", "Sym(", "Str(", "LParen", "RBracket", "Eof",
3624 "Newline", "..",
3625 ] {
3626 assert!(!shown.contains(tell), "{src:?} -> {shown}");
3627 }
3628 assert!(e.line >= 1 && e.col >= 1, "{src:?} not located: {shown}");
3629 }
3630 }
3631
3632 #[test]
3634 fn a_lex_error_is_located_too() {
3635 let e = parse_program("x = 1\ny = 2; z = 3").expect_err("no semicolons");
3636 assert_eq!((e.line, e.col), (2, 6), "{e}");
3637 assert!(e.message.contains("new line"), "{e}");
3638 }
3639
3640 #[test]
3643 fn the_parser_rejects_garbage() {
3644 assert!(parse_program("def").is_err());
3645 assert!(parse_program("(1").is_err());
3646 assert!(parse_program("end").is_err());
3647 }
3648}