use cljrs_builtins::SPECIAL_FORMS;
use cljrs_builtins::form::form_to_value;
use cljrs_env::env::Env;
use cljrs_env::error::{EvalError, EvalResult};
use cljrs_gc::GcPtr;
use cljrs_reader::Form;
use cljrs_reader::form::FormKind;
use cljrs_value::value::SetValue;
use cljrs_value::{Keyword, PersistentList, PersistentVector, Symbol, Value};
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
static GENSYM_COUNTER: AtomicU64 = AtomicU64::new(0);
pub fn syntax_quote(form: &Form, env: &mut Env) -> EvalResult {
let mut gensyms = std::collections::HashMap::new();
sq_form(form, env, &mut gensyms)
}
fn sq_form(
form: &Form,
env: &mut Env,
gensyms: &mut std::collections::HashMap<String, Arc<str>>,
) -> EvalResult {
match &form.kind {
FormKind::Unquote(inner) => crate::eval::eval(inner, env),
FormKind::UnquoteSplice(_) => Err(EvalError::Runtime(
"splice-unquote outside list/vector context".into(),
)),
FormKind::Symbol(s) => {
let qualified = qualify_symbol(s, env, gensyms);
Ok(Value::symbol(Symbol::parse(&qualified)))
}
FormKind::Nil => Ok(Value::Nil),
FormKind::Bool(b) => Ok(Value::Bool(*b)),
FormKind::Int(n) => Ok(Value::Long(*n)),
FormKind::Float(f) => Ok(Value::Double(*f)),
FormKind::Str(s) => Ok(Value::string(s.clone())),
FormKind::Char(c) => Ok(Value::Char(*c)),
FormKind::Keyword(s) => Ok(Value::keyword(Keyword::parse(s))),
FormKind::AutoKeyword(s) => {
let full = format!("{}/{}", env.current_ns, s);
Ok(Value::keyword(Keyword::parse(&full)))
}
FormKind::List(forms) => {
let parts = sq_seq(forms, env, gensyms)?;
let mut out: Vec<Value> = Vec::new();
for part in parts {
match part {
Segment::One(v) => out.push(v),
Segment::Many(vs) => out.extend(vs),
}
}
Ok(Value::List(GcPtr::new(PersistentList::from_iter(out))))
}
FormKind::Vector(forms) => {
let parts = sq_seq(forms, env, gensyms)?;
let mut out: Vec<Value> = Vec::new();
for part in parts {
match part {
Segment::One(v) => out.push(v),
Segment::Many(vs) => out.extend(vs),
}
}
Ok(Value::Vector(GcPtr::new(PersistentVector::from_iter(out))))
}
FormKind::Map(forms) => {
let parts = sq_seq(forms, env, gensyms)?;
let mut out: Vec<Value> = Vec::new();
for part in parts {
match part {
Segment::One(v) => out.push(v),
Segment::Many(vs) => out.extend(vs),
}
}
if !out.len().is_multiple_of(2) {
return Err(EvalError::Runtime(
"syntax-quote map requires even number of forms".into(),
));
}
let mut m = cljrs_value::MapValue::empty();
for pair in out.chunks(2) {
m = m.assoc(pair[0].clone(), pair[1].clone());
}
Ok(Value::Map(m))
}
FormKind::Set(forms) => {
let parts = sq_seq(forms, env, gensyms)?;
let mut out: Vec<Value> = Vec::new();
for part in parts {
match part {
Segment::One(v) => out.push(v),
Segment::Many(vs) => out.extend(vs),
}
}
let set = out
.into_iter()
.fold(cljrs_value::PersistentHashSet::empty(), |s, v| s.conj(v));
Ok(Value::Set(SetValue::Hash(GcPtr::new(set))))
}
FormKind::Quote(inner) => {
let processed = sq_form(inner, env, gensyms)?;
Ok(Value::List(GcPtr::new(PersistentList::from_iter([
Value::symbol(Symbol::simple("quote")),
processed,
]))))
}
FormKind::AnonFn(body) => {
let expanded = cljrs_builtins::form::expand_anon_fn(body, form.span.clone());
sq_form(&expanded, env, gensyms)
}
_other => Ok(form_to_value(form)),
}
}
enum Segment {
One(Value),
Many(Vec<Value>),
}
fn sq_seq(
forms: &[Form],
env: &mut Env,
gensyms: &mut std::collections::HashMap<String, Arc<str>>,
) -> EvalResult<Vec<Segment>> {
let mut out = Vec::with_capacity(forms.len());
for f in forms {
match &f.kind {
FormKind::UnquoteSplice(inner) => {
let v = crate::eval::eval(inner, env)?;
let items = crate::destructure::value_to_seq_vec(&v);
out.push(Segment::Many(items));
}
_ => {
let v = sq_form(f, env, gensyms)?;
out.push(Segment::One(v));
}
}
}
Ok(out)
}
fn qualify_symbol(
s: &str,
env: &Env,
gensyms: &mut std::collections::HashMap<String, Arc<str>>,
) -> String {
if s.contains('/') {
return s.to_string();
}
if matches!(s, "nil" | "true" | "false") {
return s.to_string();
}
if let Some(base) = s.strip_suffix('#') {
let generated = gensyms.entry(s.to_string()).or_insert_with(|| {
let n = GENSYM_COUNTER.fetch_add(1, Ordering::Relaxed);
Arc::from(format!("{base}__{n}__auto__"))
});
return generated.as_ref().to_string();
}
if SPECIAL_FORMS.contains(&s)
|| matches!(s, "catch" | "finally" | "Exception" | "Throwable" | "Error")
{
return s.to_string();
}
if let Some(var_ptr) = env.globals.lookup_var_in_ns(&env.current_ns, s) {
let var_ns = var_ptr.get().namespace.as_ref().to_string();
return format!("{var_ns}/{s}");
}
format!("{}/{}", env.current_ns, s)
}