pub mod preprocess;
pub mod track;
use std::cell::RefCell;
use std::sync::Arc;
use std::vec;
use crate::builtins;
use crate::calcit::{
CORE_NS, Calcit, CalcitArgLabel, CalcitErr, CalcitErrKind, CalcitFn, CalcitFnArgs, CalcitImport, CalcitList, CalcitLocal, CalcitProc,
CalcitScope, CalcitSyntax, MethodKind, NodeLocation,
};
use crate::call_stack::{CallStackList, StackKind, using_stack};
use crate::data::cirru;
use crate::program;
use crate::util::string::has_ns_part;
fn build_runtime_cell_error(ns: &str, def: &str, call_stack: &CallStackList, cell: program::RuntimeCell) -> CalcitErr {
match cell {
program::RuntimeCell::Resolving => CalcitErr::use_msg_stack(
CalcitErrKind::Unexpected,
format!("definition is still resolving: {ns}/{def}"),
call_stack,
),
program::RuntimeCell::Errored(message) => CalcitErr::use_msg_stack(
CalcitErrKind::Unexpected,
format!("definition is in errored state: {ns}/{def}\n{message}"),
call_stack,
),
program::RuntimeCell::Cold | program::RuntimeCell::Lazy { .. } | program::RuntimeCell::Ready(_) => CalcitErr::use_msg_stack(
CalcitErrKind::Unexpected,
format!("unexpected runtime state for {ns}/{def}"),
call_stack,
),
}
}
fn require_symbol_from_program(sym: &str, ns: &str, call_stack: &CallStackList) -> Result<Calcit, CalcitErr> {
eval_symbol_from_program(sym, ns, call_stack).map(|value| value.expect("value"))
}
fn lookup_symbol_in_program_namespaces(sym: &str, file_ns: &str, call_stack: &CallStackList) -> Result<Option<Calcit>, CalcitErr> {
if let Some(value) = eval_symbol_from_program(sym, CORE_NS, call_stack)? {
Ok(Some(value))
} else if let Some(value) = eval_symbol_from_program(sym, file_ns, call_stack)? {
Ok(Some(value))
} else {
Ok(None)
}
}
fn resolve_runtime_or_compiled_def(
ns: &str,
def: &str,
def_id: Option<program::DefId>,
call_stack: &CallStackList,
) -> Result<Option<Calcit>, CalcitErr> {
program::resolve_runtime_or_compiled_def(ns, def, def_id, program::RuntimeResolveMode::Strict, call_stack).map_err(|err| match err {
program::RuntimeResolveError::RuntimeCell(cell) => build_runtime_cell_error(ns, def, call_stack, cell),
program::RuntimeResolveError::Eval(failure) => failure,
})
}
fn format_fn_arg_labels(args: &CalcitFnArgs) -> String {
match args {
CalcitFnArgs::Args(xs) => xs
.iter()
.map(|idx| CalcitLocal::read_name(*idx).to_string())
.collect::<Vec<_>>()
.join(" "),
CalcitFnArgs::MarkedArgs(xs) => xs.iter().map(ToString::to_string).collect::<Vec<_>>().join(" "),
}
}
fn format_runtime_values(values: &[Calcit]) -> String {
if values.is_empty() {
"[]".to_owned()
} else {
format!("{}", CalcitList::from(values))
}
}
fn build_fn_arity_mismatch_error(info: &CalcitFn, values: &[Calcit], call_stack: &CallStackList, phase: &str) -> CalcitErr {
let expected = info.args.param_len();
let actual = values.len();
let def_ref = info
.def_ref
.as_ref()
.map(|r| format!("{}/{}", r.def_ns, r.def_name))
.unwrap_or_else(|| format!("{}/{}", info.def_ns, info.name));
let msg = format!(
"function arity mismatch during {phase}: `{}` expected {expected} argument(s), got {actual}\n params: ({})\n values: {}\n fn-namespace: {}\n fn-name: {}\n def-ref: {}",
info.name,
format_fn_arg_labels(info.args.as_ref()),
format_runtime_values(values),
info.def_ns,
info.name,
def_ref,
);
CalcitErr::use_msg_stack(CalcitErrKind::Unexpected, msg, call_stack)
}
pub fn evaluate_expr(expr: &Calcit, scope: &CalcitScope, file_ns: &str, call_stack: &CallStackList) -> Result<Calcit, CalcitErr> {
use Calcit::*;
match expr {
Nil
| Bool(_)
| Number(_)
| Registered(_)
| Tag(_)
| Str(_)
| Ref(..)
| Tuple { .. }
| Buffer(..)
| BufList(..)
| CirruQuote(..)
| Proc(_)
| Macro { .. }
| Fn { .. }
| Struct { .. }
| Enum { .. }
| Trait { .. }
| Impl { .. }
| Syntax(_, _)
| Method(..)
| AnyRef(..) => Ok(expr.to_owned()),
Thunk(thunk) => Ok(thunk.evaluated(scope, call_stack)?),
Symbol { sym, info, location, .. } => {
evaluate_symbol(sym, scope, &info.at_ns, &info.at_def, location, call_stack)
}
Local(CalcitLocal { idx, sym, .. }) => evaluate_symbol_from_scope(*idx, sym, scope),
Import(CalcitImport { ns, def, def_id, .. }) => evaluate_symbol_from_program(def, ns, *def_id, call_stack),
List(xs) => match xs.first() {
None => Err(CalcitErr::use_msg_stack_location(
CalcitErrKind::Arity,
format!("cannot evaluate empty expr: {expr}"),
call_stack,
expr.get_location(),
)),
Some(x) => {
if x.is_expr_evaluated() {
call_expr(x, xs, scope, file_ns, call_stack, false)
} else {
let v = evaluate_expr(x, scope, file_ns, call_stack)?;
call_expr(&v, xs, scope, file_ns, call_stack, false)
}
}
},
Recur(_) => unreachable!("recur not expected to be from symbol"),
RawCode(_, code) => unreachable!("raw code `{}` cannot be called", code),
Set(_) => Err(CalcitErr::use_msg_stack_location(
CalcitErrKind::Unexpected,
"unexpected set for expr",
call_stack,
expr.get_location(),
)),
Map(_) => Err(CalcitErr::use_msg_stack_location(
CalcitErrKind::Unexpected,
"unexpected map for expr",
call_stack,
expr.get_location(),
)),
Record { .. } => Err(CalcitErr::use_msg_stack_location(
CalcitErrKind::Unexpected,
"unexpected record for expr",
call_stack,
expr.get_location(),
)),
}
}
pub fn call_expr(
v: &Calcit,
xs: &CalcitList,
scope: &CalcitScope,
file_ns: &str,
call_stack: &CallStackList,
spreading: bool,
) -> Result<Calcit, CalcitErr> {
match v {
Calcit::Proc(p) => {
let values = if spreading {
evaluate_spreaded_args_from(xs, 1, scope, file_ns, call_stack)?
} else {
evaluate_args_from(xs, 1, scope, file_ns, call_stack)?
};
builtins::handle_proc(*p, &values, call_stack)
}
Calcit::Syntax(s, def_ns) => {
let rest_nodes = xs.skip(1).expect("expected syntax rest nodes");
if using_stack() {
let next_stack = call_stack.extend_owned(def_ns, s.as_ref(), StackKind::Syntax, Calcit::from(xs), rest_nodes.to_vec());
builtins::handle_syntax(s, &rest_nodes, scope, file_ns, &next_stack).map_err(|e| {
if e.stack.is_empty() {
let mut e2 = e;
call_stack.clone_into(&mut e2.stack);
e2
} else {
e
}
})
} else {
builtins::handle_syntax(s, &rest_nodes, scope, file_ns, call_stack)
}
}
Calcit::Method(name, kind) => {
if matches!(kind, MethodKind::Invoke(_)) {
let values = if spreading {
evaluate_spreaded_args_from(xs, 1, scope, file_ns, call_stack)?
} else {
evaluate_args_from(xs, 1, scope, file_ns, call_stack)?
};
if using_stack() {
let next_stack = call_stack.extend(file_ns, name, StackKind::Method, &Calcit::Nil, &values);
builtins::meta::invoke_method(name, &values, &next_stack)
} else {
builtins::meta::invoke_method(name, &values, call_stack)
}
} else if matches!(kind, MethodKind::TagAccess) {
if xs.len() == 2 {
let obj = evaluate_expr(&xs[1], scope, file_ns, call_stack)?;
let tag = evaluate_expr(&Calcit::tag(name), scope, file_ns, call_stack)?;
if let Calcit::Map(m) = obj {
match m.get(&tag) {
Some(value) => Ok(value.to_owned()),
None => Ok(Calcit::Nil),
}
} else {
Err(CalcitErr::use_msg_stack_location(
CalcitErrKind::Type,
format!("expected a hashmap, got: {obj}"),
call_stack,
obj.get_location(),
))
}
} else {
Err(CalcitErr::use_msg_stack_location(
CalcitErrKind::Arity,
format!("tag-accessor takes only 1 argument, {xs}"),
call_stack,
xs.first().and_then(|node| node.get_location()),
))
}
} else {
Err(CalcitErr::use_msg_stack_location(
CalcitErrKind::Unexpected,
format!(
"method kind `{kind}` (`.{prefix}{name}`) is only available in JS codegen, not supported in Rust runtime. \
Use `cr js` to compile to JS, or avoid `.!` / `.-` syntax in server-side code. \
Expression: {xs}",
prefix = match kind {
MethodKind::InvokeNative => "!",
MethodKind::InvokeNativeOptional => "?!",
MethodKind::Access => "-",
MethodKind::AccessOptional => "?-",
_ => "?",
},
),
call_stack,
xs.first().and_then(|node| node.get_location()),
))
}
}
Calcit::Fn { info, .. } => {
let values = if spreading {
evaluate_spreaded_args_from(xs, 1, scope, file_ns, call_stack)?
} else {
evaluate_args_from(xs, 1, scope, file_ns, call_stack)?
};
if using_stack() {
let next_stack = call_stack.extend(&info.def_ns, &info.name, StackKind::Fn, &Calcit::from(xs), &values);
run_fn_owned(values, info, &next_stack)
} else {
run_fn_owned(values, info, call_stack)
}
}
Calcit::Macro { info, .. } => {
println!(
"[Warn] macro should already be handled during preprocessing: {}",
&Calcit::from(xs.to_owned()).lisp_str()
);
let mut current_values: Vec<Calcit> = xs.iter().skip(1).cloned().collect();
let next_stack = if using_stack() {
call_stack.extend_owned(&info.def_ns, &info.name, StackKind::Macro, Calcit::from(xs), current_values.clone())
} else {
call_stack.to_owned()
};
let mut body_scope = CalcitScope::default();
Ok(loop {
bind_marked_args(&mut body_scope, &info.args, ¤t_values, call_stack)?;
let code = evaluate_lines(info.body.as_ref().as_slice(), &body_scope, &info.def_ns, &next_stack)?;
match code {
Calcit::Recur(ys) => {
current_values = ys;
}
_ => {
break evaluate_expr(&code, scope, file_ns, &next_stack)?;
}
}
})
}
Calcit::Tag(k) => {
if xs.len() == 2 {
let v = evaluate_expr(&xs[1], scope, file_ns, call_stack)?;
match &v {
Calcit::Map(m) => match m.get(&Calcit::Tag(k.to_owned())) {
Some(value) => Ok(value.to_owned()),
None => Ok(Calcit::Nil),
},
Calcit::Record(record) => Ok(record.get(k.ref_str()).cloned().unwrap_or(Calcit::Nil)),
_ => Err(CalcitErr::use_msg_stack_location(
CalcitErrKind::Type,
format!("expected a hashmap or record, got: {v}"),
call_stack,
v.get_location(),
)),
}
} else {
Err(CalcitErr::use_msg_stack_location(
CalcitErrKind::Arity,
format!("tag only takes 1 argument, got: {}", xs.len().saturating_sub(1)),
call_stack,
xs.first().and_then(|node| node.get_location()),
))
}
}
Calcit::Registered(alias) => {
let values = if spreading {
evaluate_spreaded_args_from(xs, 1, scope, file_ns, call_stack)?
} else {
evaluate_args_from(xs, 1, scope, file_ns, call_stack)?
};
builtins::call_registered_proc(alias, values, call_stack).map_err(|e| {
if e.kind == CalcitErrKind::Var {
CalcitErr::use_msg_stack_location(
CalcitErrKind::Var,
format!("cannot evaluate symbol directly: {file_ns}/{alias}"),
call_stack,
xs.first().and_then(|node| node.get_location()),
)
} else {
e
}
})
}
a => {
let location = xs
.first()
.and_then(|node| node.get_location())
.or_else(|| a.get_location())
.or_else(|| xs.drop_left().first().and_then(|node| node.get_location()));
let expr_one_liner = {
let expr = Calcit::from(xs.to_owned());
match cirru::calcit_to_cirru(&expr) {
Ok(v) => match cirru_parser::format_expr_one_liner(&v) {
Ok(s) => s,
Err(_) => expr.lisp_str(),
},
Err(_) => expr.lisp_str(),
}
};
let operator_desc = match cirru::calcit_to_cirru(a) {
Ok(v) => match cirru_parser::format_expr_one_liner(&v) {
Ok(s) => s,
Err(_) => a.lisp_str(),
},
Err(_) => a.lisp_str(),
};
Err(CalcitErr::use_msg_stack_location_with_hint(
CalcitErrKind::Type,
format!("cannot be used as operator: {operator_desc} in {expr_one_liner}"),
call_stack,
location,
"Possible: check if a leading `,` is needed to prevent a single-line call of Cirru syntax.",
))
}
}
}
pub fn evaluate_symbol(
sym: &str,
scope: &CalcitScope,
file_ns: &str,
at_def: &str,
location: &Option<Arc<Vec<u16>>>,
call_stack: &CallStackList,
) -> Result<Calcit, CalcitErr> {
let v = match parse_ns_def(sym) {
Some((ns_part, def_part)) => match program::lookup_ns_target_in_import(file_ns, &ns_part) {
Some(target_ns) => require_symbol_from_program(&def_part, &target_ns, call_stack),
None => Err(CalcitErr::use_msg_stack_location(
CalcitErrKind::Var,
format!("unknown ns target: {ns_part}/{def_part}"),
call_stack,
Some(NodeLocation::new(
Arc::from(file_ns),
Arc::from(at_def),
location.to_owned().unwrap_or_default(),
)),
)),
},
None => {
if let Ok(v) = sym.parse::<CalcitSyntax>() {
Ok(Calcit::Syntax(v, file_ns.into()))
} else if let Some(v) = scope.get_by_name(sym) {
Ok(v.to_owned())
} else if let Ok(p) = sym.parse::<CalcitProc>() {
Ok(Calcit::Proc(p))
} else if builtins::is_registered_proc(sym) {
Ok(Calcit::Registered(sym.into()))
} else if let Some(v) = lookup_symbol_in_program_namespaces(sym, file_ns, call_stack)? {
Ok(v)
} else if let Some(target_ns) = program::lookup_def_target_in_import(file_ns, sym) {
require_symbol_from_program(sym, &target_ns, call_stack)
} else {
let vars = scope.get_names();
Err(CalcitErr::use_msg_stack_location(
CalcitErrKind::Var,
format!("unknown symbol `{sym}` in {vars}"),
call_stack,
Some(NodeLocation::new(
Arc::from(file_ns),
Arc::from(at_def),
location.to_owned().unwrap_or_default(),
)),
))
}
}
}?;
match v {
Calcit::Thunk(thunk) => thunk.evaluated(scope, call_stack),
_ => Ok(v),
}
}
pub fn evaluate_symbol_from_scope(idx: u16, sym: &str, scope: &CalcitScope) -> Result<Calcit, CalcitErr> {
if let Some(v) = scope.get(idx) {
return Ok(v.to_owned());
}
if let Some(v) = scope.get_by_name(sym) {
return Ok(v.to_owned());
}
let vars = scope.get_names();
CalcitErr::err_str(CalcitErrKind::Var, format!("unknown local `{sym}`(#{idx}) in scope {vars}"))
}
pub fn evaluate_symbol_from_program(
sym: &str,
file_ns: &str,
def_id: Option<u32>,
call_stack: &CallStackList,
) -> Result<Calcit, CalcitErr> {
let v0 = resolve_runtime_or_compiled_def(file_ns, sym, def_id.map(program::DefId), call_stack)?;
let v = if let Some(v) = v0 {
v
} else if let Some(v) = lookup_symbol_in_program_namespaces(sym, file_ns, call_stack)? {
v
} else {
return Err(CalcitErr::use_msg_stack(
CalcitErrKind::Var,
format!("expected symbol `{sym}` from path `{file_ns}`, this is a quick path, should succeed"),
call_stack,
));
};
Ok(v)
}
pub fn parse_ns_def(s: &str) -> Option<(Arc<str>, Arc<str>)> {
if !has_ns_part(s) {
return None;
}
let pieces: Vec<&str> = s.split('/').collect();
if pieces.len() == 2 {
if !pieces[0].is_empty() && !pieces[1].is_empty() {
Some((pieces[0].into(), pieces[1].into()))
} else {
None
}
} else {
None
}
}
pub fn eval_symbol_from_program(sym: &str, ns: &str, call_stack: &CallStackList) -> Result<Option<Calcit>, CalcitErr> {
if let Some(v) = resolve_runtime_or_compiled_def(ns, sym, None, call_stack)? {
return Ok(Some(v));
}
if program::has_def_code(ns, sym) {
let warnings: RefCell<Vec<_>> = RefCell::new(vec![]);
preprocess::ensure_ns_def_compiled(ns, sym, &warnings, call_stack)?;
return resolve_runtime_or_compiled_def(ns, sym, None, call_stack);
}
Ok(None)
}
pub fn run_fn(values: &[Calcit], info: &CalcitFn, call_stack: &CallStackList) -> Result<Calcit, CalcitErr> {
let mut body_scope = (*info.scope).to_owned();
match &*info.args {
CalcitFnArgs::Args(args) => {
if args.len() != values.len() {
return Err(build_fn_arity_mismatch_error(info, values, call_stack, "call"));
}
for (&arg, value) in args.iter().zip(values) {
body_scope.insert_mut(arg, value.to_owned());
}
}
CalcitFnArgs::MarkedArgs(args) => bind_marked_args(&mut body_scope, args, values, call_stack)?,
}
let v = evaluate_lines(info.body.as_slice(), &body_scope, &info.def_ns, call_stack)?;
if let Calcit::Recur(xs) = v {
let mut current_values = xs.to_vec();
loop {
match &*info.args {
CalcitFnArgs::Args(args) => {
if args.len() != current_values.len() {
return Err(build_fn_arity_mismatch_error(info, ¤t_values, call_stack, "recur"));
}
for (&arg, value) in args.iter().zip(¤t_values) {
body_scope.insert_mut(arg, value.to_owned());
}
}
CalcitFnArgs::MarkedArgs(args) => bind_marked_args(&mut body_scope, args, ¤t_values, call_stack)?,
}
let v = evaluate_lines(info.body.as_slice(), &body_scope, &info.def_ns, call_stack)?;
match v {
Calcit::Recur(xs) => current_values = xs.to_vec(),
result => return Ok(result),
}
}
}
Ok(v)
}
pub fn run_fn_owned(values: Vec<Calcit>, info: &CalcitFn, call_stack: &CallStackList) -> Result<Calcit, CalcitErr> {
let mut body_scope = (*info.scope).to_owned();
match &*info.args {
CalcitFnArgs::Args(args) => {
if args.len() != values.len() {
return Err(build_fn_arity_mismatch_error(info, &values, call_stack, "call"));
}
for (&arg, value) in args.iter().zip(values) {
body_scope.insert_mut(arg, value);
}
}
CalcitFnArgs::MarkedArgs(args) => bind_marked_args(&mut body_scope, args, &values, call_stack)?,
}
let v = evaluate_lines(&info.body, &body_scope, &info.def_ns, call_stack)?;
if let Calcit::Recur(xs) = v {
let mut current_values = xs.to_vec();
loop {
match &*info.args {
CalcitFnArgs::Args(args) => {
if args.len() != current_values.len() {
return Err(build_fn_arity_mismatch_error(info, ¤t_values, call_stack, "recur"));
}
for (&arg, value) in args.iter().zip(current_values) {
body_scope.insert_mut(arg, value);
}
}
CalcitFnArgs::MarkedArgs(args) => bind_marked_args(&mut body_scope, args, ¤t_values, call_stack)?,
}
let v = evaluate_lines(&info.body, &body_scope, &info.def_ns, call_stack)?;
match v {
Calcit::Recur(xs) => current_values = xs.to_vec(),
result => return Ok(result),
}
}
}
Ok(v)
}
#[derive(Debug, Default, PartialEq, PartialOrd)]
struct MutIndex(usize);
impl MutIndex {
fn get_and_inc(&mut self) -> usize {
let ret = self.0;
self.0 += 1;
ret
}
}
pub fn bind_marked_args(
scope: &mut CalcitScope,
args: &[CalcitArgLabel],
values: &[Calcit],
call_stack: &CallStackList,
) -> Result<(), CalcitErr> {
let mut spreading = false;
let mut optional = false;
let mut pop_args_idx = MutIndex::default();
let mut pop_values_idx = MutIndex::default();
while let Some(arg) = args.get(pop_args_idx.get_and_inc()) {
if spreading {
match arg {
CalcitArgLabel::Idx(idx) => {
let chunk = values[pop_values_idx.0..].to_vec();
pop_values_idx.0 = values.len();
scope.insert_mut(*idx, Calcit::from(CalcitList::Vector(chunk)));
if pop_args_idx.0 < args.len() {
return Err(CalcitErr::use_msg_stack(
CalcitErrKind::Arity,
format!("invalid argument declaration after `&` in signature `{}`", render_marked_args(args)),
call_stack,
));
}
}
_ => {
return Err(CalcitErr::use_msg_stack(
CalcitErrKind::Arity,
format!("invalid argument declaration after `&` in signature `{}`", render_marked_args(args)),
call_stack,
));
}
}
} else {
match arg {
CalcitArgLabel::RestMark => spreading = true,
CalcitArgLabel::OptionalMark => optional = true,
CalcitArgLabel::Idx(idx) => match values.get(pop_values_idx.get_and_inc()) {
Some(v) => {
scope.insert_mut(*idx, v.to_owned());
}
None => {
if optional {
scope.insert_mut(*idx, Calcit::Nil);
} else {
return Err(CalcitErr::use_msg_stack(
CalcitErrKind::Arity,
format!(
"too few values `{values:?}` for arguments `{}`; missing required argument `{}`",
render_marked_args(args),
CalcitLocal::read_name(*idx)
),
call_stack,
));
}
}
},
}
}
}
if pop_values_idx.0 >= values.len() {
Ok(())
} else {
let extra_count = values.len() - pop_values_idx.0;
Err(CalcitErr::use_msg_stack(
CalcitErrKind::Arity,
format!(
"too many values `{values:?}` for arguments `{}`; {} extra value(s) are not handled",
render_marked_args(args),
extra_count
),
call_stack,
))
}
}
fn render_marked_args(args: &[CalcitArgLabel]) -> String {
let mut parts: Vec<String> = vec![];
for arg in args {
match arg {
CalcitArgLabel::RestMark => parts.push("&".to_owned()),
CalcitArgLabel::OptionalMark => parts.push("?".to_owned()),
CalcitArgLabel::Idx(idx) => parts.push(CalcitLocal::read_name(*idx).to_string()),
}
}
format!("({})", parts.join(" "))
}
pub fn evaluate_lines(lines: &[Calcit], scope: &CalcitScope, file_ns: &str, call_stack: &CallStackList) -> Result<Calcit, CalcitErr> {
let mut ret: Calcit = Calcit::Nil;
for line in lines {
match evaluate_expr(line, scope, file_ns, call_stack) {
Ok(v) => ret = v,
Err(e) => return Err(e),
}
}
Ok(ret)
}
pub fn evaluate_args(
items: CalcitList,
scope: &CalcitScope,
file_ns: &str,
call_stack: &CallStackList,
) -> Result<Vec<Calcit>, CalcitErr> {
evaluate_args_from(&items, 0, scope, file_ns, call_stack)
}
pub fn evaluate_args_from(
items: &CalcitList,
start: usize,
scope: &CalcitScope,
file_ns: &str,
call_stack: &CallStackList,
) -> Result<Vec<Calcit>, CalcitErr> {
let mut ret: Vec<Calcit> = Vec::with_capacity(items.len().saturating_sub(start));
let mut idx = 0;
items.traverse_result::<CalcitErr>(&mut |item| {
if idx < start {
idx += 1;
return Ok(());
}
idx += 1;
if item.is_expr_evaluated() {
ret.push(item.to_owned());
} else {
ret.push(evaluate_expr(item, scope, file_ns, call_stack)?);
}
Ok(())
})?;
Ok(ret)
}
pub fn evaluate_spreaded_args(
items: CalcitList,
scope: &CalcitScope,
file_ns: &str,
call_stack: &CallStackList,
) -> Result<Vec<Calcit>, CalcitErr> {
evaluate_spreaded_args_from(&items, 0, scope, file_ns, call_stack)
}
pub fn evaluate_spreaded_args_from(
items: &CalcitList,
start: usize,
scope: &CalcitScope,
file_ns: &str,
call_stack: &CallStackList,
) -> Result<Vec<Calcit>, CalcitErr> {
let mut ret: Vec<Calcit> = Vec::with_capacity(items.len().saturating_sub(start));
let mut spreading = false;
let mut idx = 0;
items.traverse_result::<CalcitErr>(&mut |item| {
if idx < start {
idx += 1;
return Ok(());
}
idx += 1;
match item {
Calcit::Syntax(CalcitSyntax::ArgSpread, _) => {
spreading = true;
}
_ => {
if item.is_expr_evaluated() {
if spreading {
match item {
Calcit::List(xs) => {
xs.traverse(&mut |x| {
ret.push(x.to_owned());
});
spreading = false;
}
a => {
return Err(CalcitErr::use_msg_stack_location(
CalcitErrKind::Arity,
format!("expected list for spreading, got: {a}"),
call_stack,
a.get_location(),
));
}
}
} else {
ret.push(item.to_owned());
}
} else {
let v = evaluate_expr(item, scope, file_ns, call_stack)?;
if spreading {
match v {
Calcit::List(xs) => {
xs.traverse(&mut |x| {
ret.push(x.to_owned());
});
spreading = false;
}
a => {
return Err(CalcitErr::use_msg_stack_location(
CalcitErrKind::Arity,
format!("expected list for spreading, got: {a}"),
call_stack,
a.get_location(),
));
}
}
} else {
ret.push(v);
}
}
}
}
Ok(())
})?;
Ok(ret)
}