calcit/runner/
preprocess.rs

1use crate::{
2  builtins::{is_js_syntax_procs, is_proc_name},
3  call_stack::{extend_call_stack, CalcitStack, CallStackList, StackKind},
4  primes,
5  primes::{Calcit, CalcitErr, CalcitItems, CalcitSyntax, ImportRule, SymbolResolved::*},
6  program, runner,
7};
8
9use std::cell::RefCell;
10use std::collections::HashSet;
11use std::sync::Arc;
12
13use im_ternary_tree::TernaryTreeList;
14
15/// only macro and func are cared about during preprocessing
16/// only used in preprocess defs
17fn pick_macro_fn(x: Calcit) -> Option<Calcit> {
18  match &x {
19    Calcit::Fn { .. } | Calcit::Macro { .. } => Some(x),
20    _ => None,
21  }
22}
23
24/// returns the resolved symbol,
25/// if code related is not preprocessed, do it internally
26pub fn preprocess_ns_def(
27  raw_ns: Arc<str>,
28  raw_def: Arc<str>,
29  // pass original string representation, TODO codegen currently relies on this
30  raw_sym: Arc<str>,
31  import_rule: Option<Arc<ImportRule>>, // returns form and possible value
32  check_warnings: &RefCell<Vec<String>>,
33  call_stack: &rpds::ListSync<CalcitStack>,
34) -> Result<(Calcit, Option<Calcit>), CalcitErr> {
35  let ns = &raw_ns;
36  let def = &raw_def;
37  let original_sym = &raw_sym;
38  // println!("preprocessing def: {}/{}", ns, def);
39  match program::lookup_evaled_def(ns, def) {
40    Some(v) => {
41      // println!("{}/{} has inited", ns, def);
42      Ok((
43        Calcit::Symbol {
44          sym: original_sym.to_owned(),
45          ns: ns.to_owned(),
46          at_def: def.to_owned(),
47          resolved: Some(Arc::new(ResolvedDef {
48            ns: ns.to_owned(),
49            def: def.to_owned(),
50            rule: import_rule,
51          })),
52        },
53        pick_macro_fn(v),
54      ))
55    }
56    None => {
57      // println!("init for... {}/{}", ns, def);
58      match program::lookup_def_code(ns, def) {
59        Some(code) => {
60          // write a nil value first to prevent dead loop
61          program::write_evaled_def(ns, def, Calcit::Nil).map_err(|e| CalcitErr::use_msg_stack(e, call_stack))?;
62
63          let next_stack = extend_call_stack(
64            call_stack,
65            ns.to_owned(),
66            def.to_owned(),
67            StackKind::Fn,
68            code.to_owned(),
69            &TernaryTreeList::Empty,
70          );
71
72          let (resolved_code, _resolve_value) = preprocess_expr(&code, &HashSet::new(), ns.to_owned(), check_warnings, &next_stack)?;
73          // println!("\n resolve code to run: {:?}", resolved_code);
74          let v = if is_fn_or_macro(&resolved_code) {
75            match runner::evaluate_expr(&resolved_code, &rpds::HashTrieMap::new_sync(), ns.to_owned(), &next_stack) {
76              Ok(ret) => ret,
77              Err(e) => return Err(e),
78            }
79          } else {
80            Calcit::Thunk(Arc::new(resolved_code), None)
81          };
82          // println!("\nwriting value to: {}/{} {:?}", ns, def, v);
83          program::write_evaled_def(ns, def, v.to_owned()).map_err(|e| CalcitErr::use_msg_stack(e, call_stack))?;
84
85          Ok((
86            Calcit::Symbol {
87              sym: original_sym.to_owned(),
88              ns: ns.to_owned(),
89              at_def: def.to_owned(),
90              resolved: Some(Arc::new(ResolvedDef {
91                ns: ns.to_owned(),
92                def: def.to_owned(),
93                rule: Some(Arc::new(ImportRule::NsReferDef(ns.to_owned(), def.to_owned()))),
94              })),
95            },
96            pick_macro_fn(v),
97          ))
98        }
99        None if ns.starts_with('|') || ns.starts_with('"') => Ok((
100          Calcit::Symbol {
101            sym: original_sym.to_owned(),
102            ns: ns.to_owned(),
103            at_def: def.to_owned(),
104            resolved: Some(Arc::new(ResolvedDef {
105              ns: ns.to_owned(),
106              def: def.to_owned(),
107              rule: import_rule,
108            })),
109          },
110          None,
111        )),
112        None => Err(CalcitErr::use_msg_stack(
113          format!("unknown ns/def in program: {}/{}", ns, def),
114          call_stack,
115        )),
116      }
117    }
118  }
119}
120
121fn is_fn_or_macro(code: &Calcit) -> bool {
122  match code {
123    Calcit::List(xs) => match xs.get(0) {
124      Some(Calcit::Symbol { sym, .. }) => &**sym == "defn" || &**sym == "defmacro",
125      Some(Calcit::Syntax(s, ..)) => s == &CalcitSyntax::Defn || s == &CalcitSyntax::Defmacro,
126      _ => false,
127    },
128    _ => false,
129  }
130}
131
132pub fn preprocess_expr(
133  expr: &Calcit,
134  scope_defs: &HashSet<Arc<str>>,
135  file_ns: Arc<str>,
136  check_warnings: &RefCell<Vec<String>>,
137  call_stack: &CallStackList,
138) -> Result<(Calcit, Option<Calcit>), CalcitErr> {
139  // println!("preprocessing @{} {}", file_ns, expr);
140  match expr {
141    Calcit::Symbol {
142      sym: def,
143      ns: def_ns,
144      at_def,
145      ..
146    } => match runner::parse_ns_def(def) {
147      Some((ns_alias, def_part)) => {
148        if &*ns_alias == "js" {
149          Ok((
150            Calcit::Symbol {
151              sym: def.to_owned(),
152              ns: def_ns.to_owned(),
153              at_def: at_def.to_owned(),
154              resolved: Some(Arc::new(ResolvedDef {
155                ns: String::from("js").into(),
156                def: (*def_part).into(),
157                rule: None,
158              })),
159            },
160            None,
161          ))
162        } else if let Some(target_ns) = program::lookup_ns_target_in_import(def_ns.to_owned(), &ns_alias) {
163          // TODO js syntax to handle in future
164          preprocess_ns_def(target_ns, def_part, def.to_owned(), None, check_warnings, call_stack)
165        } else if program::has_def_code(&ns_alias, &def_part) {
166          // refer to namespace/def directly for some usages
167          preprocess_ns_def(ns_alias.to_owned(), def_part, def.to_owned(), None, check_warnings, call_stack)
168        } else {
169          Err(CalcitErr::use_msg_stack(format!("unknown ns target: {}", def), call_stack))
170        }
171      }
172      None => {
173        let def_ref = &**def;
174        if def_ref == "~" || def_ref == "~@" || def_ref == "&" || def_ref == "?" {
175          Ok((
176            Calcit::Symbol {
177              sym: def.to_owned(),
178              ns: def_ns.to_owned(),
179              at_def: at_def.to_owned(),
180              resolved: Some(Arc::new(ResolvedRaw)),
181            },
182            None,
183          ))
184        } else if scope_defs.contains(def) {
185          Ok((
186            Calcit::Symbol {
187              sym: def.to_owned(),
188              ns: def_ns.to_owned(),
189              at_def: at_def.to_owned(),
190              resolved: Some(Arc::new(ResolvedLocal)),
191            },
192            None,
193          ))
194        } else if CalcitSyntax::is_core_syntax(def) {
195          Ok((
196            Calcit::Syntax(
197              CalcitSyntax::from(def).map_err(|e| CalcitErr::use_msg_stack(e, call_stack))?,
198              def_ns.to_owned(),
199            ),
200            None,
201          ))
202        } else if is_proc_name(def) {
203          Ok((Calcit::Proc(def.to_owned()), None))
204        } else if program::has_def_code(primes::CORE_NS, def) {
205          preprocess_ns_def(
206            primes::CORE_NS.into(),
207            def.to_owned(),
208            def.to_owned(),
209            None,
210            check_warnings,
211            call_stack,
212          )
213        } else if program::has_def_code(def_ns, def) {
214          preprocess_ns_def(def_ns.to_owned(), def.to_owned(), def.to_owned(), None, check_warnings, call_stack)
215        } else {
216          match program::lookup_def_target_in_import(def_ns, def) {
217            Some(target_ns) => {
218              // effect
219              // TODO js syntax to handle in future
220              preprocess_ns_def(target_ns, def.to_owned(), def.to_owned(), None, check_warnings, call_stack)
221            }
222            // TODO check js_mode
223            None if is_js_syntax_procs(def) => Ok((expr.to_owned(), None)),
224            None if def.starts_with('.') => Ok((expr.to_owned(), None)),
225            None => {
226              let from_default = program::lookup_default_target_in_import(def_ns, def);
227              if let Some(target_ns) = from_default {
228                let target = Some(Arc::new(ResolvedDef {
229                  ns: target_ns.to_owned(),
230                  def: def.to_owned(),
231                  rule: Some(Arc::new(ImportRule::NsDefault(target_ns))),
232                }));
233                Ok((
234                  Calcit::Symbol {
235                    sym: def.to_owned(),
236                    ns: def_ns.to_owned(),
237                    at_def: at_def.to_owned(),
238                    resolved: target,
239                  },
240                  None,
241                ))
242              } else {
243                let mut names: Vec<Arc<str>> = Vec::with_capacity(scope_defs.len());
244                for def in scope_defs {
245                  names.push(def.to_owned());
246                }
247                let mut warnings = check_warnings.borrow_mut();
248                warnings.push(format!(
249                  "[Warn] unknown `{}` in {}/{}, locals {{{}}}",
250                  def,
251                  def_ns,
252                  at_def,
253                  names.join(" ")
254                ));
255                Ok((expr.to_owned(), None))
256              }
257            }
258          }
259        }
260      }
261    },
262    Calcit::List(xs) => {
263      if xs.is_empty() {
264        Ok((expr.to_owned(), None))
265      } else {
266        // TODO whether function bothers this...
267        // println!("start calling: {}", expr);
268        process_list_call(xs, scope_defs, file_ns, check_warnings, call_stack)
269      }
270    }
271    Calcit::Number(..) | Calcit::Str(..) | Calcit::Nil | Calcit::Bool(..) | Calcit::Keyword(..) => Ok((expr.to_owned(), None)),
272    Calcit::Proc(..) => {
273      // maybe detect method in future
274      Ok((expr.to_owned(), None))
275    }
276
277    _ => {
278      let mut warnings = check_warnings.borrow_mut();
279      warnings.push(format!("[Warn] unexpected data during preprocess: {:?}", expr));
280      Ok((expr.to_owned(), None))
281    }
282  }
283}
284
285fn process_list_call(
286  xs: &CalcitItems,
287  scope_defs: &HashSet<Arc<str>>,
288  file_ns: Arc<str>,
289  check_warnings: &RefCell<Vec<String>>,
290  call_stack: &CallStackList,
291) -> Result<(Calcit, Option<Calcit>), CalcitErr> {
292  let head = &xs[0];
293  let (head_form, head_evaled) = preprocess_expr(head, scope_defs, file_ns.to_owned(), check_warnings, call_stack)?;
294  let args = xs.drop_left();
295  let def_name = grab_def_name(head);
296
297  // println!(
298  //   "handling list call: {} {:?}, {}",
299  //   primes::CrListWrap(xs.to_owned()),
300  //   head_form,
301  //   if head_evaled.is_some() {
302  //     head_evaled.to_owned().unwrap()
303  //   } else {
304  //     Calcit::Nil
305  //   }
306  // );
307
308  // == Tips ==
309  // Macro from value: will be called during processing
310  // Func from value: for checking arity
311  // Keyword: transforming into keyword expression
312  // Syntax: handled directly during preprocessing
313  // Thunk: invalid here
314  match (&head_form, &head_evaled) {
315    (Calcit::Keyword(..), _) => {
316      if args.len() == 1 {
317        let code = Calcit::List(TernaryTreeList::from(&[
318          Calcit::Symbol {
319            sym: String::from("get").into(),
320            ns: String::from(primes::CORE_NS).into(),
321            at_def: String::from(primes::GENERATED_DEF).into(),
322            resolved: Some(Arc::new(ResolvedDef {
323              ns: String::from(primes::CORE_NS).into(),
324              def: String::from("get").into(),
325              rule: None,
326            })),
327          },
328          args[0].to_owned(),
329          head.to_owned(),
330        ]));
331        preprocess_expr(&code, scope_defs, file_ns, check_warnings, call_stack)
332      } else {
333        Err(CalcitErr::use_msg_stack(format!("{} expected single argument", head), call_stack))
334      }
335    }
336    (
337      _,
338      Some(Calcit::Macro {
339        name,
340        def_ns,
341        args: def_args,
342        body,
343        ..
344      }),
345    ) => {
346      let mut current_values = Box::new(args.to_owned());
347
348      // println!("eval macro: {}", primes::CrListWrap(xs.to_owned()));
349      // println!("macro... {} {}", x, CrListWrap(current_values.to_owned()));
350
351      let code = Calcit::List(xs.to_owned());
352      let next_stack = extend_call_stack(call_stack, def_ns.to_owned(), name.to_owned(), StackKind::Macro, code, &args);
353
354      loop {
355        // need to handle recursion
356        // println!("evaling line: {:?}", body);
357        let body_scope = runner::bind_args(def_args, &current_values, &rpds::HashTrieMap::new_sync(), &next_stack)?;
358        let code = runner::evaluate_lines(body, &body_scope, def_ns.to_owned(), &next_stack)?;
359        match code {
360          Calcit::Recur(ys) => {
361            current_values = Box::new(ys.to_owned());
362          }
363          _ => {
364            // println!("gen code: {} {}", code, &code.lisp_str());
365            return preprocess_expr(&code, scope_defs, file_ns, check_warnings, &next_stack);
366          }
367        }
368      }
369    }
370    (Calcit::Syntax(name, name_ns), _) => match name {
371      CalcitSyntax::Quasiquote => Ok((
372        preprocess_quasiquote(name, name_ns.to_owned(), &args, scope_defs, file_ns, check_warnings, call_stack)?,
373        None,
374      )),
375      CalcitSyntax::Defn | CalcitSyntax::Defmacro => Ok((
376        preprocess_defn(name, name_ns.to_owned(), &args, scope_defs, file_ns, check_warnings, call_stack)?,
377        None,
378      )),
379      CalcitSyntax::CoreLet => Ok((
380        preprocess_call_let(name, name_ns.to_owned(), &args, scope_defs, file_ns, check_warnings, call_stack)?,
381        None,
382      )),
383      CalcitSyntax::If
384      | CalcitSyntax::Try
385      | CalcitSyntax::Macroexpand
386      | CalcitSyntax::MacroexpandAll
387      | CalcitSyntax::Macroexpand1
388      | CalcitSyntax::Reset => Ok((
389        preprocess_each_items(name, name_ns.to_owned(), &args, scope_defs, file_ns, check_warnings, call_stack)?,
390        None,
391      )),
392      CalcitSyntax::Quote | CalcitSyntax::Eval | CalcitSyntax::HintFn => {
393        Ok((preprocess_quote(name, name_ns.to_owned(), &args, scope_defs, file_ns)?, None))
394      }
395      CalcitSyntax::Defatom => Ok((
396        preprocess_defatom(name, name_ns.to_owned(), &args, scope_defs, file_ns, check_warnings, call_stack)?,
397        None,
398      )),
399    },
400    (Calcit::Thunk(..), _) => Err(CalcitErr::use_msg_stack(
401      format!("does not know how to preprocess a thunk: {}", head),
402      call_stack,
403    )),
404
405    (
406      _,
407      Some(Calcit::Fn {
408        name: f_name,
409        args: f_args,
410        ..
411      }),
412    ) => {
413      check_fn_args(f_args, &args, file_ns.to_owned(), f_name.to_owned(), def_name, check_warnings);
414      let mut ys = Vec::with_capacity(args.len() + 1);
415      ys.push(head_form);
416      for a in &args {
417        let (form, _v) = preprocess_expr(a, scope_defs, file_ns.to_owned(), check_warnings, call_stack)?;
418        ys.push(form);
419      }
420      Ok((Calcit::List(TernaryTreeList::from(&ys)), None))
421    }
422    (_, _) => {
423      let mut ys = Vec::with_capacity(args.len() + 1);
424      ys.push(head_form);
425      for a in &args {
426        let (form, _v) = preprocess_expr(a, scope_defs, file_ns.to_owned(), check_warnings, call_stack)?;
427        ys.push(form);
428      }
429      Ok((Calcit::List(TernaryTreeList::from(&ys)), None))
430    }
431  }
432}
433
434// detects arguments of top-level functions when possible
435fn check_fn_args(
436  defined_args: &[Arc<str>],
437  params: &CalcitItems,
438  file_ns: Arc<str>,
439  f_name: Arc<str>,
440  def_name: Arc<str>,
441  check_warnings: &RefCell<Vec<String>>,
442) {
443  let mut i = 0;
444  let mut j = 0;
445  let mut optional = false;
446
447  loop {
448    let d = defined_args.get(i);
449    let r = params.get(j);
450
451    match (d, r) {
452      (None, None) => return,
453      (_, Some(Calcit::Symbol { sym, .. })) if &**sym == "&" => {
454        // dynamic values, can't tell yet
455        return;
456      }
457      (Some(sym), _) if &**sym == "&" => {
458        // dynamic args rule, all okay
459        return;
460      }
461      (Some(sym), _) if &**sym == "?" => {
462        // dynamic args rule, all okay
463        optional = true;
464        i += 1;
465        continue;
466      }
467      (Some(_), None) => {
468        if optional {
469          i += 1;
470          j += 1;
471          continue;
472        } else {
473          let mut warnings = check_warnings.borrow_mut();
474          warnings.push(format!(
475            "[Warn] lack of args in {} `{:?}` with `{}`, at {}/{}",
476            f_name,
477            defined_args,
478            primes::CrListWrap(params.to_owned()),
479            file_ns,
480            def_name
481          ));
482          return;
483        }
484      }
485      (None, Some(_)) => {
486        let mut warnings = check_warnings.borrow_mut();
487        warnings.push(format!(
488          "[Warn] too many args for {} `{:?}` with `{}`, at {}/{}",
489          f_name,
490          defined_args,
491          primes::CrListWrap(params.to_owned()),
492          file_ns,
493          def_name
494        ));
495        return;
496      }
497      (Some(_), Some(_)) => {
498        i += 1;
499        j += 1;
500        continue;
501      }
502    }
503  }
504}
505
506// TODO this native implementation only handles symbols
507fn grab_def_name(x: &Calcit) -> Arc<str> {
508  match x {
509    Calcit::Symbol { at_def: def_name, .. } => def_name.to_owned(),
510    _ => String::from("??").into(),
511  }
512}
513
514// tradition rule for processing exprs
515pub fn preprocess_each_items(
516  head: &CalcitSyntax,
517  head_ns: Arc<str>,
518  args: &CalcitItems,
519  scope_defs: &HashSet<Arc<str>>,
520  file_ns: Arc<str>,
521  check_warnings: &RefCell<Vec<String>>,
522  call_stack: &CallStackList,
523) -> Result<Calcit, CalcitErr> {
524  let mut xs: CalcitItems = TernaryTreeList::from(&[Calcit::Syntax(head.to_owned(), head_ns)]);
525  for a in args {
526    let (form, _v) = preprocess_expr(a, scope_defs, file_ns.to_owned(), check_warnings, call_stack)?;
527    xs = xs.push_right(form);
528  }
529  Ok(Calcit::List(xs))
530}
531
532pub fn preprocess_defn(
533  head: &CalcitSyntax,
534  head_ns: Arc<str>,
535  args: &CalcitItems,
536  scope_defs: &HashSet<Arc<str>>,
537  file_ns: Arc<str>,
538  check_warnings: &RefCell<Vec<String>>,
539  call_stack: &CallStackList,
540) -> Result<Calcit, CalcitErr> {
541  // println!("defn args: {}", primes::CrListWrap(args.to_owned()));
542  let mut xs: CalcitItems = TernaryTreeList::from(&[Calcit::Syntax(head.to_owned(), head_ns)]);
543  match (args.get(0), args.get(1)) {
544    (
545      Some(Calcit::Symbol {
546        sym: def_name,
547        ns: def_name_ns,
548        at_def,
549        ..
550      }),
551      Some(Calcit::List(ys)),
552    ) => {
553      let mut body_defs: HashSet<Arc<str>> = scope_defs.to_owned();
554
555      xs = xs.push_right(Calcit::Symbol {
556        sym: def_name.to_owned(),
557        ns: def_name_ns.to_owned(),
558        at_def: at_def.to_owned(),
559        resolved: Some(Arc::new(ResolvedRaw)),
560      });
561      let mut zs: CalcitItems = TernaryTreeList::Empty;
562      for y in ys {
563        match y {
564          Calcit::Symbol {
565            sym, ns: def_ns, at_def, ..
566          } => {
567            check_symbol(sym, args, check_warnings);
568            zs = zs.push_right(Calcit::Symbol {
569              sym: sym.to_owned(),
570              ns: def_ns.to_owned(),
571              at_def: at_def.to_owned(),
572              resolved: Some(Arc::new(ResolvedRaw)),
573            });
574            // skip argument syntax marks
575            if &**sym != "&" && &**sym != "?" {
576              body_defs.insert(sym.to_owned());
577            }
578          }
579          _ => {
580            return Err(CalcitErr::use_msg_stack(
581              format!("expected defn args to be symbols, got: {}", y),
582              call_stack,
583            ))
584          }
585        }
586      }
587      xs = xs.push_right(Calcit::List(zs));
588
589      for (idx, a) in args.into_iter().enumerate() {
590        if idx >= 2 {
591          let (form, _v) = preprocess_expr(a, &body_defs, file_ns.to_owned(), check_warnings, call_stack)?;
592          xs = xs.push_right(form);
593        }
594      }
595      Ok(Calcit::List(xs))
596    }
597    (Some(a), Some(b)) => Err(CalcitErr::use_msg_stack(
598      format!("defn/defmacro expected name and args: {} {}", a, b),
599      call_stack,
600    )),
601    (a, b) => Err(CalcitErr::use_msg_stack(
602      format!("defn or defmacro expected name and args, got {:?} {:?}", a, b,),
603      call_stack,
604    )),
605  }
606}
607
608// warn if this symbol is used
609fn check_symbol(sym: &str, args: &CalcitItems, check_warnings: &RefCell<Vec<String>>) {
610  if is_proc_name(sym) || CalcitSyntax::is_core_syntax(sym) || program::has_def_code(primes::CORE_NS, sym) {
611    let mut warnings = check_warnings.borrow_mut();
612    warnings.push(format!(
613      "[Warn] local binding `{}` shadowed `calcit.core/{}`, with {}",
614      sym,
615      sym,
616      primes::CrListWrap(args.to_owned())
617    ));
618  }
619}
620
621pub fn preprocess_call_let(
622  head: &CalcitSyntax,
623  head_ns: Arc<str>,
624  args: &CalcitItems,
625  scope_defs: &HashSet<Arc<str>>,
626  file_ns: Arc<str>,
627  check_warnings: &RefCell<Vec<String>>,
628  call_stack: &CallStackList,
629) -> Result<Calcit, CalcitErr> {
630  let mut xs: CalcitItems = TernaryTreeList::from(&[Calcit::Syntax(head.to_owned(), head_ns)]);
631  let mut body_defs: HashSet<Arc<str>> = scope_defs.to_owned();
632  let binding = match args.get(0) {
633    Some(Calcit::Nil) => Calcit::Nil,
634    Some(Calcit::List(ys)) if ys.len() == 2 => match (&ys[0], &ys[1]) {
635      (Calcit::Symbol { sym, .. }, a) => {
636        check_symbol(sym, args, check_warnings);
637        body_defs.insert(sym.to_owned());
638        let (form, _v) = preprocess_expr(a, &body_defs, file_ns.to_owned(), check_warnings, call_stack)?;
639        Calcit::List(TernaryTreeList::from(&[ys[0].to_owned(), form]))
640      }
641      (a, b) => {
642        return Err(CalcitErr::use_msg_stack(
643          format!("invalid pair for &let binding: {} {}", a, b),
644          call_stack,
645        ))
646      }
647    },
648    Some(Calcit::List(ys)) => {
649      return Err(CalcitErr::use_msg_stack(
650        format!("expected binding of a pair, got {:?}", ys),
651        call_stack,
652      ))
653    }
654    Some(a) => {
655      return Err(CalcitErr::use_msg_stack(
656        format!("expected binding of a pair, got {}", a),
657        call_stack,
658      ))
659    }
660    None => {
661      return Err(CalcitErr::use_msg_stack(
662        "expected binding of a pair, got nothing".to_owned(),
663        call_stack,
664      ))
665    }
666  };
667  xs = xs.push_right(binding);
668  for (idx, a) in args.into_iter().enumerate() {
669    if idx > 0 {
670      let (form, _v) = preprocess_expr(a, &body_defs, file_ns.to_owned(), check_warnings, call_stack)?;
671      xs = xs.push_right(form);
672    }
673  }
674  Ok(Calcit::List(xs))
675}
676
677pub fn preprocess_quote(
678  head: &CalcitSyntax,
679  head_ns: Arc<str>,
680  args: &CalcitItems,
681  _scope_defs: &HashSet<Arc<str>>,
682  _file_ns: Arc<str>,
683) -> Result<Calcit, CalcitErr> {
684  let mut xs: CalcitItems = TernaryTreeList::from(&[Calcit::Syntax(head.to_owned(), head_ns)]);
685  for a in args {
686    xs = xs.push_right(a.to_owned());
687  }
688  Ok(Calcit::List(xs))
689}
690
691pub fn preprocess_defatom(
692  head: &CalcitSyntax,
693  head_ns: Arc<str>,
694  args: &CalcitItems,
695  scope_defs: &HashSet<Arc<str>>,
696  file_ns: Arc<str>,
697  check_warnings: &RefCell<Vec<String>>,
698  call_stack: &CallStackList,
699) -> Result<Calcit, CalcitErr> {
700  let mut xs: CalcitItems = TernaryTreeList::from(&[Calcit::Syntax(head.to_owned(), head_ns)]);
701  for a in args {
702    // TODO
703    let (form, _v) = preprocess_expr(a, scope_defs, file_ns.to_owned(), check_warnings, call_stack)?;
704    xs = xs.push_right(form.to_owned());
705  }
706  Ok(Calcit::List(xs))
707}
708
709/// need to handle experssions inside unquote snippets
710pub fn preprocess_quasiquote(
711  head: &CalcitSyntax,
712  head_ns: Arc<str>,
713  args: &CalcitItems,
714  scope_defs: &HashSet<Arc<str>>,
715  file_ns: Arc<str>,
716  check_warnings: &RefCell<Vec<String>>,
717  call_stack: &CallStackList,
718) -> Result<Calcit, CalcitErr> {
719  let mut xs: CalcitItems = TernaryTreeList::from(&[Calcit::Syntax(head.to_owned(), head_ns)]);
720  for a in args {
721    xs = xs.push_right(preprocess_quasiquote_internal(
722      a,
723      scope_defs,
724      file_ns.to_owned(),
725      check_warnings,
726      call_stack,
727    )?);
728  }
729  Ok(Calcit::List(xs))
730}
731
732pub fn preprocess_quasiquote_internal(
733  x: &Calcit,
734  scope_defs: &HashSet<Arc<str>>,
735  file_ns: Arc<str>,
736  check_warnings: &RefCell<Vec<String>>,
737  call_stack: &CallStackList,
738) -> Result<Calcit, CalcitErr> {
739  match x {
740    Calcit::List(ys) if ys.is_empty() => Ok(x.to_owned()),
741    Calcit::List(ys) => match &ys[0] {
742      Calcit::Symbol { sym, .. } if &**sym == "~" || &**sym == "~@" => {
743        let mut xs: CalcitItems = TernaryTreeList::Empty;
744        for y in ys {
745          let (form, _) = preprocess_expr(y, scope_defs, file_ns.to_owned(), check_warnings, call_stack)?;
746          xs = xs.push_right(form.to_owned());
747        }
748        Ok(Calcit::List(xs))
749      }
750      _ => {
751        let mut xs: CalcitItems = TernaryTreeList::Empty;
752        for y in ys {
753          xs = xs.push_right(preprocess_quasiquote_internal(y, scope_defs, file_ns.to_owned(), check_warnings, call_stack)?.to_owned());
754        }
755        Ok(Calcit::List(xs))
756      }
757    },
758    _ => Ok(x.to_owned()),
759  }
760}