calcit/
runner.rs

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  // println!("eval code: {}", expr.lisp_str());
16
17  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            // dirty check for namespaced imported variables
31            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        // println!("eval expr: {}", expr.lisp_str());
56        // println!("eval expr: {}", x);
57
58        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              // println!("proc: {}", expr);
69              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            // TODO moving to preprocess
114            let mut current_values = Box::new(rest_nodes.to_owned());
115            // println!("eval macro: {} {}", x, expr.lisp_str()));
116            // println!("macro... {} {}", x, CrListWrap(current_values.to_owned()));
117
118            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              // need to handle recursion
129              let body_scope = bind_args(args, &current_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                  // println!("gen code: {} {}", x, &code.lisp_str()));
137                  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        // although scope is detected first, it would trigger warning during preprocess
204        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    // extra check to make sure code in thunks evaluated
228    Calcit::Thunk(code, None) => {
229      let evaled_v = evaluate_expr(&code, scope, file_ns.into(), call_stack)?;
230      // and write back to program state to fix duplicated evalution
231      // still using thunk since js and IR requires bare code
232      let next = if builtins::effects::is_rust_eval() {
233        // no longer useful for evaling
234        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, &current_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
299/// create new scope by writing new args
300/// notice that `&` is a mark for spreading, `?` for optional arguments
301pub 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
410/// evaluate symbols before calling a function
411/// notice that `&` is used to spread a list
412pub 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                // extract thunk before calling functions
433                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          // extract thunk before calling functions
453          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}