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_ASSERT_PANICS: &str = "blue-assert-panics";
556
557pub const LOWERED_TEST_META: &str = "blue-test-meta";
566
567pub const LOWERED_MAP: &str = "hash-map";
575
576pub const LOWERED_CONCAT: &str = "concat";
581
582pub const OPTIONAL_MARKER: &str = "&optional";
583
584pub const LOWERED_OPTIONAL_ARITY: &str = "blue-optional-arity";
585
586pub const LOWERED_OPTIONAL_ARG: &str = "blue-optional-arg";
587
588pub const LOWERED_FIELD: &str = "blue-field";
589
590pub const LOWERED_MERGE: &str = "blue-merge";
591
592pub const LOWERED_SPLICE: &str = "blue-splice";
593
594pub const LOWERED_KWARGS: &str = "blue-kwargs";
595
596pub const LOWERED_ERROR_MATCH: &str = "blue-rescues?";
597
598pub const LOWERED_ENSURE: &str = "blue-ensure";
599
600pub const LOWERED_RERAISE: &str = "blue-reraise";
601
602pub const LOWERED_ATTEMPTED: &str = "blue-attempted";
603
604pub const LOWERED_FAILED: &str = "blue-failed";
605
606pub const LOWERED_HEADS: &[&str] = &[
607 "list",
608 "concat",
609 "not",
610 "apply",
611 "equal?",
612 "not=",
613 "and",
614 "or",
615 "mod",
616 "join_lines",
617 "unlines",
618 "line_break",
619];
620
621pub const RESCUED: &str = "rescued-error";
622
623pub const SURFACE_KEYWORDS: &[&str] = &[
624 "if",
625 "begin",
626 "unless",
627 "def",
628 "defmacro",
629 "quote",
630 "unquote",
631 "unquote_splice",
632 "test",
633 "assert",
634 "assert_panics",
635 "fn",
636 "case",
637];
638
639pub const BLOCK_KEYWORDS: &[&str] = &[
651 "do", "end", "else", "elsif", "rescue", "ensure", "true", "false", "nil",
652];
653
654pub const OPERATOR_KEYWORDS: &[&str] = &["and", "or", "not"];
655
656pub fn is_reserved_word(name: &str) -> bool {
663 SURFACE_KEYWORDS.contains(&name)
664 || BLOCK_KEYWORDS.contains(&name)
665 || OPERATOR_KEYWORDS.contains(&name)
666}
667
668pub fn is_callable_name(name: &str) -> bool {
677 if qualified(name).is_some() {
679 return true;
680 }
681 !is_reserved_word(name)
682 && matches!(
683 lex(name).as_deref(),
684 Ok([t, e]) if matches!(&t.kind, TokenKind::Ident(n) if n == name)
685 && e.kind == TokenKind::Eof
686 )
687}
688
689pub const QUALIFIER: char = '/';
694
695pub const BUILTIN_QUALIFIER: &str = "blue";
698
699#[must_use]
701pub fn qualify(pkg: &str, name: &str) -> String {
702 format!("{pkg}{QUALIFIER}{name}")
703}
704
705#[must_use]
711pub fn qualified(sym: &str) -> Option<(&str, &str)> {
712 let (pkg, name) = sym.split_once(QUALIFIER)?;
713 let ident = |s: &str| {
714 matches!(
715 lex(s).as_deref(),
716 Ok([t, e]) if matches!(&t.kind, TokenKind::Ident(n) if n == s) && e.kind == TokenKind::Eof
717 )
718 };
719 let named = |s: &str| ident(s) || matches!(s, "true" | "false" | "nil");
720 (ident(pkg) && !is_reserved_word(pkg) && named(name)).then_some((pkg, name))
721}
722
723#[must_use]
731pub fn keyword_doc(word: &str) -> &'static str {
732 match word {
733 "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).",
734 "unless" => "`unless c … end` is read as `if not c` and written that way (B0026).",
735 "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`.",
736 "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 `|>`.",
737 "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.",
738 "and" => "`a and b`: logical and; `&&` is read as `and` and written `and` (B0025).",
739 "or" => "`a or b`: logical or; `||` is read as `or` and written `or` (B0025).",
740 "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).",
741 "def" => "`def name(a, b) … end`: a named function; its value is the last expression. Parameters may be typed: `def f(n: Int) -> Int`.",
742 "defmacro" => "`defmacro name(x) quote … unquote(x) … end end`: a macro, a rewrite of the unevaluated form.",
743 "quote" => "`quote … end`: the form itself as data, not its value. Used inside `defmacro`.",
744 "unquote" => "`unquote(x)`: inside `quote`, splice in the value of `x`.",
745 "unquote_splice" => "`unquote_splice(xs)`: inside `quote`, splice the items of the form `xs` in place, not the form itself.",
746 "test" => "`test \"what must hold\" … end`: a test block, run by `blue test` and ignored by `blue run`. `test \"n\", {satisfies: [:nist_ac_6], target: f}` declares the controls it verifies and the definition it targets (B0068, B0069).",
747 "assert" => "`assert expr`: inside a test, fail it unless `expr` is truthy; the failure quotes the expression.",
748 "assert_panics" => "`assert_panics expr`: inside a test, pass only when `expr`, run in a child held to a pure frame, ends on a capability breach with no host call made.",
749 "fn" => "`fn(x) x * 2 end`: an anonymous function, a value. Blue has no brace or do blocks.",
750 "case" => "`case v when 1 … when 2 … else … end`: compares with `==`; it does not destructure.",
751 "do" => "Reserved and refused: blue has no do-blocks. Pass `fn(x) … end` instead.",
752 "end" => "Closes `def`, `defmacro`, `if`, `case`, `fn`, `test` and `quote`.",
753 "else" => "The branch of `if` or `case` taken when nothing before it matched.",
754 "elsif" => "A further condition in an `if` chain; an `else` holding only an `if` is written `elsif` (B0027).",
755 "true" => "The boolean true.",
756 "false" => "The boolean false. Only `false` and `nil` are falsy; 0, \"\" and [] are truthy.",
757 "nil" => "The absent value. It is not `[]` (okite D0004), and only `nil == nil`.",
758 _ => "",
759 }
760}
761
762fn infix(op: &str) -> Option<&'static Infix> {
763 INFIX.iter().find(|i| i.op == op)
764}
765
766pub const SYMBOL_SPELLINGS: &[(&str, &str)] = &[("&&", "and"), ("||", "or"), ("!", "not")];
767
768fn word_for(op: &str) -> &str {
769 SYMBOL_SPELLINGS
770 .iter()
771 .find(|(sym, _)| *sym == op)
772 .map_or(op, |(_, word)| word)
773}
774
775fn infix_of(kind: &TokenKind) -> Option<&'static Infix> {
776 match kind {
777 TokenKind::Op(o) => infix(word_for(o)),
778 TokenKind::Ident(w) if w == "and" || w == "or" => infix(w),
779 _ => None,
780 }
781}
782
783const PIPE_POWER: (u8, u8) = (0, 1);
784const RESCUE_POWER: (u8, u8) = (0, 1);
785
786struct Parser {
787 toks: Vec<Token>,
788 pos: usize,
789 depth: usize,
797 max_depth: usize,
804 src: String,
805 comment_spans: Vec<Span>,
806 notes: Vec<Note>,
807 quoting: usize,
810}
811
812pub const MAX_EXPR_DEPTH: usize = 256;
819
820type Fail = Box<ParseError>;
832
833fn fail(message: impl Into<String>, span: Span) -> Fail {
834 Box::new(ParseError::new(message, span))
835}
836
837impl Parser {
838 fn over(src: &str, all: Vec<Token>, max_depth: usize) -> Self {
839 let comment_spans = all
840 .iter()
841 .filter(|t| matches!(t.kind, TokenKind::Comment(_)))
842 .map(|t| t.span)
843 .collect();
844 let toks = all
845 .into_iter()
846 .filter(|t| !matches!(t.kind, TokenKind::Comment(_)))
847 .collect();
848 Parser {
849 toks,
850 pos: 0,
851 depth: 0,
852 max_depth,
853 src: src.to_string(),
854 comment_spans,
855 notes: Vec::new(),
856 quoting: 0,
857 }
858 }
859
860 fn note(&mut self, spelling: Spelling, span: Span, written: &str, canonical: &str) {
861 self.notes.push(Note {
862 spelling,
863 span,
864 written: written.to_string(),
865 canonical: canonical.to_string(),
866 });
867 }
868
869 fn text(&self, span: Span) -> &str {
870 self.src.get(span.start..span.end).unwrap_or("")
871 }
872
873 fn comment_within(&self, lo: usize, hi: usize) -> bool {
874 self.comment_spans
875 .iter()
876 .any(|c| c.start >= lo && c.start < hi)
877 }
878
879 fn touches_previous(&self) -> bool {
880 let here = self.peek_span();
881 self.toks
882 .get(self.pos.wrapping_sub(1))
883 .is_some_and(|prev| prev.span.end == here.start)
884 }
885
886 fn peek(&self) -> &TokenKind {
887 &self.toks[self.pos.min(self.toks.len() - 1)].kind
888 }
889
890 fn peek_span(&self) -> Span {
891 self.toks[self.pos.min(self.toks.len() - 1)].span
892 }
893
894 fn bump(&mut self) -> TokenKind {
895 let k = self.toks[self.pos.min(self.toks.len() - 1)].kind.clone();
896 if self.pos < self.toks.len() {
897 self.pos += 1;
898 }
899 k
900 }
901
902 fn at(&self, k: &TokenKind) -> bool {
903 self.peek() == k
904 }
905
906 fn eat(&mut self, k: &TokenKind) -> bool {
907 if self.at(k) {
908 self.bump();
909 true
910 } else {
911 false
912 }
913 }
914
915 fn expect(&mut self, k: &TokenKind, what: &str) -> Result<(), Fail> {
916 if self.eat(k) {
917 Ok(())
918 } else {
919 Err(self.error(format!("expected {what}, found {}", self.peek())))
920 }
921 }
922
923 fn error(&self, message: impl Into<String>) -> Fail {
924 fail(message, self.peek_span())
925 }
926
927 fn skip_newlines(&mut self) {
929 while matches!(self.peek(), TokenKind::Newline) {
930 self.bump();
931 }
932 }
933
934 fn at_ident(&self, name: &str) -> bool {
935 matches!(self.peek(), TokenKind::Ident(n) if n == name)
936 }
937
938 fn mark(&self) -> usize {
941 self.peek_span().start
942 }
943
944 fn span_since(&self, start: usize) -> Span {
950 let end = self
951 .toks
952 .get(self.pos.saturating_sub(1))
953 .map_or(start, |t| t.span.end);
954 Span::new(start, end.max(start))
955 }
956
957 fn program(&mut self) -> Result<Vec<Spanned>, Fail> {
958 let mut out = Vec::new();
959 loop {
960 self.skip_newlines();
961 if matches!(self.peek(), TokenKind::Eof) {
962 break;
963 }
964 if let TokenKind::Doc { module, text } = self.peek().clone() {
965 out.push(self.documentation(module, text, out.is_empty())?);
966 continue;
967 }
968 out.push(self.statement()?);
969 self.end_of_statement(&[])?;
970 }
971 Ok(out)
972 }
973
974 fn documentation(&mut self, module: bool, text: String, first: bool) -> Result<Spanned, Fail> {
977 let span = self.peek_span();
978 self.bump();
979 let word = if module { "@moduledoc" } else { "@doc" };
980 let head = sym_at(
981 Span::new(span.start, span.start + word.len()),
982 if module {
983 crate::doc::MODULEDOC_HEAD
984 } else {
985 crate::doc::DOC_HEAD
986 },
987 );
988 let text = crate::doc::text_node(span, text);
989 self.end_of_statement(&[])?;
990 if module {
991 if !first {
992 return Err(fail(
993 "`@moduledoc` is the first form of a file: it documents the whole file",
994 span,
995 ));
996 }
997 return Ok(list_at(span, vec![head, text]));
998 }
999 self.skip_newlines();
1000 if matches!(self.peek(), TokenKind::Doc { .. } | TokenKind::Eof) {
1001 return Err(fail(
1002 "`@doc` documents the definition below it, and none follows",
1003 span,
1004 ));
1005 }
1006 let def = self.statement()?;
1007 self.end_of_statement(&[])?;
1008 if !crate::doc::is_definition(&def) {
1009 return Err(fail(
1010 "`@doc` documents the definition below it: a `def`, a `defmacro` or a \
1011 top-level `name = value`",
1012 span,
1013 ));
1014 }
1015 Ok(list_at(
1016 Span::new(span.start, def.span.end),
1017 vec![head, text, def],
1018 ))
1019 }
1020
1021 fn end_of_statement(&self, terminators: &[&str]) -> Result<(), Fail> {
1039 let next = self.peek();
1040 if matches!(next, TokenKind::Newline | TokenKind::Eof)
1041 || terminators.iter().any(|t| self.at_ident(t))
1042 {
1043 return Ok(());
1044 }
1045 let prev = self.toks.get(self.pos.wrapping_sub(1)).map(|t| &t.kind);
1046 let prev_name = match prev {
1047 Some(TokenKind::Ident(n)) => Some(n.as_str()),
1048 _ => None,
1049 };
1050 let callee = prev_name.unwrap_or("f");
1051 let why = match (prev, next) {
1052 (Some(TokenKind::Ident(t)), _) if t == "then" => {
1053 "blue's `if` has no `then`: put the branch on the next line".to_string()
1054 }
1055 (_, TokenKind::Ident(d)) if d == "do" => "blue has no `do` blocks: pass a function \
1056 as an argument, `f(xs, fn(x) … end)`"
1057 .to_string(),
1058 (_, TokenKind::LBracket) => {
1059 let index = match (self.toks.get(self.pos + 1), self.toks.get(self.pos + 2)) {
1060 (Some(i), Some(close)) if close.kind == TokenKind::RBracket => match &i.kind {
1061 TokenKind::Int(n) => n.to_string(),
1062 TokenKind::Ident(n) => n.clone(),
1063 _ => "i".to_string(),
1064 },
1065 _ => "i".to_string(),
1066 };
1067 let list = prev_name.unwrap_or("xs");
1068 format!(
1069 "an index touches what it indexes: write `{list}[{index}]`, with no space \
1070 before `[`"
1071 )
1072 }
1073 (_, TokenKind::LBrace) => "blue has no brace blocks: pass a function as an \
1074 argument, `map(xs, fn(x) … end)`"
1075 .to_string(),
1076 (
1077 Some(TokenKind::Ident(_)),
1078 TokenKind::Ident(_)
1079 | TokenKind::Int(_)
1080 | TokenKind::Float(_)
1081 | TokenKind::Str(_)
1082 | TokenKind::InterpolatedStr { .. }
1083 | TokenKind::Sym(_)
1084 | TokenKind::Words { .. }
1085 | TokenKind::True
1086 | TokenKind::False
1087 | TokenKind::Nil,
1088 ) => {
1089 let arg = match next {
1090 TokenKind::Ident(a) => a.clone(),
1091 _ => "x".to_string(),
1092 };
1093 format!("a call needs parentheses: write `{callee}({arg})`")
1094 }
1095 _ => "put each statement on its own line".to_string(),
1096 };
1097 Err(self.error(format!("expected the end of the line, found {next}: {why}")))
1098 }
1099
1100 fn statement(&mut self) -> Result<Spanned, Fail> {
1113 let max = self.max_depth;
1127 if self.depth >= max {
1128 return Err(self.error(format!(
1129 "statement nests deeper than {max}; refusing to \
1130 recurse further (this is a limit, not a syntax error)"
1131 )));
1132 }
1133 self.depth += 1;
1134 let r = self.statement_inner();
1135 self.depth -= 1;
1136 r
1137 }
1138
1139 fn statement_inner(&mut self) -> Result<Spanned, Fail> {
1140 if let TokenKind::Ident(name) = self.peek().clone() {
1141 if self.peek_at(1) == "=" && !is_reserved_word(&name) {
1142 let name_span = self.peek_span();
1143 self.bump(); self.bump(); self.skip_newlines();
1146 let value = self.expr(0)?;
1147 return Ok(list_at(
1151 self.span_since(name_span.start),
1152 vec![sym_at(name_span, "define"), sym_at(name_span, &name), value],
1153 ));
1154 }
1155 }
1156 let e = self.expr(0)?;
1157 if let SpannedForm::List(items) = &e.form {
1158 if items.len() == 3
1159 && items[0].as_symbol() == Some("define")
1160 && items[1].as_symbol().is_some()
1161 && self.text(items[0].span) == "define"
1162 {
1163 self.note(Spelling::OperatorSpelling, e.span, "define(x, v)", "x = v");
1164 }
1165 }
1166 Ok(e)
1167 }
1168
1169 fn peek_at(&self, n: usize) -> String {
1172 match self.toks.get(self.pos + n).map(|t| &t.kind) {
1173 Some(TokenKind::Op(o)) => o.clone(),
1174 _ => String::new(),
1175 }
1176 }
1177
1178 fn expr(&mut self, min_bp: u8) -> Result<Spanned, Fail> {
1180 let max = self.max_depth;
1190 if self.depth >= max {
1191 return Err(self.error(format!(
1192 "expression nests deeper than {max}; refusing to \
1193 recurse further (this is a limit, not a syntax error)"
1194 )));
1195 }
1196 self.depth += 1;
1197 let r = self.expr_inner(min_bp);
1198 self.depth -= 1;
1199 r
1200 }
1201
1202 #[inline(never)]
1203 fn rescue_node(&self, start: usize, op_span: Span, lhs: Spanned, fallback: Spanned) -> Spanned {
1204 list_at(
1205 self.span_since(start),
1206 vec![
1207 sym_at(op_span, "try"),
1208 lhs,
1209 list_at(
1210 op_span.merge(fallback.span),
1211 vec![
1212 sym_at(op_span, "catch"),
1213 list_at(op_span, vec![sym_at(op_span, "_")]),
1214 fallback,
1215 ],
1216 ),
1217 ],
1218 )
1219 }
1220
1221 #[inline(never)]
1222 fn infix_spelling(&mut self, callee: Option<&str>, op_span: Span) {
1223 let written = self.text(op_span).to_string();
1224 match (callee, self.peek().clone()) {
1225 (None, _) => self.note(Spelling::CallForm, op_span, "|>", "a call"),
1226 (Some(c), TokenKind::Op(o)) if word_for(&o) != o => {
1227 self.note(Spelling::LogicalSymbol, op_span, &written, c);
1228 }
1229 (Some(_), TokenKind::Op(o)) if written != o => {
1230 self.note(Spelling::OperatorSpelling, op_span, &written, &o);
1231 }
1232 _ => {}
1233 }
1234 }
1235
1236 #[inline(never)]
1237 fn nil_test(
1238 &mut self,
1239 start: usize,
1240 op_span: Span,
1241 lhs: &Spanned,
1242 rhs: &Spanned,
1243 ) -> Option<Spanned> {
1244 let (subject, written) = match (&lhs.form, &rhs.form) {
1245 (_, SpannedForm::Nil) if !matches!(lhs.form, SpannedForm::Nil) => (lhs, "x == nil"),
1246 (SpannedForm::Nil, _) if !matches!(rhs.form, SpannedForm::Nil) => (rhs, "nil == x"),
1247 _ => return None,
1248 };
1249 let span = self.span_since(start);
1250 self.note(Spelling::NilTest, span, written, "x.nil?");
1251 Some(list_at(
1252 span,
1253 vec![
1254 sym_at(op_span, LOWERED_FIELD),
1255 subject.clone(),
1256 atom_at(op_span, Atom::Keyword("nil?".to_string())),
1257 ],
1258 ))
1259 }
1260
1261 #[inline(never)]
1262 fn infix_node(
1263 &mut self,
1264 start: usize,
1265 callee: Option<&str>,
1266 op_span: Span,
1267 lhs: Spanned,
1268 rhs: Spanned,
1269 ) -> Spanned {
1270 if callee == Some("-") && matches!(lhs.form, SpannedForm::Atom(Atom::Int(0))) {
1271 self.note(
1272 Spelling::OperatorSpelling,
1273 lhs.span.merge(op_span),
1274 "0 - x",
1275 "-x",
1276 );
1277 }
1278
1279 if callee == Some("equal?") {
1280 if let Some(test) = self.nil_test(start, op_span, &lhs, &rhs) {
1281 return test;
1282 }
1283 }
1284
1285 if callee.is_none() {
1286 let rhs_span = rhs.span;
1291 let threaded = match rhs.form {
1292 SpannedForm::List(mut items) if !items.is_empty() => {
1293 items.insert(1, lhs);
1294 list_at(self.span_since(start), items)
1295 }
1296 callee => list_at(
1297 self.span_since(start),
1298 vec![Spanned::new(rhs_span, callee), lhs],
1299 ),
1300 };
1301 match &threaded.form {
1302 SpannedForm::List(items)
1303 if items.len() == 2 && items[0].as_symbol() == Some("not") =>
1304 {
1305 let (head, operand) = (items[0].span, items[1].clone());
1306 self.negation(head, threaded.span, operand)
1307 }
1308 SpannedForm::List(items)
1309 if items.len() == 4
1310 && items[0].as_symbol() == Some("if")
1311 && matches!(items[3].form, SpannedForm::Nil) =>
1312 {
1313 list_at(threaded.span, items[..3].to_vec())
1314 }
1315 SpannedForm::List(items)
1316 if items.len() == 3 && items[0].as_symbol() == Some("equal?") =>
1317 {
1318 let (op, a, b) = (items[0].span, items[1].clone(), items[2].clone());
1319 match self.nil_test(start, op, &a, &b) {
1320 Some(test) => test,
1321 None => threaded,
1322 }
1323 }
1324 _ => threaded,
1325 }
1326 } else {
1327 list_at(
1333 self.span_since(start),
1334 vec![sym_at(op_span, callee.unwrap_or_default()), lhs, rhs],
1335 )
1336 }
1337 }
1338
1339 fn expr_inner(&mut self, min_bp: u8) -> Result<Spanned, Fail> {
1340 let start = self.mark();
1341 let mut lhs = self.prefix()?;
1342
1343 loop {
1344 match self.peek() {
1346 TokenKind::Dot => {
1347 let dot = self.peek_span();
1348 self.bump();
1349 lhs = self.finish_send(start, lhs, dot)?;
1350 continue;
1351 }
1352 TokenKind::LParen | TokenKind::LBracket
1353 if self.at(&TokenKind::LParen) || self.touches_previous() =>
1354 {
1355 lhs = self.applied(start, lhs)?;
1356 continue;
1357 }
1358 _ => {}
1359 }
1360
1361 let op_span = self.peek_span();
1365 if self.at_ident("rescue") {
1366 if RESCUE_POWER.0 < min_bp {
1367 break;
1368 }
1369 self.bump();
1370 self.skip_newlines();
1371 let fallback = self.expr(RESCUE_POWER.1)?;
1372 lhs = self.rescue_node(start, op_span, lhs, fallback);
1373 continue;
1374 }
1375 let (callee, (lbp, rbp)) = match self.peek() {
1376 TokenKind::Pipe => (None, PIPE_POWER),
1377 other => match infix_of(other) {
1378 Some(i) => (Some(i.callee), i.power),
1379 None => break,
1380 },
1381 };
1382 if lbp < min_bp {
1383 break;
1384 }
1385 self.infix_spelling(callee, op_span);
1386 self.bump();
1387 self.skip_newlines();
1388 let rhs = self.expr(rbp)?;
1389 lhs = self.infix_node(start, callee, op_span, lhs, rhs);
1390 }
1391
1392 Ok(lhs)
1393 }
1394
1395 #[inline(never)]
1396 fn interpolation(span: Span, parts: &[String], exprs: &[String]) -> Result<Spanned, Fail> {
1397 let mut acc = atom_at(span, Atom::Str(parts[0].clone()));
1407 for (i, raw) in exprs.iter().enumerate() {
1408 let inner = parse_expr(raw).map_err(|e| {
1409 fail(
1410 format!("in interpolation `#{{{raw}}}`: {}", e.message),
1411 span,
1412 )
1413 })?;
1414 let inner = Spanned::from_sexp_at(&inner, span);
1415 acc = list_at(span, vec![sym_at(span, LOWERED_CONCAT), acc, inner]);
1416 acc = list_at(
1419 span,
1420 vec![
1421 sym_at(span, LOWERED_CONCAT),
1422 acc,
1423 atom_at(span, Atom::Str(parts[i + 1].clone())),
1424 ],
1425 );
1426 }
1427 Ok(acc)
1428 }
1429
1430 #[inline(never)]
1437 fn lines_literal(
1438 &mut self,
1439 span: Span,
1440 parts: Vec<String>,
1441 exprs: Vec<String>,
1442 ) -> Result<Spanned, Fail> {
1443 let mut lines: Vec<(Vec<String>, Vec<String>)> = vec![(vec![String::new()], Vec::new())];
1444 for (i, part) in parts.iter().enumerate() {
1445 let mut pieces = part.split('\n');
1446 if let Some(head) = pieces.next() {
1447 if let Some(last) = lines.last_mut().and_then(|l| l.0.last_mut()) {
1448 last.push_str(head);
1449 }
1450 }
1451 for piece in pieces {
1452 lines.push((vec![piece.to_string()], Vec::new()));
1453 }
1454 if let Some(raw) = exprs.get(i) {
1455 if let Some(line) = lines.last_mut() {
1456 line.1.push(raw.clone());
1457 line.0.push(String::new());
1458 }
1459 }
1460 }
1461 let empty = |l: &(Vec<String>, Vec<String>)| l.1.is_empty() && l.0 == [""];
1462 if lines.len() == 2 && lines.iter().all(empty) {
1463 self.note(Spelling::NewlineEscape, span, "\"\\n\"", "line_break");
1464 return Ok(sym_at(span, "line_break"));
1465 }
1466 let trailing = lines.last().is_some_and(empty);
1467 if trailing {
1468 lines.pop();
1469 }
1470 let head = if trailing { "unlines" } else { "join_lines" };
1471 let mut items = vec![sym_at(span, "list")];
1472 for (lits, raws) in &lines {
1473 let item = if raws.is_empty() {
1474 atom_at(span, Atom::Str(lits[0].clone()))
1475 } else if raws.len() == 1 && lits.iter().all(String::is_empty) {
1476 let inner = parse_expr(&raws[0]).map_err(|e| {
1477 fail(
1478 format!("in interpolation `#{{{}}}`: {}", raws[0], e.message),
1479 span,
1480 )
1481 })?;
1482 Spanned::from_sexp_at(&inner, span)
1483 } else {
1484 Self::interpolation(span, lits, raws)?
1485 };
1486 items.push(item);
1487 }
1488 self.note(
1489 Spelling::NewlineEscape,
1490 span,
1491 "\"…\\n…\"",
1492 &format!("{head}([…])"),
1493 );
1494 Ok(list_at(
1495 span,
1496 vec![sym_at(span, head), list_at(span, items)],
1497 ))
1498 }
1499
1500 #[inline(never)]
1504 fn applied(&mut self, start: usize, lhs: Spanned) -> Result<Spanned, Fail> {
1505 if self.at(&TokenKind::LParen) {
1506 let (args, splat) = self.call_args()?;
1507 let call = self.call_node(self.span_since(start), vec![lhs], args, splat);
1508 Ok(self.call_spelling(call))
1509 } else {
1510 self.index(start, lhs)
1511 }
1512 }
1513
1514 #[inline(never)]
1517 fn index(&mut self, start: usize, object: Spanned) -> Result<Spanned, Fail> {
1518 let open = self.peek_span();
1519 self.bump();
1520 self.skip_newlines();
1521 let key = self.expr(0)?;
1522 self.skip_newlines();
1523 self.expect(&TokenKind::RBracket, "`]` closing the index")?;
1524 let span = self.span_since(start);
1525 Ok(if is_index_key(&key) {
1526 list_at(span, vec![sym_at(open, "get"), object, key])
1527 } else {
1528 list_at(span, vec![sym_at(open, "nth"), key, object])
1529 })
1530 }
1531
1532 #[inline(never)]
1533 fn not_prefix(&mut self, span: Span) -> Result<Spanned, Fail> {
1534 if self.at(&TokenKind::LParen) && self.touches_previous() {
1535 self.note(Spelling::OperatorSpelling, span, "not(x)", "not x");
1536 let (args, splat) = self.call_args()?;
1537 if args.len() != 1 || splat.is_some() {
1538 let call = self.call_node(
1539 self.span_since(span.start),
1540 vec![sym_at(span, "not")],
1541 args,
1542 splat,
1543 );
1544 return Ok(call);
1545 }
1546 let operand = args.into_iter().next().expect("one argument");
1547 return Ok(self.negation(span, self.span_since(span.start), operand));
1548 }
1549 let rhs = self.expr(11)?;
1550 Ok(self.negation(span, self.span_since(span.start), rhs))
1551 }
1552
1553 fn prefix(&mut self) -> Result<Spanned, Fail> {
1554 if self.at(&TokenKind::LParen) {
1555 let span = self.peek_span();
1556 self.bump();
1557 if self.arrow_lambda_ahead() {
1558 return self.arrow_lambda(span);
1559 }
1560 return self.group();
1561 }
1562 self.prefix_token()
1563 }
1564
1565 fn group(&mut self) -> Result<Spanned, Fail> {
1566 self.skip_newlines();
1567 let inner = self.expr(0)?;
1568 self.skip_newlines();
1569 self.expect(&TokenKind::RParen, "`)`")?;
1570 Ok(inner)
1571 }
1572
1573 #[inline(never)]
1574 fn prefix_token(&mut self) -> Result<Spanned, Fail> {
1575 let span = self.peek_span();
1576 match self.bump() {
1577 TokenKind::Int(v) => Ok(atom_at(span, Atom::Int(v))),
1578 TokenKind::Float(v) => Ok(atom_at(span, Atom::Float(v))),
1579 TokenKind::Str(s) if self.quoting == 0 && s.contains('\n') => {
1580 self.lines_literal(span, vec![s], Vec::new())
1581 }
1582 TokenKind::Str(s) => Ok(atom_at(span, Atom::Str(s))),
1583 TokenKind::Doc { module, .. } => Err(fail(
1584 format!(
1585 "`{}` sits at the top level, above the definition it documents, never \
1586 inside a form",
1587 if module { "@moduledoc" } else { "@doc" }
1588 ),
1589 span,
1590 )),
1591
1592 TokenKind::InterpolatedStr { parts, exprs }
1603 if self.quoting == 0 && parts.iter().any(|p| p.contains('\n')) =>
1604 {
1605 self.lines_literal(span, parts, exprs)
1606 }
1607 TokenKind::InterpolatedStr { parts, exprs } => Self::interpolation(span, &parts, &exprs),
1608 TokenKind::Sym(s) => Ok(atom_at(span, Atom::Keyword(s))),
1609 TokenKind::Words { flavor, words } => {
1610 let open = Span::new(span.start, span.start + 3);
1611 let mut items = vec![sym_at(open, "list")];
1612 items.extend(words.into_iter().map(|(w, at)| {
1613 atom_at(
1614 at,
1615 match flavor {
1616 crate::words::Flavor::Strings => Atom::Str(w),
1617 crate::words::Flavor::Keywords => Atom::Keyword(w),
1618 },
1619 )
1620 }));
1621 Ok(list_at(span, items))
1622 }
1623 TokenKind::True => Ok(atom_at(span, Atom::Bool(true))),
1624 TokenKind::False => Ok(atom_at(span, Atom::Bool(false))),
1625 TokenKind::Nil => Ok(Spanned::new(span, SpannedForm::Nil)),
1626
1627 TokenKind::Op(o) if o == "-" => {
1628 let rhs = self.expr(11)?; Ok(list_at(
1630 self.span_since(span.start),
1631 vec![sym_at(span, "-"), atom_at(span, Atom::Int(0)), rhs],
1632 ))
1633 }
1634 TokenKind::Op(o) if o == "!" => {
1635 let written = self.text(span).to_string();
1636 self.note(Spelling::LogicalSymbol, span, &written, "not");
1637 let rhs = self.expr(11)?;
1638 Ok(self.negation(span, self.span_since(span.start), rhs))
1639 }
1640 TokenKind::Ident(w) if w == "not" => self.not_prefix(span),
1641 TokenKind::Ident(w) if (w == "and" || w == "or") && self.at(&TokenKind::LParen) => {
1642 Ok(sym_at(span, &w))
1643 }
1644 TokenKind::Ident(w) if w == "and" || w == "or" => Err(fail(
1645 format!("`{w}` joins two expressions: `a {w} b`"),
1646 span,
1647 )),
1648
1649 TokenKind::LParen => {
1650 self.skip_newlines();
1651 let inner = self.expr(0)?;
1652 self.skip_newlines();
1653 self.expect(&TokenKind::RParen, "`)`")?;
1654 Ok(inner)
1658 }
1659
1660 TokenKind::LBracket => self.list_literal(span),
1661 TokenKind::LBrace => self.map_literal(span),
1662
1663 TokenKind::Ident(name) if self.at(&TokenKind::PathSep) => {
1664 self.qualified_name(span, &name)
1665 }
1666 TokenKind::PathSep => Err(fail(
1667 "a qualified name is `package::name`, with the package written before `::`",
1668 span,
1669 )),
1670 TokenKind::Ident(name) => match name.as_str() {
1671 "if" => self.if_form(span, false),
1672 "begin" if !(self.at(&TokenKind::LParen) && self.touches_previous()) => {
1673 self.begin_form(span)
1674 }
1675 "rescue" | "ensure" => Err(fail(
1676 format!("`{name}` belongs to a `begin … end` block or a `def` body, after the statements it protects"),
1677 span,
1678 )),
1679 "unless" => {
1680 self.note(Spelling::Unless, span, "unless", "if not");
1681 self.if_form(span, true)
1682 }
1683 "def" => self.def_form(span),
1684 "defmacro" => self.defmacro_form(span),
1685 "case" => self.case_form(span),
1686 "fn" => self.lambda_form(span),
1687 "test" => self.test_form(span),
1688 "assert" => self.assert_form(span),
1689 "assert_panics" => self.assert_panics_form(span),
1690 "quote" => self.quote_form(span),
1691 "unquote" => self.unquote_form(span, false),
1692 "unquote_splice" => self.unquote_form(span, true),
1693 "do" => Err(fail(
1694 "blue has no `do` blocks: pass a function as an argument, \
1695 `f(xs, fn(x) … end)`",
1696 span,
1697 )),
1698 "end" => Err(fail(
1699 "unexpected `end`: nothing open here for it to close",
1700 span,
1701 )),
1702 _ => Ok(sym_at(span, &name)),
1703 },
1704
1705 other => Err(fail(format!("expected an expression, found {other}"), span)),
1706 }
1707 }
1708
1709 fn qualified_name(&mut self, pkg_span: Span, pkg: &str) -> Result<Spanned, Fail> {
1719 if is_reserved_word(pkg) {
1720 return Err(fail(
1721 format!("`{pkg}` is a reserved word and cannot name a package"),
1722 pkg_span,
1723 ));
1724 }
1725 self.bump(); let name_span = self.peek_span();
1727 let name = match self.bump() {
1728 TokenKind::Ident(n) => n,
1729 TokenKind::True => "true".to_string(),
1731 TokenKind::False => "false".to_string(),
1732 TokenKind::Nil => "nil".to_string(),
1733 other => {
1734 return Err(fail(
1735 format!("expected a name after `{pkg}::`, found {other}"),
1736 name_span,
1737 ))
1738 }
1739 };
1740 if self.at(&TokenKind::PathSep) {
1741 return Err(self.error(format!(
1742 "a qualified name has one qualifier: `package::name`, and `{pkg}::{name}::…` has two"
1743 )));
1744 }
1745 Ok(sym_at(
1746 self.span_since(pkg_span.start),
1747 &qualify(pkg, &name),
1748 ))
1749 }
1750
1751 #[inline(never)]
1758 fn negation(&mut self, head: Span, whole: Span, operand: Spanned) -> Spanned {
1759 if let SpannedForm::List(items) = &operand.form {
1760 let flipped = match items.first().and_then(Spanned::as_symbol) {
1761 Some("equal?") => Some("not="),
1762 Some("not=") => Some("equal?"),
1763 _ => None,
1764 };
1765 if let (Some(to), 3) = (flipped, items.len()) {
1766 let (written, canonical) = if to == "not=" {
1767 ("not (a == b)", "a != b")
1768 } else {
1769 ("not (a != b)", "a == b")
1770 };
1771 self.note(Spelling::NegatedComparison, whole, written, canonical);
1772 let mut out = items.clone();
1773 out[0] = sym_at(items[0].span, to);
1774 if to == "equal?" {
1775 if let Some(test) =
1776 self.nil_test(whole.start, items[0].span, &items[1], &items[2])
1777 {
1778 return test;
1779 }
1780 }
1781 return list_at(whole, out);
1782 }
1783 }
1784 list_at(whole, vec![sym_at(head, "not"), operand])
1785 }
1786
1787 #[inline(never)]
1788 fn call_spelling(&mut self, call: Spanned) -> Spanned {
1789 let SpannedForm::List(items) = &call.form else {
1790 return call;
1791 };
1792 let Some(head) = items.first().and_then(Spanned::as_symbol) else {
1793 return call;
1794 };
1795 let argc = items.len() - 1;
1796 if head == "get" && argc == 2 && is_index_key(&items[2]) {
1797 self.note(Spelling::Index, call.span, "get(m, :k)", "m[:k]");
1798 return call;
1799 }
1800 if head == "nth" && argc == 2 && !is_index_key(&items[1]) {
1801 self.note(Spelling::Index, call.span, "nth(i, xs)", "xs[i]");
1802 return call;
1803 }
1804 if head == "equal?" && argc == 2 {
1805 let (op_span, start) = (items[0].span, call.span.start);
1806 if let Some(test) = self.nil_test(start, op_span, &items[1], &items[2]) {
1807 return test;
1808 }
1809 }
1810 let operator = match (head, argc) {
1811 ("and" | "or" | "mod" | "equal?", 2) => Some(head),
1812 ("list", _) => Some("list"),
1813 _ => None,
1814 };
1815 if let Some(op) = operator {
1816 let canonical = match op {
1817 "list" => "[…]".to_string(),
1818 "mod" => "a % b".to_string(),
1819 "equal?" => "a == b".to_string(),
1820 other => format!("a {other} b"),
1821 };
1822 self.note(
1823 Spelling::OperatorSpelling,
1824 call.span,
1825 &format!("{head}(…)"),
1826 &canonical,
1827 );
1828 return call;
1829 }
1830 if head == "apply"
1831 && self.text(items[0].span) == "apply"
1832 && argc >= 2
1833 && items[1].as_symbol().is_some_and(is_callable_name)
1834 {
1835 self.note(Spelling::Apply, call.span, "apply(f, …, xs)", "f(…, *xs)");
1836 return call;
1837 }
1838 if head == "try" {
1839 return self.try_spelling(call);
1840 }
1841 call
1842 }
1843
1844 fn try_spelling(&mut self, call: Spanned) -> Spanned {
1845 let SpannedForm::List(items) = &call.form else {
1846 return call;
1847 };
1848 let Some((catch, body)) = items[1..].split_last() else {
1849 return call;
1850 };
1851 let Some(clause) = catch.as_list() else {
1852 return call;
1853 };
1854 let binder = match clause {
1855 [head, binding, handler @ ..]
1856 if head.as_symbol() == Some("catch") && !handler.is_empty() =>
1857 {
1858 match binding.as_list() {
1859 Some([b]) => b.as_symbol().map(|n| (n.to_string(), b.span)),
1860 _ => None,
1861 }
1862 }
1863 _ => None,
1864 };
1865 let Some((name, binder_span)) = binder else {
1866 return call;
1867 };
1868 if body.is_empty() {
1869 return call;
1870 }
1871 self.note(Spelling::TryCatch, call.span, "try(…, catch(…))", "rescue");
1872 let handler = &clause[2..];
1873 let rename =
1874 name.starts_with('_') && name != "_" && !handler.iter().any(|h| mentions(h, &name));
1875 let body = if body.len() == 1 {
1876 body[0].clone()
1877 } else {
1878 let span = cover(body, call.span);
1879 let mut list = vec![sym_at(Span::new(span.start, span.start), "begin")];
1880 list.extend(body.iter().cloned());
1881 list_at(span, list)
1882 };
1883 let handler = if handler.len() == 1 {
1884 handler[0].clone()
1885 } else {
1886 let span = cover(handler, catch.span);
1887 let mut list = vec![sym_at(Span::new(span.start, span.start), "begin")];
1888 list.extend(handler.iter().cloned());
1889 list_at(span, list)
1890 };
1891 let binder = sym_at(binder_span, if rename { "_" } else { &name });
1892 list_at(
1893 call.span,
1894 vec![
1895 items[0].clone(),
1896 body,
1897 list_at(
1898 catch.span,
1899 vec![
1900 clause[0].clone(),
1901 list_at(clause[1].span, vec![binder]),
1902 handler,
1903 ],
1904 ),
1905 ],
1906 )
1907 }
1908
1909 fn finish_send(&mut self, start: usize, recv: Spanned, dot: Span) -> Result<Spanned, Fail> {
1910 let name_span = self.peek_span();
1911 let name = match self.bump() {
1912 TokenKind::Ident(n) if is_reserved_word(&n) => {
1926 return Err(self.error(format!(
1927 "`{n}` is a reserved word and cannot be a method name — \
1928 `recv.{n}` would lower onto the `{n}` form itself"
1929 )))
1930 }
1931 TokenKind::Ident(n) => n,
1932 other => {
1933 return Err(fail(
1934 format!("expected a method name after `.`, found {other}"),
1935 name_span,
1936 ))
1937 }
1938 };
1939 if !self.at(&TokenKind::LParen) {
1940 return Ok(list_at(
1941 self.span_since(start),
1942 vec![
1943 sym_at(dot, LOWERED_FIELD),
1944 recv,
1945 atom_at(name_span, Atom::Keyword(name)),
1946 ],
1947 ));
1948 }
1949 self.note(
1950 Spelling::CallForm,
1951 dot.merge(name_span),
1952 &format!("x.{name}(…)"),
1953 &format!("{name}(x, …)"),
1954 );
1955 let (args, splat) = self.call_args()?;
1956 Ok(self.call_node(
1957 self.span_since(start),
1958 vec![sym_at(name_span, &name), recv],
1959 args,
1960 splat,
1961 ))
1962 }
1963
1964 fn call_node(
1965 &self,
1966 span: Span,
1967 head: Vec<Spanned>,
1968 args: Vec<Spanned>,
1969 splat: Option<Span>,
1970 ) -> Spanned {
1971 let mut list = Vec::with_capacity(head.len() + args.len() + 1);
1972 if let Some(at) = splat {
1973 list.push(sym_at(at, "apply"));
1974 }
1975 list.extend(head);
1976 list.extend(args);
1977 list_at(span, list)
1978 }
1979
1980 fn call_args(&mut self) -> Result<(Vec<Spanned>, Option<Span>), Fail> {
1981 self.expect(&TokenKind::LParen, "`(`")?;
1982 let mut args = Vec::new();
1983 let mut splat = None;
1984 self.skip_newlines();
1985 if self.eat(&TokenKind::RParen) {
1986 return Ok((args, splat));
1987 }
1988 loop {
1989 self.skip_newlines();
1990 if splat.is_some() {
1991 return Err(
1992 self.error("a splat `*xs` passes a list as the LAST arguments: write it last")
1993 );
1994 }
1995 if self.at_spread() || matches!(self.peek(), TokenKind::Label(_)) {
1996 let open = self.peek_span();
1997 let (segments, spread) = self.map_entries(open, &TokenKind::RParen)?;
1998 if !spread {
1999 return Err(fail(
2000 "a keyword entry in a call needs a `**` spread: pass a map, f({a: 1})",
2001 open,
2002 ));
2003 }
2004 args.push(self.merged(open, LOWERED_KWARGS, segments));
2005 return Ok((args, splat));
2006 }
2007 if matches!(self.peek(), TokenKind::Op(o) if o == "*") {
2008 splat = Some(self.peek_span());
2009 self.bump();
2010 }
2011 args.push(self.expr(0)?);
2012 self.skip_newlines();
2013 if self.eat(&TokenKind::Comma) {
2014 if self.trailing_comma(&TokenKind::RParen) {
2015 break;
2016 }
2017 continue;
2018 }
2019 self.expect(&TokenKind::RParen, "`,` or `)`")?;
2020 break;
2021 }
2022 Ok((args, splat))
2023 }
2024
2025 fn trailing_comma(&mut self, closer: &TokenKind) -> bool {
2026 let comma = self
2027 .toks
2028 .get(self.pos.wrapping_sub(1))
2029 .map_or(self.peek_span(), |t| t.span);
2030 self.skip_newlines();
2031 if self.at(closer) {
2032 self.note(Spelling::TrailingComma, comma, ",", "");
2033 self.bump();
2034 return true;
2035 }
2036 false
2037 }
2038
2039 fn list_literal(&mut self, open: Span) -> Result<Spanned, Fail> {
2040 let mut items = vec![sym_at(open, "list")];
2041 self.skip_newlines();
2042 if self.eat(&TokenKind::RBracket) {
2043 return Ok(list_at(self.span_since(open.start), items));
2044 }
2045 loop {
2046 self.skip_newlines();
2047 items.push(self.expr(0)?);
2048 self.skip_newlines();
2049 if self.eat(&TokenKind::Comma) {
2050 if self.trailing_comma(&TokenKind::RBracket) {
2051 break;
2052 }
2053 continue;
2054 }
2055 self.expect(&TokenKind::RBracket, "`,` or `]`")?;
2056 break;
2057 }
2058 Ok(list_at(self.span_since(open.start), items))
2059 }
2060
2061 fn map_literal(&mut self, open: Span) -> Result<Spanned, Fail> {
2068 self.skip_newlines();
2069 if self.eat(&TokenKind::RBrace) {
2070 return Ok(list_at(
2071 self.span_since(open.start),
2072 vec![sym_at(open, LOWERED_MAP)],
2073 ));
2074 }
2075 let (mut segments, spread) = self.map_entries(open, &TokenKind::RBrace)?;
2076 if !spread {
2077 return Ok(segments.remove(0));
2078 }
2079 Ok(self.merged(open, LOWERED_MERGE, segments))
2080 }
2081
2082 fn merged(&self, open: Span, head: &str, segments: Vec<Spanned>) -> Spanned {
2083 let mut items = vec![sym_at(open, head)];
2084 items.extend(segments);
2085 list_at(self.span_since(open.start), items)
2086 }
2087
2088 fn at_spread(&self) -> bool {
2089 matches!(self.peek(), TokenKind::Op(o) if o == "**")
2090 }
2091
2092 fn map_entries(
2093 &mut self,
2094 open: Span,
2095 closer: &TokenKind,
2096 ) -> Result<(Vec<Spanned>, bool), Fail> {
2097 let mut segments = Vec::new();
2098 let mut items = vec![sym_at(open, LOWERED_MAP)];
2099 let mut spread = false;
2100 loop {
2101 self.skip_newlines();
2102 if self.at_spread() {
2103 let star = self.peek_span();
2104 self.bump();
2105 let value = self.expr(0)?;
2106 if items.len() > 1 {
2107 let span = items[1].span.merge(items[items.len() - 1].span);
2108 items[0] = sym_at(items[1].span, LOWERED_MAP);
2109 segments.push(list_at(span, std::mem::take(&mut items)));
2110 items = vec![sym_at(open, LOWERED_MAP)];
2111 }
2112 segments.push(list_at(
2113 star.merge(value.span),
2114 vec![sym_at(star, LOWERED_SPLICE), value],
2115 ));
2116 spread = true;
2117 } else {
2118 self.map_entry(&mut items, closer)?;
2119 }
2120 self.skip_newlines();
2121 if self.eat(&TokenKind::Comma) {
2122 if self.trailing_comma(closer) {
2123 break;
2124 }
2125 continue;
2126 }
2127 self.expect(closer, "`,` or the end of the entries")?;
2128 break;
2129 }
2130 if items.len() > 1 || !spread {
2131 let whole = self.span_since(open.start);
2132 segments.push(list_at(whole, items));
2133 }
2134 Ok((segments, spread))
2135 }
2136
2137 fn map_entry(&mut self, items: &mut Vec<Spanned>, closer: &TokenKind) -> Result<(), Fail> {
2138 match self.peek().clone() {
2139 TokenKind::Label(name) => {
2140 let label_span = self.peek_span();
2141 self.bump();
2142 self.skip_newlines();
2143 items.push(atom_at(label_span, Atom::Keyword(name.clone())));
2144 if matches!(self.peek(), TokenKind::Comma) || self.at(closer) {
2145 items.push(sym_at(label_span, &name));
2146 } else {
2147 let value = self.expr(0)?;
2148 if matches!(&value.form, SpannedForm::Atom(Atom::Symbol(s)) if *s == name)
2149 && label_name(&name)
2150 {
2151 let written = format!("{name}: {name}");
2152 let canonical = format!("{name}:");
2153 self.note(
2154 Spelling::MapPun,
2155 label_span.merge(value.span),
2156 &written,
2157 &canonical,
2158 );
2159 }
2160 items.push(value);
2161 }
2162 }
2163 _ => {
2164 let k = self.expr(0)?;
2165 self.skip_newlines();
2166 if let SpannedForm::Atom(Atom::Keyword(name)) = &k.form {
2167 if blue_lang_name(name) {
2168 let written = format!(":{name} =>");
2169 let canonical = format!("{name}:");
2170 self.note(Spelling::KeywordRocket, k.span, &written, &canonical);
2171 }
2172 }
2173 self.expect(&TokenKind::Rocket, "`=>` in a map literal")?;
2174 self.skip_newlines();
2175 items.push(k);
2176 items.push(self.expr(0)?);
2177 }
2178 }
2179 Ok(())
2180 }
2181
2182 fn if_chain(&mut self, head: Span) -> Result<Spanned, Fail> {
2192 let cond = self.expr(0)?;
2193 let then = self.body(&["elsif", "else", "end"])?;
2194 let els = self.else_part()?;
2195 let mut out = vec![sym_at(head, "if"), cond, then];
2196 if let Some(e) = els {
2197 out.push(e);
2198 }
2199 Ok(list_at(self.span_since(head.start), out))
2200 }
2201
2202 fn else_part(&mut self) -> Result<Option<Spanned>, Fail> {
2203 if self.at_ident("elsif") {
2204 let elsif = self.peek_span();
2205 self.bump();
2206 return Ok(Some(self.if_chain(elsif)?));
2207 }
2208 if !self.at_ident("else") {
2209 self.expect_ident("end")?;
2210 return Ok(None);
2211 }
2212 let else_span = self.peek_span();
2213 self.bump();
2214 let save = self.pos;
2215 self.skip_newlines();
2216 let leads_with_if = self.at_ident("if") || self.at_ident("unless");
2217 self.pos = save;
2218 let e = self.body(&["end"])?;
2219 let end_at = self.peek_span().start;
2220 self.expect_ident("end")?;
2221 if matches!(e.form, SpannedForm::Nil) && !self.comment_within(else_span.start, end_at) {
2222 self.note(Spelling::ElseNil, else_span, "else nil", "no else");
2223 return Ok(None);
2224 }
2225 let single_if = matches!(&e.form, SpannedForm::List(items)
2226 if (items.len() == 3 || items.len() == 4) && items[0].as_symbol() == Some("if"));
2227 if leads_with_if && single_if {
2228 self.note(Spelling::ElseIf, else_span, "else if", "elsif");
2229 }
2230 Ok(Some(e))
2231 }
2232
2233 fn if_form(&mut self, head: Span, negate: bool) -> Result<Spanned, Fail> {
2234 let cond = self.expr(0)?;
2235 let cond = if negate {
2236 let inner = cond.span;
2239 self.negation(head, head.merge(inner), cond)
2240 } else {
2241 cond
2242 };
2243 let then = self.body(&["elsif", "else", "end"])?;
2256 let els = self.else_part()?;
2257 let mut out = vec![sym_at(head, "if"), cond, then];
2258 if let Some(e) = els {
2259 out.push(e);
2260 }
2261 Ok(list_at(self.span_since(head.start), out))
2262 }
2263
2264 fn case_form(&mut self, head: Span) -> Result<Spanned, Fail> {
2290 let subject = self.expr(0)?;
2291 self.skip_newlines();
2292
2293 let subject_var = "case-subject";
2296 let mut arms: Vec<Spanned> = Vec::new();
2297 let mut otherwise: Option<Spanned> = None;
2298 let mut else_kw: Option<Span> = None;
2301
2302 loop {
2303 self.skip_newlines();
2304 if self.at_ident("end") {
2305 break;
2306 }
2307 let maybe_else = self.peek_span();
2308 if self.eat_ident("else") {
2309 else_kw = Some(maybe_else);
2310 otherwise = Some(self.body(&["end"])?);
2311 continue;
2312 }
2313 let when = self.peek_span();
2314 if !self.eat_ident("when") {
2315 return Err(self.error(format!(
2316 "expected `when`, `else` or `end` in a case, found {}",
2317 self.peek()
2318 )));
2319 }
2320 self.skip_newlines();
2321 let pattern = self.expr(0)?;
2322 let body = self.body(&["when", "else", "end"])?;
2323 let test = list_at(
2327 when.merge(pattern.span),
2328 vec![sym_at(when, "equal?"), sym_at(when, subject_var), pattern],
2329 );
2330 arms.push(list_at(when.merge(body.span), vec![test, body]));
2331 }
2332 self.expect_ident("end")?;
2333
2334 if arms.is_empty() && otherwise.is_none() {
2335 return Err(self.error("a case needs at least one `when` or an `else`".to_string()));
2336 }
2337
2338 let mut cond = vec![sym_at(head, "cond")];
2343 cond.extend(arms);
2344 let else_span = else_kw.unwrap_or(head);
2351 let else_body = otherwise.unwrap_or_else(|| Spanned::new(else_span, SpannedForm::Nil));
2352 cond.push(list_at(
2353 else_span.merge(else_body.span),
2354 vec![sym_at(else_span, "else"), else_body],
2355 ));
2356
2357 let whole = self.span_since(head.start);
2358 let binding = list_at(
2359 head.merge(subject.span),
2360 vec![sym_at(head, subject_var), subject],
2361 );
2362 Ok(list_at(
2363 whole,
2364 vec![
2365 sym_at(head, "let"),
2366 list_at(binding.span, vec![binding]),
2367 list_at(whole, cond),
2368 ],
2369 ))
2370 }
2371
2372 fn arrow_lambda_ahead(&self) -> bool {
2375 let mut i = self.pos;
2376 let kind = |i: usize| self.toks.get(i).map(|t| &t.kind);
2377 if !matches!(kind(i), Some(TokenKind::RParen)) {
2378 loop {
2379 if matches!(kind(i), Some(TokenKind::Op(o)) if o == "*") {
2380 i += 1;
2381 }
2382 if !matches!(kind(i), Some(TokenKind::Ident(_))) {
2383 return false;
2384 }
2385 i += 1;
2386 match kind(i) {
2387 Some(TokenKind::Comma) => i += 1,
2388 Some(TokenKind::RParen) => break,
2389 _ => return false,
2390 }
2391 }
2392 }
2393 matches!(kind(i + 1), Some(TokenKind::Op(o)) if o == "->")
2394 }
2395
2396 fn arrow_lambda(&mut self, head: Span) -> Result<Spanned, Fail> {
2403 let mut params: Vec<Spanned> = Vec::new();
2404 while !self.at(&TokenKind::RParen) {
2405 let p_span = self.peek_span();
2406 if self.at_star() {
2407 params.extend(self.rest_parameter()?);
2408 break;
2409 }
2410 match self.bump() {
2411 TokenKind::Ident(p) => params.push(sym_at(p_span, &p)),
2412 other => {
2413 return Err(fail(
2414 format!("expected a parameter name, found {other}"),
2415 p_span,
2416 ))
2417 }
2418 }
2419 self.eat(&TokenKind::Comma);
2420 }
2421 self.bump();
2422 self.bump();
2423 self.skip_newlines();
2424 let params_span = cover(¶ms, head);
2425 let body = self.expr(0)?;
2426 Ok(list_at(
2427 self.span_since(head.start),
2428 vec![sym_at(head, "lambda"), list_at(params_span, params), body],
2429 ))
2430 }
2431
2432 fn rest_parameter(&mut self) -> Result<[Spanned; 2], Fail> {
2433 let star = self.peek_span();
2434 self.bump();
2435 let name_span = self.peek_span();
2436 match self.bump() {
2437 TokenKind::Ident(p) => {
2438 let whole = star.merge(name_span);
2439 Ok([sym_at(whole, "&rest"), sym_at(whole, &p)])
2440 }
2441 other => Err(fail(
2442 format!("expected a parameter name after `*`, found {other}"),
2443 name_span,
2444 )),
2445 }
2446 }
2447
2448 fn at_star(&self) -> bool {
2449 matches!(self.peek(), TokenKind::Op(o) if o == "*")
2450 }
2451
2452 fn optional_mark(
2453 &self,
2454 name: String,
2455 span: Span,
2456 optional_from: &mut Option<usize>,
2457 index: usize,
2458 ) -> Result<String, Fail> {
2459 match name.strip_suffix('?').filter(|n| !n.is_empty()) {
2460 Some(bare) => {
2461 optional_from.get_or_insert(index);
2462 Ok(bare.to_string())
2463 }
2464 None if optional_from.is_some() => Err(fail(
2465 format!("the required parameter `{name}` cannot follow an optional one"),
2466 span,
2467 )),
2468 None => Ok(name),
2469 }
2470 }
2471
2472 fn rest_is_last(&mut self) -> Result<(), Fail> {
2473 self.skip_newlines();
2474 if self.at(&TokenKind::Comma) {
2475 return Err(self.error("a rest parameter `*name` comes last".to_string()));
2476 }
2477 Ok(())
2478 }
2479
2480 fn lambda_form(&mut self, head: Span) -> Result<Spanned, Fail> {
2490 let mut params: Vec<Spanned> = Vec::new();
2491 if !self.at(&TokenKind::LParen) {
2492 self.note(Spelling::ParameterList, head, "fn", "fn()");
2493 }
2494 if self.at(&TokenKind::LParen) {
2495 self.bump();
2496 self.skip_newlines();
2497 if !self.eat(&TokenKind::RParen) {
2498 loop {
2499 self.skip_newlines();
2500 let p_span = self.peek_span();
2501 if self.at_star() {
2502 params.extend(self.rest_parameter()?);
2503 self.rest_is_last()?;
2504 self.expect(&TokenKind::RParen, "`)` after the rest parameter")?;
2505 break;
2506 }
2507 match self.bump() {
2508 TokenKind::Ident(p) => params.push(sym_at(p_span, &p)),
2509 other => {
2510 return Err(fail(
2511 format!("expected a parameter name, found {other}"),
2512 p_span,
2513 ))
2514 }
2515 }
2516 self.skip_newlines();
2517 if self.eat(&TokenKind::Comma) {
2518 if self.trailing_comma(&TokenKind::RParen) {
2519 break;
2520 }
2521 continue;
2522 }
2523 self.expect(&TokenKind::RParen, "`,` or `)`")?;
2524 break;
2525 }
2526 }
2527 }
2528 let params_span = cover(¶ms, head);
2529 let body = self.body(&["end"])?;
2530 self.expect_ident("end")?;
2531 Ok(list_at(
2532 self.span_since(head.start),
2533 vec![sym_at(head, "lambda"), list_at(params_span, params), body],
2534 ))
2535 }
2536
2537 fn test_form(&mut self, head: Span) -> Result<Spanned, Fail> {
2543 let name_span = self.peek_span();
2544 let name = match self.bump() {
2545 TokenKind::Str(s) => s,
2546 other => {
2547 return Err(fail(
2548 format!("expected a string name after `test`, found {other}"),
2549 name_span,
2550 ))
2551 }
2552 };
2553 let meta = if self.eat(&TokenKind::Comma) {
2554 self.skip_newlines();
2555 Some(self.test_declaration()?)
2556 } else {
2557 None
2558 };
2559 let body = self.body(&["end"])?;
2560 self.expect_ident("end")?;
2561 let mut items = vec![sym_at(head, "deftest"), atom_at(name_span, Atom::Str(name))];
2562 items.extend(meta);
2563 items.push(body);
2564 Ok(list_at(self.span_since(head.start), items))
2565 }
2566
2567 fn test_declaration(&mut self) -> Result<Spanned, Fail> {
2577 const SHAPE: &str = "a test's declaration is a map: \
2578 `test \"name\", {satisfies: %i[nist_ac_6], target: parse_token}`";
2579 let open = self.peek_span();
2580 if !self.eat(&TokenKind::LBrace) {
2581 return Err(fail(SHAPE, open));
2582 }
2583 let map = self.map_literal(open)?;
2584 let items = match map.as_list() {
2585 Some(items) if items.first().and_then(Spanned::as_symbol) == Some(LOWERED_MAP) => {
2586 items.to_vec()
2587 }
2588 _ => return Err(fail(SHAPE, map.span)),
2589 };
2590 if items.len() == 1 {
2591 return Err(fail(
2592 "an empty declaration declares nothing: write `satisfies:`, `target:` or neither",
2593 map.span,
2594 ));
2595 }
2596 let mut keys = Vec::new();
2597 let mut satisfies: Option<(Span, Vec<Spanned>)> = None;
2598 let mut target: Option<Spanned> = None;
2599 for pair in items[1..].chunks(2) {
2600 let [key, value] = pair else {
2601 return Err(fail(SHAPE, map.span));
2602 };
2603 let name = key.as_keyword().unwrap_or_default().to_string();
2604 if keys.contains(&name) {
2605 return Err(fail(format!("`{name}:` is written twice"), key.span));
2606 }
2607 match name.as_str() {
2608 "satisfies" => {
2609 let controls = match value.as_list() {
2610 Some([head, rest @ ..]) if head.as_symbol() == Some("list") => rest,
2611 _ => {
2612 return Err(fail(
2613 "`satisfies:` takes a list of control keywords: \
2614 `satisfies: %i[nist_ac_6 nist_cm_7]`",
2615 value.span,
2616 ))
2617 }
2618 };
2619 if controls.is_empty() {
2620 return Err(fail(
2621 "`satisfies: []` claims no control: leave the key out",
2622 value.span,
2623 ));
2624 }
2625 let mut seen: Vec<&str> = Vec::new();
2626 for c in controls {
2627 let Some(word) = c.as_keyword() else {
2628 return Err(fail(
2629 "a control is a keyword such as `:nist_ac_6`",
2630 c.span,
2631 ));
2632 };
2633 if seen.contains(&word) {
2634 return Err(fail(format!("`:{word}` is listed twice"), c.span));
2635 }
2636 seen.push(word);
2637 }
2638 satisfies = Some((value.span, controls.to_vec()));
2639 }
2640 "target" => {
2641 if value.as_symbol().is_none() {
2642 return Err(fail(
2643 "`target:` names the definition the test verifies: \
2644 `target: parse_token`",
2645 value.span,
2646 ));
2647 }
2648 target = Some(value.clone());
2649 }
2650 _ => {
2651 return Err(fail(
2652 format!(
2653 "a test declares `satisfies:` and `target:`; `{}` is not one",
2654 self.text(key.span)
2655 ),
2656 key.span,
2657 ))
2658 }
2659 }
2660 keys.push(name);
2661 }
2662 let mut entries = vec![items[0].clone()];
2663 if let Some((span, mut controls)) = satisfies {
2664 let written: Vec<String> = controls
2665 .iter()
2666 .map(|c| c.as_keyword().unwrap_or_default().to_string())
2667 .collect();
2668 controls.sort_by(|a, b| a.as_keyword().cmp(&b.as_keyword()));
2669 let sorted: Vec<String> = controls
2670 .iter()
2671 .map(|c| c.as_keyword().unwrap_or_default().to_string())
2672 .collect();
2673 if written != sorted {
2674 self.note(
2675 Spelling::TestDeclaration,
2676 span,
2677 &format!("%i[{}]", written.join(" ")),
2678 &format!("%i[{}]", sorted.join(" ")),
2679 );
2680 }
2681 let mut list = vec![sym_at(span, "list")];
2682 list.extend(controls);
2683 entries.push(atom_at(span, Atom::Keyword("satisfies".into())));
2684 entries.push(list_at(span, list));
2685 }
2686 if keys.first().map(String::as_str) == Some("target") && keys.len() == 2 {
2687 self.note(
2688 Spelling::TestDeclaration,
2689 map.span,
2690 "{target: …, satisfies: …}",
2691 "{satisfies: …, target: …}",
2692 );
2693 }
2694 if let Some(t) = target {
2695 entries.push(atom_at(t.span, Atom::Keyword("target".into())));
2696 entries.push(t);
2697 }
2698 Ok(list_at(
2699 map.span,
2700 vec![sym_at(open, LOWERED_TEST_META), list_at(map.span, entries)],
2701 ))
2702 }
2703
2704 fn assert_panics_form(&mut self, head: Span) -> Result<Spanned, Fail> {
2711 let e = self.expr(0)?;
2712 let quoted = Spanned::new(e.span, SpannedForm::Quote(Box::new(e.clone())));
2713 let thunk = list_at(
2714 e.span,
2715 vec![sym_at(head, "lambda"), list_at(e.span, Vec::new()), e],
2716 );
2717 Ok(list_at(
2718 self.span_since(head.start),
2719 vec![sym_at(head, LOWERED_ASSERT_PANICS), quoted, thunk],
2720 ))
2721 }
2722
2723 fn assert_form(&mut self, head: Span) -> Result<Spanned, Fail> {
2747 let e = self.expr(0)?;
2748 let quoted = Spanned::new(e.span, SpannedForm::Quote(Box::new(e.clone())));
2749 Ok(list_at(
2750 self.span_since(head.start),
2751 vec![sym_at(head, LOWERED_ASSERT), quoted, e],
2752 ))
2753 }
2754
2755 fn defmacro_form(&mut self, head: Span) -> Result<Spanned, Fail> {
2764 let name_span = self.peek_span();
2765 let name = match self.bump() {
2766 TokenKind::Ident(n) => n,
2767 other => {
2768 return Err(fail(
2769 format!("expected a name after `defmacro`, found {other}"),
2770 name_span,
2771 ))
2772 }
2773 };
2774 self.not_reserved(&name, name_span, "a `defmacro` name")?;
2775 let mut params: Vec<Spanned> = Vec::new();
2776 if self.at(&TokenKind::LParen) {
2777 self.bump();
2778 self.skip_newlines();
2779 if !self.eat(&TokenKind::RParen) {
2780 loop {
2781 self.skip_newlines();
2782 let p_span = self.peek_span();
2783 match self.bump() {
2784 TokenKind::Ident(p) => params.push(sym_at(p_span, &p)),
2785 TokenKind::Label(p) => {
2786 return Err(self.error(format!(
2787 "macro parameter `{p}` cannot be typed: a macro receives \
2788 source forms, not values"
2789 )))
2790 }
2791 other => {
2792 return Err(fail(
2793 format!("expected a parameter name, found {other}"),
2794 p_span,
2795 ))
2796 }
2797 }
2798 self.skip_newlines();
2799 if self.eat(&TokenKind::Comma) {
2800 if self.trailing_comma(&TokenKind::RParen) {
2801 break;
2802 }
2803 continue;
2804 }
2805 self.expect(&TokenKind::RParen, "`,` or `)`")?;
2806 break;
2807 }
2808 }
2809 }
2810 if matches!(self.peek(), TokenKind::Op(o) if o == "->") {
2811 return Err(self.error(
2812 "a macro has no return type: it produces source forms, not values".to_string(),
2813 ));
2814 }
2815
2816 let params_span = cover(¶ms, name_span);
2817 let body = self.body(&["end"])?;
2818 self.expect_ident("end")?;
2819
2820 Ok(list_at(
2823 self.span_since(head.start),
2824 vec![
2825 sym_at(head, "defmacro"),
2826 sym_at(name_span, &name),
2827 list_at(params_span, params),
2828 body,
2829 ],
2830 ))
2831 }
2832
2833 fn quote_form(&mut self, head: Span) -> Result<Spanned, Fail> {
2839 self.quoting += 1;
2840 let body = self.body(&["end"]);
2841 self.quoting -= 1;
2842 let body = body?;
2843 self.expect_ident("end")?;
2844 Ok(Spanned::new(
2853 self.span_since(head.start),
2854 SpannedForm::Quasiquote(Box::new(body)),
2855 ))
2856 }
2857
2858 fn unquote_form(&mut self, head: Span, splice: bool) -> Result<Spanned, Fail> {
2860 self.expect(&TokenKind::LParen, "`(` after unquote")?;
2861 self.skip_newlines();
2862 let inner = self.expr(0)?;
2863 self.skip_newlines();
2864 self.expect(&TokenKind::RParen, "`)`")?;
2865 let span = self.span_since(head.start);
2866 Ok(Spanned::new(
2867 span,
2868 if splice {
2869 SpannedForm::UnquoteSplice(Box::new(inner))
2870 } else {
2871 SpannedForm::Unquote(Box::new(inner))
2872 },
2873 ))
2874 }
2875
2876 fn protected_body(&mut self, head: Span) -> Result<Spanned, Fail> {
2877 let body = self.body(&["rescue", "ensure", "end"])?;
2878 if self.at_ident("rescue") || self.at_ident("ensure") {
2879 return self.rescue_tail(head, body);
2880 }
2881 Ok(body)
2882 }
2883
2884 fn begin_form(&mut self, head: Span) -> Result<Spanned, Fail> {
2885 let body = self.body(&["rescue", "ensure", "end"])?;
2886 if !(self.at_ident("rescue") || self.at_ident("ensure")) {
2887 return Err(self.error(
2888 "a `begin` block protects its statements with `rescue` or `ensure`; without either, write the statements directly",
2889 ));
2890 }
2891 let tree = self.rescue_tail(head, body)?;
2892 self.expect_ident("end")?;
2893 Ok(Spanned::new(self.span_since(head.start), tree.form))
2894 }
2895
2896 fn rescue_tail(&mut self, head: Span, body: Spanned) -> Result<Spanned, Fail> {
2897 let mut clauses: Vec<Spanned> = Vec::new();
2898 let mut catch_all: Option<(Spanned, Spanned, Span)> = None;
2899 while self.at_ident("rescue") {
2900 let kw = self.peek_span();
2901 self.bump();
2902 if catch_all.is_some() {
2903 return Err(fail(
2904 "a `rescue NAME` catches every error, so it is the last `rescue`; put the kinds it would shadow above it",
2905 kw,
2906 ));
2907 }
2908 match self.peek().clone() {
2909 TokenKind::Sym(_) => {
2910 let mut kinds = Vec::new();
2911 loop {
2912 let k_span = self.peek_span();
2913 match self.bump() {
2914 TokenKind::Sym(k) => kinds.push(atom_at(k_span, Atom::Keyword(k))),
2915 other => {
2916 return Err(fail(
2917 format!("expected an error kind such as `:not_found`, found {other}"),
2918 k_span,
2919 ))
2920 }
2921 }
2922 if self.eat(&TokenKind::Comma) {
2923 self.skip_newlines();
2924 continue;
2925 }
2926 break;
2927 }
2928 let binder = if self.eat_ident("as") {
2929 let b_span = self.peek_span();
2930 match self.bump() {
2931 TokenKind::Ident(b) if !is_reserved_word(&b) => Some(sym_at(b_span, &b)),
2932 other => {
2933 return Err(fail(
2934 format!("expected the name the error is bound to after `as`, found {other}"),
2935 b_span,
2936 ))
2937 }
2938 }
2939 } else {
2940 None
2941 };
2942 let handler = self.body(&["rescue", "ensure", "end"])?;
2943 let mut test = vec![sym_at(kw, LOWERED_ERROR_MATCH), sym_at(kw, RESCUED)];
2944 test.extend(kinds);
2945 let test = list_at(cover(&test, kw), test);
2946 let handler = match binder {
2947 Some(b) => let_bound(b, handler, kw),
2948 None => handler,
2949 };
2950 clauses.push(list_at(kw.merge(handler.span), vec![test, handler]));
2951 }
2952 TokenKind::Ident(b) if !is_reserved_word(&b) => {
2953 let b_span = self.peek_span();
2954 self.bump();
2955 let handler = self.body(&["rescue", "ensure", "end"])?;
2956 catch_all = Some((sym_at(b_span, &b), handler, kw));
2957 }
2958 _ => {
2959 return Err(fail(
2960 "`rescue` names what it catches: `rescue :kind`, `rescue :a, :b as e`, or `rescue e` for every error (`rescue _` discards it)",
2961 kw,
2962 ))
2963 }
2964 }
2965 }
2966 let ensure = if self.at_ident("ensure") {
2967 self.bump();
2968 let cleanup = self.body(&["rescue", "end"])?;
2969 if self.at_ident("rescue") {
2970 return Err(self.error("`ensure` comes after every `rescue` of its block"));
2971 }
2972 Some(cleanup)
2973 } else {
2974 None
2975 };
2976 let whole = self.span_since(head.start);
2977 let protected = if clauses.is_empty() {
2978 match catch_all {
2979 None => body,
2980 Some((binder, handler, kw)) => list_at(
2981 whole,
2982 vec![
2983 sym_at(head, "try"),
2984 body,
2985 list_at(
2986 kw.merge(handler.span),
2987 vec![
2988 sym_at(kw, "catch"),
2989 list_at(binder.span, vec![binder]),
2990 handler,
2991 ],
2992 ),
2993 ],
2994 ),
2995 }
2996 } else {
2997 let first_kw = clauses[0].span;
2998 let otherwise = match catch_all {
2999 Some((binder, handler, kw)) => {
3000 let bound = let_bound(binder, handler, kw);
3001 list_at(kw.merge(bound.span), vec![sym_at(kw, "else"), bound])
3002 }
3003 None => list_at(
3004 head,
3005 vec![
3006 sym_at(head, "else"),
3007 list_at(
3008 head,
3009 vec![sym_at(head, LOWERED_RERAISE), sym_at(head, RESCUED)],
3010 ),
3011 ],
3012 ),
3013 };
3014 let mut cond = vec![sym_at(first_kw, "cond")];
3015 cond.extend(clauses);
3016 cond.push(otherwise);
3017 let cond_span = cover(&cond, first_kw);
3018 list_at(
3019 whole,
3020 vec![
3021 sym_at(head, "try"),
3022 body,
3023 list_at(
3024 cond_span,
3025 vec![
3026 sym_at(first_kw, "catch"),
3027 list_at(first_kw, vec![sym_at(first_kw, RESCUED)]),
3028 list_at(cond_span, cond),
3029 ],
3030 ),
3031 ],
3032 )
3033 };
3034 Ok(match ensure {
3035 None => protected,
3036 Some(cleanup) => {
3037 let attempted = list_at(
3038 protected.span,
3039 vec![sym_at(head, LOWERED_ATTEMPTED), protected],
3040 );
3041 let failed = list_at(
3042 head,
3043 vec![sym_at(head, LOWERED_FAILED), sym_at(head, RESCUED)],
3044 );
3045 let caught = list_at(
3046 whole,
3047 vec![
3048 sym_at(head, "try"),
3049 attempted,
3050 list_at(
3051 head,
3052 vec![
3053 sym_at(head, "catch"),
3054 list_at(head, vec![sym_at(head, RESCUED)]),
3055 failed,
3056 ],
3057 ),
3058 ],
3059 );
3060 list_at(
3061 whole,
3062 vec![sym_at(head, LOWERED_ENSURE), caught, thunk(cleanup, head)],
3063 )
3064 }
3065 })
3066 }
3067
3068 fn def_form(&mut self, head: Span) -> Result<Spanned, Fail> {
3069 let name_span = self.peek_span();
3070 let name = match self.bump() {
3071 TokenKind::Ident(n) => n,
3072 other => {
3073 return Err(fail(
3074 format!("expected a name after `def`, found {other}"),
3075 name_span,
3076 ))
3077 }
3078 };
3079 self.not_reserved(&name, name_span, "a `def` name")?;
3080 let mut params: Vec<(String, Span, Option<Spanned>)> = Vec::new();
3081 let mut optional_from: Option<usize> = None;
3082 let mut rest: Vec<Spanned> = Vec::new();
3083 if !self.at(&TokenKind::LParen) {
3084 self.note(
3085 Spelling::ParameterList,
3086 name_span,
3087 &format!("def {name}"),
3088 &format!("def {name}()"),
3089 );
3090 }
3091 if self.at(&TokenKind::LParen) {
3092 self.bump();
3093 self.skip_newlines();
3094 if !self.eat(&TokenKind::RParen) {
3095 loop {
3096 self.skip_newlines();
3097 let p_span = self.peek_span();
3098 if self.at_star() {
3099 if optional_from.is_some() {
3100 return Err(fail(
3101 "a rest parameter cannot follow an optional one",
3102 p_span,
3103 ));
3104 }
3105 rest = self.rest_parameter()?.to_vec();
3106 self.rest_is_last()?;
3107 self.expect(&TokenKind::RParen, "`)` after the rest parameter")?;
3108 break;
3109 }
3110 match self.bump() {
3111 TokenKind::Ident(p) => {
3113 self.not_reserved(&p, p_span, "a parameter name")?;
3114 let p =
3115 self.optional_mark(p, p_span, &mut optional_from, params.len())?;
3116 params.push((p, p_span, None));
3117 }
3118 TokenKind::Label(p) => {
3120 self.not_reserved(&p, p_span, "a parameter name")?;
3121 let p =
3122 self.optional_mark(p, p_span, &mut optional_from, params.len())?;
3123 self.skip_newlines();
3124 let ty = self.type_expr()?;
3125 params.push((p, p_span, Some(ty)));
3126 }
3127 other => {
3128 return Err(fail(
3129 format!("expected a parameter name, found {other}"),
3130 p_span,
3131 ))
3132 }
3133 }
3134 self.skip_newlines();
3135 if self.eat(&TokenKind::Comma) {
3136 if self.trailing_comma(&TokenKind::RParen) {
3137 break;
3138 }
3139 continue;
3140 }
3141 self.expect(&TokenKind::RParen, "`,` or `)`")?;
3142 break;
3143 }
3144 }
3145 }
3146 let sig_span = self.span_since(name_span.start);
3148
3149 let ret = if matches!(self.peek(), TokenKind::Op(o) if o == "->") {
3151 self.bump();
3152 self.skip_newlines();
3153 Some(self.type_expr()?)
3154 } else {
3155 None
3156 };
3157
3158 let begins = {
3159 let save = self.pos;
3160 self.skip_newlines();
3161 let at = self.at_ident("begin").then(|| self.peek_span());
3162 self.pos = save;
3163 at
3164 };
3165 let body = self.protected_body(head)?;
3166 if let Some(b) = begins {
3167 let protects = matches!(&body.form, SpannedForm::List(items)
3168 if matches!(items.first().and_then(Spanned::as_symbol), Some("try") | Some(LOWERED_ENSURE)));
3169 if protects && body.span.start == b.start {
3170 self.note(
3171 Spelling::TryCatch,
3172 b,
3173 "begin … end as a whole def body",
3174 "the clauses on the def itself",
3175 );
3176 }
3177 }
3178 self.expect_ident("end")?;
3179 let whole = self.span_since(head.start);
3180
3181 let annotated = ret.is_some() || params.iter().any(|(_, _, t)| t.is_some());
3182 if !annotated {
3183 let mut sig = vec![sym_at(name_span, &name)];
3184 for (i, (p, s, _)) in params.into_iter().enumerate() {
3185 if optional_from == Some(i) {
3186 sig.push(sym_at(s, OPTIONAL_MARKER));
3187 }
3188 sig.push(sym_at(s, &p));
3189 }
3190 sig.extend(rest);
3191 return Ok(list_at(
3192 whole,
3193 vec![sym_at(head, "define"), list_at(sig_span, sig), body],
3194 ));
3195 }
3196
3197 let mut sig = vec![sym_at(name_span, &name)];
3205 for (i, (p, s, t)) in params.into_iter().enumerate() {
3206 if optional_from == Some(i) {
3207 sig.push(sym_at(s, OPTIONAL_MARKER));
3208 }
3209 let ty = t.unwrap_or_else(|| sym_at(s, "dyn"));
3210 sig.push(list_at(s.merge(ty.span), vec![sym_at(s, &p), ty]));
3211 }
3212 sig.extend(rest);
3213 Ok(list_at(
3214 whole,
3215 vec![
3216 sym_at(head, "define-typed"),
3217 list_at(sig_span, sig),
3218 ret.unwrap_or_else(|| sym_at(head, "dyn")),
3219 body,
3220 ],
3221 ))
3222 }
3223
3224 fn type_expr(&mut self) -> Result<Spanned, Fail> {
3226 let name_span = self.peek_span();
3227 let name = match self.bump() {
3228 TokenKind::Ident(n) => n,
3229 other => {
3230 return Err(fail(
3231 format!("expected a type name, found {other}"),
3232 name_span,
3233 ))
3234 }
3235 };
3236 self.not_reserved(&name, name_span, "a type name")?;
3237 if self.at(&TokenKind::LParen) {
3238 self.bump();
3239 self.skip_newlines();
3240 let mut list = vec![sym_at(name_span, &name)];
3241 if !self.eat(&TokenKind::RParen) {
3242 loop {
3243 self.skip_newlines();
3244 list.push(self.type_expr()?);
3245 self.skip_newlines();
3246 if self.eat(&TokenKind::Comma) {
3247 if self.trailing_comma(&TokenKind::RParen) {
3248 break;
3249 }
3250 continue;
3251 }
3252 self.expect(&TokenKind::RParen, "`,` or `)`")?;
3253 break;
3254 }
3255 }
3256 return Ok(list_at(self.span_since(name_span.start), list));
3257 }
3258 Ok(sym_at(name_span, &name))
3259 }
3260
3261 fn not_reserved(&self, name: &str, at: Span, what: &str) -> Result<(), Fail> {
3262 if is_reserved_word(name) {
3263 return Err(fail(
3264 format!("`{name}` is a reserved word and cannot be {what}"),
3265 at,
3266 ));
3267 }
3268 Ok(())
3269 }
3270
3271 fn eat_ident(&mut self, name: &str) -> bool {
3273 if self.at_ident(name) {
3274 self.bump();
3275 true
3276 } else {
3277 false
3278 }
3279 }
3280
3281 fn expect_ident(&mut self, name: &str) -> Result<(), Fail> {
3282 if self.at_ident(name) {
3283 self.bump();
3284 Ok(())
3285 } else {
3286 Err(self.error(format!("expected `{name}`, found {}", self.peek())))
3287 }
3288 }
3289
3290 fn body(&mut self, terminators: &[&str]) -> Result<Spanned, Fail> {
3293 let start = self.mark();
3296 let mut forms = Vec::new();
3297 loop {
3298 self.skip_newlines();
3299 if matches!(self.peek(), TokenKind::Eof) {
3300 return Err(self.error(format!(
3301 "unterminated block: expected {} before the end of the file",
3302 one_of(terminators)
3303 )));
3304 }
3305 if terminators.iter().any(|t| self.at_ident(t)) {
3306 break;
3307 }
3308 forms.push(self.statement()?);
3309 self.end_of_statement(terminators)?;
3310 }
3311 Ok(match forms.len() {
3312 0 => Spanned::new(Span::new(start, start), SpannedForm::Nil),
3313 1 => forms.into_iter().next().expect("checked len"),
3314 _ => {
3315 let span = cover(&forms, Span::new(start, start));
3321 let head = Span::new(span.start, span.start);
3327 let mut list = vec![sym_at(head, "begin")];
3328 list.extend(forms);
3329 list_at(span, list)
3330 }
3331 })
3332 }
3333}
3334
3335fn one_of(words: &[&str]) -> String {
3338 let quoted: Vec<String> = words.iter().map(|w| format!("`{w}`")).collect();
3339 match quoted.split_last() {
3340 None => String::new(),
3341 Some((last, [])) => last.clone(),
3342 Some((last, rest)) => format!("{} or {last}", rest.join(", ")),
3343 }
3344}
3345
3346fn let_bound(binder: Spanned, body: Spanned, at: Span) -> Spanned {
3347 let span = at.merge(body.span);
3348 let pair = list_at(binder.span, vec![binder, sym_at(at, RESCUED)]);
3349 list_at(
3350 span,
3351 vec![sym_at(at, "let"), list_at(pair.span, vec![pair]), body],
3352 )
3353}
3354
3355fn thunk(body: Spanned, at: Span) -> Spanned {
3356 let span = at.merge(body.span);
3357 list_at(
3358 span,
3359 vec![sym_at(at, "lambda"), list_at(at, Vec::new()), body],
3360 )
3361}
3362
3363#[must_use]
3368pub fn label_name(name: &str) -> bool {
3369 matches!(
3370 lex(&format!("{name}:")).as_deref(),
3371 Ok([t, e]) if matches!(&t.kind, TokenKind::Label(n) if n == name) && e.kind == TokenKind::Eof
3372 )
3373}
3374
3375fn blue_lang_name(name: &str) -> bool {
3376 matches!(
3377 lex(name).as_deref(),
3378 Ok([t, e]) if matches!(&t.kind, TokenKind::Ident(n) if n == name) && e.kind == TokenKind::Eof
3379 ) && !name.ends_with(['?', '!'])
3380}
3381
3382fn mentions(form: &Spanned, name: &str) -> bool {
3383 match &form.form {
3384 SpannedForm::Atom(Atom::Symbol(s)) => s == name,
3385 SpannedForm::List(items) => items.iter().any(|i| mentions(i, name)),
3386 SpannedForm::Quote(i)
3387 | SpannedForm::Quasiquote(i)
3388 | SpannedForm::Unquote(i)
3389 | SpannedForm::UnquoteSplice(i) => mentions(i, name),
3390 _ => false,
3391 }
3392}
3393
3394fn sym_at(span: Span, s: &str) -> Spanned {
3396 Spanned::new(span, SpannedForm::Atom(Atom::Symbol(s.to_string())))
3397}
3398
3399fn atom_at(span: Span, a: Atom) -> Spanned {
3401 Spanned::new(span, SpannedForm::Atom(a))
3402}
3403
3404fn list_at(span: Span, items: Vec<Spanned>) -> Spanned {
3406 Spanned::new(span, SpannedForm::List(items))
3407}
3408
3409fn cover(items: &[Spanned], fallback: Span) -> Span {
3417 let merged = items
3418 .iter()
3419 .fold(Span::synthetic(), |acc, s| acc.merge(s.span));
3420 if merged.is_synthetic() {
3421 fallback
3422 } else {
3423 merged
3424 }
3425}
3426
3427#[cfg(test)]
3428mod tests {
3429 use super::*;
3430
3431 fn q(src: &str) -> String {
3435 parse_expr(src)
3436 .map(|s| s.to_string())
3437 .unwrap_or_else(|e| panic!("{src:?}: {e}"))
3438 }
3439
3440 #[test]
3441 fn a_reserved_word_is_not_a_def_name_a_parameter_or_a_type() {
3442 for src in [
3443 "def def a(x)\n x\nend",
3444 "def end()\n 1\nend",
3445 "def f(end)\n 1\nend",
3446 "def f(a: not)\n 1\nend",
3447 "def f(a) -> not a\n a\nend",
3448 "def f(a: List(1 + 2))\n a\nend",
3449 "defmacro defmacro m(a)\n quote\n a\n end\nend",
3450 ] {
3451 assert!(parse_program(src).is_err(), "{src:?} parsed");
3452 }
3453 }
3454
3455 #[test]
3456 fn a_type_application_takes_types_of_any_arity() {
3457 assert_eq!(
3458 q("def f(a: Map(Str, List(Int))) -> Unit()\n a\nend"),
3459 "(define-typed (f (a (Map Str (List Int)))) (Unit) a)"
3460 );
3461 }
3462
3463 #[test]
3466 fn arithmetic_respects_precedence() {
3467 assert_eq!(q("1 + 2 * 3"), "(+ 1 (* 2 3))");
3468 assert_eq!(q("(1 + 2) * 3"), "(* (+ 1 2) 3)");
3469 }
3470
3471 #[test]
3472 fn comparison_binds_looser_than_arithmetic() {
3473 assert_eq!(q("a + 1 < b"), "(< (+ a 1) b)");
3474 }
3475
3476 #[test]
3481 fn logical_operators_bind_loosest_and_lower_to_tataras_names() {
3482 assert_eq!(q("a && b || c"), "(or (and a b) c)");
3483 }
3484
3485 #[test]
3486 fn left_associativity() {
3487 assert_eq!(q("1 - 2 - 3"), "(- (- 1 2) 3)");
3488 }
3489
3490 #[test]
3491 fn a_dot_without_parentheses_reads_a_field() {
3492 assert_eq!(q("user.name"), "(blue-field user :name)");
3493 }
3494
3495 #[test]
3496 fn method_call_with_args() {
3497 assert_eq!(q("user.greet(1, 2)"), "(greet user 1 2)");
3498 }
3499
3500 #[test]
3501 fn chained_fields_read_left_to_right() {
3502 assert_eq!(q("a.b.c"), "(blue-field (blue-field a :b) :c)");
3503 assert_eq!(q("a.b(1).c"), "(blue-field (b a 1) :c)");
3504 }
3505
3506 #[test]
3507 fn plain_call() {
3508 assert_eq!(q("f(1, 2)"), "(f 1 2)");
3509 }
3510
3511 #[test]
3514 fn pipeline_threads_into_first_argument() {
3515 assert_eq!(q("x |> f"), "(f x)");
3516 assert_eq!(q("x |> f(1)"), "(f x 1)");
3517 assert_eq!(q("x |> f |> g"), "(g (f x))");
3518 }
3519
3520 #[test]
3521 fn pipeline_binds_looser_than_arithmetic() {
3522 assert_eq!(q("1 + 2 |> f"), "(f (+ 1 2))");
3523 }
3524
3525 #[test]
3531 fn label_and_rocket_produce_the_same_tree_for_a_symbol_key() {
3532 assert_eq!(q("{a: 1}"), q("{:a => 1}"));
3533 assert_eq!(q("{a: 1}"), "(hash-map :a 1)");
3534 }
3535
3536 #[test]
3539 fn a_string_key_has_no_shorthand() {
3540 assert_eq!(q(r#"{"k" => 1}"#), r#"(hash-map "k" 1)"#);
3541 }
3542
3543 #[test]
3544 fn a_newline_escape_and_its_lines_are_one_tree() {
3545 for (escaped, lines) in [
3546 (r#""a\nb""#, r#"join_lines(["a", "b"])"#),
3547 (r##""#{m}\n#{h}""##, "join_lines([m, h])"),
3548 (r##""n=#{n}\ndone\n""##, r##"unlines(["n=#{n}", "done"])"##),
3549 (r#""\n""#, "line_break"),
3550 (r#""a\n\n\tb\n""#, r#"unlines(["a", "", "\tb"])"#),
3551 (r#""\na""#, r#"join_lines(["", "a"])"#),
3552 (r#""\n\n""#, r#"unlines(["", ""])"#),
3553 ] {
3554 assert_eq!(q(escaped), q(lines), "{escaped}");
3555 }
3556 assert_eq!(q(r#""a\tb""#), q(r#""a\tb""#));
3557 assert!(!q(r#""a\tb""#).contains("join_lines"));
3558 }
3559
3560 #[test]
3561 fn a_newline_escape_is_noted_and_a_quoted_one_is_data() {
3562 let notes = spelling_notes(r#""a\nb""#).unwrap();
3563 assert_eq!(notes.len(), 1);
3564 assert_eq!(notes[0].spelling, Spelling::NewlineEscape);
3565 assert!(spelling_notes(r#""a\tb""#).unwrap().is_empty());
3566 let quoted = q("def f()\n quote\n \"x\\ny\"\n end\nend\n");
3567 assert!(!quoted.contains("join_lines"), "{quoted}");
3568 assert!(
3569 spelling_notes("def f()\n quote\n \"x\\ny\"\n end\nend\n")
3570 .unwrap()
3571 .is_empty()
3572 );
3573 }
3574
3575 #[test]
3576 fn list_literal() {
3577 assert_eq!(q("[1, 2, 3]"), "(list 1 2 3)");
3578 assert_eq!(q("[]"), "(list)");
3579 }
3580
3581 #[test]
3584 fn if_else_end() {
3585 assert_eq!(q("if a\n 1\nelse\n 2\nend"), "(if a 1 2)");
3586 }
3587
3588 #[test]
3589 fn if_without_else() {
3590 assert_eq!(q("if a\n 1\nend"), "(if a 1)");
3591 }
3592
3593 #[test]
3596 fn unless_is_a_negated_if() {
3597 assert_eq!(q("unless a\n 1\nend"), "(if (not a) 1)");
3598 }
3599
3600 #[test]
3601 fn multi_statement_body_becomes_begin() {
3602 assert_eq!(q("if a\n 1\n 2\nend"), "(if a (begin 1 2))");
3603 }
3604
3605 #[test]
3606 fn def_lowers_to_define() {
3607 assert_eq!(
3608 q("def add(a, b)\n a + b\nend"),
3609 "(define (add a b) (+ a b))"
3610 );
3611 }
3612
3613 #[test]
3614 fn def_with_no_params() {
3615 assert_eq!(q("def zero()\n 0\nend"), "(define (zero) 0)");
3616 }
3617
3618 fn refused(src: &str) -> String {
3619 match parse_expr(src) {
3620 Ok(s) => panic!("{src:?} should be refused, parsed to {s}"),
3621 Err(e) => e.to_string(),
3622 }
3623 }
3624
3625 #[test]
3626 fn a_question_mark_parameter_is_optional() {
3627 assert_eq!(q("def f(a, b?)\n b\nend"), "(define (f a &optional b) b)");
3628 assert_eq!(q("def f(a?, b?)\n a\nend"), "(define (f &optional a b) a)");
3629 assert_eq!(q("def f(a?)\n a\nend"), "(define (f &optional a) a)");
3630 }
3631
3632 #[test]
3633 fn an_annotated_optional_parameter_keeps_its_type_after_the_marker() {
3634 assert_eq!(
3635 q("def f(a: Int, b?: Str) -> Int\n a\nend"),
3636 "(define-typed (f (a Int) &optional (b Str)) Int a)"
3637 );
3638 assert_eq!(
3639 q("def f(a, b?: Str)\n a\nend"),
3640 "(define-typed (f (a dyn) &optional (b Str)) dyn a)"
3641 );
3642 }
3643
3644 #[test]
3645 fn a_def_without_question_marks_carries_no_marker() {
3646 assert_eq!(q("def f(a, b)\n a\nend"), "(define (f a b) a)");
3647 assert_eq!(q("def f(a, *r)\n a\nend"), "(define (f a &rest r) a)");
3648 }
3649
3650 #[test]
3651 fn a_required_parameter_cannot_follow_an_optional_one() {
3652 assert!(refused("def f(a?, b)\n a\nend").contains("`b` cannot follow an optional"));
3653 assert!(refused("def f(a, b?, c: Int)\n a\nend").contains("`c` cannot follow"));
3654 }
3655
3656 #[test]
3657 fn a_rest_parameter_cannot_follow_an_optional_one() {
3658 assert!(refused("def f(a, b?, *r)\n a\nend").contains("rest parameter cannot follow"));
3659 }
3660
3661 #[test]
3664 fn literals_lower_to_atoms() {
3665 assert_eq!(q("42"), "42");
3666 assert_eq!(q("true"), "#t");
3667 assert_eq!(q(":ok"), ":ok");
3668 assert_eq!(q(r#""hi""#), r#""hi""#);
3669 }
3670
3671 #[test]
3672 fn unary_minus_and_not() {
3673 assert_eq!(q("-x"), "(- 0 x)");
3674 assert_eq!(q("!x"), "(not x)");
3675 }
3676
3677 #[test]
3680 fn a_program_is_a_sequence_of_forms() {
3681 let forms = parse_program("def f()\n 1\nend\nf()").expect("parse");
3682 assert_eq!(forms.len(), 2);
3683 assert_eq!(forms[1].to_string(), "(f)");
3684 }
3685
3686 #[test]
3687 fn unterminated_block_is_an_error_naming_what_was_expected() {
3688 let e = parse_program("if a\n 1").expect_err("must fail");
3689 assert!(e.message.contains("unterminated"), "{}", e.message);
3690 }
3691
3692 #[test]
3693 fn a_parse_error_carries_a_span_into_the_source() {
3694 let src = "1 + )";
3695 let e = parse_program(src).expect_err("must fail");
3696 assert!(e.span.start < src.len(), "span {:?} outside source", e.span);
3697 }
3698
3699 #[test]
3706 fn a_spaced_index_is_refused_naming_the_touching_one() {
3707 let e = parse_program("y = xs [0]").expect_err("must not split into two statements");
3708 assert!(e.message.contains("`xs[0]`"), "{}", e.message);
3709 assert_eq!((e.line, e.col), (1, 8), "{e}");
3710 }
3711
3712 #[test]
3713 fn an_index_is_the_tree_of_get_or_nth() {
3714 for (short, long) in [
3715 ("m[:k]", "get(m, :k)"),
3716 (r#"m["k"]"#, r#"get(m, "k")"#),
3717 ("xs[0]", "nth(0, xs)"),
3718 ("xs[i + 1]", "nth(i + 1, xs)"),
3719 ("f(x)[2]", "nth(2, f(x))"),
3720 ("m[:a][:b]", "get(get(m, :a), :b)"),
3721 ("xs[0][:k]", "get(nth(0, xs), :k)"),
3722 ("[1, 2][1]", "nth(1, [1, 2])"),
3723 ] {
3724 assert_eq!(q(short), q(long), "{short}");
3725 }
3726 assert_eq!(q("get(m, k)"), "(get m k)");
3727 let notes = spelling_notes("get(m, :k)\nnth(0, xs)\nget(m, k)\n").unwrap();
3728 assert_eq!(
3729 notes
3730 .iter()
3731 .filter(|n| n.spelling == Spelling::Index)
3732 .count(),
3733 2
3734 );
3735 }
3736
3737 #[test]
3739 fn a_call_without_parentheses_is_refused_naming_the_call() {
3740 let e = parse_program("a = f x").expect_err("must not split into two statements");
3741 assert!(e.message.contains("`f(x)`"), "{}", e.message);
3742 assert_eq!((e.line, e.col), (1, 7), "{e}");
3743 }
3744
3745 #[test]
3747 fn two_statements_on_one_line_are_refused_in_a_body() {
3748 for src in [
3749 "def f(xs)\n xs [0]\nend",
3750 "if a\n f x\nend",
3751 "fn(x) x 1 end",
3752 "test \"t\"\n assert a b\nend",
3753 "1 2",
3754 ] {
3755 assert!(parse_program(src).is_err(), "{src:?} must not parse");
3756 }
3757 }
3758
3759 #[test]
3761 fn an_arrow_lambda_lowers_to_the_same_tree_as_fn() {
3762 for (arrow, block) in [
3763 ("(x) -> x * 2", "fn(x) x * 2 end"),
3764 ("(a, b) -> a + b", "fn(a, b) a + b end"),
3765 ("() -> 1", "fn() 1 end"),
3766 ("map(xs, (x) -> x + 1)", "map(xs, fn(x) x + 1 end)"),
3767 ] {
3768 let a = parse_program(arrow).expect(arrow);
3769 let b = parse_program(block).expect(block);
3770 assert_eq!(format!("{a:?}"), format!("{b:?}"), "{arrow}");
3771 }
3772 assert_eq!(
3773 parse_program("(x)").map(|p| p.len()).ok(),
3774 Some(1),
3775 "a parenthesised name is still a group"
3776 );
3777 }
3778
3779 #[test]
3783 fn a_statement_may_end_at_a_newline_the_end_or_a_closing_keyword() {
3784 for src in [
3785 "fn(x) x + 1 end",
3786 "map(fn(x) x end, xs)",
3787 "if a\n 1\nelse\n 2\nend",
3788 "if a 1 else 2 end",
3789 "case x\nwhen 1 :a\nwhen 2 :b\nelse :c\nend",
3790 "x = 1\ny = 2",
3791 "x = 1",
3792 ] {
3793 assert!(
3794 parse_program(src).is_ok(),
3795 "{src:?}: {:?}",
3796 parse_program(src)
3797 );
3798 }
3799 }
3800
3801 #[test]
3803 fn then_is_refused_by_name() {
3804 let e = parse_program("x = if c then 1 else 2 end").expect_err("then is not a keyword");
3805 assert!(e.message.contains("`then`"), "{}", e.message);
3806 }
3807
3808 #[test]
3810 fn a_brace_block_is_refused_naming_fn() {
3811 let e = parse_program("xs.map { |x| x }").expect_err("no brace blocks");
3812 assert!(e.message.contains("fn(x)"), "{}", e.message);
3813 }
3814
3815 #[test]
3822 fn a_qualified_name_is_one_symbol() {
3823 assert_eq!(q("retsu::first(xs)"), "(retsu/first xs)");
3824 assert_eq!(q("retsu::first"), "retsu/first");
3825 assert_eq!(q("xs |> retsu::first"), "(retsu/first xs)");
3826 assert_eq!(q("blue::length(xs)"), "(blue/length xs)");
3827 }
3828
3829 #[test]
3831 fn whitespace_around_the_qualifier_is_accepted() {
3832 assert_eq!(q("retsu :: first(xs)"), "(retsu/first xs)");
3833 }
3834
3835 #[test]
3838 fn a_reserved_word_after_the_qualifier_is_a_name() {
3839 assert_eq!(q("kueri::if(a, b)"), "(kueri/if a b)");
3840 assert_eq!(q("heni::test(x)"), "(heni/test x)");
3841 assert_eq!(q("sabi::fn"), "sabi/fn");
3842 }
3843
3844 #[test]
3845 fn a_qualified_name_has_exactly_one_qualifier() {
3846 for (src, says) in [
3847 ("::first(xs)", "`package::name`"),
3848 ("a::b::c", "one qualifier"),
3849 ("if::x", "reserved word"),
3850 ("retsu::1", "expected a name after `retsu::`"),
3851 ] {
3852 let e = parse_program(src).expect_err(src);
3853 assert!(e.message.contains(says), "{src}: {}", e.message);
3854 }
3855 }
3856
3857 #[test]
3860 fn qualified_reads_only_the_package_name_shape() {
3861 assert_eq!(qualified("retsu/first"), Some(("retsu", "first")));
3862 assert_eq!(qualified("kueri/if"), Some(("kueri", "if")));
3863 assert_eq!(qualified("nisshi/pair?"), Some(("nisshi", "pair?")));
3864 for not in ["/", "a/", "/a", "a/b/c", "if/x", "hash-map", "a"] {
3865 assert_eq!(qualified(not), None, "{not}");
3866 }
3867 assert!(is_callable_name("kueri/if"));
3868 }
3869
3870 #[test]
3875 fn a_symbol_may_end_in_a_question_or_bang() {
3876 assert_eq!(q("[:pair?, :set!]"), "(list :pair? :set!)");
3877 assert_eq!(q("use(\"n\", [:empty?])"), "(use \"n\" (list :empty?))");
3878 assert_eq!(q(":a != :b"), "(not= :a :b)");
3879 }
3880
3881 #[test]
3885 fn a_parse_error_displays_line_col_and_the_source_text() {
3886 let e = parse_program("x = 1\ny = [1, 2 y]").expect_err("must fail");
3887 assert_eq!(e.to_string(), "2:11: expected `,` or `]`, found `y`");
3888 assert_eq!(e.span, Span::new(16, 17), "the span stays, as data");
3889 }
3890
3891 #[test]
3893 fn no_error_message_names_a_rust_variant() {
3894 for src in [
3895 "f(1 2)",
3896 "{a: 1 b}",
3897 "def 1\nend",
3898 "x.1",
3899 "if a\n",
3900 "fn(1) 2 end",
3901 "[1 :b]",
3902 "1 +",
3903 "(1",
3904 ] {
3905 let e = parse_program(src).expect_err(src);
3906 let shown = e.to_string();
3907 for tell in [
3908 "Ident(", "Int(", "Op(", "Label(", "Sym(", "Str(", "LParen", "RBracket", "Eof",
3909 "Newline", "..",
3910 ] {
3911 assert!(!shown.contains(tell), "{src:?} -> {shown}");
3912 }
3913 assert!(e.line >= 1 && e.col >= 1, "{src:?} not located: {shown}");
3914 }
3915 }
3916
3917 #[test]
3919 fn a_lex_error_is_located_too() {
3920 let e = parse_program("x = 1\ny = 2; z = 3").expect_err("no semicolons");
3921 assert_eq!((e.line, e.col), (2, 6), "{e}");
3922 assert!(e.message.contains("new line"), "{e}");
3923 }
3924
3925 #[test]
3928 fn the_parser_rejects_garbage() {
3929 assert!(parse_program("def").is_err());
3930 assert!(parse_program("(1").is_err());
3931 assert!(parse_program("end").is_err());
3932 }
3933}