use super::names::{sym_name, to_vba_string};
use super::Compiler;
use crate::form::{sym_text, Form, List};
use crate::headtable::resolve_head_alias;
use crate::intrinsics::{fold, is_numeric};
use crate::messages::{raise, Refusal};
impl Compiler {
pub(super) fn emit_deflambda(&mut self, lst: &List) -> Result<String, Refusal> {
if lst.count() < 4 {
return Err(raise("vla-deflambda-arity", &[]));
}
let name_tok = sym_text(lst.nth(2)?)?;
let params = lst.nth_list(3)?;
let mut body_idx = 4;
let mut ldoc = String::new();
if lst.count() >= 5 {
if let Form::Str(s) = lst.nth(4)? {
ldoc = s.clone();
body_idx = 5;
}
}
if lst.count() != body_idx {
return Err(raise(
"vla-deflambda-body-not-one-formula",
&[
("name", &name_tok),
("n", &(lst.count() - body_idx + 1).to_string()),
],
));
}
let mut plist: Vec<String> = Vec::with_capacity(params.count());
for e in ¶ms.items {
if e.is_list() {
return Err(raise(
"vla-deflambda-params-not-plain",
&[("name", &name_tok)],
));
}
plist.push(sym_name(&e.atom_text().unwrap_or_default())?);
}
let plist = plist.join(", ");
let formula = format!(
"=LAMBDA({plist}{}{})",
if plist.is_empty() { "" } else { ", " },
emit_formula(lst.nth(body_idx)?)?
);
self.record_sub_doc(&name_tok, &ldoc, "lambda");
let add_part = "ThisWorkbook.Names.Add";
let arg_part = format!(
"Name:=\"{}\", RefersTo:=\"{}\"",
sym_name(&name_tok)?,
formula.replace('"', "\"\"")
);
Ok(if ldoc.is_empty() {
format!("{add_part} {arg_part}")
} else {
format!("{add_part}({arg_part}).Comment = {}", to_vba_string(&ldoc))
})
}
}
pub fn emit_formula(v: &Form) -> Result<String, Refusal> {
let lst = match v {
Form::Str(s) => return Ok(formula_text(s)),
Form::Sym(s) => {
if v.is_keyword_arg() {
return Err(raise("vla-formula-no-named-args", &[("tok", s)]));
}
return Ok(match fold(s).as_str() {
"true" => "TRUE".to_string(),
"false" => "FALSE".to_string(),
_ => sym_name(s)?,
});
}
Form::List(l) => l,
};
let h = resolve_head_alias(&fold(&lst.head_sym()?));
match h.as_str() {
"+" | "-" | "*" | "/" | "&" | "=" | "<>" | "<" | ">" | "<=" | ">=" => {
if h == "-" && lst.count() == 2 {
return Ok(format!("(-{})", emit_formula(lst.nth(2)?)?));
}
let mut r = String::new();
for i in 2..=lst.count() {
if !r.is_empty() {
r.push_str(&h);
}
r.push_str(&emit_formula(lst.nth(i)?)?);
}
Ok(format!("({r})"))
}
"mod" => {
if lst.count() != 3 {
return Err(raise("vla-formula-mod-arity", &[]));
}
Ok(format!(
"MOD({}, {})",
emit_formula(lst.nth(2)?)?,
emit_formula(lst.nth(3)?)?
))
}
"not" => Ok(format!("NOT({})", emit_formula(lst.nth(2)?)?)),
"and" | "or" => {
let mut parts = Vec::with_capacity(lst.count());
for i in 2..=lst.count() {
parts.push(emit_formula(lst.nth(i)?)?);
}
Ok(format!("{}({})", h.to_uppercase(), parts.join(", ")))
}
"if" => {
if lst.count() >= 3 {
if let Form::List(then_form) = lst.nth(3)? {
if let Some(Form::Sym(th)) = then_form.items.first() {
if fold(th) == "then" {
return Err(raise("vla-formula-if-then-block", &[]));
}
}
}
}
if lst.count() < 3 || lst.count() > 4 {
return Err(raise("vla-formula-if-arity", &[]));
}
let mut r = format!(
"IF({}, {}",
emit_formula(lst.nth(2)?)?,
emit_formula(lst.nth(3)?)?
);
if lst.count() == 4 {
r.push_str(&format!(", {}", emit_formula(lst.nth(4)?)?));
}
r.push(')');
Ok(r)
}
"quote" => {
if lst.count() != 2 {
return Err(raise("vla-quote-arity", &[]));
}
formula_quote(lst.nth(2)?)
}
"lambda" => {
if lst.count() != 3 {
return Err(raise("vla-formula-lambda-arity", &[]));
}
let lp = lst.nth_list(2)?;
let mut parts: Vec<String> = Vec::with_capacity(lp.count());
for pe in &lp.items {
if pe.is_list() {
return Err(raise("vla-formula-lambda-params-not-plain", &[]));
}
parts.push(sym_name(&pe.atom_text().unwrap_or_default())?);
}
let r = parts.join(", ");
Ok(format!(
"LAMBDA({r}{}{})",
if r.is_empty() { "" } else { ", " },
emit_formula(lst.nth(3)?)?
))
}
"set!" | "obj-set!" | "dim" | "begin" | "while" | "for" | "for-each" | "for-each-row"
| "debug-print" | "on-error" | "label" | "goto" | "deflambda" | "include" => {
Err(raise("vla-formula-is-statement", &[("head", &h)]))
}
_ => {
let mut parts = Vec::with_capacity(lst.count());
for i in 2..=lst.count() {
parts.push(emit_formula(lst.nth(i)?)?);
}
Ok(format!(
"{}({})",
sym_name(&lst.head_sym()?)?,
parts.join(", ")
))
}
}
}
pub fn formula_quote(v: &Form) -> Result<String, Refusal> {
match v {
Form::List(lst) => {
let mut parts = Vec::with_capacity(lst.count());
for e in &lst.items {
if e.is_list() {
return Err(raise("vla-formula-array-no-nest", &[]));
}
parts.push(formula_quote(e)?);
}
Ok(format!("{{{}}}", parts.join(",")))
}
Form::Str(s) => Ok(formula_text(s)),
Form::Sym(s) => Ok(if is_numeric(s) {
s.clone()
} else {
formula_text(s)
}),
}
}
pub fn formula_text(content: &str) -> String {
format!("\"{}\"", content.replace('"', "\"\""))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::reader::read_forms;
fn formula(src: &str) -> String {
emit_formula(&read_forms(src).unwrap()[0]).unwrap()
}
#[test]
fn the_dialect_renders_as_l14_pinned() {
assert_eq!(formula("(* x 2)"), "(x*2)");
assert_eq!(formula("(> a b)"), "(a>b)");
assert_eq!(
formula("(if (= (mod n 2) 0) \"even\" \"odd\")"),
"IF((MOD(n, 2)=0), \"even\", \"odd\")"
);
assert_eq!(formula("(quote (1 2 3))"), "{1,2,3}");
assert_eq!(
formula("(and a (not b) (or c d))"),
"AND(a, NOT(b), OR(c, d))"
);
assert_eq!(
formula("(sum (lambda (x) (+ x 1)) rng)"),
"sum(LAMBDA(x, (x+1)), rng)"
);
assert_eq!(formula("true"), "TRUE");
assert_eq!(formula("tax-rate"), "tax_rate");
}
#[test]
fn f7_sign_and_quote() {
assert_eq!(formula("(- x)"), "(-x)");
assert_eq!(formula("(- (- x) 1)"), "((-x)-1)");
assert_eq!(
formula("(if x \"say \\\"hi\\\"\" \"nothing\")"),
"IF(x, \"say \"\"hi\"\"\", \"nothing\")"
);
assert_eq!(
formula("(quote (\"a \\\"b\\\"\" 2 sym))"),
"{\"a \"\"b\"\"\",2,\"sym\"}"
);
}
#[test]
fn the_refusals() {
let err = |src: &str| emit_formula(&read_forms(src).unwrap()[0]).unwrap_err().id;
assert_eq!(err("(set! x 1)"), "vla-formula-is-statement");
assert_eq!(err("(if (> x 1) (then 2))"), "vla-formula-if-then-block");
assert_eq!(err("(if x)"), "vla-formula-if-arity");
assert_eq!(err("(mod a b c)"), "vla-formula-mod-arity");
assert_eq!(err("(quote ((1 2) 3))"), "vla-formula-array-no-nest");
assert_eq!(err("(f :key 1)"), "vla-formula-no-named-args");
assert_eq!(
err("(lambda ((x)) x)"),
"vla-formula-lambda-params-not-plain"
);
}
}