1use crate::builtins::SPECIAL_FORMS;
7use crate::builtins::form::{expand_reader_conds_cow, form_to_value, select_reader_cond};
8use crate::env::env::Env;
9use crate::env::error::{EvalError, EvalResult};
10use cljrs_gc::GcPtr;
11use cljrs_reader::Form;
12use cljrs_reader::form::FormKind;
13use cljrs_value::value::SetValue;
14use cljrs_value::{Keyword, PersistentList, PersistentVector, Symbol, Value};
15use std::sync::Arc;
16use std::sync::atomic::{AtomicU64, Ordering};
17
18static GENSYM_COUNTER: AtomicU64 = AtomicU64::new(0);
19
20pub fn syntax_quote(form: &Form, env: &mut Env) -> EvalResult {
22 let mut gensyms = std::collections::HashMap::new();
23 sq_form(form, env, &mut gensyms)
24}
25
26fn sq_form(
27 form: &Form,
28 env: &mut Env,
29 gensyms: &mut std::collections::HashMap<String, Arc<str>>,
30) -> EvalResult {
31 match &form.kind {
32 FormKind::Unquote(inner) => crate::interp::eval::eval(inner, env),
34
35 FormKind::UnquoteSplice(_) => Err(EvalError::Runtime(
37 "splice-unquote outside list/vector context".into(),
38 )),
39
40 FormKind::Symbol(s) => {
42 let qualified = qualify_symbol(s, env, gensyms);
43 Ok(Value::symbol(Symbol::parse(&qualified)))
44 }
45
46 FormKind::Nil => Ok(Value::Nil),
48 FormKind::Bool(b) => Ok(Value::Bool(*b)),
49 FormKind::Int(n) => Ok(Value::Long(*n)),
50 FormKind::Float(f) => Ok(Value::Double(*f)),
51 FormKind::Str(s) => Ok(Value::string(s.clone())),
52 FormKind::Char(c) => Ok(Value::Char(*c)),
53 FormKind::Keyword(s) => Ok(Value::keyword(Keyword::parse(s))),
54 FormKind::AutoKeyword(s) => {
55 let full = env
56 .globals
57 .resolve_auto_keyword(&env.current_ns, s)
58 .map_err(EvalError::Runtime)?;
59 Ok(Value::keyword(Keyword::parse(&full)))
60 }
61 FormKind::AutoSymbol(s) => {
62 let full = env
63 .globals
64 .resolve_auto_keyword(&env.current_ns, s)
65 .map_err(EvalError::Runtime)?;
66 Ok(Value::symbol(Symbol::parse(&full)))
67 }
68
69 FormKind::List(forms) => {
71 let parts = sq_seq(forms, env, gensyms)?;
72 let mut out: Vec<Value> = Vec::new();
74 for part in parts {
75 match part {
76 Segment::One(v) => out.push(v),
77 Segment::Many(vs) => out.extend(vs),
78 }
79 }
80 Ok(Value::List(GcPtr::new(PersistentList::from_iter(out))))
81 }
82
83 FormKind::Vector(forms) => {
85 let parts = sq_seq(forms, env, gensyms)?;
86 let mut out: Vec<Value> = Vec::new();
87 for part in parts {
88 match part {
89 Segment::One(v) => out.push(v),
90 Segment::Many(vs) => out.extend(vs),
91 }
92 }
93 Ok(Value::Vector(GcPtr::new(PersistentVector::from_iter(out))))
94 }
95
96 FormKind::Map(forms) => {
98 let parts = sq_seq(forms, env, gensyms)?;
99 let mut out: Vec<Value> = Vec::new();
100 for part in parts {
101 match part {
102 Segment::One(v) => out.push(v),
103 Segment::Many(vs) => out.extend(vs),
104 }
105 }
106 if !out.len().is_multiple_of(2) {
107 return Err(EvalError::Runtime(
108 "syntax-quote map requires even number of forms".into(),
109 ));
110 }
111 let mut m = cljrs_value::MapValue::empty();
112 for pair in out.chunks(2) {
113 m = m.assoc(pair[0].clone(), pair[1].clone());
114 }
115 Ok(Value::Map(m))
116 }
117
118 FormKind::Set(forms) => {
120 let parts = sq_seq(forms, env, gensyms)?;
121 let mut out: Vec<Value> = Vec::new();
122 for part in parts {
123 match part {
124 Segment::One(v) => out.push(v),
125 Segment::Many(vs) => out.extend(vs),
126 }
127 }
128 let set = out
129 .into_iter()
130 .fold(cljrs_value::PersistentHashSet::empty(), |s, v| s.conj(v));
131 Ok(Value::Set(SetValue::Hash(GcPtr::new(set))))
132 }
133
134 FormKind::Quote(inner) => {
137 let processed = sq_form(inner, env, gensyms)?;
138 Ok(Value::List(GcPtr::new(PersistentList::from_iter([
139 Value::symbol(Symbol::simple("quote")),
140 processed,
141 ]))))
142 }
143
144 FormKind::ReaderCond {
146 splicing: false,
147 clauses,
148 } => match select_reader_cond(clauses) {
149 Some(selected) => sq_form(selected, env, gensyms),
150 None => Ok(Value::Nil),
151 },
152
153 FormKind::ReaderCond { splicing: true, .. } => Err(EvalError::Runtime(
155 "splicing reader conditional not in a sequence context".into(),
156 )),
157
158 FormKind::AnonFn(body) => {
160 let expanded = crate::builtins::form::expand_anon_fn(body, form.span.clone());
161 sq_form(&expanded, env, gensyms)
162 }
163
164 _other => form_to_value(form),
166 }
167}
168
169enum Segment {
170 One(Value),
171 Many(Vec<Value>),
172}
173
174fn sq_seq(
177 forms: &[Form],
178 env: &mut Env,
179 gensyms: &mut std::collections::HashMap<String, Arc<str>>,
180) -> EvalResult<Vec<Segment>> {
181 let forms = expand_reader_conds_cow(forms);
182 let mut out = Vec::with_capacity(forms.len());
183 for f in forms.iter() {
184 match &f.kind {
185 FormKind::UnquoteSplice(inner) => {
186 let v = crate::interp::eval::eval(inner, env)?;
188 let items = crate::interp::destructure::value_to_seq_vec(&v);
189 out.push(Segment::Many(items));
190 }
191 _ => {
192 let v = sq_form(f, env, gensyms)?;
193 out.push(Segment::One(v));
194 }
195 }
196 }
197 Ok(out)
198}
199
200fn qualify_symbol(
212 s: &str,
213 env: &Env,
214 gensyms: &mut std::collections::HashMap<String, Arc<str>>,
215) -> String {
216 if let Some(idx) = s.find('/') {
219 if idx > 0 && idx < s.len() - 1 {
220 let (ns_part, name_part) = (&s[..idx], &s[idx + 1..]);
221 let resolved_ns = env
222 .globals
223 .resolve_alias(&env.current_ns, ns_part)
224 .unwrap_or_else(|| Arc::from(ns_part));
225 return format!("{resolved_ns}/{name_part}");
226 }
227 return s.to_string();
228 }
229 if matches!(s, "nil" | "true" | "false") {
231 return s.to_string();
232 }
233 if let Some(base) = s.strip_suffix('#') {
235 let generated = gensyms.entry(s.to_string()).or_insert_with(|| {
236 let n = crate::env::policy::next_transaction_gensym()
237 .unwrap_or_else(|| GENSYM_COUNTER.fetch_add(1, Ordering::Relaxed));
238 Arc::from(format!("{base}__{n}__auto__"))
239 });
240 return generated.as_ref().to_string();
241 }
242 if SPECIAL_FORMS.contains(&s)
244 || matches!(s, "catch" | "finally" | "Exception" | "Throwable" | "Error")
245 {
246 return s.to_string();
247 }
248 if let Some(var_ptr) = env.globals.lookup_var_in_ns(&env.current_ns, s) {
251 let var_ns = var_ptr.get().namespace.as_ref().to_string();
252 return format!("{var_ns}/{s}");
253 }
254 format!("{}/{}", env.current_ns, s)
256}