use std::cell::RefCell;
use std::collections::HashSet;
use std::fmt::Write;
use std::sync::Arc;
use cirru_edn::EdnTag;
use crate::builtins::meta::js_gensym;
use crate::calcit::{self, Calcit, CalcitList, CalcitProc, CalcitSyntax};
use super::symbols::escape_var;
use super::{ImportsDict, to_js_code};
pub(super) fn gen_args_code(
body: &CalcitList,
ns: &str,
local_defs: &HashSet<Arc<str>>,
file_imports: &RefCell<ImportsDict>,
tags: &RefCell<HashSet<EdnTag>>,
) -> Result<String, String> {
let mut result = String::from("");
let var_prefix = if ns == "calcit.core" { "" } else { "$clt." };
let mut spreading = false;
for x in body {
match x {
Calcit::Syntax(CalcitSyntax::ArgSpread, _) => {
spreading = true;
}
_ => {
if !result.is_empty() {
result.push_str(", ")
}
if spreading {
result.push_str("...");
result.push_str(var_prefix);
result.push_str("listToArray(");
result.push_str(&to_js_code(x, ns, local_defs, file_imports, tags, None)?);
result.push(')');
spreading = false
} else {
result.push_str(&to_js_code(x, ns, local_defs, file_imports, tags, None)?);
}
}
}
}
Ok(result)
}
pub(super) fn gen_call_args_with_temps(
body: &CalcitList,
ns: &str,
local_defs: &HashSet<Arc<str>>,
file_imports: &RefCell<ImportsDict>,
tags: &RefCell<HashSet<EdnTag>>,
aggressive: bool,
inline_all: bool,
) -> Result<(String, String), String> {
let mut prelude = String::from("");
let mut args_code = String::from("");
let mut spreading = false;
let var_prefix = if ns == calcit::CORE_NS { "" } else { "$clt." };
let max_depth = if aggressive {
0
} else if body.len() <= 2 {
2
} else if body.len() > 4 {
0
} else {
1
};
for x in body {
match x {
Calcit::Syntax(CalcitSyntax::ArgSpread, _) => {
spreading = true;
}
_ => {
if !args_code.is_empty() {
args_code.push_str(", ");
}
let should_inline = if is_complex_syntax(x) {
false
} else if inline_all {
true
} else {
is_expr_depth_at_most(x, max_depth)
};
let arg_code = if should_inline {
to_js_code(x, ns, local_defs, file_imports, tags, None)?
} else {
let tmp = escape_var(&js_gensym("tmp"));
let expr = to_js_code(x, ns, local_defs, file_imports, tags, None)?;
writeln!(prelude, "let {tmp} = {expr};").expect("write");
tmp
};
if spreading {
write!(args_code, "...{var_prefix}listToArray({arg_code})").expect("write");
spreading = false;
} else {
args_code.push_str(&arg_code);
}
}
}
}
Ok((prelude, args_code))
}
fn is_simple_expr(x: &Calcit) -> bool {
match x {
Calcit::Local { .. }
| Calcit::Symbol { .. }
| Calcit::Number(_)
| Calcit::Bool(_)
| Calcit::Nil
| Calcit::Str(_)
| Calcit::Tag(_)
| Calcit::Import(_)
| Calcit::Fn { .. }
| Calcit::Thunk(..)
| Calcit::Proc(..)
| Calcit::Method(..) => true,
Calcit::Syntax(..) => true,
Calcit::List(xs) => xs.is_empty(),
_ => false,
}
}
fn is_expr_depth_at_most(x: &Calcit, depth: usize) -> bool {
if depth == 0 {
return is_simple_expr(x);
}
match x {
Calcit::List(xs) => {
if xs.is_empty() {
return true;
}
let next_depth = if xs.len() <= 4 && depth < 4 { depth + 1 } else { depth };
xs.iter().all(|item| is_expr_depth_at_most(item, next_depth - 1))
}
_ => is_simple_expr(x),
}
}
fn is_complex_syntax(x: &Calcit) -> bool {
match x {
Calcit::List(xs) => match xs.first() {
Some(Calcit::Syntax(syn, _)) => matches!(
syn,
CalcitSyntax::If | CalcitSyntax::Try | CalcitSyntax::CoreLet | CalcitSyntax::Defn | CalcitSyntax::Defmacro
),
Some(Calcit::Proc(CalcitProc::Raise)) => true,
_ => false,
},
_ => false,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn joins_plain_args() {
let body = CalcitList::from(&[Calcit::Number(1.0), Calcit::Bool(true)]);
let local_defs: HashSet<Arc<str>> = HashSet::new();
let file_imports = RefCell::new(ImportsDict::new());
let tags = RefCell::new(HashSet::new());
let code = gen_args_code(&body, "app.main", &local_defs, &file_imports, &tags).expect("gen args");
assert_eq!(code, "1, true");
}
#[test]
fn wraps_spread_with_list_to_array() {
let body = CalcitList::from(&[Calcit::Syntax(CalcitSyntax::ArgSpread, Arc::from("app.main")), Calcit::Number(1.0)]);
let local_defs: HashSet<Arc<str>> = HashSet::new();
let file_imports = RefCell::new(ImportsDict::new());
let tags = RefCell::new(HashSet::new());
let code = gen_args_code(&body, "app.main", &local_defs, &file_imports, &tags).expect("gen args");
assert_eq!(code, "...$clt.listToArray(1)");
}
}