1use tatara_lisp::{Atom, Span, Spanned, SpannedForm};
26
27#[derive(Clone, Copy, Debug, PartialEq, Eq)]
29pub enum HeadKind {
30 SpecialForm,
31 Macro,
32 Value,
33}
34
35#[derive(Clone, Copy, Debug, PartialEq, Eq)]
37pub enum BinderKind {
38 Parameter,
39 Assignment,
41 Function,
43 Let,
44 Catch,
45}
46
47#[derive(Clone, Debug, PartialEq)]
54pub struct Import {
55 pub package: String,
56 pub names: Vec<(String, Span)>,
59 pub listed: bool,
61 pub span: Span,
63 pub package_span: Span,
65}
66
67#[must_use]
76pub fn use_target(form: &Spanned) -> Option<Import> {
77 let items = form.as_list()?;
78 let (head, arg, list) = match items {
79 [head, arg] => (head, arg, None),
80 [head, arg, list] => (head, arg, Some(list)),
81 _ => return None,
82 };
83 let (SpannedForm::Atom(Atom::Symbol(s)), SpannedForm::Atom(Atom::Str(name))) =
84 (&head.form, &arg.form)
85 else {
86 return None;
87 };
88 if s != "use" {
89 return None;
90 }
91 let mut names = Vec::new();
92 if let Some(list) = list {
93 let parts = list.as_list()?;
94 if parts.first().and_then(Spanned::as_symbol) != Some("list") {
95 return None;
96 }
97 for p in &parts[1..] {
98 match &p.form {
99 SpannedForm::Atom(Atom::Keyword(k)) => names.push((k.clone(), p.span)),
100 _ => return None,
101 }
102 }
103 }
104 Some(Import {
105 package: name.clone(),
106 names,
107 listed: list.is_some(),
108 span: form.span,
109 package_span: arg.span,
110 })
111}
112
113pub const LEGACY_NAMES: &str = "legacy_names";
115
116#[derive(Clone, Debug, PartialEq)]
136pub struct Legacy {
137 pub since: String,
138 pub kind: LegacyKind,
139 pub span: Span,
140}
141
142#[derive(Clone, Debug, PartialEq)]
144pub enum LegacyKind {
145 Prefix(String),
147 Moved(Vec<Moved>),
149 Flat(Vec<(String, Span)>),
150}
151
152#[derive(Clone, Debug, PartialEq, Eq)]
160pub struct Moved {
161 pub old: String,
162 pub home: String,
163 pub new: String,
164 pub append: Option<String>,
165}
166
167#[must_use]
173pub fn legacy_target(form: &Spanned) -> Option<Legacy> {
174 let items = form.as_list()?;
175 let [head, since, what] = items else {
176 return None;
177 };
178 if head.as_symbol() != Some(LEGACY_NAMES) {
179 return None;
180 }
181 let SpannedForm::Atom(Atom::Str(since)) = &since.form else {
182 return None;
183 };
184 let str_of = |s: &Spanned| match &s.form {
185 SpannedForm::Atom(Atom::Str(x)) => Some(x.clone()),
186 _ => None,
187 };
188 let keyword_of = |s: &Spanned| match &s.form {
189 SpannedForm::Atom(Atom::Keyword(k)) => Some((k.clone(), s.span)),
190 _ => None,
191 };
192 let kind = if let Some(prefix) = str_of(what) {
193 LegacyKind::Prefix(prefix)
194 } else if let Some(flat) = list_items(what)
195 .filter(|items| !items.is_empty())
196 .and_then(|items| items.iter().map(keyword_of).collect::<Option<Vec<_>>>())
197 {
198 LegacyKind::Flat(flat)
199 } else {
200 let moves = list_items(what)?
201 .iter()
202 .map(|pair| {
203 let (old, target, append) = match list_items(pair)? {
204 [old, target] => (old, target, None),
205 [old, target, append] => (old, target, Some(str_of(append)?)),
206 _ => return None,
207 };
208 let (old, target) = (str_of(old)?, str_of(target)?);
209 let (home, new) = target
210 .split_once("::")
211 .map_or((String::new(), target.clone()), |(h, n)| {
212 (h.to_string(), n.to_string())
213 });
214 Some(Moved {
215 old,
216 home,
217 new,
218 append,
219 })
220 })
221 .collect::<Option<Vec<_>>>()?;
222 LegacyKind::Moved(moves)
223 };
224 Some(Legacy {
225 since: since.clone(),
226 kind,
227 span: form.span,
228 })
229}
230
231fn list_items(form: &Spanned) -> Option<&[Spanned]> {
233 let items = form.as_list()?;
234 (items.first()?.as_symbol() == Some("list")).then(|| &items[1..])
235}
236
237pub const SHAPED_FORMS: &[&str] = &[
242 "lambda",
243 "let",
244 "let*",
245 "letrec",
246 "try",
247 "cond",
248 "quote",
249 "quasiquote",
250 "provide",
251 "require",
252];
253
254pub trait Scopes {
256 fn head_kind(&self, name: &str) -> Option<HeadKind>;
259 fn open(&mut self);
261 fn bind(&mut self, name: &str, span: Span, kind: BinderKind);
263 fn close(&mut self);
265 fn reference(&mut self, node: &Spanned, name: &str, opaque: bool);
268 fn call(&mut self, node: &Spanned, _whole: &Spanned, name: &str, opaque: bool) {
269 self.reference(node, name, opaque);
270 }
271 fn place(&mut self, node: &Spanned, name: &str, opaque: bool) {
272 self.reference(node, name, opaque);
273 }
274 fn head(&mut self, _node: &Spanned, _name: &str, _kind: HeadKind) {}
277 fn template_symbol(&mut self, _node: &Spanned, _name: &str) {}
280}
281
282pub fn walk_program(forms: &[Spanned], pass: &mut dyn Scopes) {
286 for form in forms {
287 walk_top(form, pass);
288 }
289}
290
291pub fn walk_top(form: &Spanned, pass: &mut dyn Scopes) {
293 Walker { pass, depth: 0 }.walk(form, false);
294}
295
296#[must_use]
306pub fn definitions_of(form: &Spanned) -> Vec<(String, Span, HeadKind)> {
307 let mut out = Vec::new();
308 definition_nodes(form, &mut |node, kind| {
309 if let Some(n) = node.as_symbol() {
310 out.push((n.to_string(), node.span, kind));
311 }
312 });
313 out
314}
315
316pub fn definition_nodes<'a>(form: &'a Spanned, out: &mut dyn FnMut(&'a Spanned, HeadKind)) {
319 let Some(items) = form.as_list() else { return };
320 let head = items.first().and_then(Spanned::as_symbol);
321 match head {
322 Some("defmacro") => {
323 if let Some(n) = items.get(1).filter(|n| n.as_symbol().is_some()) {
324 out(n, HeadKind::Macro);
325 }
326 }
327 Some("define" | "define-typed") => {
328 if let Some(n) = defined_name_node(items) {
329 out(n, HeadKind::Value);
330 }
331 if items.get(1).is_some_and(|t| t.as_symbol().is_some()) {
334 for item in &items[2..] {
335 definition_nodes(item, out);
336 }
337 }
338 }
339 Some("lambda" | "let" | "let*" | "letrec" | "deftest" | "quote" | "quasiquote") => {}
340 Some("try") => {
341 for item in &items[1..] {
343 let is_clause = item
344 .as_list()
345 .and_then(|c| c.first())
346 .and_then(Spanned::as_symbol)
347 .is_some_and(|h| h == "catch" || h == "finally");
348 if !is_clause {
349 definition_nodes(item, out);
350 }
351 }
352 }
353 _ => {
354 for item in items {
355 definition_nodes(item, out);
356 }
357 }
358 }
359}
360
361#[must_use]
362pub fn nullary_definition(form: &Spanned) -> Option<&str> {
363 let items = form.as_list()?;
364 let head = items.first()?.as_symbol()?;
365 if head != "define" && head != "define-typed" {
366 return None;
367 }
368 match items.get(1)?.as_list()? {
369 [name] => name.as_symbol(),
370 _ => None,
371 }
372}
373
374#[must_use]
376pub fn defined_name_node(items: &[Spanned]) -> Option<&Spanned> {
377 let head = items.first()?.as_symbol()?;
378 if head != "define" && head != "define-typed" {
379 return None;
380 }
381 let target = items.get(1)?;
382 if target.as_symbol().is_some() {
383 return Some(target);
384 }
385 let name = target.as_list()?.first()?;
386 name.as_symbol().map(|_| name)
387}
388
389#[must_use]
392pub fn params_of(list: &Spanned) -> Vec<(String, Span)> {
393 if let Some(n) = list.as_symbol() {
394 return vec![(n.to_string(), list.span)];
395 }
396 list.as_list()
397 .map(|ps| ps.iter().filter_map(param).collect())
398 .unwrap_or_default()
399}
400
401fn param(p: &Spanned) -> Option<(String, Span)> {
402 let n = p.as_symbol()?;
403 if n.starts_with('&') || n == "." {
404 return None;
405 }
406 Some((n.to_string(), p.span))
407}
408
409struct Walker<'p> {
410 pass: &'p mut dyn Scopes,
411 depth: usize,
413}
414
415impl Walker<'_> {
416 fn open(&mut self) {
417 self.depth += 1;
418 self.pass.open();
419 }
420
421 fn close(&mut self) {
422 self.depth -= 1;
423 self.pass.close();
424 }
425
426 fn hoist(&mut self, body: &[Spanned]) {
430 for f in body {
431 let function = f.as_list().is_some_and(|items| {
432 items.first().and_then(Spanned::as_symbol) == Some("define")
433 && items.get(1).is_some_and(|t| t.as_list().is_some())
434 });
435 let pass = &mut *self.pass;
436 definition_nodes(f, &mut |node, kind| {
437 let Some(name) = node.as_symbol() else { return };
438 let bk = match kind {
439 HeadKind::Value if !function => BinderKind::Assignment,
440 _ => BinderKind::Function,
441 };
442 pass.bind(name, node.span, bk);
443 });
444 }
445 }
446
447 fn body(&mut self, params: &[(String, Span)], forms: &[Spanned], opaque: bool) {
450 self.open();
451 for (p, s) in params {
452 self.pass.bind(p, *s, BinderKind::Parameter);
453 }
454 self.hoist(forms);
455 for f in forms {
456 self.walk(f, opaque);
457 }
458 self.close();
459 }
460
461 fn walk(&mut self, form: &Spanned, opaque: bool) {
462 match &form.form {
463 SpannedForm::Atom(Atom::Symbol(n)) => self.pass.reference(form, n, opaque),
464 SpannedForm::Quote(_) => {}
465 SpannedForm::Quasiquote(inner) => self.walk_template(inner, opaque),
466 SpannedForm::Unquote(inner) | SpannedForm::UnquoteSplice(inner) => {
467 self.walk(inner, opaque);
468 }
469 SpannedForm::List(items) if !items.is_empty() => self.walk_list(form, items, opaque),
470 _ => {}
471 }
472 }
473
474 fn walk_template(&mut self, form: &Spanned, opaque: bool) {
476 match &form.form {
477 SpannedForm::Atom(Atom::Symbol(n)) => self.pass.template_symbol(form, n),
478 SpannedForm::Unquote(inner) | SpannedForm::UnquoteSplice(inner) => {
479 self.walk(inner, opaque);
480 }
481 SpannedForm::List(items) => {
482 for i in items {
483 self.walk_template(i, opaque);
484 }
485 }
486 SpannedForm::Quasiquote(inner) => self.walk_template(inner, opaque),
487 _ => {}
488 }
489 }
490
491 fn walk_all(&mut self, items: &[Spanned], opaque: bool) {
492 for i in items {
493 self.walk(i, opaque);
494 }
495 }
496
497 fn walk_list(&mut self, form: &Spanned, items: &[Spanned], opaque: bool) {
498 let Some(head) = items[0].as_symbol() else {
499 self.walk_all(items, opaque);
500 return;
501 };
502 if head == "deftest" && self.depth == 0 {
505 if let Some(body) = items.get(2) {
506 self.body(&[], std::slice::from_ref(body), opaque);
507 }
508 return;
509 }
510 if (head == "use" || head == LEGACY_NAMES) && self.depth == 0 {
513 self.pass.head(&items[0], head, HeadKind::SpecialForm);
514 return;
515 }
516 if head == "define" || head == "define-typed" {
519 self.pass.head(&items[0], head, HeadKind::SpecialForm);
520 self.walk_define(head, items, opaque);
521 return;
522 }
523 if head == "defmacro" && self.depth == 0 {
527 self.pass.head(&items[0], head, HeadKind::SpecialForm);
528 let params = items.get(2).map(params_of).unwrap_or_default();
529 self.body(¶ms, items.get(3..).unwrap_or(&[]), opaque);
530 return;
531 }
532 match self.pass.head_kind(head) {
533 Some(HeadKind::SpecialForm) => {
534 self.pass.head(&items[0], head, HeadKind::SpecialForm);
535 self.walk_special(head, items, opaque);
536 }
537 Some(HeadKind::Macro) => {
538 self.pass.head(&items[0], head, HeadKind::Macro);
540 for i in &items[1..] {
541 self.walk(i, true);
542 }
543 }
544 Some(HeadKind::Value) | None => {
545 self.pass.call(&items[0], form, head, opaque);
546 self.walk_all(&items[1..], opaque);
547 }
548 }
549 }
550
551 fn walk_define(&mut self, head: &str, items: &[Spanned], opaque: bool) {
552 let Some(target) = items.get(1) else { return };
553 if target.as_symbol().is_some() {
554 self.walk_all(&items[2..], opaque);
556 return;
557 }
558 let Some(sig) = target.as_list() else {
559 self.walk_all(&items[1..], opaque);
560 return;
561 };
562 let params: Vec<(String, Span)> = sig
563 .iter()
564 .skip(1)
565 .filter_map(|p| {
566 if head == "define-typed" && p.as_list().is_some() {
567 let n = p.as_list()?.first()?;
568 Some((n.as_symbol()?.to_string(), n.span))
569 } else {
570 param(p)
571 }
572 })
573 .collect();
574 let body_from = if head == "define-typed" { 3 } else { 2 };
576 self.body(¶ms, items.get(body_from..).unwrap_or(&[]), opaque);
577 }
578
579 fn walk_special(&mut self, head: &str, items: &[Spanned], opaque: bool) {
580 let args = &items[1..];
581 match head {
582 "quote" | "provide" | "require" => {}
583 "quasiquote" => {
584 for a in args {
585 self.walk_template(a, opaque);
586 }
587 }
588 "lambda" => {
589 let params = args.first().map(params_of).unwrap_or_default();
590 self.body(¶ms, args.get(1..).unwrap_or(&[]), opaque);
591 }
592 "cond" => {
593 for clause in args {
594 match clause.as_list() {
595 Some(parts) => {
596 let rest = match parts.first().and_then(Spanned::as_symbol) {
597 Some("else") => &parts[1..],
598 _ => parts,
599 };
600 for p in rest {
601 if p.as_symbol() != Some("=>") {
602 self.walk(p, opaque);
603 }
604 }
605 }
606 None => self.walk(clause, opaque),
607 }
608 }
609 }
610 "let" | "let*" | "letrec" => self.walk_let(head, args, opaque),
611 "set!" => match args.split_first() {
612 Some((target, rest)) if target.as_symbol().is_some() => {
613 self.pass
614 .place(target, target.as_symbol().expect("matched"), opaque);
615 self.walk_all(rest, opaque);
616 }
617 _ => self.walk_all(args, opaque),
618 },
619 "try" => {
620 for a in args {
621 let clause = a.as_list();
622 match clause.and_then(|c| c.first()).and_then(Spanned::as_symbol) {
623 Some("catch") => {
624 let c = clause.expect("matched");
625 let var = c.get(1).and_then(|v| {
626 v.as_list()
627 .and_then(|l| l.first())
628 .and_then(|s| s.as_symbol().map(|n| (n.to_string(), s.span)))
629 .or_else(|| v.as_symbol().map(|n| (n.to_string(), v.span)))
630 });
631 self.open();
632 if let Some((n, s)) = var {
633 self.pass.bind(&n, s, BinderKind::Catch);
634 }
635 self.walk_all(c.get(2..).unwrap_or(&[]), opaque);
636 self.close();
637 }
638 Some("finally") => {
639 let c = clause.expect("matched");
640 self.walk_all(&c[1..], opaque);
641 }
642 _ => self.walk(a, opaque),
643 }
644 }
645 }
646 _ => self.walk_all(args, opaque),
649 }
650 }
651
652 fn walk_let(&mut self, head: &str, args: &[Spanned], opaque: bool) {
653 let (name, rest) = match args.first() {
655 Some(n) if n.as_symbol().is_some() && head == "let" => (Some(n), &args[1..]),
656 _ => (None, args),
657 };
658 let Some(bindings) = rest.first().and_then(Spanned::as_list) else {
659 self.walk_all(rest, opaque);
660 return;
661 };
662 let pairs: Vec<(String, Span, Option<&Spanned>)> = bindings
663 .iter()
664 .filter_map(|b| match b.as_list() {
665 Some(pair) => {
666 let n = pair.first()?;
667 Some((n.as_symbol()?.to_string(), n.span, pair.get(1)))
668 }
669 None => b.as_symbol().map(|n| (n.to_string(), b.span, None)),
670 })
671 .collect();
672 let body = rest.get(1..).unwrap_or(&[]);
673 if head == "let" {
674 for (_, _, init) in &pairs {
676 if let Some(e) = init {
677 self.walk(e, opaque);
678 }
679 }
680 }
681 self.open();
682 match head {
683 "let" => {
684 if let Some(n) = name {
685 self.pass.bind(
686 n.as_symbol().expect("matched"),
687 n.span,
688 BinderKind::Function,
689 );
690 }
691 for (n, s, _) in &pairs {
692 self.pass.bind(n, *s, BinderKind::Let);
693 }
694 }
695 "let*" => {
696 for (n, s, init) in &pairs {
697 if let Some(e) = init {
698 self.walk(e, opaque);
699 }
700 self.pass.bind(n, *s, BinderKind::Let);
701 }
702 }
703 _ => {
704 for (n, s, _) in &pairs {
705 self.pass.bind(n, *s, BinderKind::Let);
706 }
707 for (_, _, init) in &pairs {
708 if let Some(e) = init {
709 self.walk(e, opaque);
710 }
711 }
712 }
713 }
714 self.hoist(body);
715 self.walk_all(body, opaque);
716 self.close();
717 }
718}