use tatara_lisp::{Atom, Span, Spanned, SpannedForm};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum HeadKind {
SpecialForm,
Macro,
Value,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum BinderKind {
Parameter,
Assignment,
Function,
Let,
Catch,
}
#[derive(Clone, Debug, PartialEq)]
pub struct Import {
pub package: String,
pub names: Vec<(String, Span)>,
pub listed: bool,
pub span: Span,
pub package_span: Span,
}
#[must_use]
pub fn use_target(form: &Spanned) -> Option<Import> {
let items = form.as_list()?;
let (head, arg, list) = match items {
[head, arg] => (head, arg, None),
[head, arg, list] => (head, arg, Some(list)),
_ => return None,
};
let (SpannedForm::Atom(Atom::Symbol(s)), SpannedForm::Atom(Atom::Str(name))) =
(&head.form, &arg.form)
else {
return None;
};
if s != "use" {
return None;
}
let mut names = Vec::new();
if let Some(list) = list {
let parts = list.as_list()?;
if parts.first().and_then(Spanned::as_symbol) != Some("list") {
return None;
}
for p in &parts[1..] {
match &p.form {
SpannedForm::Atom(Atom::Keyword(k)) => names.push((k.clone(), p.span)),
_ => return None,
}
}
}
Some(Import {
package: name.clone(),
names,
listed: list.is_some(),
span: form.span,
package_span: arg.span,
})
}
pub const LEGACY_NAMES: &str = "legacy_names";
#[derive(Clone, Debug, PartialEq)]
pub struct Legacy {
pub since: String,
pub prefix: String,
pub span: Span,
}
#[must_use]
pub fn legacy_target(form: &Spanned) -> Option<Legacy> {
let items = form.as_list()?;
let [head, since, prefix] = items else {
return None;
};
if head.as_symbol() != Some(LEGACY_NAMES) {
return None;
}
let (SpannedForm::Atom(Atom::Str(since)), SpannedForm::Atom(Atom::Str(prefix))) =
(&since.form, &prefix.form)
else {
return None;
};
Some(Legacy {
since: since.clone(),
prefix: prefix.clone(),
span: form.span,
})
}
pub const SHAPED_FORMS: &[&str] = &[
"lambda",
"let",
"let*",
"letrec",
"try",
"cond",
"quote",
"quasiquote",
"provide",
"require",
];
pub trait Scopes {
fn head_kind(&self, name: &str) -> Option<HeadKind>;
fn open(&mut self);
fn bind(&mut self, name: &str, span: Span, kind: BinderKind);
fn close(&mut self);
fn reference(&mut self, node: &Spanned, name: &str, opaque: bool);
fn head(&mut self, _node: &Spanned, _name: &str, _kind: HeadKind) {}
fn template_symbol(&mut self, _node: &Spanned, _name: &str) {}
}
pub fn walk_program(forms: &[Spanned], pass: &mut dyn Scopes) {
for form in forms {
walk_top(form, pass);
}
}
pub fn walk_top(form: &Spanned, pass: &mut dyn Scopes) {
Walker { pass, depth: 0 }.walk(form, false);
}
#[must_use]
pub fn definitions_of(form: &Spanned) -> Vec<(String, Span, HeadKind)> {
let mut out = Vec::new();
definition_nodes(form, &mut |node, kind| {
if let Some(n) = node.as_symbol() {
out.push((n.to_string(), node.span, kind));
}
});
out
}
pub fn definition_nodes<'a>(form: &'a Spanned, out: &mut dyn FnMut(&'a Spanned, HeadKind)) {
let Some(items) = form.as_list() else { return };
let head = items.first().and_then(Spanned::as_symbol);
match head {
Some("defmacro") => {
if let Some(n) = items.get(1).filter(|n| n.as_symbol().is_some()) {
out(n, HeadKind::Macro);
}
}
Some("define" | "define-typed") => {
if let Some(n) = defined_name_node(items) {
out(n, HeadKind::Value);
}
if items.get(1).is_some_and(|t| t.as_symbol().is_some()) {
for item in &items[2..] {
definition_nodes(item, out);
}
}
}
Some("lambda" | "let" | "let*" | "letrec" | "deftest" | "quote" | "quasiquote") => {}
Some("try") => {
for item in &items[1..] {
let is_clause = item
.as_list()
.and_then(|c| c.first())
.and_then(Spanned::as_symbol)
.is_some_and(|h| h == "catch" || h == "finally");
if !is_clause {
definition_nodes(item, out);
}
}
}
_ => {
for item in items {
definition_nodes(item, out);
}
}
}
}
#[must_use]
pub fn defined_name_node(items: &[Spanned]) -> Option<&Spanned> {
let head = items.first()?.as_symbol()?;
if head != "define" && head != "define-typed" {
return None;
}
let target = items.get(1)?;
if target.as_symbol().is_some() {
return Some(target);
}
let name = target.as_list()?.first()?;
name.as_symbol().map(|_| name)
}
#[must_use]
pub fn params_of(list: &Spanned) -> Vec<(String, Span)> {
if let Some(n) = list.as_symbol() {
return vec![(n.to_string(), list.span)];
}
list.as_list()
.map(|ps| ps.iter().filter_map(param).collect())
.unwrap_or_default()
}
fn param(p: &Spanned) -> Option<(String, Span)> {
let n = p.as_symbol()?;
if n.starts_with('&') || n == "." {
return None;
}
Some((n.to_string(), p.span))
}
struct Walker<'p> {
pass: &'p mut dyn Scopes,
depth: usize,
}
impl Walker<'_> {
fn open(&mut self) {
self.depth += 1;
self.pass.open();
}
fn close(&mut self) {
self.depth -= 1;
self.pass.close();
}
fn hoist(&mut self, body: &[Spanned]) {
for f in body {
let function = f.as_list().is_some_and(|items| {
items.first().and_then(Spanned::as_symbol) == Some("define")
&& items.get(1).is_some_and(|t| t.as_list().is_some())
});
let pass = &mut *self.pass;
definition_nodes(f, &mut |node, kind| {
let Some(name) = node.as_symbol() else { return };
let bk = match kind {
HeadKind::Value if !function => BinderKind::Assignment,
_ => BinderKind::Function,
};
pass.bind(name, node.span, bk);
});
}
}
fn body(&mut self, params: &[(String, Span)], forms: &[Spanned], opaque: bool) {
self.open();
for (p, s) in params {
self.pass.bind(p, *s, BinderKind::Parameter);
}
self.hoist(forms);
for f in forms {
self.walk(f, opaque);
}
self.close();
}
fn walk(&mut self, form: &Spanned, opaque: bool) {
match &form.form {
SpannedForm::Atom(Atom::Symbol(n)) => self.pass.reference(form, n, opaque),
SpannedForm::Quote(_) => {}
SpannedForm::Quasiquote(inner) => self.walk_template(inner, opaque),
SpannedForm::Unquote(inner) | SpannedForm::UnquoteSplice(inner) => {
self.walk(inner, opaque);
}
SpannedForm::List(items) if !items.is_empty() => self.walk_list(items, opaque),
_ => {}
}
}
fn walk_template(&mut self, form: &Spanned, opaque: bool) {
match &form.form {
SpannedForm::Atom(Atom::Symbol(n)) => self.pass.template_symbol(form, n),
SpannedForm::Unquote(inner) | SpannedForm::UnquoteSplice(inner) => {
self.walk(inner, opaque);
}
SpannedForm::List(items) => {
for i in items {
self.walk_template(i, opaque);
}
}
SpannedForm::Quasiquote(inner) => self.walk_template(inner, opaque),
_ => {}
}
}
fn walk_all(&mut self, items: &[Spanned], opaque: bool) {
for i in items {
self.walk(i, opaque);
}
}
fn walk_list(&mut self, items: &[Spanned], opaque: bool) {
let Some(head) = items[0].as_symbol() else {
self.walk_all(items, opaque);
return;
};
if head == "deftest" && self.depth == 0 {
if let Some(body) = items.get(2) {
self.body(&[], std::slice::from_ref(body), opaque);
}
return;
}
if (head == "use" || head == LEGACY_NAMES) && self.depth == 0 {
self.pass.head(&items[0], head, HeadKind::SpecialForm);
return;
}
if head == "define" || head == "define-typed" {
self.pass.head(&items[0], head, HeadKind::SpecialForm);
self.walk_define(head, items, opaque);
return;
}
if head == "defmacro" && self.depth == 0 {
self.pass.head(&items[0], head, HeadKind::SpecialForm);
let params = items.get(2).map(params_of).unwrap_or_default();
self.body(¶ms, items.get(3..).unwrap_or(&[]), opaque);
return;
}
match self.pass.head_kind(head) {
Some(HeadKind::SpecialForm) => {
self.pass.head(&items[0], head, HeadKind::SpecialForm);
self.walk_special(head, items, opaque);
}
Some(HeadKind::Macro) => {
self.pass.head(&items[0], head, HeadKind::Macro);
for i in &items[1..] {
self.walk(i, true);
}
}
Some(HeadKind::Value) | None => {
self.walk(&items[0], opaque);
self.walk_all(&items[1..], opaque);
}
}
}
fn walk_define(&mut self, head: &str, items: &[Spanned], opaque: bool) {
let Some(target) = items.get(1) else { return };
if target.as_symbol().is_some() {
self.walk_all(&items[2..], opaque);
return;
}
let Some(sig) = target.as_list() else {
self.walk_all(&items[1..], opaque);
return;
};
let params: Vec<(String, Span)> = sig
.iter()
.skip(1)
.filter_map(|p| {
if head == "define-typed" {
let pair = p.as_list()?;
let n = pair.first()?;
Some((n.as_symbol()?.to_string(), n.span))
} else {
param(p)
}
})
.collect();
let body_from = if head == "define-typed" { 3 } else { 2 };
self.body(¶ms, items.get(body_from..).unwrap_or(&[]), opaque);
}
fn walk_special(&mut self, head: &str, items: &[Spanned], opaque: bool) {
let args = &items[1..];
match head {
"quote" | "provide" | "require" => {}
"quasiquote" => {
for a in args {
self.walk_template(a, opaque);
}
}
"lambda" => {
let params = args.first().map(params_of).unwrap_or_default();
self.body(¶ms, args.get(1..).unwrap_or(&[]), opaque);
}
"cond" => {
for clause in args {
match clause.as_list() {
Some(parts) => {
let rest = match parts.first().and_then(Spanned::as_symbol) {
Some("else") => &parts[1..],
_ => parts,
};
for p in rest {
if p.as_symbol() != Some("=>") {
self.walk(p, opaque);
}
}
}
None => self.walk(clause, opaque),
}
}
}
"let" | "let*" | "letrec" => self.walk_let(head, args, opaque),
"try" => {
for a in args {
let clause = a.as_list();
match clause.and_then(|c| c.first()).and_then(Spanned::as_symbol) {
Some("catch") => {
let c = clause.expect("matched");
let var = c.get(1).and_then(|v| {
v.as_list()
.and_then(|l| l.first())
.and_then(|s| s.as_symbol().map(|n| (n.to_string(), s.span)))
.or_else(|| v.as_symbol().map(|n| (n.to_string(), v.span)))
});
self.open();
if let Some((n, s)) = var {
self.pass.bind(&n, s, BinderKind::Catch);
}
self.walk_all(c.get(2..).unwrap_or(&[]), opaque);
self.close();
}
Some("finally") => {
let c = clause.expect("matched");
self.walk_all(&c[1..], opaque);
}
_ => self.walk(a, opaque),
}
}
}
_ => self.walk_all(args, opaque),
}
}
fn walk_let(&mut self, head: &str, args: &[Spanned], opaque: bool) {
let (name, rest) = match args.first() {
Some(n) if n.as_symbol().is_some() && head == "let" => (Some(n), &args[1..]),
_ => (None, args),
};
let Some(bindings) = rest.first().and_then(Spanned::as_list) else {
self.walk_all(rest, opaque);
return;
};
let pairs: Vec<(String, Span, Option<&Spanned>)> = bindings
.iter()
.filter_map(|b| match b.as_list() {
Some(pair) => {
let n = pair.first()?;
Some((n.as_symbol()?.to_string(), n.span, pair.get(1)))
}
None => b.as_symbol().map(|n| (n.to_string(), b.span, None)),
})
.collect();
let body = rest.get(1..).unwrap_or(&[]);
if head == "let" {
for (_, _, init) in &pairs {
if let Some(e) = init {
self.walk(e, opaque);
}
}
}
self.open();
match head {
"let" => {
if let Some(n) = name {
self.pass.bind(
n.as_symbol().expect("matched"),
n.span,
BinderKind::Function,
);
}
for (n, s, _) in &pairs {
self.pass.bind(n, *s, BinderKind::Let);
}
}
"let*" => {
for (n, s, init) in &pairs {
if let Some(e) = init {
self.walk(e, opaque);
}
self.pass.bind(n, *s, BinderKind::Let);
}
}
_ => {
for (n, s, _) in &pairs {
self.pass.bind(n, *s, BinderKind::Let);
}
for (_, _, init) in &pairs {
if let Some(e) = init {
self.walk(e, opaque);
}
}
}
}
self.hoist(body);
self.walk_all(body, opaque);
self.close();
}
}