1use std::collections::HashMap;
71
72use crate::ast::Sexp;
73use crate::error::{LispError, MacroDefHead, Result};
74use crate::macro_expand::{macro_def_from, MacroArgCarrier, MacroDef};
75use crate::span::Span;
76use crate::spanned::{Spanned, SpannedForm};
77
78impl MacroArgCarrier for Spanned {
79 type Site = Span;
85
86 fn lift_default(default: &Sexp, site: Span) -> Self {
87 Spanned::from_sexp_at(default, site)
88 }
89
90 fn collect_rest(items: Vec<Self>, site: Span) -> Self {
91 Spanned::new(site, SpannedForm::List(items))
92 }
93}
94
95#[derive(Clone, Default)]
97pub struct SpannedExpander {
98 macros: HashMap<String, MacroDef>,
99}
100
101impl SpannedExpander {
102 pub fn new() -> Self {
103 Self::default()
104 }
105
106 pub fn has(&self, name: &str) -> bool {
107 self.macros.contains_key(name)
108 }
109
110 pub fn len(&self) -> usize {
111 self.macros.len()
112 }
113
114 pub fn is_empty(&self) -> bool {
115 self.macros.is_empty()
116 }
117
118 pub fn get_macro(&self, name: &str) -> Option<&MacroDef> {
120 self.macros.get(name)
121 }
122
123 pub fn macro_names(&self) -> impl Iterator<Item = &str> {
125 self.macros.keys().map(|s| s.as_str())
126 }
127
128 pub fn try_register_macro(&mut self, form: &Spanned) -> Result<bool> {
134 if let Some(def) = spanned_macro_def_from(form)? {
135 self.macros.insert(def.name.clone(), def);
136 Ok(true)
137 } else {
138 Ok(false)
139 }
140 }
141
142 pub fn expand_program(&mut self, forms: Vec<Spanned>) -> Result<Vec<Spanned>> {
145 let mut out = Vec::new();
146 for form in forms {
147 if self.try_register_macro(&form)? {
148 continue;
149 }
150 out.push(self.expand(&form)?);
151 }
152 Ok(out)
153 }
154
155 pub fn expand(&self, form: &Spanned) -> Result<Spanned> {
158 let SpannedForm::List(list) = &form.form else {
159 return Ok(form.clone());
160 };
161 if let Some(head_name) = list.first().and_then(Spanned::as_symbol) {
162 if let Some(def) = self.macros.get(head_name) {
163 let expanded = self.apply(def, form.span, &list[1..])?;
164 return self.expand(&expanded);
165 }
166 }
167 let mut out_children: Vec<Spanned> = Vec::with_capacity(list.len());
168 for child in list {
169 out_children.push(self.expand(child)?);
170 }
171 Ok(Spanned::new(form.span, SpannedForm::List(out_children)))
172 }
173
174 fn apply(&self, def: &MacroDef, call_span: Span, args: &[Spanned]) -> Result<Spanned> {
176 let bindings = bind_spanned_args(&def.name, &def.params, args, call_span)?;
177 substitute_spanned(def.template_body(), &bindings, call_span)
178 }
179}
180
181type Bindings = HashMap<String, Spanned>;
190
191fn bind_spanned_args(
202 macro_name: &str,
203 params: &crate::macro_expand::MacroParams,
204 args: &[Spanned],
205 call_span: Span,
206) -> Result<Bindings> {
207 let vals = params.bind_carrier(macro_name, args, call_span)?;
208 Ok(params
209 .names()
210 .into_iter()
211 .map(String::from)
212 .zip(vals)
213 .collect())
214}
215
216fn substitute_spanned(template: &Sexp, bindings: &Bindings, call_span: Span) -> Result<Spanned> {
227 match template {
228 Sexp::Unquote(inner) => template_eval(inner, bindings, call_span),
229 Sexp::UnquoteSplice(_) => Err(LispError::Compile {
230 form: "unquote-splice".into(),
231 message: "`,@` may only appear inside a list".into(),
232 }),
233 Sexp::List(items) => {
234 let mut out: Vec<Spanned> = Vec::with_capacity(items.len());
235 for item in items {
236 if let Sexp::UnquoteSplice(inner) = item {
237 let evaluated = template_eval(inner, bindings, call_span)?;
238 splice_into(&evaluated, &mut out);
239 } else {
240 out.push(substitute_spanned(item, bindings, call_span)?);
241 }
242 }
243 Ok(Spanned::new(call_span, SpannedForm::List(out)))
244 }
245 Sexp::Quote(inner) => {
246 let inner = substitute_spanned(inner, bindings, call_span)?;
247 Ok(Spanned::new(call_span, SpannedForm::Quote(Box::new(inner))))
248 }
249 Sexp::Quasiquote(inner) => {
250 let inner = substitute_spanned(inner, bindings, call_span)?;
251 Ok(Spanned::new(
252 call_span,
253 SpannedForm::Quasiquote(Box::new(inner)),
254 ))
255 }
256 Sexp::Nil => Ok(Spanned::new(call_span, SpannedForm::Nil)),
257 Sexp::Atom(a) => Ok(Spanned::new(call_span, SpannedForm::Atom(a.clone()))),
258 }
259}
260
261fn spanned_macro_def_from(form: &Spanned) -> Result<Option<MacroDef>> {
277 let Some(list) = form.as_list() else {
278 return Ok(None);
279 };
280 let Some(head) = list.first().and_then(Spanned::as_symbol) else {
281 return Ok(None);
282 };
283 if MacroDefHead::from_keyword(head).is_none() {
284 return Ok(None);
285 }
286 macro_def_from(&form.to_sexp())
287}
288
289fn splice_into(evaluated: &Spanned, out: &mut Vec<Spanned>) {
292 match &evaluated.form {
293 SpannedForm::List(children) => out.extend(children.iter().cloned()),
294 SpannedForm::Nil => {}
295 _ => out.push(evaluated.clone()),
296 }
297}
298
299fn template_eval(expr: &Sexp, bindings: &Bindings, call_span: Span) -> Result<Spanned> {
319 match expr {
320 Sexp::Atom(crate::ast::Atom::Symbol(name)) => {
321 match bindings.get(name) {
326 Some(val) => Ok(val.clone()),
327 None => Err(LispError::Compile {
328 form: format!(",{name}"),
329 message: "unbound in macro template".into(),
330 }),
331 }
332 }
333 Sexp::Atom(a) => Ok(Spanned::new(call_span, SpannedForm::Atom(a.clone()))),
334 Sexp::Nil => Ok(Spanned::new(call_span, SpannedForm::Nil)),
335 Sexp::Quote(inner) => Ok(Spanned::from_sexp_at(inner, call_span)),
336 Sexp::Quasiquote(inner) => substitute_spanned(inner, bindings, call_span),
341 Sexp::Unquote(inner) => template_eval(inner, bindings, call_span),
345 Sexp::UnquoteSplice(_) => Err(LispError::Compile {
346 form: "template-eval".into(),
347 message: "`,@` only valid directly inside a list".into(),
348 }),
349 Sexp::List(items) => {
350 if items.is_empty() {
351 return Ok(Spanned::new(call_span, SpannedForm::List(Vec::new())));
352 }
353 let head = items[0].as_symbol().ok_or_else(|| LispError::Compile {
354 form: "template-eval".into(),
355 message: "first element of a template-time list must be a symbol".into(),
356 })?;
357 match head {
358 "quote" => {
359 let arg = items.get(1).ok_or_else(|| LispError::Compile {
360 form: "quote".into(),
361 message: "expected one arg".into(),
362 })?;
363 Ok(Spanned::from_sexp_at(arg, call_span))
364 }
365 "car" => {
366 let xs = template_eval_list(&items[1..], 1, "car", bindings, call_span)?;
367 let inner = template_eval(&xs[0].1, bindings, call_span)?;
368 let list = require_spanned_list(&inner, "car")?;
369 if list.is_empty() {
370 return Err(LispError::Compile {
371 form: "car".into(),
372 message: "car of empty list".into(),
373 });
374 }
375 Ok(list[0].clone())
376 }
377 "cdr" => {
378 let xs = template_eval_list(&items[1..], 1, "cdr", bindings, call_span)?;
379 let inner = template_eval(&xs[0].1, bindings, call_span)?;
380 let list = require_spanned_list(&inner, "cdr")?;
381 if list.is_empty() {
382 return Err(LispError::Compile {
383 form: "cdr".into(),
384 message: "cdr of empty list".into(),
385 });
386 }
387 Ok(Spanned::new(
388 call_span,
389 SpannedForm::List(list[1..].to_vec()),
390 ))
391 }
392 "cons" => {
393 let xs = template_eval_list(&items[1..], 2, "cons", bindings, call_span)?;
394 let h = template_eval(&xs[0].1, bindings, call_span)?;
395 let t = template_eval(&xs[1].1, bindings, call_span)?;
396 let mut out = vec![h];
397 match t.form {
398 SpannedForm::List(children) => out.extend(children),
399 SpannedForm::Nil => {}
400 _ => out.push(t),
401 }
402 Ok(Spanned::new(call_span, SpannedForm::List(out)))
403 }
404 "list" => {
405 let mut out: Vec<Spanned> = Vec::with_capacity(items.len() - 1);
406 for child in &items[1..] {
407 out.push(template_eval(child, bindings, call_span)?);
408 }
409 Ok(Spanned::new(call_span, SpannedForm::List(out)))
410 }
411 "null?" => {
412 let xs = template_eval_list(&items[1..], 1, "null?", bindings, call_span)?;
413 let v = template_eval(&xs[0].1, bindings, call_span)?;
414 let is_null = matches!(&v.form, SpannedForm::Nil)
415 || matches!(&v.form, SpannedForm::List(c) if c.is_empty());
416 Ok(Spanned::new(
417 call_span,
418 SpannedForm::Atom(crate::ast::Atom::Bool(is_null)),
419 ))
420 }
421 "pair?" => {
422 let xs = template_eval_list(&items[1..], 1, "pair?", bindings, call_span)?;
423 let v = template_eval(&xs[0].1, bindings, call_span)?;
424 let ok = matches!(&v.form, SpannedForm::List(c) if !c.is_empty());
425 Ok(Spanned::new(
426 call_span,
427 SpannedForm::Atom(crate::ast::Atom::Bool(ok)),
428 ))
429 }
430 "list?" => {
431 let xs = template_eval_list(&items[1..], 1, "list?", bindings, call_span)?;
432 let v = template_eval(&xs[0].1, bindings, call_span)?;
433 let ok = matches!(&v.form, SpannedForm::List(_) | SpannedForm::Nil);
434 Ok(Spanned::new(
435 call_span,
436 SpannedForm::Atom(crate::ast::Atom::Bool(ok)),
437 ))
438 }
439 "length" => {
440 let xs = template_eval_list(&items[1..], 1, "length", bindings, call_span)?;
441 let v = template_eval(&xs[0].1, bindings, call_span)?;
442 let n = match &v.form {
443 SpannedForm::Nil => 0,
444 SpannedForm::List(c) => c.len() as i64,
445 _ => {
446 return Err(LispError::Compile {
447 form: "length".into(),
448 message: "expected a list".into(),
449 })
450 }
451 };
452 Ok(Spanned::new(
453 call_span,
454 SpannedForm::Atom(crate::ast::Atom::Int(n)),
455 ))
456 }
457 "if" => {
458 if items.len() != 4 {
459 return Err(LispError::Compile {
460 form: "if".into(),
461 message: "expected (if cond then else)".into(),
462 });
463 }
464 let c = template_eval(&items[1], bindings, call_span)?;
465 let truthy = !matches!(
466 &c.form,
467 SpannedForm::Nil | SpannedForm::Atom(crate::ast::Atom::Bool(false))
468 );
469 if truthy {
470 template_eval(&items[2], bindings, call_span)
471 } else {
472 template_eval(&items[3], bindings, call_span)
473 }
474 }
475 other => Err(LispError::Compile {
476 form: other.into(),
477 message: "operation not supported in macro template `,expr`. Supported: \
478 quote, car, cdr, cons, list, null?, pair?, list?, length, if"
479 .into(),
480 }),
481 }
482 }
483 }
484}
485
486fn template_eval_list<'a>(
489 args: &'a [Sexp],
490 expected: usize,
491 fn_name: &'static str,
492 _bindings: &Bindings,
493 _call_span: Span,
494) -> Result<Vec<(usize, &'a Sexp)>> {
495 if args.len() != expected {
496 return Err(LispError::Compile {
497 form: fn_name.into(),
498 message: format!("expected {expected} args, got {}", args.len()),
499 });
500 }
501 Ok(args.iter().enumerate().collect())
502}
503
504fn require_spanned_list<'a>(s: &'a Spanned, fn_name: &'static str) -> Result<&'a [Spanned]> {
505 match &s.form {
506 SpannedForm::List(c) => Ok(c.as_slice()),
507 SpannedForm::Nil => Ok(&[]),
508 _ => Err(LispError::Compile {
509 form: fn_name.into(),
510 message: "expected a list".into(),
511 }),
512 }
513}
514
515#[cfg(test)]
516mod tests {
517 use super::*;
518 use crate::reader::{read, read_spanned};
519
520 fn parse(src: &str) -> Sexp {
521 read(src).unwrap().into_iter().next().unwrap()
522 }
523
524 #[test]
525 fn identity_macro_preserves_arg_span() {
526 let src = "(defmacro id (x) `,x) (id 42)";
527 let forms = read_spanned(src).unwrap();
528 let mut e = SpannedExpander::new();
529 let out = e.expand_program(forms).unwrap();
530 assert_eq!(out.len(), 1);
531 assert_eq!(out[0].to_sexp(), Sexp::int(42));
533 assert!(!out[0].span.is_synthetic());
535 let expected_start = src.find("42").unwrap();
536 assert_eq!(out[0].span, Span::new(expected_start, expected_start + 2));
537 }
538
539 #[test]
540 fn wrap_macro_substitution_preserves_each_arg_span() {
541 let src = "(defmacro wrap (x) `(list ,x ,x)) (wrap hello)";
542 let forms = read_spanned(src).unwrap();
543 let mut e = SpannedExpander::new();
544 let out = e.expand_program(forms).unwrap();
545 assert_eq!(out[0].to_sexp(), parse("(list hello hello)"));
546 let SpannedForm::List(children) = &out[0].form else {
548 panic!()
549 };
550 let list_span = children[0].span;
552 assert_eq!(children[1].span, children[2].span);
555 assert_ne!(children[1].span, list_span);
556 assert!(!children[1].span.is_synthetic());
557 }
558
559 #[test]
560 fn rest_param_splice_preserves_argument_spans() {
561 let src = "(defmacro call (f &rest args) `(,f ,@args)) (call foo a b c)";
562 let forms = read_spanned(src).unwrap();
563 let mut e = SpannedExpander::new();
564 let out = e.expand_program(forms).unwrap();
565 assert_eq!(out[0].to_sexp(), parse("(foo a b c)"));
566 let SpannedForm::List(children) = &out[0].form else {
567 panic!()
568 };
569 for c in children {
572 assert!(!c.span.is_synthetic(), "{:?}", c);
573 }
574 }
575
576 #[test]
577 fn nested_macro_expansion_preserves_original_arg_span() {
578 let src = "(defmacro twice (x) `(list ,x ,x))
579 (defmacro quad (x) `(twice ,x))
580 (quad hey)";
581 let forms = read_spanned(src).unwrap();
582 let mut e = SpannedExpander::new();
583 let out = e.expand_program(forms).unwrap();
584 assert_eq!(out[0].to_sexp(), parse("(list hey hey)"));
585 let SpannedForm::List(children) = &out[0].form else {
586 panic!()
587 };
588 assert!(!children[1].span.is_synthetic());
590 assert_eq!(children[1].span, children[2].span);
591 }
592
593 #[test]
594 fn non_macro_form_passes_through_with_original_spans() {
595 let src = "(foo bar baz)";
596 let forms = read_spanned(src).unwrap();
597 let mut e = SpannedExpander::new();
598 let out = e.expand_program(forms).unwrap();
599 assert_eq!(out[0].to_sexp(), parse("(foo bar baz)"));
600 assert_eq!(out[0].span, Span::new(0, src.len()));
602 }
603
604 #[test]
605 fn unbound_unquote_errors() {
606 let src = "(defmacro bad (x) `(list ,y)) (bad 1)";
607 let forms = read_spanned(src).unwrap();
608 let mut e = SpannedExpander::new();
609 assert!(e.expand_program(forms).is_err());
610 }
611
612 #[test]
613 fn missing_required_arg_errors() {
614 let src = "(defmacro need-two (a b) `(,a ,b)) (need-two 1)";
615 let forms = read_spanned(src).unwrap();
616 let mut e = SpannedExpander::new();
617 assert!(e.expand_program(forms).is_err());
618 }
619
620 #[test]
621 fn empty_rest_splices_nothing() {
622 let src = "(defmacro f (x &rest r) `(list ,x ,@r)) (f 1)";
623 let forms = read_spanned(src).unwrap();
624 let mut e = SpannedExpander::new();
625 let out = e.expand_program(forms).unwrap();
626 assert_eq!(out[0].to_sexp(), parse("(list 1)"));
627 }
628
629 #[test]
635 fn optional_param_default_agrees_with_plain_expander() {
636 use crate::macro_expand::Expander;
637
638 let src = "
639 (defmacro greet (name &optional (greeting \"hi\") punct)
640 `(list ,greeting ,name ,punct))
641 (greet bob)
642 (greet bob \"yo\")
643 (greet bob \"yo\" bang)
644 ";
645 let plain_out = Expander::new().expand_program(read(src).unwrap()).unwrap();
646 let spanned_out = SpannedExpander::new()
647 .expand_program(read_spanned(src).unwrap())
648 .unwrap();
649
650 assert_eq!(plain_out.len(), 3);
651 assert_eq!(plain_out.len(), spanned_out.len());
652 for (p, s) in plain_out.iter().zip(spanned_out.iter()) {
653 assert_eq!(p, &s.to_sexp());
654 }
655 let listed = |trailing: Sexp| {
659 Sexp::List(vec![
660 Sexp::symbol("list"),
661 Sexp::string("hi"),
662 Sexp::symbol("bob"),
663 trailing,
664 ])
665 };
666 assert_eq!(plain_out[0], listed(Sexp::Nil));
667 assert_eq!(
669 plain_out[1],
670 Sexp::List(vec![
671 Sexp::symbol("list"),
672 Sexp::string("yo"),
673 Sexp::symbol("bob"),
674 Sexp::Nil,
675 ])
676 );
677 assert_eq!(plain_out[2], parse("(list \"yo\" bob bang)"));
678 }
679
680 #[test]
684 fn absent_optional_default_wears_the_call_site_span() {
685 let src = "(defmacro f (a &optional (b 7)) `(list ,a ,b)) (f 1)";
686 let out = SpannedExpander::new()
687 .expand_program(read_spanned(src).unwrap())
688 .unwrap();
689 let SpannedForm::List(children) = &out[0].form else {
690 panic!("expected a list")
691 };
692 let call_start = src.rfind("(f 1)").unwrap();
693 let call_span = Span::new(call_start, call_start + "(f 1)".len());
694 assert_eq!(children[2].to_sexp(), Sexp::int(7));
697 assert!(!children[2].span.is_synthetic());
698 assert_eq!(children[2].span, call_span);
699 }
700
701 #[test]
706 fn surplus_args_rejected_like_plain_expander() {
707 use crate::macro_expand::Expander;
708
709 let src = "(defmacro two (a b) `(list ,a ,b)) (two 1 2 3)";
710 let plain = Expander::new().expand_program(read(src).unwrap());
711 let spanned = SpannedExpander::new().expand_program(read_spanned(src).unwrap());
712 assert!(plain.is_err(), "plain expander accepted a surplus arg");
713 assert!(spanned.is_err(), "spanned expander accepted a surplus arg");
714 }
715
716 #[test]
719 fn malformed_lambda_list_rejected_like_plain_expander() {
720 use crate::macro_expand::Expander;
721
722 for src in [
723 "(defmacro f (a &rest) `(list ,a))",
725 "(defmacro f (a &rest r junk) `(list ,a))",
727 "(defmacro f (&optional (b)) `(list ,b))",
729 "(defmacro f (1) `(list))",
731 ] {
732 let plain = Expander::new().expand_program(read(src).unwrap());
733 let spanned = SpannedExpander::new().expand_program(read_spanned(src).unwrap());
734 assert!(plain.is_err(), "plain expander accepted: {src}");
735 assert!(spanned.is_err(), "spanned expander accepted: {src}");
736 }
737 }
738
739 #[test]
740 fn agrees_with_plain_expander_on_output() {
741 use crate::macro_expand::Expander;
742
743 let src = "
744 (defmacro wrap (x) `(list ,x ,x))
745 (defmacro call (f &rest args) `(,f ,@args))
746 (defmacro twice (x) `(list ,x ,x))
747 (defmacro quad (x) `(twice ,x))
748 (wrap hello)
749 (call foo a b c)
750 (quad hey)
751 (outer (wrap deep))
752 ";
753 let plain_forms = read(src).unwrap();
754 let spanned_forms = read_spanned(src).unwrap();
755
756 let mut plain = Expander::new();
757 let plain_out = plain.expand_program(plain_forms).unwrap();
758
759 let mut spanned = SpannedExpander::new();
760 let spanned_out = spanned.expand_program(spanned_forms).unwrap();
761
762 assert_eq!(plain_out.len(), spanned_out.len());
763 for (p, s) in plain_out.iter().zip(spanned_out.iter()) {
764 assert_eq!(p, &s.to_sexp());
765 }
766 }
767}