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