Merge pull request #486 from nihilist-narwhal:fix-function-name-arguments
This commit is contained in:
commit
eb5f366786
|
|
@ -750,6 +750,20 @@ impl<'h> Evaluator<'h> {
|
|||
Ev::V(Value::Bool(r))
|
||||
}
|
||||
"MAP" | "REDUCE" | "SCAN" | "BYROW" | "BYCOL" | "MAKEARRAY" => self.higher_order(name, args),
|
||||
"GROUPBY" | "PIVOTBY" => {
|
||||
// The aggregation may be a function given by name (`SUM`): pass it as its name.
|
||||
let i = if name == "GROUPBY" { 2 } else { 3 };
|
||||
match args.get(i).and_then(|a| self.function_by_name(a)) {
|
||||
Some(f) => {
|
||||
let mut args = args.to_vec();
|
||||
if let Some(a) = args.get_mut(i) {
|
||||
*a = Expr::Text(f.into());
|
||||
}
|
||||
self.builtin(name, &args)
|
||||
}
|
||||
None => self.builtin(name, args),
|
||||
}
|
||||
}
|
||||
"NOW" | "TODAY" | "RAND" | "RANDBETWEEN" | "RANDARRAY" => {
|
||||
self.host.note_volatile();
|
||||
self.builtin(name, args)
|
||||
|
|
@ -921,7 +935,26 @@ impl<'h> Evaluator<'h> {
|
|||
r
|
||||
}
|
||||
|
||||
fn lambda_arg(&mut self, e: Option<&Expr>) -> Option<Arc<Lambda>> {
|
||||
/// A worksheet function given by name where a function is expected (`BYROW(A1:C3,SUM)`,
|
||||
/// `GROUPBY(A2:A9,B2:B9,SUM)`): its name, unless a LET/LAMBDA variable or a defined name has
|
||||
/// that name.
|
||||
fn function_by_name(&self, e: &Expr) -> Option<String> {
|
||||
let Expr::Name(n) = e else { return None };
|
||||
if self.env.iter().any(|(k, _)| k.eq_ignore_ascii_case(n)) || self.host.workbook().name(n, self.sheet).is_some() {
|
||||
return None;
|
||||
}
|
||||
let u = n.to_ascii_uppercase();
|
||||
gridcraft_functions::lookup(&u).map(|_| u)
|
||||
}
|
||||
|
||||
/// The function argument of a LAMBDA helper; a function given by name takes `arity`
|
||||
/// arguments.
|
||||
fn lambda_arg(&mut self, e: Option<&Expr>, arity: usize) -> Option<Arc<Lambda>> {
|
||||
if let Some(f) = e.and_then(|x| self.function_by_name(x)) {
|
||||
let params: Vec<String> = (0..arity).map(|i| format!("_xleta.{i}")).collect();
|
||||
let body = Expr::Call(f, params.iter().map(|p| Expr::Name(p.clone())).collect());
|
||||
return Some(Arc::new(Lambda { params, body, env: Vec::new() }));
|
||||
}
|
||||
match e.map(|x| self.eval(x)) {
|
||||
Some(Ev::L(l)) => Some(l),
|
||||
_ => None,
|
||||
|
|
@ -940,7 +973,7 @@ impl<'h> Evaluator<'h> {
|
|||
if args.len() < 2 {
|
||||
return err(CellError::Value);
|
||||
}
|
||||
let Some(l) = self.lambda_arg(args.last()) else { return err(CellError::Value) };
|
||||
let Some(l) = self.lambda_arg(args.last(), args.len() - 1) else { return err(CellError::Value) };
|
||||
let arrays: Vec<Arc<Array>> = args[..args.len() - 1].iter().map(|a| as_array(self.value(a))).collect();
|
||||
let rows = arrays.iter().map(|a| a.rows).max().unwrap_or(1);
|
||||
let cols = arrays.iter().map(|a| a.cols).max().unwrap_or(1);
|
||||
|
|
@ -963,7 +996,7 @@ impl<'h> Evaluator<'h> {
|
|||
}
|
||||
let mut acc = self.value(&args[0]);
|
||||
let arr = as_array(self.value(&args[1]));
|
||||
let Some(l) = self.lambda_arg(args.get(2)) else { return err(CellError::Value) };
|
||||
let Some(l) = self.lambda_arg(args.get(2), 2) else { return err(CellError::Value) };
|
||||
if l.params.len() != 2 {
|
||||
return err(CellError::Value);
|
||||
}
|
||||
|
|
@ -987,7 +1020,7 @@ impl<'h> Evaluator<'h> {
|
|||
return err(CellError::Value);
|
||||
}
|
||||
let arr = as_array(self.value(&args[0]));
|
||||
let Some(l) = self.lambda_arg(args.get(1)) else { return err(CellError::Value) };
|
||||
let Some(l) = self.lambda_arg(args.get(1), 1) else { return err(CellError::Value) };
|
||||
let Some(p) = l.params.first().cloned() else { return err(CellError::Value) };
|
||||
let by_row = name == "BYROW";
|
||||
let n = if by_row { arr.rows } else { arr.cols };
|
||||
|
|
@ -1016,7 +1049,7 @@ impl<'h> Evaluator<'h> {
|
|||
if rows < 1.0 || cols < 1.0 || rows * cols > MAX_CELLS as f64 {
|
||||
return err(CellError::Value);
|
||||
}
|
||||
let Some(l) = self.lambda_arg(args.get(2)) else { return err(CellError::Value) };
|
||||
let Some(l) = self.lambda_arg(args.get(2), 2) else { return err(CellError::Value) };
|
||||
if l.params.len() != 2 {
|
||||
return err(CellError::Value);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -553,6 +553,24 @@ fn let_lambda() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn functions_given_by_name() {
|
||||
let mut t = T::new();
|
||||
for (i, (k, v)) in [("a", 1), ("b", -2), ("a", 3)].iter().enumerate() {
|
||||
t.set(&format!("A{}", i + 1), k);
|
||||
t.set(&format!("B{}", i + 1), &v.to_string());
|
||||
}
|
||||
t.set("D1", "=BYROW(B1:B3,SUM)");
|
||||
assert_eq!(t.num("D2"), -2.0);
|
||||
t.set("E1", "=MAP(B1:B3,ABS)");
|
||||
assert_eq!(t.num("E2"), 2.0);
|
||||
t.set("F1", "=REDUCE(0,B1:B3,SUM)");
|
||||
assert_eq!(t.num("F1"), 2.0);
|
||||
t.set("G1", "=GROUPBY(A1:A3,B1:B3,SUM,0,0)");
|
||||
assert_eq!(t.get("G1"), Value::from("a"));
|
||||
assert_eq!(t.num("H1"), 4.0);
|
||||
// A LET variable of that name is still the variable.
|
||||
t.set("J1", "=LET(ABS,LAMBDA(x,x*10),SUM(MAP(B1:B3,ABS)))");
|
||||
assert_eq!(t.num("J1"), 20.0);
|
||||
fn lambda_argument_count() {
|
||||
let mut t = T::new();
|
||||
t.wb.names.push(gridcraft_model::DefinedName {
|
||||
|
|
|
|||
|
|
@ -362,6 +362,10 @@ impl Parser {
|
|||
if w.chars().next().is_some_and(|c| c.is_ascii_digit()) {
|
||||
return Err(self.err(format!("unexpected `{w}`")));
|
||||
}
|
||||
// A function passed by name is saved as `_xleta.SUM`.
|
||||
if let Some(f) = w.get(..7).filter(|p| p.eq_ignore_ascii_case("_xleta.")).and_then(|_| w.get(7..)) {
|
||||
return Ok(Expr::Name(f.to_ascii_uppercase()));
|
||||
}
|
||||
Ok(Expr::Name(w.to_string()))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ fn normalises() {
|
|||
assert_eq!(rt("=sum(a1:b2)"), "SUM(A1:B2)");
|
||||
assert_eq!(rt("= 1 + 2"), "1+2");
|
||||
assert_eq!(rt("_xlfn.XLOOKUP(1,A:A,B:B)"), "XLOOKUP(1,A:A,B:B)");
|
||||
assert_eq!(rt("_xlfn.GROUPBY(A1:A3,B1:B3,_xleta.SUM)"), "GROUPBY(A1:A3,B1:B3,SUM)");
|
||||
assert_eq!(rt("_XLUDF.concat(\"a\",\"b\")"), "_xludf.CONCAT(\"a\",\"b\")");
|
||||
assert_eq!(rt("true"), "TRUE");
|
||||
assert_eq!(rt("'Sheet1'!A1"), "Sheet1!A1");
|
||||
|
|
|
|||
Loading…
Reference in New Issue