1use std::collections::HashMap;
38use std::sync::{Arc, OnceLock};
39
40use crate::form::{sym_text, Form, List};
41use crate::formula_syntax;
42use crate::intrinsics::{fold, is_numeric_literal_text, str_, val};
43use crate::messages::{raise, Refusal};
44use crate::printer::write_pretty;
45use crate::reader::{count_lf, parse_all, src_line_tag, tokenize, FORM_DEPTH_LIMIT};
46use crate::refers::letters_to_col;
47use crate::sheet::{column_letters, MAX_COLUMN, MAX_ROW};
48
49#[derive(Clone, Debug)]
51pub struct Macro {
52 pub params: Vec<String>,
54 pub rest_name: String,
56 pub template: Form,
57 pub doc: String,
59 pub name: String,
61}
62
63const RESERVED: &[(&str, &str)] = &[
66 ("quote", "data-literal form"),
67 ("quasiquote", "template-shielding form"),
68 ("unquote", "quasiquote escape form"),
69 ("unquote-splicing", "quasiquote splice form"),
70 ("symbol", "identifier-fusion primitive"),
71 ("car", "list-head primitive"),
72 ("cdr", "list-tail primitive"),
73 ("cddr", "list-tail primitive"),
74 ("cons", "list-construction primitive"),
75 ("list", "list-construction primitive"),
76 ("null?", "empty-list primitive"),
77 ("eq?", "atom-comparison primitive"),
78 ("equal?", "structural-comparison primitive"),
79 ("quote-if", "expand-time conditional"),
80 (
81 "cond",
82 "multi-clause conditional (folds at expand time when possible, defers to runtime otherwise)",
83 ),
84 ("+expand", "expand-time addition primitive"),
85 ("=expand", "expand-time equality primitive"),
86 ("<>expand", "expand-time inequality primitive"),
87 (">expand", "expand-time comparison primitive"),
88 ("<expand", "expand-time comparison primitive"),
89 (">=expand", "expand-time comparison primitive"),
90 ("<=expand", "expand-time comparison primitive"),
91 ("-expand", "expand-time subtraction primitive"),
93 ("*expand", "expand-time multiplication primitive"),
94 ("quotient-expand", "expand-time integer quotient primitive"),
95 ("mod-expand", "expand-time remainder primitive"),
96 ("&expand", "expand-time text-joining primitive"),
97 ("characters-expand", "expand-time text-splitting primitive"),
98 ("address-expand", "expand-time address primitive"),
99 ("row-expand", "expand-time row-number primitive"),
100 ("column-expand", "expand-time column-number primitive"),
101 ("column-letters-expand", "expand-time column-letters primitive"),
102 ("formula-expand", "expand-time formula-syntax operation"),
104];
105
106const LISTOPS: &[&str] = &[
110 "car",
111 "cdr",
112 "cddr",
113 "cons",
114 "list",
115 "null?",
116 "eq?",
117 "equal?",
118 "quote-if",
119 "cond",
120 "+expand",
121 "=expand",
122 "<>expand",
123 ">expand",
124 "<expand",
125 ">=expand",
126 "<=expand",
127 "-expand",
128 "*expand",
129 "quotient-expand",
130 "mod-expand",
131 "&expand",
132 "characters-expand",
133 "address-expand",
134 "row-expand",
135 "column-expand",
136 "column-letters-expand",
137];
138
139fn is_listops(head_fold: &str) -> bool {
140 LISTOPS.contains(&head_fold)
141}
142
143#[derive(Clone, Debug)]
147enum Node {
148 Sym(Arc<str>),
149 Str(Arc<str>),
150 List(Seq),
151}
152
153#[derive(Clone, Debug)]
160struct Seq {
161 run: Arc<Run>,
162 start: usize,
163 line: u32,
164 forms: u32,
165 depth: u32,
166}
167
168#[derive(Debug)]
170struct Run {
171 items: Vec<Node>,
172 deepest: OnceLock<Vec<u32>>,
176}
177
178impl Run {
179 fn deepest_from(&self, k: usize) -> u32 {
180 let deepest = self.deepest.get_or_init(|| {
181 let mut v = vec![0; self.items.len() + 1];
182 for i in (0..self.items.len()).rev() {
183 v[i] = v[i + 1].max(self.items[i].weight().1);
184 }
185 v
186 });
187 deepest[k]
188 }
189}
190
191impl Seq {
192 fn new(items: Vec<Node>, line: u32) -> Seq {
193 let (mut forms, mut deepest) = (1u32, 0u32);
194 for item in &items {
195 let (f, d) = item.weight();
196 forms = forms.saturating_add(f);
197 deepest = deepest.max(d);
198 }
199 Seq {
200 run: Arc::new(Run {
201 items,
202 deepest: OnceLock::new(),
203 }),
204 start: 0,
205 line,
206 forms,
207 depth: deepest.saturating_add(1),
208 }
209 }
210
211 fn items(&self) -> &[Node] {
212 &self.run.items[self.start..]
213 }
214
215 fn count(&self) -> usize {
216 self.run.items.len() - self.start
217 }
218
219 fn tail(&self, n: usize) -> Seq {
224 let passed = self.items()[..n]
225 .iter()
226 .fold(0u32, |sum, item| sum.saturating_add(item.weight().0));
227 let start = self.start + n;
228 Seq {
229 run: Arc::clone(&self.run),
230 start,
231 line: 0,
232 forms: if self.forms == u32::MAX {
233 u32::MAX
234 } else {
235 self.forms - passed
236 },
237 depth: self.run.deepest_from(start).saturating_add(1),
238 }
239 }
240}
241
242impl Node {
243 fn sym(text: &str) -> Node {
244 Node::Sym(Arc::from(text))
245 }
246
247 fn list(items: Vec<Node>) -> Node {
249 Node::List(Seq::new(items, 0))
250 }
251
252 fn of(f: &Form) -> Node {
254 match f {
255 Form::Sym(s) => Node::Sym(Arc::from(s.as_str())),
256 Form::Str(s) => Node::Str(Arc::from(s.as_str())),
257 Form::List(l) => Node::List(Seq::new(l.items.iter().map(Node::of).collect(), l.line)),
258 }
259 }
260
261 fn form(&self) -> Form {
264 match self {
265 Node::Sym(s) => Form::Sym(s.to_string()),
266 Node::Str(s) => Form::Str(s.to_string()),
267 Node::List(l) => Form::List(List::new(
268 l.items().iter().map(Node::form).collect(),
269 l.line,
270 )),
271 }
272 }
273
274 fn weight(&self) -> (u32, u32) {
277 match self {
278 Node::List(l) => (l.forms, l.depth),
279 _ => (1, 0),
280 }
281 }
282
283 fn is_list(&self) -> bool {
284 matches!(self, Node::List(_))
285 }
286
287 fn atom_text(&self) -> Option<String> {
290 match self {
291 Node::Sym(s) => Some(s.to_string()),
292 Node::Str(s) => Some(format!("\"{s}")),
293 Node::List(_) => None,
294 }
295 }
296
297 fn head_is(&self, name: &str) -> bool {
300 match self {
301 Node::List(l) => {
302 matches!(l.items().first(), Some(Node::Sym(s)) if fold(s) == fold(name))
303 }
304 _ => false,
305 }
306 }
307}
308
309fn nth(items: &[Node], i: usize) -> Result<&Node, Refusal> {
312 if i == 0 || i > items.len() {
313 return Err(raise("vla-form-missing-element", &[("n", &i.to_string())]));
314 }
315 Ok(&items[i - 1])
316}
317
318fn nth_list(items: &[Node], i: usize) -> Result<&Seq, Refusal> {
321 match nth(items, i)? {
322 Node::List(l) => Ok(l),
323 other => Err(raise(
324 "vla-expected-list-at-position",
325 &[
326 ("n", &i.to_string()),
327 ("value", &other.atom_text().unwrap_or_default()),
328 ],
329 )),
330 }
331}
332
333#[derive(Default)]
337struct Env<'r> {
338 bindings: HashMap<&'r str, Node>,
339 rest_name: &'r str,
340 rest_items: Vec<Node>,
341}
342
343struct Rule {
346 params: Vec<String>,
347 rest_name: String,
348 template: Node,
349}
350
351#[derive(Default)]
354pub struct Expander {
355 macros: HashMap<String, Macro>,
356 rules: HashMap<String, Rule>,
358 pub budget_on: bool,
360 pub expand_budget: u64,
361 pub expand_fired: u64,
363 formula_open: bool,
366}
367
368pub const EXPANSION_SIZE_LIMIT: usize = 1_000_000;
379
380impl Expander {
381 pub fn new() -> Expander {
382 Expander::default()
383 }
384
385 pub fn get_macro(&self, name: &str) -> Option<&Macro> {
387 self.macros.get(&fold(name))
388 }
389
390 pub fn macro_doc(&self, name: &str) -> String {
392 self.get_macro(name)
393 .map(|m| m.doc.clone())
394 .unwrap_or_default()
395 }
396
397 pub fn define_macro(&mut self, f: &List) -> Result<(), Refusal> {
400 let sig = f.nth_list(2)?;
401 let mname = sig.head_sym()?;
402 let mfold = fold(&mname);
403 if let Some((word, desc)) = RESERVED.iter().find(|(w, _)| *w == mfold) {
404 return Err(raise(
405 "vla-defmacro-reserved-name",
406 &[("word", word), ("desc", desc)],
407 ));
408 }
409
410 let mut params = Vec::new();
411 let mut rest_name = String::new();
412 let mut j = 2;
413 while j <= sig.count() {
414 let t = sym_text(sig.nth(j)?)?;
415 if t == "&" {
416 if j + 1 > sig.count() {
417 return Err(raise(
418 "vla-defmacro-rest-param-missing",
419 &[("name", &mname)],
420 ));
421 }
422 rest_name = sym_text(sig.nth(j + 1)?)?;
423 if j + 1 != sig.count() {
424 return Err(raise(
425 "vla-defmacro-rest-param-not-last",
426 &[("name", &mname)],
427 ));
428 }
429 break;
430 }
431 params.push(t);
432 j += 1;
433 }
434
435 let mut doc = String::new();
438 let mut doc_idx = 3;
439 if f.count() >= 4 {
440 if let Form::Str(s) = f.nth(3)? {
441 doc = s.clone();
442 doc_idx = 4;
443 }
444 }
445
446 let template = if f.count() == doc_idx {
447 f.nth(doc_idx)?.clone()
448 } else if f.count() > doc_idx {
449 let mut items = vec![Form::sym("begin")];
450 items.extend(f.items[doc_idx - 1..].iter().cloned());
451 Form::list(items)
452 } else {
453 return Err(raise("vla-defmacro-missing-template", &[("name", &mname)]));
454 };
455
456 self.rules.insert(
458 mfold.clone(),
459 Rule {
460 params: params.iter().map(|p| fold(p)).collect(),
461 rest_name: fold(&rest_name),
462 template: Node::of(&template),
463 },
464 );
465 self.macros.insert(
466 mfold,
467 Macro {
468 params,
469 rest_name,
470 template,
471 doc,
472 name: mname,
473 },
474 );
475 Ok(())
476 }
477
478 pub fn expand(&mut self, d: &Form, depth: u32) -> Result<Form, Refusal> {
480 self.expand_at(&Node::of(d), depth, 1)
481 }
482
483 fn expand_at(&mut self, d: &Node, depth: u32, walk: u32) -> Result<Form, Refusal> {
487 if let Node::List(lst) = d {
488 if walk > FORM_DEPTH_LIMIT {
489 return Err(too_deep(lst.line));
490 }
491 }
492 if depth > 200 {
493 return Err(raise("vla-macro-expansion-too-deep", &[]));
494 }
495 let mut cur = d.clone();
496 let mut chain_len: u32 = 0;
497 let mut pending_head: Vec<Node> = Vec::new();
498 loop {
499 let Node::List(lst) = &cur else {
500 return Ok(cur.form());
501 };
502 let items = lst.items();
503 let Some(Node::Sym(head)) = items.first() else {
504 break;
505 };
506 let head_fold = fold(head);
507 if head_fold == "quote" {
509 return Ok(cur.form());
510 }
511 if head_fold == "symbol" {
513 return Ok(fuse_symbol(items, &Env::default())?.form());
514 }
515 if head_fold == "formula-expand" {
517 return self.formula_expand(items, depth, walk);
518 }
519 if matches!(
520 head_fold.as_str(),
521 "quasiquote" | "unquote" | "unquote-splicing"
522 ) {
523 return Err(raise(
524 "vla-quasiquote-outside-template",
525 &[("head", &head_fold)],
526 ));
527 }
528 if is_listops(&head_fold) {
530 return Ok(eval_listops_prim(&head_fold, items, &Env::default())?.form());
531 }
532 if head_fold == "begin" && items.len() > 1 && lst.line == 0 {
533 pending_head.extend(items[1..items.len() - 1].iter().cloned());
537 chain_len += 1;
538 if chain_len > 5000 {
539 return Err(raise("vla-macro-self-expansion-limit", &[]));
540 }
541 let last = items[items.len() - 1].clone();
542 cur = last;
543 } else if head_fold == "begin" {
544 break;
545 } else {
546 let Some(rule) = self.rules.get(&head_fold) else {
547 break;
548 };
549 if self.budget_on && self.expand_fired >= self.expand_budget {
550 break; }
552 chain_len += 1;
553 if chain_len > 5000 {
554 return Err(raise("vla-macro-self-expansion-limit", &[]));
555 }
556 let r = expand_one(items, rule)?;
557 self.weigh(&r)?;
558 self.expand_fired += 1; cur = r;
560 }
561 }
562 let Node::List(cur_list) = &cur else {
565 unreachable!("the loop leaves only on a list");
566 };
567 let mut out = Vec::with_capacity(cur_list.count());
568 for e in cur_list.items() {
569 out.push(self.expand_at(e, depth, walk + 1)?);
570 }
571 let out = Form::List(List::new(out, cur_list.line));
572 if pending_head.is_empty() {
573 return Ok(out);
574 }
575 let mut final_out = vec![Form::sym("begin")];
576 for e in &pending_head {
577 final_out.push(self.expand_at(e, depth, walk + 1)?);
578 }
579 final_out.push(out);
580 Ok(Form::list(final_out))
581 }
582
583 fn formula_expand(&mut self, items: &[Node], depth: u32, walk: u32) -> Result<Form, Refusal> {
594 if items.len() != 2 {
595 return Err(raise(
596 "vla-formula-expand-arity",
597 &[("n", &(items.len() - 1).to_string())],
598 ));
599 }
600 if self.formula_open {
601 return Err(raise("vla-formula-expand-nested", &[]));
602 }
603 self.formula_open = true;
604 let expr = self.expand_at(&items[1], depth, walk + 1);
605 self.formula_open = false;
606 Ok(Form::Str(formula_syntax::write(&expr?)?))
607 }
608
609 pub fn expand_one_pass(&mut self, d: &Form) -> Result<Form, Refusal> {
612 self.expand_one_pass_at(&Node::of(d), 1)
613 }
614
615 fn expand_one_pass_at(&mut self, d: &Node, walk: u32) -> Result<Form, Refusal> {
618 let Node::List(lst) = d else {
619 return Ok(d.form());
620 };
621 if walk > FORM_DEPTH_LIMIT {
622 return Err(too_deep(lst.line));
623 }
624 let items = lst.items();
625 if let Some(Node::Sym(head)) = items.first() {
626 let head_fold = fold(head);
627 if head_fold == "quote" {
628 return Ok(d.form());
629 }
630 if let Some(rule) = self.rules.get(&head_fold) {
631 if !(self.budget_on && self.expand_fired >= self.expand_budget) {
632 let r = expand_one(items, rule)?;
633 self.weigh(&r)?;
634 self.expand_fired += 1;
635 return Ok(r.form());
636 }
637 }
638 }
639 let mut out = Vec::with_capacity(items.len());
640 for e in items {
641 out.push(self.expand_one_pass_at(e, walk + 1)?);
642 }
643 Ok(Form::List(List::new(out, lst.line)))
644 }
645
646 fn weigh(&self, r: &Node) -> Result<(), Refusal> {
658 let (forms, depth) = r.weight();
659 if forms as usize <= EXPANSION_SIZE_LIMIT && depth <= FORM_DEPTH_LIMIT {
660 return Ok(());
661 }
662 let mut forms = 0usize;
663 let mut stack = vec![(r, 1u32)];
664 while let Some((f, depth)) = stack.pop() {
665 forms += 1;
666 if forms > EXPANSION_SIZE_LIMIT {
667 return Err(raise(
668 "vla-macro-expansion-too-large",
669 &[("limit", "1,000,000")],
670 ));
671 }
672 if let Node::List(l) = f {
673 if depth > FORM_DEPTH_LIMIT {
674 return Err(too_deep(l.line));
675 }
676 stack.extend(l.items().iter().map(|item| (item, depth + 1)));
677 }
678 }
679 Ok(())
680 }
681
682 pub fn collect(&mut self, forms: Vec<Form>) -> Result<Vec<Form>, Refusal> {
686 let mut body = Vec::with_capacity(forms.len());
687 for f in forms {
688 if f.head_is("defmacro") {
689 let Form::List(l) = &f else { unreachable!() };
690 self.define_macro(l)?;
691 } else {
692 body.push(f);
693 }
694 }
695 Ok(body)
696 }
697}
698
699pub fn expand_text(
704 source: &str,
705 prelude: &str,
706 to_fixpoint: bool,
707) -> Result<(String, u64), Refusal> {
708 let line_offset = count_lf(prelude) as u32 + 1;
709 let text = format!("{prelude}\r\n{source}");
710 let forms = parse_all(&tokenize(&text), line_offset)?;
711 let mut ex = Expander::new();
712 let body = ex.collect(forms)?;
713 let mut out = String::new();
714 for f in &body {
715 let expanded = if to_fixpoint {
716 ex.expand(f, 0)?
717 } else {
718 ex.expand_one_pass(f)?
719 };
720 out.push_str(&write_pretty(&expanded, 0));
721 out.push_str("\r\n");
722 }
723 Ok((out, ex.expand_fired))
724}
725
726fn too_deep(line: u32) -> Refusal {
729 raise(
730 "vla-form-too-deep",
731 &[
732 ("limit", &FORM_DEPTH_LIMIT.to_string()),
733 ("loc", &src_line_tag(line, 0)),
734 ],
735 )
736}
737
738fn expand_one(call: &[Node], rule: &Rule) -> Result<Node, Refusal> {
741 let argc = call.len() - 1;
742 let name = call[0].atom_text().unwrap_or_default();
743 if rule.rest_name.is_empty() {
744 if argc != rule.params.len() {
745 return Err(raise(
746 "vla-macro-arity",
747 &[
748 ("name", &name),
749 ("n", &rule.params.len().to_string()),
750 ("given", &argc.to_string()),
751 ],
752 ));
753 }
754 } else if argc < rule.params.len() {
755 return Err(raise(
756 "vla-macro-arity-min",
757 &[
758 ("name", &name),
759 ("n", &rule.params.len().to_string()),
760 ("given", &argc.to_string()),
761 ],
762 ));
763 }
764 let mut env = Env {
765 bindings: HashMap::new(),
766 rest_name: &rule.rest_name,
767 rest_items: Vec::new(),
768 };
769 for (j, p) in rule.params.iter().enumerate() {
770 env.bindings.insert(p, call[j + 1].clone());
771 }
772 env.rest_items
773 .extend(call[rule.params.len() + 1..].iter().cloned());
774 substitute(&rule.template, &env, false)
775}
776
777fn substitute(t: &Node, env: &Env, in_quasi: bool) -> Result<Node, Refusal> {
782 let lst = match t {
783 Node::List(l) => l,
784 Node::Sym(s) if !in_quasi => {
785 let key = fold(s);
786 if !env.rest_name.is_empty() && key == env.rest_name {
787 return Ok(Node::list(env.rest_items.clone()));
789 }
790 if let Some(v) = env.bindings.get(key.as_str()) {
791 return Ok(v.clone());
792 }
793 return Ok(t.clone());
794 }
795 _ => return Ok(t.clone()),
796 };
797 let items = lst.items();
798
799 if let Some(Node::Sym(head)) = items.first() {
800 let h = fold(head);
801 match h.as_str() {
802 "symbol" => return fuse_symbol(items, env),
803 "quasiquote" => {
804 if in_quasi {
805 return Err(raise("vla-quasiquote-nested", &[]));
806 }
807 if items.len() != 2 {
808 return Err(raise(
809 "vla-quasiquote-arity",
810 &[("n", &(items.len() - 1).to_string())],
811 ));
812 }
813 return substitute(nth(items, 2)?, env, true);
814 }
815 "unquote" => {
816 if !in_quasi {
817 return Err(raise("vla-unquote-outside-quasiquote", &[]));
818 }
819 if items.len() != 2 {
820 return Err(raise(
821 "vla-unquote-arity",
822 &[("n", &(items.len() - 1).to_string())],
823 ));
824 }
825 return substitute(nth(items, 2)?, env, false);
826 }
827 "unquote-splicing" => {
828 if !in_quasi {
831 return Err(raise("vla-unquote-splicing-outside-quasiquote", &[]));
832 }
833 return Err(raise("vla-unquote-splicing-not-element", &[]));
834 }
835 _ if is_listops(&h) && !in_quasi => {
836 return eval_listops_prim(&h, items, env);
837 }
838 _ => {}
839 }
840 }
841
842 let mut out = Vec::with_capacity(items.len());
843 for e in items {
844 let mut spliced = false;
845 if in_quasi && e.head_is("unquote-splicing") {
846 let Node::List(spl) = e else { unreachable!() };
847 let spl = spl.items();
848 if spl.len() != 2 {
849 return Err(raise(
850 "vla-unquote-splicing-arity",
851 &[("n", &(spl.len() - 1).to_string())],
852 ));
853 }
854 let val = substitute(nth(spl, 2)?, env, false)?;
855 let Node::List(val) = val else {
856 return Err(raise("vla-unquote-splicing-not-list", &[]));
857 };
858 out.extend(val.items().iter().cloned());
859 spliced = true;
860 } else if !in_quasi {
861 if let Node::Sym(s) = e {
862 if !env.rest_name.is_empty() && fold(s) == env.rest_name {
863 out.extend(env.rest_items.iter().cloned());
865 spliced = true;
866 }
867 }
868 }
869 if !spliced {
870 out.push(substitute(e, env, in_quasi)?);
871 }
872 }
873 Ok(Node::list(out))
876}
877
878fn fuse_symbol(lst: &[Node], env: &Env) -> Result<Node, Refusal> {
882 if lst.len() < 2 {
883 return Err(raise("vla-symbol-arity", &[]));
884 }
885 let mut fused = String::new();
886 for k in 2..=lst.len() {
887 let piece = substitute(nth(lst, k)?, env, false)?;
888 match piece {
889 Node::List(_) => {
890 return Err(raise(
891 "vla-symbol-arg-is-list",
892 &[("n", &(k - 1).to_string())],
893 ));
894 }
895 Node::Str(s) | Node::Sym(s) => fused.push_str(&s),
896 }
897 }
898 Ok(match fused.strip_prefix('"') {
899 Some(rest) => Node::Str(Arc::from(rest)),
900 None => Node::Sym(Arc::from(fused.as_str())),
901 })
902}
903
904fn eval_listops_prim(head_fold: &str, lst: &[Node], env: &Env) -> Result<Node, Refusal> {
907 match head_fold {
908 "car" => eval_car(lst, env),
909 "cdr" => eval_cdr(lst, env),
910 "cddr" => eval_cddr(lst, env),
911 "cons" => eval_cons(lst, env),
912 "list" => eval_list(lst, env),
913 "null?" => eval_nullq(lst, env),
914 "eq?" => eval_eq(lst, env),
915 "equal?" => eval_equal(lst, env),
916 "quote-if" => eval_quote_if(lst, env),
917 "cond" => eval_cond_from(lst, 2, env),
918 "&expand" => eval_concat_expand(lst, env),
919 "characters-expand" => eval_characters_expand(lst, env),
920 "address-expand" => eval_address_expand(lst, env),
921 "row-expand" | "column-expand" => eval_address_part_expand(head_fold, lst, env),
922 "column-letters-expand" => eval_column_letters_expand(lst, env),
923 _ => eval_arith_expand(head_fold, lst, env),
924 }
925}
926
927fn resolve_listops_arg(arg: &Node, env: &Env) -> Result<Node, Refusal> {
930 let v = substitute(arg, env, false)?;
931 if v.head_is("quote") {
932 let Node::List(qc) = &v else { unreachable!() };
933 let qc = qc.items();
934 if qc.len() != 2 {
935 return Err(raise("vla-quote-arity", &[]));
936 }
937 return Ok(qc[1].clone());
938 }
939 Ok(v)
940}
941
942fn arity_n(lst: &[Node]) -> String {
943 (lst.len() - 1).to_string()
944}
945
946fn eval_car(lst: &[Node], env: &Env) -> Result<Node, Refusal> {
947 if lst.len() != 2 {
948 return Err(raise("vla-car-arity", &[("n", &arity_n(lst))]));
949 }
950 let v = resolve_listops_arg(nth(lst, 2)?, env)?;
951 let Node::List(c) = &v else {
952 return Err(raise(
953 "vla-car-expected-list",
954 &[("value", &v.atom_text().unwrap_or_default())],
955 ));
956 };
957 let Some(first) = c.items().first() else {
958 return Err(raise("vla-car-empty-list", &[]));
959 };
960 Ok(first.clone())
961}
962
963fn list_tail(c: &Seq, n: usize, caller: &str) -> Result<Node, Refusal> {
966 if c.count() < n {
967 return Err(raise(
968 "vla-list-too-short",
969 &[
970 ("caller", caller),
971 ("n", &n.to_string()),
972 ("count", &c.count().to_string()),
973 ],
974 ));
975 }
976 Ok(Node::List(c.tail(n)))
977}
978
979fn eval_cdr(lst: &[Node], env: &Env) -> Result<Node, Refusal> {
980 if lst.len() != 2 {
981 return Err(raise("vla-cdr-arity", &[("n", &arity_n(lst))]));
982 }
983 let v = resolve_listops_arg(nth(lst, 2)?, env)?;
984 let Node::List(c) = &v else {
985 return Err(raise(
986 "vla-cdr-expected-list",
987 &[("value", &v.atom_text().unwrap_or_default())],
988 ));
989 };
990 list_tail(c, 1, "cdr")
991}
992
993fn eval_cddr(lst: &[Node], env: &Env) -> Result<Node, Refusal> {
994 if lst.len() != 2 {
995 return Err(raise("vla-cddr-arity", &[("n", &arity_n(lst))]));
996 }
997 let v = resolve_listops_arg(nth(lst, 2)?, env)?;
998 let Node::List(c) = &v else {
999 return Err(raise(
1000 "vla-cddr-expected-list",
1001 &[("value", &v.atom_text().unwrap_or_default())],
1002 ));
1003 };
1004 list_tail(c, 2, "cddr")
1005}
1006
1007fn eval_cons(lst: &[Node], env: &Env) -> Result<Node, Refusal> {
1012 if lst.len() != 3 {
1013 return Err(raise("vla-cons-arity", &[("n", &arity_n(lst))]));
1014 }
1015 let item = resolve_listops_arg(nth(lst, 2)?, env)?;
1016 let tail = resolve_listops_arg(nth(lst, 3)?, env)?;
1017 let Node::List(tail) = &tail else {
1018 return Err(raise(
1019 "vla-cons-second-not-list",
1020 &[("value", &tail.atom_text().unwrap_or_default())],
1021 ));
1022 };
1023 let mut out = Vec::with_capacity(tail.count() + 1);
1024 out.push(item);
1025 out.extend(tail.items().iter().cloned());
1026 Ok(Node::list(out))
1027}
1028
1029fn eval_list(lst: &[Node], env: &Env) -> Result<Node, Refusal> {
1030 let mut out = Vec::with_capacity(lst.len());
1031 for k in 2..=lst.len() {
1032 out.push(resolve_listops_arg(nth(lst, k)?, env)?);
1033 }
1034 Ok(Node::list(out))
1035}
1036
1037fn bool_sym(b: bool) -> Node {
1038 Node::sym(if b { "true" } else { "false" })
1039}
1040
1041fn atom_equal(a: &Node, b: &Node) -> bool {
1043 match (a, b) {
1044 (Node::Sym(x), Node::Sym(y)) => fold(x) == fold(y),
1045 (Node::Str(x), Node::Str(y)) => x == y,
1046 _ => false,
1047 }
1048}
1049
1050fn deep_equal(a: &Node, b: &Node) -> bool {
1052 match (a, b) {
1053 (Node::List(x), Node::List(y)) => {
1054 x.count() == y.count()
1055 && x.items()
1056 .iter()
1057 .zip(y.items())
1058 .all(|(p, q)| deep_equal(p, q))
1059 }
1060 (Node::List(_), _) | (_, Node::List(_)) => false,
1061 _ => atom_equal(a, b),
1062 }
1063}
1064
1065fn eval_nullq(lst: &[Node], env: &Env) -> Result<Node, Refusal> {
1066 if lst.len() != 2 {
1067 return Err(raise("vla-nullq-arity", &[("n", &arity_n(lst))]));
1068 }
1069 let v = resolve_listops_arg(nth(lst, 2)?, env)?;
1070 Ok(match &v {
1071 Node::List(c) => bool_sym(c.count() == 0),
1072 _ => bool_sym(false),
1073 })
1074}
1075
1076fn eval_eq(lst: &[Node], env: &Env) -> Result<Node, Refusal> {
1077 if lst.len() != 3 {
1078 return Err(raise("vla-eq-arity", &[("n", &arity_n(lst))]));
1079 }
1080 let a = resolve_listops_arg(nth(lst, 2)?, env)?;
1081 let b = resolve_listops_arg(nth(lst, 3)?, env)?;
1082 if a.is_list() || b.is_list() {
1083 return Err(raise("vla-eq-expected-atom", &[]));
1084 }
1085 Ok(bool_sym(atom_equal(&a, &b)))
1086}
1087
1088fn eval_equal(lst: &[Node], env: &Env) -> Result<Node, Refusal> {
1089 if lst.len() != 3 {
1090 return Err(raise("vla-equal-arity", &[("n", &arity_n(lst))]));
1091 }
1092 let a = resolve_listops_arg(nth(lst, 2)?, env)?;
1093 let b = resolve_listops_arg(nth(lst, 3)?, env)?;
1094 Ok(bool_sym(deep_equal(&a, &b)))
1095}
1096
1097fn eval_quote_if(lst: &[Node], env: &Env) -> Result<Node, Refusal> {
1099 if lst.len() != 4 {
1100 return Err(raise("vla-quote-if-arity", &[("n", &arity_n(lst))]));
1101 }
1102 let t = resolve_listops_arg(nth(lst, 2)?, env)?;
1103 let tf = match &t {
1104 Node::List(_) => return Err(raise("vla-quote-if-test-is-list", &[])),
1105 Node::Str(_) => {
1106 return Err(raise(
1107 "vla-quote-if-test-invalid",
1108 &[("value", &t.atom_text().unwrap_or_default())],
1109 ))
1110 }
1111 Node::Sym(s) => fold(s),
1112 };
1113 let branch = match tf.as_str() {
1114 "true" => nth(lst, 3)?,
1115 "false" => nth(lst, 4)?,
1116 _ => {
1117 return Err(raise(
1118 "vla-quote-if-test-invalid",
1119 &[("value", &t.atom_text().unwrap_or_default())],
1120 ))
1121 }
1122 };
1123 substitute(branch, env, false)
1124}
1125
1126fn eval_arith_expand(op: &str, lst: &[Node], env: &Env) -> Result<Node, Refusal> {
1133 if lst.len() != 3 {
1134 return Err(raise(
1135 "vla-arith-expand-arity",
1136 &[("op", op), ("n", &arity_n(lst))],
1137 ));
1138 }
1139 let a = resolve_listops_arg(nth(lst, 2)?, env)?;
1140 let b = resolve_listops_arg(nth(lst, 3)?, env)?;
1141 let [ta, tb] = number_texts(op, [&a, &b])?;
1142 let na = val(&ta);
1143 let nb = val(&tb);
1144 let number = |x: f64| Node::sym(str_(x).trim());
1145 Ok(match op {
1146 "+expand" => number(na + nb),
1147 "=expand" => bool_sym(na == nb),
1148 "<>expand" => bool_sym(na != nb),
1149 ">expand" => bool_sym(na > nb),
1150 "<expand" => bool_sym(na < nb),
1151 ">=expand" => bool_sym(na >= nb),
1152 "<=expand" => bool_sym(na <= nb),
1153 "-expand" => number(na - nb),
1154 "*expand" => number(na * nb),
1155 _ => {
1156 for (n, text) in [(na, &ta), (nb, &tb)] {
1157 if n.fract() != 0.0 {
1158 return Err(raise(
1159 "vla-arith-expand-not-whole",
1160 &[("op", op), ("value", text)],
1161 ));
1162 }
1163 }
1164 if nb == 0.0 {
1165 return Err(raise("vla-arith-expand-divide-by-zero", &[("op", op)]));
1166 }
1167 if op == "quotient-expand" {
1168 number((na / nb).trunc())
1169 } else {
1170 number(na - nb * (na / nb).floor())
1171 }
1172 }
1173 })
1174}
1175
1176fn number_texts(op: &str, operands: [&Node; 2]) -> Result<[String; 2], Refusal> {
1179 let mut texts = [String::new(), String::new()];
1180 for (k, v) in operands.iter().enumerate() {
1181 let Some(text) = v.atom_text() else {
1182 return Err(raise("vla-arith-expand-not-number-list", &[("op", op)]));
1183 };
1184 texts[k] = text;
1185 }
1186 for text in &texts {
1187 if !is_numeric_literal_text(text) {
1188 return Err(raise(
1189 "vla-arith-expand-not-number-value",
1190 &[("op", op), ("value", text)],
1191 ));
1192 }
1193 }
1194 Ok(texts)
1195}
1196
1197fn number_text(op: &str, v: &Node) -> Result<String, Refusal> {
1199 let Some(text) = v.atom_text() else {
1200 return Err(raise("vla-arith-expand-not-number-list", &[("op", op)]));
1201 };
1202 if !is_numeric_literal_text(&text) {
1203 return Err(raise(
1204 "vla-arith-expand-not-number-value",
1205 &[("op", op), ("value", &text)],
1206 ));
1207 }
1208 Ok(text)
1209}
1210
1211fn expand_arity(op: &str, want: usize, lst: &[Node]) -> Refusal {
1213 raise(
1214 "vla-expand-arity",
1215 &[
1216 ("op", op),
1217 ("want", &want.to_string()),
1218 ("n", &arity_n(lst)),
1219 ],
1220 )
1221}
1222
1223fn atom_text_expand(v: &Node) -> String {
1226 match v {
1227 Node::Str(s) | Node::Sym(s) => s.to_string(),
1228 Node::List(_) => String::new(),
1229 }
1230}
1231
1232fn eval_concat_expand(lst: &[Node], env: &Env) -> Result<Node, Refusal> {
1238 if lst.len() < 2 {
1239 return Err(raise("vla-concat-expand-arity", &[]));
1240 }
1241 let mut joined = String::new();
1242 for k in 2..=lst.len() {
1243 let piece = resolve_listops_arg(nth(lst, k)?, env)?;
1244 if piece.is_list() {
1245 return Err(raise(
1246 "vla-expand-arg-is-list",
1247 &[("op", "&expand"), ("n", &(k - 1).to_string())],
1248 ));
1249 }
1250 joined.push_str(&atom_text_expand(&piece));
1251 }
1252 Ok(Node::Str(Arc::from(joined.as_str())))
1253}
1254
1255fn eval_characters_expand(lst: &[Node], env: &Env) -> Result<Node, Refusal> {
1260 if lst.len() != 2 {
1261 return Err(expand_arity("characters-expand", 1, lst));
1262 }
1263 let v = resolve_listops_arg(nth(lst, 2)?, env)?;
1264 if v.is_list() {
1265 return Err(raise(
1266 "vla-expand-arg-is-list",
1267 &[("op", "characters-expand"), ("n", "1")],
1268 ));
1269 }
1270 let items = atom_text_expand(&v)
1271 .chars()
1272 .map(|c| Node::Str(Arc::from(c.to_string().as_str())))
1273 .collect();
1274 Ok(Node::list(items))
1275}
1276
1277fn eval_address_expand(lst: &[Node], env: &Env) -> Result<Node, Refusal> {
1282 const OP: &str = "address-expand";
1283 if lst.len() != 3 {
1284 return Err(expand_arity(OP, 2, lst));
1285 }
1286 let a = resolve_listops_arg(nth(lst, 2)?, env)?;
1287 let b = resolve_listops_arg(nth(lst, 3)?, env)?;
1288 let [ta, tb] = number_texts(OP, [&a, &b])?;
1289 let (row, col) = (val(&ta), val(&tb));
1290 if row.fract() != 0.0 || !(1.0..=f64::from(MAX_ROW)).contains(&row) {
1291 return Err(raise(
1292 "vla-address-expand-row",
1293 &[("op", OP), ("value", &ta)],
1294 ));
1295 }
1296 if col.fract() != 0.0 || !(1.0..=f64::from(MAX_COLUMN)).contains(&col) {
1297 return Err(raise(
1298 "vla-address-expand-column",
1299 &[("op", OP), ("value", &tb)],
1300 ));
1301 }
1302 let address = format!("{}{}", column_letters(col as u32), str_(row).trim());
1303 Ok(Node::Str(Arc::from(address.as_str())))
1304}
1305
1306fn eval_address_part_expand(op: &str, lst: &[Node], env: &Env) -> Result<Node, Refusal> {
1311 if lst.len() != 2 {
1312 return Err(expand_arity(op, 1, lst));
1313 }
1314 let v = resolve_listops_arg(nth(lst, 2)?, env)?;
1315 if v.is_list() {
1316 return Err(raise("vla-expand-arg-is-list", &[("op", op), ("n", "1")]));
1317 }
1318 let text = atom_text_expand(&v);
1319 let Some((row, col)) = parse_address_text(&text, op == "column-expand") else {
1320 return Err(raise(
1321 "vla-address-expand-not-address",
1322 &[("op", op), ("value", &text)],
1323 ));
1324 };
1325 let n = if op == "row-expand" { row } else { col };
1326 Ok(Node::sym(str_(f64::from(n)).trim()))
1327}
1328
1329fn parse_address_text(s: &str, letters_alone: bool) -> Option<(u32, u32)> {
1339 let b = s.as_bytes();
1340 let n = b.len();
1341 let i = usize::from(n >= 1 && b[0] == b'$');
1342 let mut j = i;
1343 while j < n && b[j].is_ascii_alphabetic() {
1344 j += 1;
1345 }
1346 if j == i || j - i > 3 {
1347 return None;
1348 }
1349 let col = letters_to_col(&s[i..j])?;
1350 if j == n {
1351 return letters_alone.then_some((0, col));
1352 }
1353 if b[j] == b'$' {
1354 j += 1;
1355 }
1356 let mut k = j;
1357 while k < n && b[k].is_ascii_digit() {
1358 k += 1;
1359 }
1360 if k < n || k == j || k - j > 7 {
1361 return None;
1362 }
1363 let row: u32 = s[j..k].parse().ok()?;
1364 if row == 0 || row > MAX_ROW {
1365 return None;
1366 }
1367 Some((row, col))
1368}
1369
1370fn eval_column_letters_expand(lst: &[Node], env: &Env) -> Result<Node, Refusal> {
1373 const OP: &str = "column-letters-expand";
1374 if lst.len() != 2 {
1375 return Err(expand_arity(OP, 1, lst));
1376 }
1377 let v = resolve_listops_arg(nth(lst, 2)?, env)?;
1378 let text = number_text(OP, &v)?;
1379 let col = val(&text);
1380 if col.fract() != 0.0 || !(1.0..=f64::from(MAX_COLUMN)).contains(&col) {
1381 return Err(raise(
1382 "vla-address-expand-column",
1383 &[("op", OP), ("value", &text)],
1384 ));
1385 }
1386 Ok(Node::Str(Arc::from(column_letters(col as u32).as_str())))
1387}
1388
1389fn eval_cond_from(lst: &[Node], start_idx: usize, env: &Env) -> Result<Node, Refusal> {
1393 if start_idx > lst.len() {
1394 return Ok(Node::list(vec![Node::sym("begin")]));
1395 }
1396 let cl = nth_list(lst, start_idx)?.items();
1397 if cl.len() != 2 {
1398 return Err(raise(
1399 "vla-cond-clause-shape",
1400 &[("n", &cl.len().to_string())],
1401 ));
1402 }
1403 if let Node::Sym(s) = nth(cl, 1)? {
1404 if fold(s) == "else" {
1405 if start_idx != lst.len() {
1406 return Err(raise("vla-cond-else-not-last", &[]));
1407 }
1408 return substitute(nth(cl, 2)?, env, false);
1409 }
1410 }
1411 let t = resolve_listops_arg(nth(cl, 1)?, env)?;
1412 let tf = match &t {
1413 Node::Sym(s) => fold(s),
1414 _ => String::new(),
1415 };
1416 if tf == "true" {
1417 return substitute(nth(cl, 2)?, env, false);
1418 }
1419 if tf == "false" {
1420 return eval_cond_from(lst, start_idx + 1, env);
1421 }
1422 let raw_form = substitute(nth(cl, 2)?, env, false)?;
1423 let rest_form = eval_cond_from(lst, start_idx + 1, env)?;
1424 Ok(Node::list(vec![
1425 Node::sym("if"),
1426 t,
1427 Node::list(vec![Node::sym("then"), raw_form]),
1428 Node::list(vec![Node::sym("else"), rest_form]),
1429 ]))
1430}
1431
1432#[cfg(test)]
1433mod tests {
1434 use super::*;
1435 use crate::reader::read_forms;
1436
1437 const PRELUDE: &str = include_str!("../testdata/prelude.vla");
1438
1439 fn expand(src: &str) -> String {
1442 let (text, _) = expand_text(src, PRELUDE, true).unwrap_or_else(|e| panic!("{src}: {e}"));
1443 text.replace("\r\n", "\n")
1444 }
1445
1446 fn expand_err(src: &str) -> Refusal {
1448 match expand_text(src, PRELUDE, true) {
1449 Ok((text, _)) => panic!("{src} expanded instead of refusing: {text}"),
1450 Err(e) => e,
1451 }
1452 }
1453
1454 fn one(src: &str) -> String {
1455 expand(src).trim_end().to_string()
1456 }
1457
1458 #[test]
1459 fn ordinary_substitution_and_the_rest_parameter() {
1460 assert_eq!(
1461 one("(defmacro (make-adder n) (begin (dim total Long) (set! total (+ total n)))) (make-adder 5)"),
1462 "(begin (dim total Long) (set! total (+ total 5)))"
1463 );
1464 assert_eq!(
1465 one("(defmacro (all & xs) (list-of xs)) (all a b c)"),
1466 "(list-of a b c)"
1467 );
1468 assert_eq!(
1471 one("(defmacro (whole & xs) (f xs 1)) (whole a b)"),
1472 "(f a b 1)"
1473 );
1474 assert_eq!(one("(defmacro (bare & xs) xs) (bare a b)"), "(a b)");
1475 assert_eq!(
1477 one("(defmacro (two x) \"the doc\" (a x) (b x)) (two 1)"),
1478 "(begin (a 1) (b 1))"
1479 );
1480 assert_eq!(
1481 one("(defmacro (lone) \"just this\") (lone)"),
1482 "\"just this\""
1483 );
1484 assert_eq!(
1486 one("(defmacro (inner x) (set! x 1)) (defmacro (outer y) (inner y)) (outer q)"),
1487 "(set! q 1)"
1488 );
1489 assert_eq!(one("(quote (when unless))"), "(quote (when unless))");
1490 }
1491
1492 #[test]
1493 fn the_prelude_macros_fire_and_the_count_is_reported() {
1494 let (text, fired) = expand_text("(when c a b)", PRELUDE, true).unwrap();
1495 assert_eq!(text.trim_end(), "(if c (then a b))");
1496 assert_eq!(fired, 1);
1497 let (text, fired) = expand_text("(when c a b)", PRELUDE, false).unwrap();
1498 assert_eq!(text.trim_end(), "(if c (then a b))");
1499 assert_eq!(fired, 1);
1500 let (_, fired) = expand_text("(set! x 1)", PRELUDE, true).unwrap();
1501 assert_eq!(fired, 0);
1502 }
1503
1504 #[test]
1505 fn arity_refusals_name_the_macro() {
1506 let r = expand_err("(defmacro (two a b) (f a b)) (two 1)");
1507 assert_eq!(r.id, "vla-macro-arity");
1508 assert_eq!(r.text, "macro 'two' expects 2 argument(s), got 1");
1509 let r = expand_err("(defmacro (atleast a & r) (f a r)) (atleast)");
1510 assert_eq!(r.id, "vla-macro-arity-min");
1511 assert_eq!(
1512 r.text,
1513 "macro 'atleast' expects at least 1 argument(s), got 0"
1514 );
1515 assert_eq!(
1516 expand_err("(defmacro (m a &) a)").id,
1517 "vla-defmacro-rest-param-missing"
1518 );
1519 assert_eq!(
1520 expand_err("(defmacro (m & r x) r)").id,
1521 "vla-defmacro-rest-param-not-last"
1522 );
1523 assert_eq!(
1524 expand_err("(defmacro (m x))").id,
1525 "vla-defmacro-missing-template"
1526 );
1527 }
1528
1529 #[test]
1531 fn listops_car_cdr_cddr_cons_list() {
1532 assert_eq!(one("(car (quote (1 2 3)))"), "1");
1533 assert_eq!(one("(cdr (quote (1 2 3)))"), "(2 3)");
1534 assert_eq!(one("(cddr (quote (1 2 3 4)))"), "(3 4)");
1535 assert!(expand_err("(car (quote ()))")
1536 .text
1537 .contains("car: expected a non-empty list"));
1538 assert!(expand_err("(cdr (quote ()))")
1539 .text
1540 .contains("cdr: expected a list of at least 1 element(s), got 0"));
1541 assert!(expand_err("(cddr (quote (1)))")
1542 .text
1543 .contains("cddr: expected a list of at least 2 element(s), got 1"));
1544 assert!(expand_err("(car 5)")
1545 .text
1546 .contains("car: expected a list, got '5'"));
1547 assert!(expand_err("(car (quote (1 2)) (quote (3 4)))")
1548 .text
1549 .contains("car expects exactly 1 argument, got 2"));
1550 assert_eq!(one("(cons 1 (quote (2 3)))"), "(1 2 3)");
1551 assert!(expand_err("(cons 1 2)")
1552 .text
1553 .contains("cons: second argument must be a list, got '2'"));
1554 assert_eq!(one("(list 1 2 3)"), "(1 2 3)");
1555 assert_eq!(one("(list)"), "()");
1556 }
1557
1558 #[test]
1559 fn listops_predicates_and_quote_if() {
1560 assert_eq!(one("(null? (quote ()))"), "true");
1561 assert_eq!(one("(null? (quote (1)))"), "false");
1562 assert_eq!(one("(null? 5)"), "false");
1563 assert_eq!(one("(eq? (quote a) (quote a))"), "true");
1564 assert_eq!(one("(eq? (quote a) (quote b))"), "false");
1565 assert_eq!(one("(eq? (quote A) (quote a))"), "true");
1566 assert_eq!(one("(eq? \"a\" (quote a))"), "false");
1567 assert!(expand_err("(eq? (quote (1)) (quote (1)))")
1568 .text
1569 .contains("eq?: expected an atom, got a list"));
1570 assert_eq!(
1571 one("(equal? (quote (1 2 (3 4))) (quote (1 2 (3 4))))"),
1572 "true"
1573 );
1574 assert_eq!(
1575 one("(equal? (quote (1 2 (3 4))) (quote (1 2 (3 5))))"),
1576 "false"
1577 );
1578 assert_eq!(
1579 one("(quote-if (null? (quote ())) (debug-print \"base\") (car (quote ())))"),
1580 "(debug-print \"base\")"
1581 );
1582 assert_eq!(
1583 one("(quote-if (null? (quote (1))) (car (quote ())) (debug-print \"recurse\"))"),
1584 "(debug-print \"recurse\")"
1585 );
1586 assert!(
1587 expand_err("(quote-if 5 (debug-print \"a\") (debug-print \"b\"))")
1588 .text
1589 .contains("quote-if: test must resolve to true or false")
1590 );
1591 assert!(
1592 expand_err("(quote-if (null? (quote ())) (debug-print \"a\"))")
1593 .text
1594 .contains("quote-if expects exactly 3 arguments")
1595 );
1596 for (word, frag) in [
1597 (
1598 "car",
1599 "'car' is the list-head primitive and cannot be a macro name",
1600 ),
1601 ("cond", "'cond' is the multi-clause conditional"),
1602 ("quote", "'quote' is the data-literal form"),
1603 ("+expand", "'+expand' is the expand-time addition primitive"),
1604 ] {
1605 let r = expand_err(&format!("(defmacro ({word} x) x)"));
1606 assert_eq!(r.id, "vla-defmacro-reserved-name");
1607 assert!(r.text.contains(frag), "{word}: {}", r.text);
1608 }
1609 assert_eq!(
1610 one("(defmacro (first-of xs) (car xs)) (first-of (quote (7 8 9)))"),
1611 "7"
1612 );
1613 assert_eq!(
1614 one("(defmacro (demo xs) (quasiquote (unquote (car xs)))) (demo (quote (1 2 3)))"),
1615 "1"
1616 );
1617 }
1618
1619 #[test]
1620 fn a_self_terminating_walker_emits_one_statement_per_row() {
1621 let walker = "(defmacro (walk-rows lst) (quote-if (null? lst) (begin) (begin (debug-print (car lst)) (walk-rows (cdr lst)))))";
1622 let text = one(&format!("{walker} (walk-rows (quote (10 20 30)))"));
1623 assert_eq!(
1626 text,
1627 "(begin (debug-print 10) (debug-print 20) (debug-print 30) (begin))"
1628 );
1629 }
1630
1631 #[test]
1633 fn expand_time_arithmetic() {
1634 assert_eq!(one("(+expand 2 3)"), "5");
1635 assert_eq!(one("(+expand -2 3)"), "1");
1636 assert_eq!(one("(+expand 2.5 1.5)"), "4");
1637 assert_eq!(one("(+expand 0.1 0.2)"), ".3");
1638 assert_eq!(one("(=expand 5 5)"), "true");
1639 assert_eq!(one("(=expand 5 6)"), "false");
1640 assert_eq!(one("(<>expand 5 6)"), "true");
1641 assert_eq!(one("(>expand 5 3)"), "true");
1642 assert_eq!(one("(>expand 3 5)"), "false");
1643 assert_eq!(one("(<expand 3 5)"), "true");
1644 assert_eq!(one("(>=expand 5 5)"), "true");
1645 assert_eq!(one("(<=expand 5 5)"), "true");
1646 assert!(expand_err("(+expand 2 (quote x))")
1647 .text
1648 .contains("+expand: expected a number, got 'x'"));
1649 assert!(expand_err("(>expand (quote (1)) 2)")
1650 .text
1651 .contains(">expand: expected a number, got a list"));
1652 assert!(expand_err("(+expand (quote 1,000) 1)")
1653 .text
1654 .contains("+expand: expected a number, got '1,000'"));
1655 assert!(expand_err("(+expand 2)")
1656 .text
1657 .contains("+expand expects exactly 2 arguments, got 1"));
1658 assert_eq!(
1659 one("(quote-if (>expand 5 3) (debug-print \"yes\") (debug-print \"no\"))"),
1660 "(debug-print \"yes\")"
1661 );
1662 assert_eq!(
1663 one("(defmacro (bigger x) (quote-if (>expand x 10) (quote yes) (quote no))) (bigger 20)"),
1664 "(quote yes)"
1665 );
1666 }
1667
1668 #[test]
1670 fn the_expand_time_object_model_folds_and_refuses() {
1671 let refused = |src: &str, id: &str, text: &str| {
1672 let e = expand_err(src);
1673 assert_eq!((e.id.as_str(), e.text.as_str()), (id, text), "{src}");
1674 };
1675 assert_eq!(one("(-expand 5 2)"), "3");
1678 assert_eq!(one("(-expand 2 5)"), "-3");
1679 assert_eq!(one("(-expand 2.5 1)"), "1.5");
1680 assert_eq!(one("(*expand 6 7)"), "42");
1681 assert_eq!(one("(*expand 1.5 2)"), "3");
1682 assert_eq!(one("(quotient-expand 7 2)"), "3");
1683 assert_eq!(one("(quotient-expand -7 2)"), "-3");
1684 assert_eq!(one("(mod-expand 7 3)"), "1");
1685 assert_eq!(one("(mod-expand -7 2)"), "1");
1686 assert_eq!(one("(mod-expand 7 -2)"), "-1");
1687 refused(
1688 "(quotient-expand 7.5 2)",
1689 "vla-arith-expand-not-whole",
1690 "quotient-expand: expected a whole number, got '7.5'",
1691 );
1692 refused(
1693 "(mod-expand 7 0)",
1694 "vla-arith-expand-divide-by-zero",
1695 "mod-expand: cannot divide by zero",
1696 );
1697 refused(
1698 "(-expand 1)",
1699 "vla-arith-expand-arity",
1700 "-expand expects exactly 2 arguments, got 1",
1701 );
1702 refused(
1703 "(*expand 2 \"3\")",
1704 "vla-arith-expand-not-number-value",
1705 "*expand: expected a number, got '\"3'",
1706 );
1707 assert_eq!(one("(&expand \"A\" 1)"), "\"A1\"");
1709 assert_eq!(one("(&expand x)"), "\"x\"");
1710 assert_eq!(
1711 one("(&expand \"=SUM(\" (address-expand 1 2) \":\" (address-expand 9 2) \")\")"),
1712 "\"=SUM(B1:B9)\""
1713 );
1714 refused(
1715 "(&expand)",
1716 "vla-concat-expand-arity",
1717 "&expand expects at least 1 argument, got 0",
1718 );
1719 refused(
1720 "(&expand \"a\" (quote (b)))",
1721 "vla-expand-arg-is-list",
1722 "&expand: argument 2 is a list, expected an atom or string literal",
1723 );
1724 assert_eq!(one("(characters-expand \"ab#\")"), "(\"a\" \"b\" \"#\")");
1725 assert_eq!(one("(characters-expand \"\")"), "()");
1726 assert_eq!(one("(car (cdr (characters-expand \"xyz\")))"), "\"y\"");
1727 assert_eq!(
1728 one("(car (cdr (characters-expand \"a\u{1F600}b\")))"),
1729 "\"\u{1F600}\""
1730 );
1731 assert_eq!(
1732 one("(null? (cdr (cdr (cdr (characters-expand \"a\u{1F600}b\")))))"),
1733 "true"
1734 );
1735 refused(
1736 "(characters-expand (quote (a)))",
1737 "vla-expand-arg-is-list",
1738 "characters-expand: argument 1 is a list, expected an atom or string literal",
1739 );
1740 refused(
1741 "(characters-expand \"a\" \"b\")",
1742 "vla-expand-arity",
1743 "characters-expand expects exactly 1 argument(s), got 2",
1744 );
1745 assert_eq!(one("(address-expand 3 2)"), "\"B3\"");
1747 assert_eq!(one("(address-expand 1 16384)"), "\"XFD1\"");
1748 assert_eq!(one("(address-expand 1048576 27)"), "\"AA1048576\"");
1749 refused(
1750 "(address-expand 0 1)",
1751 "vla-address-expand-row",
1752 "address-expand: a row is a whole number from 1 to 1048576, got '0'",
1753 );
1754 refused(
1755 "(address-expand 1 16385)",
1756 "vla-address-expand-column",
1757 "address-expand: a column is a whole number from 1 to 16384, got '16385'",
1758 );
1759 refused(
1760 "(address-expand 1.5 1)",
1761 "vla-address-expand-row",
1762 "address-expand: a row is a whole number from 1 to 1048576, got '1.5'",
1763 );
1764 assert_eq!(one("(row-expand \"B3\")"), "3");
1765 assert_eq!(one("(column-expand \"$AA$10\")"), "27");
1766 assert_eq!(one("(column-expand \"xfd1\")"), "16384");
1767 assert_eq!(one("(column-expand \"AA\")"), "27");
1768 for (src, value) in [
1769 ("(row-expand \"AA\")", "AA"),
1770 ("(row-expand \"Sheet1!B3\")", "Sheet1!B3"),
1771 ("(column-expand \"B3:C4\")", "B3:C4"),
1772 ("(row-expand \"B0\")", "B0"),
1773 ] {
1774 let op = &src[1..src.find(' ').unwrap()];
1775 refused(
1776 src,
1777 "vla-address-expand-not-address",
1778 &format!("{op}: expected an address such as B3, got '{value}'"),
1779 );
1780 }
1781 assert_eq!(one("(column-letters-expand 28)"), "\"AB\"");
1782 refused(
1783 "(column-letters-expand 0)",
1784 "vla-address-expand-column",
1785 "column-letters-expand: a column is a whole number from 1 to 16384, got '0'",
1786 );
1787 assert_eq!(
1789 one("(defmacro (below a) (address-expand (+expand (row-expand a) 1) (column-expand a))) (below \"C7\")"),
1790 "\"C8\""
1791 );
1792 assert_eq!(
1793 one(concat!(
1794 "(defmacro (picture r txt) (picture-from r 1 (characters-expand txt))) ",
1795 "(defmacro (picture-from r c m) (quote-if (null? m) (begin) (begin ",
1796 "(quote-if (eq? (car m) \"#\") (cell \"S\" (address-expand r c) 1) (begin)) ",
1797 "(picture-from r (+expand c 1) (cdr m))))) (picture 2 \".#.#\")"
1798 )),
1799 "(begin (begin) (cell \"S\" \"B2\" 1) (begin) (cell \"S\" \"D2\" 1) (begin))"
1800 );
1801 for (word, desc) in [
1803 ("-expand", "subtraction"),
1804 ("address-expand", "address"),
1805 ("characters-expand", "text-splitting"),
1806 ] {
1807 refused(
1808 &format!("(defmacro ({word} x) x)"),
1809 "vla-defmacro-reserved-name",
1810 &format!("defmacro '{word}': '{word}' is the expand-time {desc} primitive and cannot be a macro name"),
1811 );
1812 }
1813 }
1814
1815 #[test]
1818 fn every_listops_name_is_reserved() {
1819 let reserved: Vec<&str> = RESERVED.iter().map(|(w, _)| *w).collect();
1820 let quote_bundle = [
1821 "quote",
1822 "quasiquote",
1823 "unquote",
1824 "unquote-splicing",
1825 "symbol",
1826 ];
1827 let walk_operations = ["formula-expand"];
1829 for name in LISTOPS {
1830 assert!(
1831 reserved.contains(name),
1832 "{name} is recognised but not reserved"
1833 );
1834 }
1835 for name in reserved {
1836 assert!(
1837 LISTOPS.contains(&name)
1838 || quote_bundle.contains(&name)
1839 || walk_operations.contains(&name),
1840 "{name} is reserved but not recognised"
1841 );
1842 }
1843 assert_eq!(LISTOPS.len(), 27);
1844 assert_eq!(one("(formula-expand (+ 1 2))"), "\"=1+2\"");
1845 }
1846
1847 #[test]
1849 fn formula_expand_expands_its_argument_first() {
1850 assert_eq!(
1853 one("(defmacro (edge cell twin) (and (= cell 1) (= twin 0)))\n\
1854 (formula-expand (if (edge Input!B5 Input.last!B5) 1 0))"),
1855 "\"=IF(AND(Input!B5=1,Input.last!B5=0),1,0)\""
1856 );
1857 assert_eq!(
1860 one(
1861 "(defmacro (rule sheet range expr) (formula sheet range (formula-expand expr)))\n\
1862 (rule \"Board\" \"A1:B2\" (+ A1 1))"
1863 ),
1864 "(formula \"Board\" \"A1:B2\" \"=A1+1\")"
1865 );
1866 assert_eq!(
1869 one("(formula-expand (ref \"Input\" (address-expand 5 2)))"),
1870 "\"=Input!B5\""
1871 );
1872 assert_eq!(one("(formula-expand \"=A1+1\")"), "\"=\\\"=A1+1\\\"\"");
1874 assert_eq!(
1875 expand_err("(formula-expand (+ (formula-expand A1) 1))").id,
1876 "vla-formula-expand-nested"
1877 );
1878 assert_eq!(
1879 expand_err("(formula-expand)").id,
1880 "vla-formula-expand-arity"
1881 );
1882 assert_eq!(
1883 expand_err("(formula-expand 1 2)").id,
1884 "vla-formula-expand-arity"
1885 );
1886 assert_eq!(
1887 expand_err("(&expand (formula-expand A1))").id,
1888 "vla-expand-arg-is-list"
1889 );
1890 assert_eq!(
1891 expand_err("(defmacro (formula-expand x) x)").id,
1892 "vla-defmacro-reserved-name"
1893 );
1894 let mut ex = Expander::new();
1896 let bad = read_forms("(formula-expand (+ (formula-expand A1) 1))").unwrap();
1897 assert!(ex.expand(&bad[0], 0).is_err());
1898 let good = read_forms("(formula-expand A1)").unwrap();
1899 assert_eq!(ex.expand(&good[0], 0).unwrap(), Form::string("=A1"));
1900 }
1901
1902 #[test]
1904 fn cond_folds_defers_and_refuses() {
1905 assert_eq!(
1906 one("(cond (true (debug-print \"a\")) (false (car (quote ()))))"),
1907 "(debug-print \"a\")"
1908 );
1909 assert_eq!(
1910 one("(cond (false (car (quote ()))) (true (debug-print \"b\")))"),
1911 "(debug-print \"b\")"
1912 );
1913 assert_eq!(
1914 one("(cond ((null? (quote ())) (debug-print \"yes\")) (else (debug-print \"no\")))"),
1915 "(debug-print \"yes\")"
1916 );
1917 assert_eq!(
1918 one("(cond ((null? (quote (1))) (debug-print \"wrong\")) (else (debug-print \"right\")))"),
1919 "(debug-print \"right\")"
1920 );
1921 assert_eq!(
1923 one("(cond ((> x 0) (debug-print \"pos\")) (else (debug-print \"nonpos\")))"),
1924 "(if (> x 0) (then (debug-print \"pos\")) (else (debug-print \"nonpos\")))"
1925 );
1926 assert_eq!(
1927 one("(cond ((> x 0) (debug-print \"pos\")) (true (debug-print \"fallback\")))"),
1928 "(if (> x 0) (then (debug-print \"pos\")) (else (debug-print \"fallback\")))"
1929 );
1930 assert_eq!(one("(cond (false (debug-print \"a\")))"), "(begin)");
1931 assert_eq!(one("(cond)"), "(begin)");
1932 assert!(
1933 expand_err("(cond (else (debug-print \"a\")) (true (debug-print \"b\")))")
1934 .text
1935 .contains("cond: 'else' must be the last clause")
1936 );
1937 assert!(expand_err("(cond ((> 1 0)))")
1938 .text
1939 .contains("cond: each clause must be (test form) or (else form), got 1 element(s)"));
1940 assert_eq!(
1941 one("(defmacro (branch x) (cond ((null? x) (quote empty)) (else (quote nonempty)))) (branch (quote ()))"),
1942 "(quote empty)"
1943 );
1944 }
1945
1946 #[test]
1948 fn quasiquote_shields_and_unquote_punches_through() {
1949 assert_eq!(one("(symbol \"make-\" \"bold\")"), "make-bold");
1950 assert_eq!(one("(symbol \"bold\")"), "bold");
1951 assert!(expand_err("(symbol)")
1952 .text
1953 .contains("symbol expects at least 1 argument, got 0"));
1954 assert_eq!(
1955 one("(defmacro (make-getter suffix) (function (symbol \"get-\" suffix) () String (return \"hi\"))) (make-getter widget)"),
1956 "(function get-widget () String (return \"hi\"))"
1957 );
1958 for (word, frag) in [
1959 ("symbol", "'symbol' is the identifier-fusion primitive"),
1960 ("quasiquote", "'quasiquote' is the template-shielding form"),
1961 ("unquote", "'unquote' is the quasiquote escape form"),
1962 (
1963 "unquote-splicing",
1964 "'unquote-splicing' is the quasiquote splice form",
1965 ),
1966 ] {
1967 assert!(expand_err(&format!("(defmacro ({word} x) x)"))
1968 .text
1969 .contains(frag));
1970 }
1971 assert_eq!(
1972 one("(defmacro (demo x) (quasiquote (x (unquote x)))) (demo hello)"),
1973 "(x hello)"
1974 );
1975 assert_eq!(
1976 one("(defmacro (demo & rest) (quasiquote (literal (unquote-splicing rest) end))) (demo a b c)"),
1977 "(literal a b c end)"
1978 );
1979 assert!(expand_err(
1980 "(defmacro (demo x) (quasiquote (a (quasiquote (unquote x))))) (demo hello)"
1981 )
1982 .text
1983 .contains("nested quasiquote is not supported"));
1984 assert!(expand_err("(unquote x)")
1985 .text
1986 .contains("'unquote' only has meaning inside a defmacro's own template"));
1987 assert!(expand_err("(defmacro (demo x) (unquote x)) (demo hello)")
1988 .text
1989 .contains("unquote used outside quasiquote"));
1990 assert!(
1991 expand_err("(defmacro (demo x) (unquote-splicing x)) (demo hello)")
1992 .text
1993 .contains("unquote-splicing used outside quasiquote")
1994 );
1995 assert!(
1996 expand_err("(defmacro (demo x) (quasiquote (unquote-splicing x))) (demo hello)")
1997 .text
1998 .contains("unquote-splicing must appear as a list element, not as a value")
1999 );
2000 assert!(expand_err(
2001 "(defmacro (demo x) (quasiquote (a (unquote-splicing x) b))) (demo hello)"
2002 )
2003 .text
2004 .contains("unquote-splicing: operand did not resolve to a list"));
2005 assert!(
2006 expand_err("(defmacro (demo x) (quasiquote a b)) (demo hello)")
2007 .text
2008 .contains("quasiquote expects exactly 1 argument, got 2")
2009 );
2010 assert!(
2011 expand_err("(defmacro (demo x) (quasiquote (unquote x y))) (demo hello)")
2012 .text
2013 .contains("unquote expects exactly 1 argument, got 2")
2014 );
2015 assert!(expand_err(
2016 "(defmacro (demo x) (quasiquote (a (unquote-splicing x y) b))) (demo hello)"
2017 )
2018 .text
2019 .contains("unquote-splicing expects exactly 1 argument, got 2"));
2020 }
2021
2022 #[test]
2023 fn lines_survive_where_the_vba_keeps_them() {
2024 let mut ex = Expander::new();
2027 let defs = read_forms("(defmacro (wrap x) (begin x))").unwrap();
2028 let body = ex.collect(defs).unwrap();
2029 assert!(body.is_empty());
2030 let call = read_forms("\n\n(wrap (f 1))").unwrap().remove(0);
2031 let out = ex.expand(&call, 0).unwrap();
2034 assert_eq!(out, read_forms("(f 1)").unwrap()[0]);
2035 assert_eq!(out.as_list().unwrap().line, 3);
2036 let defs = read_forms("(defmacro (wrap2 x) (begin (g 0) x))").unwrap();
2037 ex.collect(defs).unwrap();
2038 let call = read_forms("\n\n(wrap2 (f 1))").unwrap().remove(0);
2039 let out = ex.expand(&call, 0).unwrap();
2040 let Form::List(l) = &out else { panic!() };
2041 assert_eq!(l.line, 0);
2042 assert_eq!(l.items[1].as_list().unwrap().line, 0);
2043 assert_eq!(l.items[2].as_list().unwrap().line, 3);
2044 let hand = read_forms("\n(begin (f 1) (g 2))").unwrap().remove(0);
2046 let out = ex.expand(&hand, 0).unwrap();
2047 assert_eq!(out.as_list().unwrap().line, 2);
2048 }
2049
2050 #[test]
2051 fn every_golden_form_expands_under_the_prelude() {
2052 const GOLDEN: &str = include_str!("../testdata/instructions_golden.vla");
2056 let (text, fired) = expand_text(GOLDEN, PRELUDE, true).unwrap();
2057 assert!(fired > 100, "{fired} applications");
2058 assert!(text.starts_with("(const hot-pink \"#FF69B4\")"));
2059 }
2060
2061 #[test]
2062 fn a_macro_nesting_a_call_past_the_depth_limit_is_refused_by_the_walk() {
2063 let deep10 = "(defmacro (deep10) (a (a (a (a (a (a (a (a (a (a 1)))))))))))\r\n";
2068 let around = |n: usize| format!("{deep10}{}(deep10){}", "(".repeat(n), ")".repeat(n));
2069 let (_, fired) = expand_text(&around(54), "", true).unwrap();
2070 assert_eq!(fired, 1);
2071 let r = expand_text(&around(55), "", true).unwrap_err();
2072 assert_eq!(r.id, "vla-form-too-deep");
2073 assert_eq!(
2074 r.text,
2075 "lists nested more than 64 deep - no program needs that many"
2076 );
2077 let mut ex = Expander::new();
2079 let deep = read_forms(&format!("{}{}", "(".repeat(64), ")".repeat(64))).unwrap();
2080 let wrapped = Form::list(vec![deep[0].clone()]);
2081 assert_eq!(
2082 ex.expand_one_pass(&wrapped).unwrap_err().id,
2083 "vla-form-too-deep"
2084 );
2085 assert_eq!(ex.expand(&wrapped, 0).unwrap_err().id, "vla-form-too-deep");
2086 }
2087
2088 #[test]
2089 fn a_macro_that_deepens_or_doubles_its_argument_is_refused_by_name() {
2090 let deeper = expand_text("(defmacro (deeper x) (deeper (x))) (deeper 1)", "", true);
2100 assert_eq!(deeper.unwrap_err().id, "vla-form-too-deep");
2101 let dup = expand_text("(defmacro (dup x) (dup (x x))) (dup 1)", "", true).unwrap_err();
2102 assert_eq!(dup.id, "vla-macro-expansion-too-large");
2103 assert_eq!(
2104 dup.text,
2105 "one macro application wrote more than 1,000,000 forms (a macro that doubles its argument?)"
2106 );
2107 let mut ex = Expander::new();
2109 let forms = read_forms("(defmacro (wrap x) (w (w (w x))))").unwrap();
2110 ex.collect(forms).unwrap();
2111 let deep = read_forms(&format!("(wrap {}1{})", "(".repeat(62), ")".repeat(62))).unwrap();
2112 assert_eq!(
2113 ex.expand_one_pass(&deep[0]).unwrap_err().id,
2114 "vla-form-too-deep"
2115 );
2116 }
2117
2118 #[test]
2119 fn a_long_walk_writes_millions_of_forms_but_none_too_many_at_once() {
2120 let walker = "(defmacro (walk-rows lst) (quote-if (null? lst) (begin) \
2127 (begin (debug-print (car lst)) (walk-rows (cdr lst)))))\r\n";
2128 let rows: Vec<String> = (1..=2000).map(|i| i.to_string()).collect();
2129 let program = format!(
2130 "{walker}(sub t () (walk-rows (quote ({}))))",
2131 rows.join(" ")
2132 );
2133 let (text, fired) = expand_text(&program, "", true).unwrap();
2134 assert_eq!(fired, 2001, "one application a row, and the empty end");
2135 assert!(text.contains("(debug-print 2000)"));
2136 }
2137
2138 fn walked(n: &Node) -> (u32, u32) {
2140 match n {
2141 Node::List(l) => {
2142 let (mut forms, mut deepest) = (1, 0);
2143 for item in l.items() {
2144 let (f, d) = walked(item);
2145 forms += f;
2146 deepest = deepest.max(d);
2147 }
2148 (forms, deepest + 1)
2149 }
2150 _ => (1, 0),
2151 }
2152 }
2153
2154 #[test]
2155 fn a_list_keeps_the_weight_its_tree_has_and_a_view_its_own() {
2156 for text in [
2161 "(a (b (c (d))) \"s\" (e f) ((g)) h)",
2162 "((((1))) 2 (3 (4 (5 (6)))) () 7)",
2163 "(1 2 3)",
2164 "()",
2165 "(quote (a quote b))",
2166 ] {
2167 let node = Node::of(&read_forms(text).unwrap()[0]);
2168 let Node::List(seq) = &node else { panic!() };
2169 assert_eq!(node.weight(), walked(&node), "{text}");
2170 for n in 1..=seq.count() {
2171 let Node::List(view) = list_tail(seq, n, "cdr").unwrap() else {
2172 panic!()
2173 };
2174 assert!(Arc::ptr_eq(&view.run, &seq.run), "{text} from {n}");
2175 let once = Node::List(view.clone());
2176 assert_eq!(once.weight(), walked(&once), "{text} from {n}");
2177 for m in 1..=view.count() {
2178 let twice = Node::List(view.tail(m));
2179 assert_eq!(twice.weight(), walked(&twice), "{text} from {n}, {m}");
2180 }
2181 let holder = Node::list(vec![once.clone(), node.clone(), once]);
2182 assert_eq!(holder.weight(), walked(&holder), "{text} held from {n}");
2183 }
2184 assert_eq!(
2185 list_tail(seq, seq.count() + 1, "cddr").unwrap_err().id,
2186 "vla-list-too-short"
2187 );
2188 }
2189 }
2190
2191 #[test]
2192 fn a_parameter_s_value_is_the_argument_itself_at_every_use() {
2193 let mut ex = Expander::new();
2198 ex.collect(read_forms("(defmacro (m x y) (f x x (cdr y) (car y)))").unwrap())
2199 .unwrap();
2200 let call = Node::of(&read_forms("(m (g 1) (quote ((h 2) 3)))").unwrap()[0]);
2201 let Node::List(call) = &call else { panic!() };
2202 let r = expand_one(call.items(), &ex.rules["m"]).unwrap();
2203 let Node::List(r) = &r else { panic!() };
2204 let shared = |n: &Node, with: &Node| match (n, with) {
2205 (Node::List(a), Node::List(b)) => Arc::ptr_eq(&a.run, &b.run),
2206 _ => false,
2207 };
2208 let [_, x, y] = call.items() else { panic!() };
2209 let Node::List(quoted) = y else { panic!() };
2210 let content = "ed.items()[1];
2211 let Node::List(rows) = content else { panic!() };
2212 let [_, x1, x2, tail, head] = r.items() else {
2213 panic!()
2214 };
2215 assert!(shared(x1, x) && shared(x2, x));
2216 assert!(shared(tail, content));
2217 let Node::List(tail) = tail else { panic!() };
2218 assert_eq!((tail.start, tail.line), (1, 0));
2219 assert!(shared(head, &rows.items()[0]));
2220 assert_eq!(r.line, 0);
2221 assert_eq!(
2222 Node::List(r.clone()).form(),
2223 read_forms("(f (g 1) (g 1) (3) (h 2))").unwrap()[0]
2224 );
2225 }
2226
2227 #[test]
2228 fn a_result_past_a_limit_is_refused_as_the_walk_refused_it() {
2229 let small = Node::of(&read_forms("(a (b) c)").unwrap()[0]);
2235 assert_eq!(small.weight(), (5, 2));
2236 let ex = Expander::new();
2237 let mut doubled = Node::sym("1");
2238 for k in 1..=19 {
2239 doubled = Node::list(vec![doubled.clone(), doubled.clone()]);
2240 let refused = ex.weigh(&doubled);
2241 assert_eq!(refused.is_err(), k == 19, "{k} doublings");
2242 }
2243 assert_eq!(doubled.weight().0, (1 << 20) - 1);
2244 assert_eq!(
2245 ex.weigh(&doubled).unwrap_err().id,
2246 "vla-macro-expansion-too-large"
2247 );
2248 let mut deep = Node::sym("1");
2249 for k in 1..=65 {
2250 deep = Node::list(vec![deep]);
2251 assert_eq!(ex.weigh(&deep).is_err(), k == 65, "{k} deep");
2252 }
2253 let r = ex.weigh(&deep).unwrap_err();
2254 assert_eq!(r.id, "vla-form-too-deep");
2255 assert_eq!(r.text, too_deep(0).text);
2256 let mut huge = Node::sym("1");
2259 for _ in 0..40 {
2260 huge = Node::list(vec![huge.clone(), huge.clone(), huge]);
2261 }
2262 let Node::List(seq) = &huge else { panic!() };
2263 assert_eq!(seq.forms, u32::MAX);
2264 assert_eq!(seq.tail(1).forms, u32::MAX);
2265 assert_eq!(
2266 ex.weigh(&huge).unwrap_err().id,
2267 "vla-macro-expansion-too-large"
2268 );
2269 }
2270
2271 #[test]
2272 fn a_quoted_list_and_a_pass_s_arguments_keep_their_lines() {
2273 let mut ex = Expander::new();
2278 ex.collect(read_forms("(defmacro (wrap2 x) (begin (g 0) x))").unwrap())
2279 .unwrap();
2280 let quoted = read_forms("\n\n(quote (a\n(b)))").unwrap().remove(0);
2281 let out = ex.expand("ed, 0).unwrap();
2282 let Form::List(q) = &out else { panic!() };
2283 let Form::List(data) = &q.items[1] else {
2284 panic!()
2285 };
2286 let Form::List(b) = &data.items[1] else {
2287 panic!()
2288 };
2289 assert_eq!((q.line, data.line, b.line), (3, 3, 4));
2290 let call = read_forms("\n\n(wrap2\n(f 1))").unwrap().remove(0);
2291 let out = ex.expand_one_pass(&call).unwrap();
2292 let Form::List(r) = &out else { panic!() };
2293 assert_eq!(r.line, 0);
2294 assert_eq!(r.items[2].as_list().unwrap().line, 4);
2295 }
2296
2297 #[test]
2298 fn the_expander_crosses_threads_as_it_did() {
2299 fn interface<T: Send + Sync + std::panic::UnwindSafe + std::panic::RefUnwindSafe>() {}
2304 interface::<Expander>();
2305 interface::<Macro>();
2306 }
2307}