1pub mod preprocess;
2pub mod track;
3
4use im_ternary_tree::TernaryTreeList;
5use std::sync::{Arc, RwLock};
6
7use crate::builtins;
8use crate::builtins::is_proc_name;
9use crate::call_stack::{extend_call_stack, CallStackList, StackKind};
10use crate::primes::{Calcit, CalcitErr, CalcitItems, CalcitScope, CalcitSyntax, CrListWrap, SymbolResolved::*, CORE_NS};
11use crate::program;
12use crate::util::string::has_ns_part;
13
14pub fn evaluate_expr(expr: &Calcit, scope: &CalcitScope, file_ns: Arc<str>, call_stack: &CallStackList) -> Result<Calcit, CalcitErr> {
15 match expr {
18 Calcit::Nil => Ok(expr.to_owned()),
19 Calcit::Bool(_) => Ok(expr.to_owned()),
20 Calcit::Number(_) => Ok(expr.to_owned()),
21 Calcit::Symbol { sym, .. } if &**sym == "&" => Ok(expr.to_owned()),
22 Calcit::Symbol { sym, ns, resolved, .. } => match resolved {
23 Some(resolved_info) => match &*resolved_info.to_owned() {
24 ResolvedDef {
25 ns: r_ns,
26 def: r_def,
27 rule,
28 } => {
29 if rule.is_some() && sym != r_def {
30 return eval_symbol_from_program(r_def, r_ns, call_stack);
32 }
33 evaluate_symbol(r_def, scope, r_ns, call_stack)
34 }
35 _ => evaluate_symbol(sym, scope, ns, call_stack),
36 },
37 _ => evaluate_symbol(sym, scope, ns, call_stack),
38 },
39 Calcit::Keyword(_) => Ok(expr.to_owned()),
40 Calcit::Str(_) => Ok(expr.to_owned()),
41 Calcit::Thunk(code, v) => match v {
42 None => evaluate_expr(code, scope, file_ns, call_stack),
43 Some(data) => Ok((**data).to_owned()),
44 },
45 Calcit::Ref(_) => Ok(expr.to_owned()),
46 Calcit::Tuple(..) => Ok(expr.to_owned()),
47 Calcit::Buffer(..) => Ok(expr.to_owned()),
48 Calcit::Recur(_) => unreachable!("recur not expected to be from symbol"),
49 Calcit::List(xs) => match xs.get(0) {
50 None => Err(CalcitErr::use_msg_stack(
51 format!("cannot evaluate empty expr: {}", expr),
52 call_stack,
53 )),
54 Some(x) => {
55 let v = evaluate_expr(x, scope, file_ns.to_owned(), call_stack)?;
59 let rest_nodes = xs.drop_left();
60 let ret = match &v {
61 Calcit::Proc(p) => {
62 let values = evaluate_args(&rest_nodes, scope, file_ns.to_owned(), call_stack)?;
63 let next_stack = extend_call_stack(call_stack, file_ns, p.to_owned(), StackKind::Proc, Calcit::Nil, &values);
64
65 if p.starts_with('.') {
66 builtins::meta::invoke_method(p.strip_prefix('.').unwrap(), &values, &next_stack)
67 } else {
68 builtins::handle_proc(p, &values, call_stack)
70 }
71 }
72 Calcit::Syntax(s, def_ns) => {
73 let next_stack = extend_call_stack(
74 call_stack,
75 file_ns,
76 s.to_string().into(),
77 StackKind::Syntax,
78 expr.to_owned(),
79 &rest_nodes,
80 );
81
82 builtins::handle_syntax(s, &rest_nodes, scope, def_ns.to_owned(), &next_stack).map_err(|e| {
83 if e.stack.is_empty() {
84 let mut e2 = e;
85 e2.stack = call_stack.to_owned();
86 e2
87 } else {
88 e
89 }
90 })
91 }
92 Calcit::Fn {
93 name,
94 def_ns,
95 scope: def_scope,
96 args,
97 body,
98 ..
99 } => {
100 let values = evaluate_args(&rest_nodes, scope, file_ns.to_owned(), call_stack)?;
101 let next_stack = extend_call_stack(call_stack, file_ns, name.to_owned(), StackKind::Fn, expr.to_owned(), &values);
102
103 run_fn(&values, def_scope, args, body, def_ns.to_owned(), &next_stack)
104 }
105 Calcit::Macro {
106 name, def_ns, args, body, ..
107 } => {
108 println!(
109 "[Warn] macro should already be handled during preprocessing: {}",
110 Calcit::List(xs.to_owned()).lisp_str()
111 );
112
113 let mut current_values = Box::new(rest_nodes.to_owned());
115 let next_stack = extend_call_stack(
119 call_stack,
120 file_ns.to_owned(),
121 name.to_owned(),
122 StackKind::Macro,
123 expr.to_owned(),
124 &rest_nodes,
125 );
126
127 Ok(loop {
128 let body_scope = bind_args(args, ¤t_values, &rpds::HashTrieMap::new_sync(), call_stack)?;
130 let code = evaluate_lines(body, &body_scope, def_ns.to_owned(), &next_stack)?;
131 match code {
132 Calcit::Recur(ys) => {
133 current_values = Box::new(ys.to_owned());
134 }
135 _ => {
136 break evaluate_expr(&code, scope, file_ns, &next_stack)?;
138 }
139 }
140 })
141 }
142 Calcit::Keyword(k) => {
143 if rest_nodes.len() == 1 {
144 let v = evaluate_expr(&rest_nodes[0], scope, file_ns, call_stack)?;
145
146 if let Calcit::Map(m) = v {
147 match m.get(&Calcit::Keyword(k.to_owned())) {
148 Some(value) => Ok(value.to_owned()),
149 None => Ok(Calcit::Nil),
150 }
151 } else {
152 Err(CalcitErr::use_msg_stack(format!("expected a hashmap, got {}", v), call_stack))
153 }
154 } else {
155 Err(CalcitErr::use_msg_stack(
156 format!("keyword only takes 1 argument, got: {}", CrListWrap(rest_nodes)),
157 call_stack,
158 ))
159 }
160 }
161 Calcit::Symbol { sym, ns, at_def, resolved } => Err(CalcitErr::use_msg_stack(
162 format!("cannot evaluate symbol directly: {}/{} in {}, {:?}", ns, sym, at_def, resolved),
163 call_stack,
164 )),
165 a => Err(CalcitErr::use_msg_stack(
166 format!("cannot be used as operator: {} in {}", a, CrListWrap(xs.to_owned())),
167 call_stack,
168 )),
169 };
170
171 ret
172 }
173 },
174 Calcit::Set(_) => Err(CalcitErr::use_msg_stack("unexpected set for expr", call_stack)),
175 Calcit::Map(_) => Err(CalcitErr::use_msg_stack("unexpected map for expr", call_stack)),
176 Calcit::Record(..) => Err(CalcitErr::use_msg_stack("unexpected record for expr", call_stack)),
177 Calcit::Proc(_) => Ok(expr.to_owned()),
178 Calcit::Macro { .. } => Ok(expr.to_owned()),
179 Calcit::Fn { .. } => Ok(expr.to_owned()),
180 Calcit::Syntax(_, _) => Ok(expr.to_owned()),
181 }
182}
183
184pub fn evaluate_symbol(sym: &str, scope: &CalcitScope, file_ns: &str, call_stack: &CallStackList) -> Result<Calcit, CalcitErr> {
185 let v = match parse_ns_def(sym) {
186 Some((ns_part, def_part)) => match program::lookup_ns_target_in_import(file_ns.into(), &ns_part) {
187 Some(target_ns) => match eval_symbol_from_program(&def_part, &target_ns, call_stack) {
188 Ok(v) => Ok(v),
189 Err(e) => Err(e),
190 },
191 None => Err(CalcitErr::use_msg_stack(
192 format!("unknown ns target: {}/{}", ns_part, def_part),
193 call_stack,
194 )),
195 },
196 None => {
197 if CalcitSyntax::is_core_syntax(sym) {
198 Ok(Calcit::Syntax(
199 CalcitSyntax::from(sym).map_err(|e| CalcitErr::use_msg_stack(e, call_stack))?,
200 file_ns.to_owned().into(),
201 ))
202 } else if scope.contains_key(sym) {
203 Ok(scope.get(sym).unwrap().to_owned())
205 } else if is_proc_name(sym) {
206 Ok(Calcit::Proc(sym.to_owned().into()))
207 } else if program::lookup_def_code(CORE_NS, sym).is_some() {
208 eval_symbol_from_program(sym, CORE_NS, call_stack)
209 } else if program::has_def_code(file_ns, sym) {
210 eval_symbol_from_program(sym, file_ns, call_stack)
211 } else {
212 match program::lookup_def_target_in_import(file_ns, sym) {
213 Some(target_ns) => eval_symbol_from_program(sym, &target_ns, call_stack),
214 None => {
215 let vars: Vec<&Arc<str>> = scope.keys().collect();
216 Err(CalcitErr::use_msg_stack(
217 format!("unknown symbol `{}` in {:?}", sym, vars),
218 call_stack,
219 ))
220 }
221 }
222 }
223 }
224 }?;
225 match v {
226 Calcit::Thunk(_code, Some(data)) => Ok((*data).to_owned()),
227 Calcit::Thunk(code, None) => {
229 let evaled_v = evaluate_expr(&code, scope, file_ns.into(), call_stack)?;
230 let next = if builtins::effects::is_rust_eval() {
233 Arc::new(Calcit::Nil)
235 } else {
236 code
237 };
238 program::write_evaled_def(file_ns, sym, Calcit::Thunk(next, Some(Arc::new(evaled_v.to_owned()))))?;
239 Ok(evaled_v)
240 }
241 _ => Ok(v),
242 }
243}
244
245pub fn parse_ns_def(s: &str) -> Option<(Arc<str>, Arc<str>)> {
246 if !has_ns_part(s) {
247 return None;
248 }
249 let pieces: Vec<&str> = s.split('/').collect();
250 if pieces.len() == 2 {
251 if !pieces[0].is_empty() && !pieces[1].is_empty() {
252 Some((pieces[0].to_owned().into(), pieces[1].to_owned().into()))
253 } else {
254 None
255 }
256 } else {
257 None
258 }
259}
260
261fn eval_symbol_from_program(sym: &str, ns: &str, call_stack: &CallStackList) -> Result<Calcit, CalcitErr> {
262 match program::lookup_evaled_def(ns, sym) {
263 Some(v) => Ok(v),
264 None => match program::lookup_def_code(ns, sym) {
265 Some(code) => {
266 let v = evaluate_expr(&code, &rpds::HashTrieMap::new_sync(), ns.into(), call_stack)?;
267 program::write_evaled_def(ns, sym, v.to_owned()).map_err(|e| CalcitErr::use_msg_stack(e, call_stack))?;
268 Ok(v)
269 }
270 None => Err(CalcitErr::use_msg_stack(
271 format!("cannot find code for def: {}/{}", ns, sym),
272 call_stack,
273 )),
274 },
275 }
276}
277
278pub fn run_fn(
279 values: &CalcitItems,
280 scope: &CalcitScope,
281 args: &[Arc<str>],
282 body: &CalcitItems,
283 file_ns: Arc<str>,
284 call_stack: &CallStackList,
285) -> Result<Calcit, CalcitErr> {
286 let mut current_values = Box::new(values.to_owned());
287 loop {
288 let body_scope = bind_args(args, ¤t_values, scope, call_stack)?;
289 let v = evaluate_lines(body, &body_scope, file_ns.to_owned(), call_stack)?;
290 match v {
291 Calcit::Recur(xs) => {
292 current_values = Box::new(xs.to_owned());
293 }
294 result => return Ok(result),
295 }
296 }
297}
298
299pub fn bind_args(
302 args: &[Arc<str>],
303 values: &CalcitItems,
304 base_scope: &CalcitScope,
305 call_stack: &CallStackList,
306) -> Result<CalcitScope, CalcitErr> {
307 let mut scope = base_scope.to_owned();
308 let mut spreading = false;
309 let mut optional = false;
310
311 let collected_args = args.to_owned();
312 let collected_values = Arc::new(values);
313 let pop_args_idx = Arc::new(RwLock::new(0));
314 let pop_values_idx = Arc::new(RwLock::new(0));
315
316 let args_pop_front = || -> Option<Arc<str>> {
317 let mut p = (*pop_args_idx).write().unwrap();
318 let ret = collected_args.get(*p);
319 *p += 1;
320 ret.map(|x| x.to_owned())
321 };
322
323 let values_pop_front = || -> Option<&Calcit> {
324 let mut p = (*pop_values_idx).write().unwrap();
325 let ret = collected_values.get(*p);
326 *p += 1;
327 ret
328 };
329 let is_args_empty = || -> bool {
330 let p = (*pop_args_idx).read().unwrap();
331 *p >= (*collected_args).len()
332 };
333 let is_values_empty = || -> bool {
334 let p = (*pop_values_idx).read().unwrap();
335 *p >= (*collected_values).len()
336 };
337
338 while let Some(sym) = args_pop_front() {
339 if spreading {
340 match &*sym {
341 "&" => return Err(CalcitErr::use_msg_stack(format!("invalid & in args: {:?}", args), call_stack)),
342 "?" => return Err(CalcitErr::use_msg_stack(format!("invalid ? in args: {:?}", args), call_stack)),
343 _ => {
344 let mut chunk: CalcitItems = TernaryTreeList::Empty;
345 while let Some(v) = values_pop_front() {
346 chunk = chunk.push_right(v.to_owned());
347 }
348 scope.insert_mut(sym.to_owned(), Calcit::List(chunk));
349 if !is_args_empty() {
350 return Err(CalcitErr::use_msg_stack(
351 format!("extra args `{:?}` after spreading in `{:?}`", collected_args, args,),
352 call_stack,
353 ));
354 }
355 }
356 }
357 } else {
358 match &*sym {
359 "&" => spreading = true,
360 "?" => optional = true,
361 _ => match values_pop_front() {
362 Some(v) => {
363 scope.insert_mut(sym.to_owned(), v.to_owned());
364 }
365 None => {
366 if optional {
367 scope.insert_mut(sym.to_owned(), Calcit::Nil);
368 } else {
369 return Err(CalcitErr::use_msg_stack(
370 format!("too few values `{}` passed to args `{:?}`", CrListWrap(values.to_owned()), args),
371 call_stack,
372 ));
373 }
374 }
375 },
376 }
377 }
378 }
379 if is_values_empty() {
380 Ok(scope)
381 } else {
382 Err(CalcitErr::use_msg_stack(
383 format!(
384 "extra args `{}` not handled while passing values `{}` to args `{:?}`",
385 CrListWrap((*collected_values).to_owned()),
386 CrListWrap(values.to_owned()),
387 args,
388 ),
389 call_stack,
390 ))
391 }
392}
393
394pub fn evaluate_lines(
395 lines: &CalcitItems,
396 scope: &CalcitScope,
397 file_ns: Arc<str>,
398 call_stack: &CallStackList,
399) -> Result<Calcit, CalcitErr> {
400 let mut ret: Calcit = Calcit::Nil;
401 for line in lines {
402 match evaluate_expr(line, scope, file_ns.to_owned(), call_stack) {
403 Ok(v) => ret = v,
404 Err(e) => return Err(e),
405 }
406 }
407 Ok(ret)
408}
409
410pub fn evaluate_args(
413 items: &CalcitItems,
414 scope: &CalcitScope,
415 file_ns: Arc<str>,
416 call_stack: &CallStackList,
417) -> Result<CalcitItems, CalcitErr> {
418 let mut ret: Vec<Calcit> = Vec::with_capacity(items.len());
419 let mut spreading = false;
420 for item in items {
421 match item {
422 Calcit::Symbol { sym: s, .. } if &**s == "&" => {
423 spreading = true;
424 }
425 _ => {
426 let v = evaluate_expr(item, scope, file_ns.to_owned(), call_stack)?;
427
428 if spreading {
429 match v {
430 Calcit::List(xs) => {
431 for x in &xs {
432 let y = match x {
434 Calcit::Thunk(code, v) => match v {
435 None => evaluate_expr(&*code, scope, file_ns.to_owned(), call_stack)?,
436 Some(data) => (**data).to_owned(),
437 },
438 _ => x.to_owned(),
439 };
440 ret.push(y.to_owned());
441 }
442 spreading = false
443 }
444 a => {
445 return Err(CalcitErr::use_msg_stack(
446 format!("expected list for spreading, got: {}", a),
447 call_stack,
448 ))
449 }
450 }
451 } else {
452 let y = match v {
454 Calcit::Thunk(code, value) => match value {
455 None => evaluate_expr(&*code, scope, file_ns.to_owned(), call_stack)?,
456 Some(data) => (*data).to_owned(),
457 },
458 _ => v.to_owned(),
459 };
460 ret.push(y);
461 }
462 }
463 }
464 }
465 Ok(TernaryTreeList::from(&ret))
466}