Skip to main content

truecalc_core/eval/functions/array/
mod.rs

1//! Array and matrix functions for Google Sheets compatibility.
2
3use crate::eval::{evaluate_expr, EvalCtx};
4use crate::parser::ast::Expr;
5use crate::types::{ErrorKind, Value};
6
7use super::{check_arity, check_arity_len, FunctionMeta, Registry};
8
9// ── 2D array helpers ──────────────────────────────────────────────────────────
10
11/// Convert a Value into a 2D grid (Vec<Vec<Value>>).
12/// - Nested Array (2D): outer = rows, inner = cols
13/// - Flat Array (1D): one row
14/// - Scalar: 1x1
15pub fn to_2d(v: &Value) -> Vec<Vec<Value>> {
16    match v {
17        Value::Array(outer) => {
18            if outer.iter().any(|e| matches!(e, Value::Array(_))) {
19                outer
20                    .iter()
21                    .map(|row| match row {
22                        Value::Array(cols) => cols.clone(),
23                        other => vec![other.clone()],
24                    })
25                    .collect()
26            } else {
27                vec![outer.clone()] // 1-D flat array → single row
28            }
29        }
30        other => vec![vec![other.clone()]], // scalar → 1×1
31    }
32}
33
34/// Convert a 2D grid back to a Value.
35/// - Empty grid → empty Array
36/// - Single row → flat Array
37/// - Multiple rows → nested Array of row Arrays
38pub fn from_2d(rows: Vec<Vec<Value>>) -> Value {
39    if rows.is_empty() {
40        return Value::Array(vec![]);
41    }
42    if rows.len() == 1 {
43        return Value::Array(rows.into_iter().next().unwrap());
44    }
45    Value::Array(rows.into_iter().map(Value::Array).collect())
46}
47
48/// Flatten a Value to a 1D Vec<Value> (row-major order).
49pub fn flatten_val(v: &Value) -> Vec<Value> {
50    match v {
51        Value::Array(outer) => {
52            if outer.iter().any(|e| matches!(e, Value::Array(_))) {
53                outer
54                    .iter()
55                    .flat_map(|row| match row {
56                        Value::Array(cols) => cols.clone(),
57                        other => vec![other.clone()],
58                    })
59                    .collect()
60            } else {
61                outer.clone()
62            }
63        }
64        other => vec![other.clone()],
65    }
66}
67
68/// Convert a Value to f64 for numeric computations.
69fn to_f64(v: &Value) -> Option<f64> {
70    match v {
71        Value::Number(n) => Some(*n),
72        Value::Bool(b) => Some(if *b { 1.0 } else { 0.0 }),
73        _ => None,
74    }
75}
76
77
78
79// ── ROWS ─────────────────────────────────────────────────────────────────────
80
81pub(crate) fn rows_fn(args: &[Value]) -> Value {
82    if let Some(e) = check_arity(args, 1, 1) {
83        return e;
84    }
85    let grid = to_2d(&args[0]);
86    Value::Number(grid.len() as f64)
87}
88
89// ── COLUMNS ───────────────────────────────────────────────────────────────────
90
91pub(crate) fn columns_fn(args: &[Value]) -> Value {
92    if let Some(e) = check_arity(args, 1, 1) {
93        return e;
94    }
95    let grid = to_2d(&args[0]);
96    let cols = grid.first().map(|r| r.len()).unwrap_or(0);
97    Value::Number(cols as f64)
98}
99
100// ── TRANSPOSE ─────────────────────────────────────────────────────────────────
101
102pub(crate) fn transpose_fn(args: &[Value]) -> Value {
103    if let Some(e) = check_arity(args, 1, 1) {
104        return e;
105    }
106    let grid = to_2d(&args[0]);
107    if grid.is_empty() {
108        return Value::Array(vec![]);
109    }
110    let nrows = grid.len();
111    let ncols = grid[0].len();
112    let transposed: Vec<Vec<Value>> = (0..ncols)
113        .map(|c| (0..nrows).map(|r| grid[r][c].clone()).collect())
114        .collect();
115    from_2d(transposed)
116}
117
118// ── ARRAY_CONSTRAIN ───────────────────────────────────────────────────────────
119
120pub(crate) fn array_constrain_fn(args: &[Value]) -> Value {
121    if let Some(e) = check_arity(args, 3, 3) {
122        return e;
123    }
124    let grid = to_2d(&args[0]);
125    let num_rows = match to_f64(&args[1]) {
126        Some(n) if n >= 1.0 => n as usize,
127        Some(n) if n < 0.0 => return Value::Error(ErrorKind::Num),
128        Some(_) => return Value::Error(ErrorKind::Ref),
129        None => return Value::Error(ErrorKind::Value),
130    };
131    let num_cols = match to_f64(&args[2]) {
132        Some(n) if n >= 1.0 => n as usize,
133        Some(n) if n < 0.0 => return Value::Error(ErrorKind::Num),
134        Some(_) => return Value::Error(ErrorKind::Ref),
135        None => return Value::Error(ErrorKind::Value),
136    };
137    let rows_to_take = num_rows.min(grid.len());
138    let result: Vec<Vec<Value>> = grid[..rows_to_take]
139        .iter()
140        .map(|row| {
141            let cols_to_take = num_cols.min(row.len());
142            row[..cols_to_take].to_vec()
143        })
144        .collect();
145    from_2d(result)
146}
147
148// ── CHOOSECOLS ────────────────────────────────────────────────────────────────
149
150fn choosecols_fn(args: &[Value]) -> Value {
151    if let Some(e) = check_arity(args, 2, usize::MAX) {
152        return e;
153    }
154    let grid = to_2d(&args[0]);
155    let ncols = grid.first().map(|r| r.len()).unwrap_or(0);
156    let mut selected_cols: Vec<usize> = Vec::new();
157    for col_arg in &args[1..] {
158        match to_f64(col_arg) {
159            Some(0.0) => return Value::Error(ErrorKind::Value),
160            Some(n) => {
161                let idx = if n < 0.0 {
162                    let i = (ncols as isize + n as isize) as usize;
163                    if n as isize + (ncols as isize) < 0 {
164                        return Value::Error(ErrorKind::Value);
165                    }
166                    i
167                } else {
168                    let i = n as usize - 1;
169                    if i >= ncols {
170                        return Value::Error(ErrorKind::Value);
171                    }
172                    i
173                };
174                selected_cols.push(idx);
175            }
176            None => return Value::Error(ErrorKind::Value),
177        }
178    }
179    let result: Vec<Vec<Value>> = grid
180        .iter()
181        .map(|row| {
182            selected_cols
183                .iter()
184                .map(|&c| row.get(c).cloned().unwrap_or(Value::Empty))
185                .collect()
186        })
187        .collect();
188    from_2d(result)
189}
190
191// ── CHOOSEROWS ────────────────────────────────────────────────────────────────
192
193fn chooserows_fn(args: &[Value]) -> Value {
194    if let Some(e) = check_arity(args, 2, usize::MAX) {
195        return e;
196    }
197    let grid = to_2d(&args[0]);
198    let nrows = grid.len();
199    let mut selected_rows: Vec<usize> = Vec::new();
200    for row_arg in &args[1..] {
201        match to_f64(row_arg) {
202            Some(0.0) => return Value::Error(ErrorKind::Value),
203            Some(n) => {
204                let idx = if n < 0.0 {
205                    let i = (nrows as isize + n as isize) as usize;
206                    if n as isize + (nrows as isize) < 0 {
207                        return Value::Error(ErrorKind::Value);
208                    }
209                    i
210                } else {
211                    let i = n as usize - 1;
212                    if i >= nrows {
213                        return Value::Error(ErrorKind::Value);
214                    }
215                    i
216                };
217                selected_rows.push(idx);
218            }
219            None => return Value::Error(ErrorKind::Value),
220        }
221    }
222    let result: Vec<Vec<Value>> = selected_rows
223        .iter()
224        .map(|&r| grid.get(r).cloned().unwrap_or_default())
225        .collect();
226    from_2d(result)
227}
228
229// ── FLATTEN ───────────────────────────────────────────────────────────────────
230// Returns a single-column (ROWS=n, COLS=1) array.
231// Google Sheets FLATTEN accepts multiple arguments and concatenates them.
232
233pub(crate) fn flatten_fn(args: &[Value]) -> Value {
234    if let Some(e) = check_arity(args, 1, usize::MAX) {
235        return e;
236    }
237    let mut flat: Vec<Value> = Vec::new();
238    for arg in args {
239        flat.extend(flatten_val(arg));
240    }
241    // Return as column vector (nested array of single-element rows)
242    let col: Vec<Vec<Value>> = flat.into_iter().map(|v| vec![v]).collect();
243    from_2d(col)
244}
245
246// ── HSTACK ────────────────────────────────────────────────────────────────────
247
248fn hstack_fn(args: &[Value]) -> Value {
249    if let Some(e) = check_arity(args, 1, usize::MAX) {
250        return e;
251    }
252    let grids: Vec<Vec<Vec<Value>>> = args.iter().map(to_2d).collect();
253    let nrows = grids.iter().map(|g| g.len()).max().unwrap_or(0);
254    let result: Vec<Vec<Value>> = (0..nrows)
255        .map(|r| {
256            grids
257                .iter()
258                .flat_map(|g| {
259                    g.get(r).cloned().unwrap_or_default()
260                })
261                .collect()
262        })
263        .collect();
264    from_2d(result)
265}
266
267// ── VSTACK ────────────────────────────────────────────────────────────────────
268
269fn vstack_fn(args: &[Value]) -> Value {
270    if let Some(e) = check_arity(args, 1, usize::MAX) {
271        return e;
272    }
273    let mut result: Vec<Vec<Value>> = Vec::new();
274    for arg in args {
275        let grid = to_2d(arg);
276        result.extend(grid);
277    }
278    from_2d(result)
279}
280
281// ── TOCOL ─────────────────────────────────────────────────────────────────────
282// Converts array to column vector (many rows, 1 col)
283// ignore: 0=keep all, 1=ignore blanks, 2=ignore errors, 3=ignore both
284// scan_by_col: if TRUE, scan column-major instead of row-major
285
286fn tocol_fn(args: &[Value]) -> Value {
287    if let Some(e) = check_arity(args, 1, 3) {
288        return e;
289    }
290    let ignore = if let Some(m) = args.get(1) {
291        match to_f64(m) {
292            Some(n) if (0.0..=3.0).contains(&n) => n as u8,
293            _ => return Value::Error(ErrorKind::Value),
294        }
295    } else {
296        0
297    };
298    let scan_by_col = args.get(2).map(|v| matches!(v, Value::Bool(true))).unwrap_or(false);
299
300    let flat = if scan_by_col {
301        // column-major order
302        let grid = to_2d(&args[0]);
303        let ncols = grid.first().map(|r| r.len()).unwrap_or(0);
304        let mut out = Vec::new();
305        for c in 0..ncols {
306            for row in &grid {
307                out.push(row[c].clone());
308            }
309        }
310        out
311    } else {
312        flatten_val(&args[0])
313    };
314
315    let filtered: Vec<Value> = flat.into_iter().filter(|v| {
316        let is_blank = matches!(v, Value::Empty) || matches!(v, Value::Text(s) if s.is_empty());
317        let is_error = matches!(v, Value::Error(_));
318        if ignore == 1 && is_blank { return false; }
319        if ignore == 2 && is_error { return false; }
320        if ignore == 3 && (is_blank || is_error) { return false; }
321        true
322    }).collect();
323
324    let col: Vec<Vec<Value>> = filtered.into_iter().map(|v| vec![v]).collect();
325    from_2d(col)
326}
327
328// ── TOROW ─────────────────────────────────────────────────────────────────────
329// Converts array to row vector (1 row, many cols)
330// ignore: 0=keep all, 1=ignore blanks, 2=ignore errors, 3=ignore both
331// scan_by_col: if TRUE, scan column-major instead of row-major
332
333fn torow_fn(args: &[Value]) -> Value {
334    if let Some(e) = check_arity(args, 1, 3) {
335        return e;
336    }
337    let ignore = if let Some(m) = args.get(1) {
338        match to_f64(m) {
339            Some(n) if (0.0..=3.0).contains(&n) => n as u8,
340            _ => return Value::Error(ErrorKind::Value),
341        }
342    } else {
343        0
344    };
345    let scan_by_col = args.get(2).map(|v| matches!(v, Value::Bool(true))).unwrap_or(false);
346
347    let flat = if scan_by_col {
348        // column-major order
349        let grid = to_2d(&args[0]);
350        let ncols = grid.first().map(|r| r.len()).unwrap_or(0);
351        let mut out = Vec::new();
352        for c in 0..ncols {
353            for row in &grid {
354                out.push(row[c].clone());
355            }
356        }
357        out
358    } else {
359        flatten_val(&args[0])
360    };
361
362    let filtered: Vec<Value> = flat.into_iter().filter(|v| {
363        let is_blank = matches!(v, Value::Empty) || matches!(v, Value::Text(s) if s.is_empty());
364        let is_error = matches!(v, Value::Error(_));
365        if ignore == 1 && is_blank { return false; }
366        if ignore == 2 && is_error { return false; }
367        if ignore == 3 && (is_blank || is_error) { return false; }
368        true
369    }).collect();
370
371    Value::Array(filtered)
372}
373
374// ── WRAPCOLS ──────────────────────────────────────────────────────────────────
375// WRAPCOLS(vector, wrap_count) — split into columns of wrap_count rows
376// Result: ceil(n/wrap_count) columns, wrap_count rows (pad last col with Empty)
377
378fn wrapcols_fn(args: &[Value]) -> Value {
379    if let Some(e) = check_arity(args, 2, 3) {
380        return e;
381    }
382    let flat = flatten_val(&args[0]);
383    let wrap_count = match to_f64(&args[1]) {
384        Some(n) if n >= 1.0 => n as usize,
385        Some(_) => return Value::Error(ErrorKind::Num),
386        None => return Value::Error(ErrorKind::Value),
387    };
388    let pad = args.get(2).cloned().unwrap_or(Value::Empty);
389
390    // Split into columns of wrap_count elements each
391    let ncols = flat.len().div_ceil(wrap_count);
392    let nrows = wrap_count;
393
394    // Build column-major layout, then transpose to row-major
395    let grid: Vec<Vec<Value>> = (0..nrows)
396        .map(|r| {
397            (0..ncols)
398                .map(|c| {
399                    let idx = c * wrap_count + r;
400                    flat.get(idx).cloned().unwrap_or_else(|| pad.clone())
401                })
402                .collect()
403        })
404        .collect();
405    from_2d(grid)
406}
407
408// ── WRAPROWS ──────────────────────────────────────────────────────────────────
409// WRAPROWS(vector, wrap_count) — split into rows of wrap_count cols
410
411fn wraprows_fn(args: &[Value]) -> Value {
412    if let Some(e) = check_arity(args, 2, 3) {
413        return e;
414    }
415    let flat = flatten_val(&args[0]);
416    let wrap_count = match to_f64(&args[1]) {
417        Some(n) if n >= 1.0 => n as usize,
418        Some(_) => return Value::Error(ErrorKind::Num),
419        None => return Value::Error(ErrorKind::Value),
420    };
421    let pad = args.get(2).cloned().unwrap_or(Value::Empty);
422
423    let nrows = flat.len().div_ceil(wrap_count);
424    let grid: Vec<Vec<Value>> = (0..nrows)
425        .map(|r| {
426            (0..wrap_count)
427                .map(|c| {
428                    let idx = r * wrap_count + c;
429                    flat.get(idx).cloned().unwrap_or_else(|| pad.clone())
430                })
431                .collect()
432        })
433        .collect();
434    from_2d(grid)
435}
436
437// ── SORT ──────────────────────────────────────────────────────────────────────
438
439pub(crate) fn sort_fn(args: &[Value]) -> Value {
440    if let Some(e) = check_arity(args, 1, 4) {
441        return e;
442    }
443    let is_1d = matches!(&args[0], Value::Array(outer) if !outer.iter().any(|e| matches!(e, Value::Array(_))));
444
445    // Google Sheets semantics: a flat 1-D row array is treated as a single row.
446    // SORT sorts *rows*; with only one row nothing changes regardless of parameters.
447    // Exception: if by_col=TRUE is requested on a 1D array, GS returns #N/A.
448    if is_1d {
449        let by_col = args.get(3).map(|v| matches!(v, Value::Bool(true))).unwrap_or(false);
450        if by_col {
451            return Value::Error(ErrorKind::NA);
452        }
453        return args[0].clone();
454    }
455
456    let mut grid = to_2d(&args[0]);
457    let sort_col = if args.len() >= 2 {
458        match to_f64(&args[1]) {
459            Some(n) if n >= 1.0 => n as usize - 1,
460            Some(_) => return Value::Error(ErrorKind::Value),
461            None => 0,
462        }
463    } else {
464        0
465    };
466    let ascending = if args.len() >= 3 {
467        match &args[2] {
468            Value::Number(n) => *n >= 0.0,
469            Value::Bool(b) => *b,
470            _ => true,
471        }
472    } else {
473        true
474    };
475
476    grid.sort_by(|a, b| {
477        let va = a.get(sort_col).unwrap_or(&Value::Empty);
478        let vb = b.get(sort_col).unwrap_or(&Value::Empty);
479        let cmp = compare_values_sort(va, vb);
480        if ascending { cmp } else { cmp.reverse() }
481    });
482    from_2d(grid)
483}
484
485fn compare_values_sort(a: &Value, b: &Value) -> std::cmp::Ordering {
486    match (a, b) {
487        (Value::Number(x), Value::Number(y)) => x.partial_cmp(y).unwrap_or(std::cmp::Ordering::Equal),
488        (Value::Text(x), Value::Text(y)) => x.cmp(y),
489        (Value::Bool(x), Value::Bool(y)) => x.cmp(y),
490        // Zone-aware instants sort by the absolute instant.
491        (Value::Zoned(x), Value::Zoned(y)) => x.utc_nanos.cmp(&y.utc_nanos),
492        _ => std::cmp::Ordering::Equal,
493    }
494}
495
496// ── SORTBY ────────────────────────────────────────────────────────────────────
497
498fn sortby_fn(args: &[Value]) -> Value {
499    if let Some(e) = check_arity(args, 2, usize::MAX) {
500        return e;
501    }
502    let is_1d = matches!(&args[0], Value::Array(outer) if !outer.iter().any(|e| matches!(e, Value::Array(_))));
503
504    if is_1d {
505        // 1D: treat each element as a separate item to sort
506        let elems = flatten_val(&args[0]);
507        let n = elems.len();
508
509        let mut sort_keys: Vec<(Vec<Value>, bool)> = Vec::new();
510        let mut i = 1;
511        while i < args.len() {
512            let key_vals = flatten_val(&args[i]);
513            if key_vals.len() != n {
514                return Value::Error(ErrorKind::Value);
515            }
516            let ascending = if i + 1 < args.len() {
517                match to_f64(&args[i + 1]) {
518                    Some(v) => v >= 0.0,
519                    None => true,
520                }
521            } else {
522                true
523            };
524            sort_keys.push((key_vals, ascending));
525            i += 2;
526        }
527
528        let mut indices: Vec<usize> = (0..n).collect();
529        indices.sort_by(|&ra, &rb| {
530            for (keys, asc) in &sort_keys {
531                let va = keys.get(ra).unwrap_or(&Value::Empty);
532                let vb = keys.get(rb).unwrap_or(&Value::Empty);
533                let cmp = compare_values_sort(va, vb);
534                if cmp != std::cmp::Ordering::Equal {
535                    return if *asc { cmp } else { cmp.reverse() };
536                }
537            }
538            std::cmp::Ordering::Equal
539        });
540
541        return Value::Array(indices.iter().map(|&r| elems[r].clone()).collect());
542    }
543
544    let grid = to_2d(&args[0]);
545    let nrows = grid.len();
546
547    // Collect (sort_key_array, order) pairs
548    let mut sort_keys: Vec<(Vec<Value>, bool)> = Vec::new();
549    let mut i = 1;
550    while i < args.len() {
551        let key_vals = flatten_val(&args[i]);
552        if key_vals.len() != nrows && nrows > 1 {
553            return Value::Error(ErrorKind::Value);
554        }
555        let ascending = if i + 1 < args.len() {
556            match to_f64(&args[i + 1]) {
557                Some(n) => n >= 0.0,
558                None => true,
559            }
560        } else {
561            true
562        };
563        sort_keys.push((key_vals, ascending));
564        i += 2;
565    }
566
567    let mut indices: Vec<usize> = (0..nrows).collect();
568    indices.sort_by(|&ra, &rb| {
569        for (keys, asc) in &sort_keys {
570            let va = keys.get(ra).unwrap_or(&Value::Empty);
571            let vb = keys.get(rb).unwrap_or(&Value::Empty);
572            let cmp = compare_values_sort(va, vb);
573            if cmp != std::cmp::Ordering::Equal {
574                return if *asc { cmp } else { cmp.reverse() };
575            }
576        }
577        std::cmp::Ordering::Equal
578    });
579
580    let sorted: Vec<Vec<Value>> = indices.iter().map(|&r| grid[r].clone()).collect();
581    drop(grid);
582    from_2d(sorted)
583}
584
585// ── UNIQUE ────────────────────────────────────────────────────────────────────
586
587pub(crate) fn unique_fn(args: &[Value]) -> Value {
588    if let Some(e) = check_arity(args, 1, 3) {
589        return e;
590    }
591    let is_1d = matches!(&args[0], Value::Array(outer) if !outer.iter().any(|e| matches!(e, Value::Array(_))));
592    let grid = to_2d(&args[0]);
593    // by_col defaults to false (deduplicate rows)
594    let by_col = args.get(1).map(|v| matches!(v, Value::Bool(true))).unwrap_or(false);
595    let exactly_once = args.get(2).map(|v| matches!(v, Value::Bool(true))).unwrap_or(false);
596
597    // Google Sheets semantics: a flat 1-D row array is treated as a single row.
598    // UNIQUE with by_col=FALSE deduplicates rows; with only one row, it is
599    // always unique and is returned as-is (regardless of exactly_once).
600    if is_1d && !by_col {
601        return args[0].clone();
602    }
603
604    if by_col {
605        // Deduplicate columns
606        let nrows = grid.len();
607        if nrows == 0 {
608            return from_2d(vec![]);
609        }
610        let ncols = grid[0].len();
611        // Build column-major representation
612        let columns: Vec<Vec<Value>> = (0..ncols)
613            .map(|c| grid.iter().map(|row| row[c].clone()).collect())
614            .collect();
615        let mut seen_cols: Vec<Vec<Value>> = Vec::new();
616        let mut counts: Vec<usize> = Vec::new();
617        for col in columns {
618            if let Some(pos) = seen_cols.iter().position(|sc| sc == &col) {
619                counts[pos] += 1;
620            } else {
621                seen_cols.push(col);
622                counts.push(1);
623            }
624        }
625        let result_cols: Vec<Vec<Value>> = seen_cols
626            .into_iter()
627            .zip(counts)
628            .filter(|(_, cnt)| !exactly_once || *cnt == 1)
629            .map(|(col, _)| col)
630            .collect();
631        // Transpose back to row-major
632        let ncols2 = result_cols.len();
633        let result: Vec<Vec<Value>> = (0..nrows)
634            .map(|r| (0..ncols2).map(|c| result_cols[c][r].clone()).collect())
635            .collect();
636        return from_2d(result);
637    }
638
639    // Deduplicate rows
640    let mut seen_rows: Vec<Vec<Value>> = Vec::new();
641    let mut counts: Vec<usize> = Vec::new();
642    for row in &grid {
643        if let Some(pos) = seen_rows.iter().position(|sr| sr == row) {
644            counts[pos] += 1;
645        } else {
646            seen_rows.push(row.clone());
647            counts.push(1);
648        }
649    }
650    let result: Vec<Vec<Value>> = seen_rows
651        .into_iter()
652        .zip(counts)
653        .filter(|(_, cnt)| !exactly_once || *cnt == 1)
654        .map(|(row, _)| row)
655        .collect();
656    from_2d(result)
657}
658
659// ── SUMPRODUCT ────────────────────────────────────────────────────────────────
660
661pub(crate) fn sumproduct_fn(args: &[Value]) -> Value {
662    if let Some(e) = check_arity(args, 1, usize::MAX) {
663        return e;
664    }
665    let arrays: Vec<Vec<Value>> = args.iter().map(flatten_val).collect();
666    let len = arrays[0].len();
667    // All arrays must have the same length
668    for arr in &arrays[1..] {
669        if arr.len() != len {
670            return Value::Error(ErrorKind::Value);
671        }
672    }
673    let mut sum = 0.0;
674    for i in 0..len {
675        let mut prod = 1.0;
676        for arr in &arrays {
677            prod *= to_f64(&arr[i]).unwrap_or(0.0);
678        }
679        sum += prod;
680    }
681    Value::Number(sum)
682}
683
684// ── SUMXMY2 ───────────────────────────────────────────────────────────────────
685
686fn sumxmy2_fn(args: &[Value]) -> Value {
687    if let Some(e) = check_arity(args, 2, 2) {
688        return e;
689    }
690    let xs = flatten_val(&args[0]);
691    let ys = flatten_val(&args[1]);
692    if xs.len() != ys.len() {
693        return Value::Error(ErrorKind::NA);
694    }
695    let mut sum = 0.0;
696    for (x, y) in xs.iter().zip(ys.iter()) {
697        // Only numeric values contribute; text, booleans, errors, empty are skipped.
698        if let (Value::Number(xn), Value::Number(yn)) = (x, y) {
699            sum += (*xn - *yn).powi(2);
700        }
701    }
702    Value::Number(sum)
703}
704
705// ── SUMX2MY2 ──────────────────────────────────────────────────────────────────
706
707fn sumx2my2_fn(args: &[Value]) -> Value {
708    if let Some(e) = check_arity(args, 2, 2) {
709        return e;
710    }
711    let xs = flatten_val(&args[0]);
712    let ys = flatten_val(&args[1]);
713    if xs.len() != ys.len() {
714        return Value::Error(ErrorKind::NA);
715    }
716    let mut sum = 0.0;
717    for (x, y) in xs.iter().zip(ys.iter()) {
718        if let (Value::Number(xn), Value::Number(yn)) = (x, y) {
719            sum += *xn * *xn - *yn * *yn;
720        }
721    }
722    Value::Number(sum)
723}
724
725// ── SUMX2PY2 ──────────────────────────────────────────────────────────────────
726
727fn sumx2py2_fn(args: &[Value]) -> Value {
728    if let Some(e) = check_arity(args, 2, 2) {
729        return e;
730    }
731    let xs = flatten_val(&args[0]);
732    let ys = flatten_val(&args[1]);
733    if xs.len() != ys.len() {
734        return Value::Error(ErrorKind::NA);
735    }
736    let mut sum = 0.0;
737    for (x, y) in xs.iter().zip(ys.iter()) {
738        if let (Value::Number(xn), Value::Number(yn)) = (x, y) {
739            sum += *xn * *xn + *yn * *yn;
740        }
741    }
742    Value::Number(sum)
743}
744
745// ── MMULT ─────────────────────────────────────────────────────────────────────
746
747fn mmult_fn(args: &[Value]) -> Value {
748    if let Some(e) = check_arity(args, 2, 2) {
749        return e;
750    }
751    let a = to_2d(&args[0]);
752    let b = to_2d(&args[1]);
753    if a.iter().chain(b.iter()).any(|row| row.iter().any(|v| matches!(v, Value::Bool(_)))) {
754        return Value::Error(ErrorKind::Value);
755    }
756    let n = a.first().map(|r| r.len()).unwrap_or(0);
757    let p = b.first().map(|r| r.len()).unwrap_or(0);
758    if b.len() != n {
759        return Value::Error(ErrorKind::Value);
760    }
761    // Convert to f64 matrices for computation
762    let af: Vec<Vec<f64>> = a.iter().map(|row| {
763        row.iter().map(|v| to_f64(v).unwrap_or(f64::NAN)).collect()
764    }).collect();
765    let bf: Vec<Vec<f64>> = b.iter().map(|row| {
766        row.iter().map(|v| to_f64(v).unwrap_or(f64::NAN)).collect()
767    }).collect();
768    if af.iter().any(|r| r.iter().any(|v| v.is_nan())) || bf.iter().any(|r| r.iter().any(|v| v.is_nan())) {
769        return Value::Error(ErrorKind::Value);
770    }
771    let result: Vec<Vec<Value>> = af.iter().map(|row_a| {
772        (0..p).map(|j| {
773            let sum: f64 = row_a.iter().enumerate().map(|(k, &av)| av * bf[k][j]).sum();
774            Value::Number(sum)
775        }).collect()
776    }).collect();
777    from_2d(result)
778}
779
780// ── MDETERM ───────────────────────────────────────────────────────────────────
781
782fn mdeterm_fn(args: &[Value]) -> Value {
783    if let Some(e) = check_arity(args, 1, 1) {
784        return e;
785    }
786    let grid = to_2d(&args[0]);
787    let n = grid.len();
788    if n == 0 {
789        return Value::Error(ErrorKind::Value);
790    }
791    for row in &grid {
792        if row.len() != n {
793            return Value::Error(ErrorKind::Value);
794        }
795    }
796    if grid.iter().any(|row| row.iter().any(|v| matches!(v, Value::Bool(_)))) {
797        return Value::Error(ErrorKind::Value);
798    }
799    // Convert to f64 matrix
800    let mut mat: Vec<Vec<f64>> = Vec::with_capacity(n);
801    for row in &grid {
802        let mut r = Vec::with_capacity(n);
803        for v in row {
804            match to_f64(v) {
805                Some(x) => r.push(x),
806                None => return Value::Error(ErrorKind::Value),
807            }
808        }
809        mat.push(r);
810    }
811    Value::Number(determinant(&mat))
812}
813
814fn determinant(mat: &[Vec<f64>]) -> f64 {
815    let n = mat.len();
816    if n == 1 {
817        return mat[0][0];
818    }
819    if n == 2 {
820        return mat[0][0] * mat[1][1] - mat[0][1] * mat[1][0];
821    }
822    let mut det = 0.0;
823    for c in 0..n {
824        let minor: Vec<Vec<f64>> = (1..n)
825            .map(|r| {
826                (0..n)
827                    .filter(|&cc| cc != c)
828                    .map(|cc| mat[r][cc])
829                    .collect()
830            })
831            .collect();
832        let sign = if c % 2 == 0 { 1.0 } else { -1.0 };
833        det += sign * mat[0][c] * determinant(&minor);
834    }
835    det
836}
837
838// ── MINVERSE ──────────────────────────────────────────────────────────────────
839
840fn minverse_fn(args: &[Value]) -> Value {
841    if let Some(e) = check_arity(args, 1, 1) {
842        return e;
843    }
844    let grid = to_2d(&args[0]);
845    let n = grid.len();
846    if n == 0 {
847        return Value::Error(ErrorKind::Value);
848    }
849    for row in &grid {
850        if row.len() != n {
851            return Value::Error(ErrorKind::Value);
852        }
853    }
854    if grid.iter().any(|row| row.iter().any(|v| matches!(v, Value::Bool(_)))) {
855        return Value::Error(ErrorKind::Value);
856    }
857    let mut mat: Vec<Vec<f64>> = Vec::with_capacity(n);
858    for row in &grid {
859        let mut r = Vec::with_capacity(n);
860        for v in row {
861            match to_f64(v) {
862                Some(x) => r.push(x),
863                None => return Value::Error(ErrorKind::Value),
864            }
865        }
866        mat.push(r);
867    }
868    match invert_matrix(mat) {
869        Some(inv) => from_2d(inv.into_iter().map(|r| r.into_iter().map(Value::Number).collect()).collect()),
870        None => Value::Error(ErrorKind::Num),
871    }
872}
873
874fn invert_matrix(mut mat: Vec<Vec<f64>>) -> Option<Vec<Vec<f64>>> {
875    let n = mat.len();
876    // Augment with identity
877    let mut inv: Vec<Vec<f64>> = (0..n)
878        .map(|i| (0..n).map(|j| if i == j { 1.0 } else { 0.0 }).collect())
879        .collect();
880    for col in 0..n {
881        // Find pivot
882        let pivot = (col..n).max_by(|&a, &b| mat[a][col].abs().partial_cmp(&mat[b][col].abs()).unwrap_or(std::cmp::Ordering::Equal))?;
883        if mat[pivot][col].abs() < 1e-12 {
884            return None; // singular
885        }
886        mat.swap(col, pivot);
887        inv.swap(col, pivot);
888        let div = mat[col][col];
889        for j in 0..n {
890            mat[col][j] /= div;
891            inv[col][j] /= div;
892        }
893        for r in 0..n {
894            if r != col {
895                let factor = mat[r][col];
896                for j in 0..n {
897                    mat[r][j] -= factor * mat[col][j];
898                    inv[r][j] -= factor * inv[col][j];
899                }
900            }
901        }
902    }
903    Some(inv)
904}
905
906// ── FREQUENCY ─────────────────────────────────────────────────────────────────
907// Array-spill function; Google Sheets returns #REF! in scalar (non-array-formula) context.
908
909fn frequency_fn(args: &[Value]) -> Value {
910    if let Some(e) = check_arity(args, 2, 2) {
911        return e;
912    }
913    // Only numeric values are counted; text, booleans and blanks are ignored.
914    let data: Vec<f64> = flatten_val(&args[0])
915        .iter()
916        .filter_map(|v| if let Value::Number(n) = v { Some(*n) } else { None })
917        .collect();
918    let bins_raw = flatten_val(&args[1]);
919    // Empty bins array → #REF! (Google Sheets behaviour)
920    if bins_raw.is_empty() || matches!(bins_raw.as_slice(), [Value::Empty]) {
921        return Value::Error(ErrorKind::Ref);
922    }
923    // Also treat an array whose only element is Empty as empty
924    let all_empty = bins_raw.iter().all(|v| matches!(v, Value::Empty));
925    if all_empty {
926        return Value::Error(ErrorKind::Ref);
927    }
928    let bins: Vec<f64> = bins_raw
929        .iter()
930        .filter_map(|v| if let Value::Number(n) = v { Some(*n) } else { None })
931        .collect();
932    if bins.is_empty() {
933        return Value::Error(ErrorKind::Ref);
934    }
935    // One bucket per bin, plus a final "greater than the last bin" bucket.
936    let mut counts = vec![0i64; bins.len() + 1];
937    for &x in &data {
938        let mut placed = false;
939        for (i, &b) in bins.iter().enumerate() {
940            if x <= b {
941                counts[i] += 1;
942                placed = true;
943                break;
944            }
945        }
946        if !placed {
947            counts[bins.len()] += 1;
948        }
949    }
950    // Sheets returns a vertical (column) array of length bins+1.
951    let col: Vec<Vec<Value>> = counts
952        .into_iter()
953        .map(|c| vec![Value::Number(c as f64)])
954        .collect();
955    from_2d(col)
956}
957
958// ── LINEST ────────────────────────────────────────────────────────────────────
959// LINEST(known_y, [known_x], [const], [stats]) → returns 1-row array [slope, intercept, ...]
960
961fn linest_fn(args: &[Value]) -> Value {
962    if let Some(e) = check_arity(args, 1, 4) {
963        return e;
964    }
965    let ys = flatten_val(&args[0]);
966    let n = ys.len();
967    // boolean or text y-values → #VALUE!
968    if ys.iter().any(|v| matches!(v, Value::Bool(_) | Value::Text(_))) {
969        return Value::Error(ErrorKind::Value);
970    }
971    if n < 2 {
972        return Value::Error(ErrorKind::NA);
973    }
974    let xs: Vec<f64> = if args.len() >= 2 {
975        let xv = flatten_val(&args[1]);
976        if xv.len() != n {
977            return Value::Error(ErrorKind::Ref);
978        }
979        xv.iter().filter_map(to_f64).collect()
980    } else {
981        (1..=n).map(|i| i as f64).collect()
982    };
983    if xs.len() != n {
984        return Value::Error(ErrorKind::Ref);
985    }
986    let y_vals: Vec<f64> = ys.iter().filter_map(to_f64).collect();
987    if y_vals.len() != n {
988        return Value::Error(ErrorKind::Value);
989    }
990    let (slope, intercept) = simple_linear_regression(&xs, &y_vals);
991    Value::Array(vec![Value::Number(slope), Value::Number(intercept)])
992}
993
994fn simple_linear_regression(xs: &[f64], ys: &[f64]) -> (f64, f64) {
995    let n = xs.len() as f64;
996    let sum_x: f64 = xs.iter().sum();
997    let sum_y: f64 = ys.iter().sum();
998    let sum_xy: f64 = xs.iter().zip(ys.iter()).map(|(x, y)| x * y).sum();
999    let sum_xx: f64 = xs.iter().map(|x| x * x).sum();
1000    let denom = n * sum_xx - sum_x * sum_x;
1001    if denom.abs() < 1e-15 {
1002        let intercept = sum_y / n;
1003        return (0.0, intercept);
1004    }
1005    let slope = (n * sum_xy - sum_x * sum_y) / denom;
1006    let intercept = (sum_y - slope * sum_x) / n;
1007    (slope, intercept)
1008}
1009
1010// ── LOGEST ────────────────────────────────────────────────────────────────────
1011// LOGEST(known_y, [known_x], [const], [stats]) → returns 1-row array [base, intercept, ...]
1012
1013fn logest_fn(args: &[Value]) -> Value {
1014    if let Some(e) = check_arity(args, 1, 4) {
1015        return e;
1016    }
1017    let ys = flatten_val(&args[0]);
1018    let n = ys.len();
1019    // boolean y-values → #VALUE! (TRUE would coerce to 1 but GS errors)
1020    if ys.iter().any(|v| matches!(v, Value::Bool(_))) {
1021        return Value::Error(ErrorKind::Value);
1022    }
1023    if n < 2 {
1024        return Value::Error(ErrorKind::NA);
1025    }
1026    let xs: Vec<f64> = if args.len() >= 2 {
1027        let xv = flatten_val(&args[1]);
1028        if xv.len() != n {
1029            return Value::Error(ErrorKind::Ref);
1030        }
1031        xv.iter().filter_map(to_f64).collect()
1032    } else {
1033        (1..=n).map(|i| i as f64).collect()
1034    };
1035    if xs.len() != n {
1036        return Value::Error(ErrorKind::Ref);
1037    }
1038    let y_vals: Vec<f64> = ys.iter().filter_map(to_f64).collect();
1039    if y_vals.len() != n {
1040        return Value::Error(ErrorKind::Value);
1041    }
1042    // Take log of y values
1043    let log_y: Vec<f64> = y_vals.iter().map(|&y| libm::log(y)).collect();
1044    if log_y.iter().any(|v| v.is_nan() || v.is_infinite()) {
1045        return Value::Error(ErrorKind::Num);
1046    }
1047    let (log_base, log_intercept) = simple_linear_regression(&xs, &log_y);
1048    let base = libm::exp(log_base);
1049    let intercept = libm::exp(log_intercept);
1050    Value::Array(vec![Value::Number(base), Value::Number(intercept)])
1051}
1052
1053// ── TREND ─────────────────────────────────────────────────────────────────────
1054// TREND(known_y, [known_x], [new_x], [const]) → array of fitted/predicted values
1055
1056fn trend_fn(args: &[Value]) -> Value {
1057    if let Some(e) = check_arity(args, 1, 4) {
1058        return e;
1059    }
1060    let ys = flatten_val(&args[0]);
1061    let n = ys.len();
1062    // boolean or text y-values → #VALUE!
1063    if ys.iter().any(|v| matches!(v, Value::Bool(_) | Value::Text(_))) {
1064        return Value::Error(ErrorKind::Value);
1065    }
1066    if n < 2 {
1067        return Value::Error(ErrorKind::NA);
1068    }
1069    let xs: Vec<f64> = if args.len() >= 2 {
1070        let xv = flatten_val(&args[1]);
1071        if xv.len() != n {
1072            return Value::Error(ErrorKind::Ref);
1073        }
1074        xv.iter().filter_map(to_f64).collect()
1075    } else {
1076        (1..=n).map(|i| i as f64).collect()
1077    };
1078    if xs.len() != n {
1079        return Value::Error(ErrorKind::Ref);
1080    }
1081    let y_vals: Vec<f64> = ys.iter().filter_map(to_f64).collect();
1082    if y_vals.len() != n {
1083        return Value::Error(ErrorKind::Value);
1084    }
1085    let new_xs: Vec<f64> = if args.len() >= 3 {
1086        flatten_val(&args[2]).iter().filter_map(to_f64).collect()
1087    } else {
1088        xs.clone()
1089    };
1090    let (slope, intercept) = simple_linear_regression(&xs, &y_vals);
1091    let result: Vec<Value> = new_xs.iter().map(|&x| Value::Number(slope * x + intercept)).collect();
1092    Value::Array(result)
1093}
1094
1095// ── GROWTH ────────────────────────────────────────────────────────────────────
1096// GROWTH(known_y, [known_x], [new_x], [const]) → exponential predictions
1097
1098fn growth_fn(args: &[Value]) -> Value {
1099    if let Some(e) = check_arity(args, 1, 4) {
1100        return e;
1101    }
1102    let ys = flatten_val(&args[0]);
1103    let n = ys.len();
1104    // boolean y-values → #VALUE! (GS errors on TRUE/FALSE in y)
1105    if ys.iter().any(|v| matches!(v, Value::Bool(_))) {
1106        return Value::Error(ErrorKind::Value);
1107    }
1108    if n < 2 {
1109        return Value::Error(ErrorKind::NA);
1110    }
1111    let xs: Vec<f64> = if args.len() >= 2 {
1112        let xv = flatten_val(&args[1]);
1113        if xv.len() != n {
1114            return Value::Error(ErrorKind::Ref);
1115        }
1116        xv.iter().filter_map(to_f64).collect()
1117    } else {
1118        (1..=n).map(|i| i as f64).collect()
1119    };
1120    if xs.len() != n {
1121        return Value::Error(ErrorKind::Ref);
1122    }
1123    let y_vals: Vec<f64> = ys.iter().filter_map(to_f64).collect();
1124    if y_vals.len() != n {
1125        return Value::Error(ErrorKind::Value);
1126    }
1127    let log_y: Vec<f64> = y_vals.iter().map(|&y| libm::log(y)).collect();
1128    if log_y.iter().any(|v| v.is_nan() || v.is_infinite()) {
1129        return Value::Error(ErrorKind::Num);
1130    }
1131    let new_xs: Vec<f64> = if args.len() >= 3 && !matches!(args[2], Value::Empty) {
1132        let vals: Vec<f64> = flatten_val(&args[2]).iter().filter_map(to_f64).collect();
1133        if vals.is_empty() { xs.clone() } else { vals }
1134    } else {
1135        xs.clone()
1136    };
1137    // b param: TRUE (default) = compute intercept normally;
1138    //          FALSE = force intercept through origin (ln(b)=0, so b=1)
1139    let use_intercept = if args.len() >= 4 {
1140        match &args[3] {
1141            Value::Bool(b) => *b,
1142            Value::Number(n) => *n != 0.0,
1143            _ => true,
1144        }
1145    } else {
1146        true
1147    };
1148    let (log_base, log_intercept) = if use_intercept {
1149        simple_linear_regression(&xs, &log_y)
1150    } else {
1151        // Force intercept = 0: slope = sum(x*lny)/sum(x^2)
1152        let sum_xy: f64 = xs.iter().zip(log_y.iter()).map(|(x, ly)| x * ly).sum();
1153        let sum_xx: f64 = xs.iter().map(|x| x * x).sum();
1154        let slope = if sum_xx.abs() < 1e-15 { 0.0 } else { sum_xy / sum_xx };
1155        (slope, 0.0)
1156    };
1157    let result: Vec<Value> = new_xs
1158        .iter()
1159        .map(|&x| Value::Number(libm::exp(log_base * x + log_intercept)))
1160        .collect();
1161    Value::Array(result)
1162}
1163
1164// ── Higher-order functions (LazyFn) ───────────────────────────────────────────
1165
1166/// Apply a LAMBDA expression with bound parameter values.
1167/// `lambda_expr` should be `Expr::FunctionCall { name: "LAMBDA", args: [p1, ..., body] }`
1168/// `bound_args` are the Values to bind to p1, p2, ...
1169fn apply_lambda(lambda_expr: &Expr, bound_args: &[Value], ctx: &mut EvalCtx<'_>) -> Option<Value> {
1170    match lambda_expr {
1171        Expr::FunctionCall { name, args, .. } if name == "LAMBDA" => {
1172            if args.is_empty() {
1173                return None;
1174            }
1175            let body = &args[args.len() - 1];
1176            let params = &args[..args.len() - 1];
1177            if params.len() != bound_args.len() {
1178                return None;
1179            }
1180            // Bind each parameter in context
1181            let mut saved: Vec<(String, Value)> = Vec::new();
1182            for (param_expr, val) in params.iter().zip(bound_args.iter()) {
1183                if let Expr::Variable(name, _) = param_expr {
1184                    let old = ctx.ctx.get(name);
1185                    saved.push((name.clone(), old));
1186                    ctx.ctx.set(name.clone(), val.clone());
1187                } else {
1188                    return None;
1189                }
1190            }
1191            let result = evaluate_expr(body, ctx);
1192            // Restore context
1193            for (name, old_val) in saved {
1194                ctx.ctx.set(name, old_val);
1195            }
1196            Some(result)
1197        }
1198        _ => None,
1199    }
1200}
1201
1202// ── BYROW ─────────────────────────────────────────────────────────────────────
1203
1204pub fn byrow_lazy_fn(args: &[Expr], ctx: &mut EvalCtx<'_>) -> Value {
1205    if let Some(e) = check_arity_len(args.len(), 2, 2) {
1206        return e;
1207    }
1208    let arr_val = evaluate_expr(&args[0], ctx);
1209    if matches!(arr_val, Value::Error(_)) {
1210        return arr_val;
1211    }
1212    let grid = to_2d(&arr_val);
1213    let lambda_expr = &args[1];
1214    let mut results: Vec<Value> = Vec::with_capacity(grid.len());
1215    for row in &grid {
1216        let row_val = Value::Array(row.clone());
1217        match apply_lambda(lambda_expr, &[row_val], ctx) {
1218            Some(v) => results.push(v),
1219            None => return Value::Error(ErrorKind::NA),
1220        }
1221    }
1222    // Return as column vector (one result per row)
1223    let col: Vec<Vec<Value>> = results.into_iter().map(|v| vec![v]).collect();
1224    from_2d(col)
1225}
1226
1227// ── BYCOL ─────────────────────────────────────────────────────────────────────
1228
1229pub fn bycol_lazy_fn(args: &[Expr], ctx: &mut EvalCtx<'_>) -> Value {
1230    if let Some(e) = check_arity_len(args.len(), 2, 2) {
1231        return e;
1232    }
1233    let arr_val = evaluate_expr(&args[0], ctx);
1234    if matches!(arr_val, Value::Error(_)) {
1235        return arr_val;
1236    }
1237    let grid = to_2d(&arr_val);
1238    let ncols = grid.first().map(|r| r.len()).unwrap_or(0);
1239    // Build columns first to avoid range-loop indexing
1240    let columns: Vec<Vec<Value>> = (0..ncols)
1241        .map(|c| grid.iter().map(|row| row[c].clone()).collect())
1242        .collect();
1243    let lambda_expr = &args[1];
1244    let mut results: Vec<Value> = Vec::with_capacity(ncols);
1245    for col in columns {
1246        // Pass flat array so SUM/MAX/MIN etc can iterate over elements
1247        let col_val = Value::Array(col);
1248        match apply_lambda(lambda_expr, &[col_val], ctx) {
1249            Some(v) => results.push(v),
1250            None => return Value::Error(ErrorKind::NA),
1251        }
1252    }
1253    // Return as row vector (one result per col)
1254    Value::Array(results)
1255}
1256
1257// ── MAP ───────────────────────────────────────────────────────────────────────
1258
1259pub fn map_lazy_fn(args: &[Expr], ctx: &mut EvalCtx<'_>) -> Value {
1260    if let Some(e) = check_arity_len(args.len(), 2, usize::MAX) {
1261        return e;
1262    }
1263    // Last arg is LAMBDA, all prior are arrays
1264    let lambda_expr = &args[args.len() - 1];
1265    let arr_count = args.len() - 1;
1266    let arrays: Vec<Vec<Value>> = args[..arr_count]
1267        .iter()
1268        .map(|a| {
1269            let v = evaluate_expr(a, ctx);
1270            flatten_val(&v)
1271        })
1272        .collect();
1273    let len = arrays[0].len();
1274    for arr in &arrays[1..] {
1275        if arr.len() != len {
1276            return Value::Error(ErrorKind::Value);
1277        }
1278    }
1279    let mut results: Vec<Value> = Vec::with_capacity(len);
1280    for i in 0..len {
1281        let bound: Vec<Value> = arrays.iter().map(|a| a[i].clone()).collect();
1282        match apply_lambda(lambda_expr, &bound, ctx) {
1283            Some(v) => results.push(v),
1284            None => return Value::Error(ErrorKind::NA),
1285        }
1286    }
1287    // Preserve shape of first array
1288    let first_grid = to_2d(&evaluate_expr(&args[0], ctx));
1289    if first_grid.len() > 1 {
1290        // 2D → reshape results
1291        let ncols = first_grid[0].len();
1292        let nrows = first_grid.len();
1293        let grid: Vec<Vec<Value>> = (0..nrows)
1294            .map(|r| (0..ncols).map(|c| results[r * ncols + c].clone()).collect())
1295            .collect();
1296        from_2d(grid)
1297    } else {
1298        Value::Array(results)
1299    }
1300}
1301
1302// ── REDUCE ────────────────────────────────────────────────────────────────────
1303
1304pub fn reduce_lazy_fn(args: &[Expr], ctx: &mut EvalCtx<'_>) -> Value {
1305    if let Some(e) = check_arity_len(args.len(), 3, 3) {
1306        return e;
1307    }
1308    let initial = evaluate_expr(&args[0], ctx);
1309    if matches!(initial, Value::Error(_)) {
1310        return initial;
1311    }
1312    let arr_val = evaluate_expr(&args[1], ctx);
1313    if matches!(arr_val, Value::Error(_)) {
1314        return arr_val;
1315    }
1316    let items = flatten_val(&arr_val);
1317    if items.is_empty() {
1318        return Value::Error(ErrorKind::Ref);
1319    }
1320    let lambda_expr = &args[2];
1321    let mut acc = initial;
1322    for item in &items {
1323        match apply_lambda(lambda_expr, &[acc.clone(), item.clone()], ctx) {
1324            Some(v) => acc = v,
1325            None => return Value::Error(ErrorKind::NA),
1326        }
1327    }
1328    acc
1329}
1330
1331// ── SCAN ──────────────────────────────────────────────────────────────────────
1332
1333pub fn scan_lazy_fn(args: &[Expr], ctx: &mut EvalCtx<'_>) -> Value {
1334    if let Some(e) = check_arity_len(args.len(), 3, 3) {
1335        return e;
1336    }
1337    let initial = evaluate_expr(&args[0], ctx);
1338    if matches!(initial, Value::Error(_)) {
1339        return initial;
1340    }
1341    let arr_val = evaluate_expr(&args[1], ctx);
1342    if matches!(arr_val, Value::Error(_)) {
1343        return arr_val;
1344    }
1345    let grid = to_2d(&arr_val);
1346    let items = flatten_val(&arr_val);
1347    let lambda_expr = &args[2];
1348    let mut acc = initial;
1349    let mut results: Vec<Value> = Vec::with_capacity(items.len());
1350    for item in &items {
1351        match apply_lambda(lambda_expr, &[acc.clone(), item.clone()], ctx) {
1352            Some(v) => {
1353                acc = v.clone();
1354                results.push(v);
1355            }
1356            None => return Value::Error(ErrorKind::NA),
1357        }
1358    }
1359    // Preserve shape of input array
1360    if grid.len() > 1 {
1361        let ncols = grid[0].len();
1362        let nrows = grid.len();
1363        let result_grid: Vec<Vec<Value>> = (0..nrows)
1364            .map(|r| (0..ncols).map(|c| results[r * ncols + c].clone()).collect())
1365            .collect();
1366        from_2d(result_grid)
1367    } else {
1368        Value::Array(results)
1369    }
1370}
1371
1372// ── MAKEARRAY ─────────────────────────────────────────────────────────────────
1373
1374pub fn makearray_lazy_fn(args: &[Expr], ctx: &mut EvalCtx<'_>) -> Value {
1375    if let Some(e) = check_arity_len(args.len(), 3, 3) {
1376        return e;
1377    }
1378    let rows_val = evaluate_expr(&args[0], ctx);
1379    let cols_val = evaluate_expr(&args[1], ctx);
1380    if matches!(rows_val, Value::Error(_)) {
1381        return rows_val;
1382    }
1383    if matches!(cols_val, Value::Error(_)) {
1384        return cols_val;
1385    }
1386    let nrows = match to_f64(&rows_val) {
1387        Some(n) if n >= 1.0 => n as usize,
1388        _ => return Value::Error(ErrorKind::Value),
1389    };
1390    let ncols = match to_f64(&cols_val) {
1391        Some(n) if n >= 1.0 => n as usize,
1392        _ => return Value::Error(ErrorKind::Value),
1393    };
1394    let lambda_expr = &args[2];
1395    let mut grid: Vec<Vec<Value>> = Vec::with_capacity(nrows);
1396    for r in 1..=nrows {
1397        let mut row = Vec::with_capacity(ncols);
1398        for c in 1..=ncols {
1399            let rv = Value::Number(r as f64);
1400            let cv = Value::Number(c as f64);
1401            match apply_lambda(lambda_expr, &[rv, cv], ctx) {
1402                Some(Value::Array(_)) => return Value::Error(ErrorKind::Value),
1403                Some(v) => row.push(v),
1404                None => return Value::Error(ErrorKind::NA),
1405            }
1406        }
1407        grid.push(row);
1408    }
1409    from_2d(grid)
1410}
1411
1412// ── Registration ─────────────────────────────────────────────────────────────
1413
1414
1415/// `ARRAYFORMULA(array_formula)` — evaluate an array formula.
1416/// In the engine this is a pass-through: the argument is already evaluated in
1417/// array context.  The function exists so formulas that wrap an expression in
1418/// ARRAYFORMULA parse and evaluate without an #NAME? error.
1419pub fn arrayformula_lazy_fn(args: &[Expr], ctx: &mut EvalCtx<'_>) -> Value {
1420    if args.len() != 1 {
1421        return Value::Error(ErrorKind::NA);
1422    }
1423    evaluate_expr(&args[0], ctx)
1424}
1425
1426pub fn register_array(registry: &mut Registry) {
1427    registry.register_eager("ROWS", rows_fn, FunctionMeta {
1428        category: "array",
1429        signature: "ROWS(array)",
1430        description: "Returns the number of rows in an array or range",
1431    });
1432    registry.register_eager("COLUMNS", columns_fn, FunctionMeta {
1433        category: "array",
1434        signature: "COLUMNS(array)",
1435        description: "Returns the number of columns in an array or range",
1436    });
1437    registry.register_eager("TRANSPOSE", transpose_fn, FunctionMeta {
1438        category: "array",
1439        signature: "TRANSPOSE(array)",
1440        description: "Transposes the rows and columns of an array",
1441    });
1442    registry.register_eager("ARRAY_CONSTRAIN", array_constrain_fn, FunctionMeta {
1443        category: "array",
1444        signature: "ARRAY_CONSTRAIN(input, num_rows, num_cols)",
1445        description: "Constrains an array to a given number of rows and columns",
1446    });
1447    registry.register_eager("CHOOSECOLS", choosecols_fn, FunctionMeta {
1448        category: "array",
1449        signature: "CHOOSECOLS(array, col_num1, ...)",
1450        description: "Returns selected columns from an array",
1451    });
1452    registry.register_eager("CHOOSEROWS", chooserows_fn, FunctionMeta {
1453        category: "array",
1454        signature: "CHOOSEROWS(array, row_num1, ...)",
1455        description: "Returns selected rows from an array",
1456    });
1457    registry.register_eager("FLATTEN", flatten_fn, FunctionMeta {
1458        category: "array",
1459        signature: "FLATTEN(array)",
1460        description: "Flattens an array into a single column",
1461    });
1462    registry.register_eager("HSTACK", hstack_fn, FunctionMeta {
1463        category: "array",
1464        signature: "HSTACK(array1, ...)",
1465        description: "Horizontally stacks arrays",
1466    });
1467    registry.register_eager("VSTACK", vstack_fn, FunctionMeta {
1468        category: "array",
1469        signature: "VSTACK(array1, ...)",
1470        description: "Vertically stacks arrays",
1471    });
1472    registry.register_eager("TOCOL", tocol_fn, FunctionMeta {
1473        category: "array",
1474        signature: "TOCOL(array, [ignore], [scan_by_col])",
1475        description: "Converts an array to a single column",
1476    });
1477    registry.register_eager("TOROW", torow_fn, FunctionMeta {
1478        category: "array",
1479        signature: "TOROW(array, [ignore], [scan_by_col])",
1480        description: "Converts an array to a single row",
1481    });
1482    registry.register_eager("WRAPCOLS", wrapcols_fn, FunctionMeta {
1483        category: "array",
1484        signature: "WRAPCOLS(vector, wrap_count, [pad_with])",
1485        description: "Wraps a vector into columns of the given length",
1486    });
1487    registry.register_eager("WRAPROWS", wraprows_fn, FunctionMeta {
1488        category: "array",
1489        signature: "WRAPROWS(vector, wrap_count, [pad_with])",
1490        description: "Wraps a vector into rows of the given length",
1491    });
1492    registry.register_eager("SORT", sort_fn, FunctionMeta {
1493        category: "array",
1494        signature: "SORT(array, [sort_index], [sort_order], [by_col])",
1495        description: "Sorts an array",
1496    });
1497    registry.register_eager("SORTBY", sortby_fn, FunctionMeta {
1498        category: "array",
1499        signature: "SORTBY(array, by_array1, [sort_order1], ...)",
1500        description: "Sorts an array based on the values in corresponding arrays",
1501    });
1502    registry.register_eager("UNIQUE", unique_fn, FunctionMeta {
1503        category: "array",
1504        signature: "UNIQUE(array, [by_col], [exactly_once])",
1505        description: "Returns unique rows or columns from an array",
1506    });
1507    registry.register_eager("SUMPRODUCT", sumproduct_fn, FunctionMeta {
1508        category: "array",
1509        signature: "SUMPRODUCT(array1, [array2], ...)",
1510        description: "Returns the sum of products of corresponding elements",
1511    });
1512    registry.register_eager("SUMXMY2", sumxmy2_fn, FunctionMeta {
1513        category: "array",
1514        signature: "SUMXMY2(array_x, array_y)",
1515        description: "Returns sum of squares of differences",
1516    });
1517    registry.register_eager("SUMX2MY2", sumx2my2_fn, FunctionMeta {
1518        category: "array",
1519        signature: "SUMX2MY2(array_x, array_y)",
1520        description: "Returns sum of (x^2 - y^2)",
1521    });
1522    registry.register_eager("SUMX2PY2", sumx2py2_fn, FunctionMeta {
1523        category: "array",
1524        signature: "SUMX2PY2(array_x, array_y)",
1525        description: "Returns sum of (x^2 + y^2)",
1526    });
1527    registry.register_eager("MMULT", mmult_fn, FunctionMeta {
1528        category: "array",
1529        signature: "MMULT(array1, array2)",
1530        description: "Returns the matrix product of two arrays",
1531    });
1532    registry.register_eager("MDETERM", mdeterm_fn, FunctionMeta {
1533        category: "array",
1534        signature: "MDETERM(array)",
1535        description: "Returns the matrix determinant",
1536    });
1537    registry.register_eager("MINVERSE", minverse_fn, FunctionMeta {
1538        category: "array",
1539        signature: "MINVERSE(array)",
1540        description: "Returns the matrix inverse",
1541    });
1542    registry.register_eager("FREQUENCY", frequency_fn, FunctionMeta {
1543        category: "array",
1544        signature: "FREQUENCY(data, bins)",
1545        description: "Calculates the frequency distribution of values",
1546    });
1547    registry.register_eager("LINEST", linest_fn, FunctionMeta {
1548        category: "array",
1549        signature: "LINEST(known_y, [known_x], [const], [stats])",
1550        description: "Returns linear regression statistics",
1551    });
1552    registry.register_eager("LOGEST", logest_fn, FunctionMeta {
1553        category: "array",
1554        signature: "LOGEST(known_y, [known_x], [const], [stats])",
1555        description: "Returns exponential regression statistics",
1556    });
1557    registry.register_eager("TREND", trend_fn, FunctionMeta {
1558        category: "array",
1559        signature: "TREND(known_y, [known_x], [new_x], [const])",
1560        description: "Returns values along a linear trend",
1561    });
1562    registry.register_eager("GROWTH", growth_fn, FunctionMeta {
1563        category: "array",
1564        signature: "GROWTH(known_y, [known_x], [new_x], [const])",
1565        description: "Returns values along an exponential trend",
1566    });
1567    registry.register_lazy("BYROW", byrow_lazy_fn, FunctionMeta {
1568        category: "array",
1569        signature: "BYROW(array, lambda)",
1570        description: "Applies a LAMBDA to each row of an array",
1571    });
1572    registry.register_lazy("BYCOL", bycol_lazy_fn, FunctionMeta {
1573        category: "array",
1574        signature: "BYCOL(array, lambda)",
1575        description: "Applies a LAMBDA to each column of an array",
1576    });
1577    registry.register_lazy("MAP", map_lazy_fn, FunctionMeta {
1578        category: "array",
1579        signature: "MAP(array1, [array2, ...], lambda)",
1580        description: "Maps a LAMBDA over one or more arrays",
1581    });
1582    registry.register_lazy("REDUCE", reduce_lazy_fn, FunctionMeta {
1583        category: "array",
1584        signature: "REDUCE(initial_value, array, lambda)",
1585        description: "Reduces an array to a single value using a LAMBDA",
1586    });
1587    registry.register_lazy("SCAN", scan_lazy_fn, FunctionMeta {
1588        category: "array",
1589        signature: "SCAN(initial_value, array, lambda)",
1590        description: "Returns running accumulation using a LAMBDA",
1591    });
1592    registry.register_lazy("MAKEARRAY", makearray_lazy_fn, FunctionMeta {
1593        category: "array",
1594        signature: "MAKEARRAY(rows, cols, lambda)",
1595        description: "Creates an array using a LAMBDA for each cell value",
1596    });
1597    registry.register_lazy("ARRAYFORMULA", arrayformula_lazy_fn, FunctionMeta {
1598        category: "array",
1599        signature: "ARRAYFORMULA(array_formula)",
1600        description: "Evaluates a formula as an array formula",
1601    });
1602}
1603
1604#[cfg(test)]
1605mod tests;