use std::mem;
use std::sync::{Arc, Mutex, OnceLock};
use cljrs_types::span::Span;
#[derive(Debug)]
pub struct Form {
pub kind: FormKind,
pub span: Span,
pub expansion: ExpansionCache,
}
impl Clone for Form {
fn clone(&self) -> Self {
let expansion = if matches!(self.kind, FormKind::List(_)) {
self.expansion.share()
} else {
ExpansionCache::default()
};
Self {
kind: self.kind.clone(),
span: self.span.clone(),
expansion,
}
}
}
impl Form {
pub fn new(kind: FormKind, span: Span) -> Self {
Self {
kind,
span,
expansion: ExpansionCache::default(),
}
}
pub fn heap_size(&self) -> usize {
mem::size_of::<FormKind>() + self.kind.heap_size()
}
pub fn is_kwargs_rest_pattern(&self) -> bool {
matches!(self.kind, FormKind::Map(_))
}
pub fn unmeta(&self) -> &Form {
let mut form = self;
while let FormKind::Meta(_, inner) = &form.kind {
form = inner;
}
form
}
pub fn quoted_value_supports_meta(&self) -> bool {
match &self.kind {
FormKind::List(_)
| FormKind::Vector(_)
| FormKind::Map(_)
| FormKind::Set(_)
| FormKind::Symbol(_)
| FormKind::AutoSymbol(_)
| FormKind::AnonFn(_)
| FormKind::Quote(_)
| FormKind::SyntaxQuote(_)
| FormKind::Unquote(_)
| FormKind::UnquoteSplice(_)
| FormKind::Deref(_)
| FormKind::Var(_) => true,
FormKind::Meta(_, inner) => inner.quoted_value_supports_meta(),
_ => false,
}
}
pub fn takes_runtime_meta(&self) -> bool {
match &self.kind {
FormKind::Vector(_) | FormKind::Map(_) | FormKind::Set(_) | FormKind::AnonFn(_) => true,
FormKind::List(parts) if parts.is_empty() => true,
FormKind::List(_) => self.is_fn_form(),
FormKind::Meta(_, inner) => inner.takes_runtime_meta(),
_ => false,
}
}
pub fn is_fn_form(&self) -> bool {
match &self.kind {
FormKind::List(parts) => matches!(
parts.first().map(|f| &f.kind),
Some(FormKind::Symbol(s)) if s == "fn" || s == "fn*"
),
_ => false,
}
}
pub fn requests_async(&self) -> bool {
match &self.kind {
FormKind::Keyword(k) => k == "async",
FormKind::Map(entries) => entries.chunks(2).any(|kv| {
matches!(&kv[0].kind, FormKind::Keyword(k) if k == "async")
&& !matches!(
kv.get(1).map(|f| &f.kind),
None | Some(FormKind::Bool(false)) | Some(FormKind::Nil)
)
}),
_ => false,
}
}
pub fn is_async_fn_form(&self) -> bool {
let (metas, inner) = self.peel_meta();
if !inner.is_fn_form() {
return false;
}
if metas.iter().any(|m| m.requests_async()) {
return true;
}
let FormKind::List(parts) = &inner.kind else {
return false;
};
Form::fn_args_request_async(&parts[1..])
}
pub fn fn_args_request_async(args: &[Form]) -> bool {
let requests = |f: &Form| f.peel_meta().0.iter().any(|m| m.requests_async());
let Some(first) = args.first() else {
return false;
};
if requests(first) {
return true;
}
first.as_symbol().is_some() && args.get(1).is_some_and(requests)
}
pub fn peel_meta(&self) -> (Vec<&Form>, &Form) {
let mut metas = Vec::new();
let mut form = self;
while let FormKind::Meta(meta, inner) = &form.kind {
metas.push(meta.as_ref());
form = inner;
}
(metas, form)
}
pub fn as_symbol(&self) -> Option<&str> {
match &self.unmeta().kind {
FormKind::Symbol(s) => Some(s),
_ => None,
}
}
pub fn as_keyword(&self) -> Option<&str> {
match &self.unmeta().kind {
FormKind::Keyword(k) => Some(k),
_ => None,
}
}
pub fn as_string(&self) -> Option<&str> {
match &self.unmeta().kind {
FormKind::Str(s) => Some(s),
_ => None,
}
}
pub fn as_list(&self) -> Option<&[Form]> {
match &self.unmeta().kind {
FormKind::List(v) => Some(v),
_ => None,
}
}
pub fn as_vector(&self) -> Option<&[Form]> {
match &self.unmeta().kind {
FormKind::Vector(v) => Some(v),
_ => None,
}
}
pub fn as_map(&self) -> Option<&[Form]> {
match &self.unmeta().kind {
FormKind::Map(v) => Some(v),
_ => None,
}
}
}
impl FormKind {
pub fn heap_size(&self) -> usize {
match self {
FormKind::Nil
| FormKind::Bool(_)
| FormKind::Int(_)
| FormKind::Float(_)
| FormKind::Char(_)
| FormKind::Symbolic(_) => 0,
FormKind::BigInt(s)
| FormKind::BigDecimal(s)
| FormKind::Ratio(s)
| FormKind::Str(s)
| FormKind::Regex(s)
| FormKind::Symbol(s)
| FormKind::Keyword(s)
| FormKind::AutoKeyword(s)
| FormKind::AutoSymbol(s) => s.capacity(),
FormKind::List(v)
| FormKind::Vector(v)
| FormKind::Map(v)
| FormKind::Set(v)
| FormKind::AnonFn(v) => vec_heap_size(v),
FormKind::Quote(f)
| FormKind::SyntaxQuote(f)
| FormKind::Unquote(f)
| FormKind::UnquoteSplice(f)
| FormKind::Deref(f)
| FormKind::Var(f) => mem::size_of::<Form>() + f.heap_size(),
FormKind::Meta(a, b) => mem::size_of::<Form>() * 2 + a.heap_size() + b.heap_size(),
FormKind::TaggedLiteral(s, f) => s.capacity() + mem::size_of::<Form>() + f.heap_size(),
FormKind::ReaderCond { clauses, .. } => vec_heap_size(clauses),
}
}
}
impl PartialEq for Form {
fn eq(&self, other: &Self) -> bool {
self.kind == other.kind
}
}
#[derive(Debug)]
pub struct CachedExpansion {
pub macro_id: u64,
pub ns: Arc<str>,
pub form: Arc<Form>,
}
#[derive(Default)]
pub struct ExpansionCache(OnceLock<Arc<ExpansionSlot>>);
type ExpansionSlot = Mutex<Option<Arc<CachedExpansion>>>;
impl ExpansionCache {
pub fn get(&self, macro_id: u64, ns: &str) -> Option<Arc<Form>> {
let slot = self.0.get()?.lock().unwrap_or_else(|e| e.into_inner());
match slot.as_ref() {
Some(c) if c.macro_id == macro_id && c.ns.as_ref() == ns => Some(c.form.clone()),
_ => None,
}
}
pub fn store(&self, macro_id: u64, ns: Arc<str>, form: Form) -> Arc<Form> {
let form = Arc::new(form);
let entry = Arc::new(CachedExpansion {
macro_id,
ns,
form: form.clone(),
});
*self.slot().lock().unwrap_or_else(|e| e.into_inner()) = Some(entry);
form
}
pub fn clear(&self) {
if let Some(slot) = self.0.get() {
*slot.lock().unwrap_or_else(|e| e.into_inner()) = None;
}
}
fn share(&self) -> Self {
Self(OnceLock::from(self.slot().clone()))
}
fn slot(&self) -> &Arc<ExpansionSlot> {
self.0.get_or_init(Default::default)
}
}
impl std::fmt::Debug for ExpansionCache {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("ExpansionCache")
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum FormKind {
Nil,
Bool(bool),
Int(i64),
BigInt(String),
Float(f64), BigDecimal(String),
Ratio(String),
Char(char),
Str(String),
Regex(String),
Symbolic(f64),
Symbol(String),
Keyword(String),
AutoKeyword(String),
AutoSymbol(String),
List(Vec<Form>),
Vector(Vec<Form>),
Map(Vec<Form>),
Set(Vec<Form>),
Quote(Box<Form>),
SyntaxQuote(Box<Form>),
Unquote(Box<Form>),
UnquoteSplice(Box<Form>),
Deref(Box<Form>),
Var(Box<Form>),
Meta(Box<Form>, Box<Form>),
AnonFn(Vec<Form>),
TaggedLiteral(String, Box<Form>),
ReaderCond {
splicing: bool,
clauses: Vec<Form>,
},
}
fn vec_heap_size(forms: &[Form]) -> usize {
mem::size_of_val(forms) + forms.iter().map(|f| f.heap_size()).sum::<usize>()
}
#[cfg(test)]
mod expansion_cache_tests {
use super::*;
fn span() -> Span {
Span::new(Arc::new("<t>".to_string()), 0, 0, 1, 1)
}
fn list() -> Form {
Form::new(
FormKind::List(vec![Form::new(FormKind::Symbol("m".into()), span())]),
span(),
)
}
#[test]
fn empty_until_stored() {
assert!(list().expansion.get(1, "user").is_none());
}
#[test]
fn hit_requires_same_macro_and_namespace() {
let site = list();
site.expansion
.store(7, Arc::from("user"), Form::new(FormKind::Int(1), span()));
assert!(site.expansion.get(7, "user").is_some());
assert!(site.expansion.get(8, "user").is_none());
assert!(site.expansion.get(7, "other").is_none());
}
#[test]
fn clones_of_a_list_share_the_slot() {
let site = list();
let copy = site.clone();
copy.expansion
.store(7, Arc::from("user"), Form::new(FormKind::Int(1), span()));
assert!(site.expansion.get(7, "user").is_some());
site.expansion.clear();
assert!(copy.expansion.get(7, "user").is_none());
}
#[test]
fn non_list_clones_do_not_allocate_a_slot() {
let sym = Form::new(FormKind::Symbol("x".into()), span());
let copy = sym.clone();
assert!(sym.expansion.0.get().is_none());
assert!(copy.expansion.0.get().is_none());
}
}