Skip to main content

formualizer_eval/builtins/lookup/
reference_info.rs

1//! Reference information functions: ROW, ROWS, COLUMN, COLUMNS
2//!
3//! Excel semantics:
4//! - ROW([reference]) - Returns the row number of a reference
5//! - ROWS(array) - Returns the number of rows in a reference
6//! - COLUMN([reference]) - Returns the column number of a reference
7//! - COLUMNS(array) - Returns the number of columns in a reference
8//!
9//! Without arguments, ROW and COLUMN return the current cell's position
10
11use crate::args::{ArgSchema, CoercionPolicy, ShapeKind};
12use crate::function::Function;
13use crate::traits::{ArgumentHandle, FunctionContext};
14use formualizer_common::{ArgKind, ExcelError, ExcelErrorKind, LiteralValue};
15use formualizer_macros::func_caps;
16use formualizer_parse::parser::ReferenceType;
17
18#[derive(Debug)]
19pub struct RowFn;
20
21impl Function for RowFn {
22    fn name(&self) -> &'static str {
23        "ROW"
24    }
25
26    fn min_args(&self) -> usize {
27        0
28    }
29
30    func_caps!(PURE);
31
32    fn arg_schema(&self) -> &'static [ArgSchema] {
33        use once_cell::sync::Lazy;
34        static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
35            vec![
36                // Optional reference
37                ArgSchema {
38                    kinds: smallvec::smallvec![ArgKind::Range],
39                    required: false,
40                    by_ref: true,
41                    shape: ShapeKind::Range,
42                    coercion: CoercionPolicy::None,
43                    max: None,
44                    repeating: None,
45                    default: None,
46                },
47            ]
48        });
49        &SCHEMA
50    }
51
52    fn eval<'a, 'b, 'c>(
53        &self,
54        args: &'c [ArgumentHandle<'a, 'b>],
55        ctx: &dyn FunctionContext<'b>,
56    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
57        if args.is_empty() {
58            // Return current cell's row if available
59            if let Some(cell_ref) = ctx.current_cell() {
60                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Int(
61                    cell_ref.coord.row() as i64,
62                )));
63            }
64            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
65                ExcelError::new(ExcelErrorKind::Value),
66            )));
67        }
68
69        // Get reference
70        let reference = match args[0].as_reference_or_eval() {
71            Ok(r) => r,
72            Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
73        };
74
75        // Extract row number from reference
76        let row = match &reference {
77            ReferenceType::Cell { row, .. } => *row,
78            ReferenceType::Range {
79                start_row: Some(sr),
80                ..
81            } => *sr,
82            _ => {
83                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
84                    ExcelError::new(ExcelErrorKind::Ref),
85                )));
86            }
87        };
88
89        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Int(
90            row as i64,
91        )))
92    }
93}
94
95#[derive(Debug)]
96pub struct RowsFn;
97
98impl Function for RowsFn {
99    fn name(&self) -> &'static str {
100        "ROWS"
101    }
102
103    fn min_args(&self) -> usize {
104        1
105    }
106
107    func_caps!(PURE);
108
109    fn arg_schema(&self) -> &'static [ArgSchema] {
110        use once_cell::sync::Lazy;
111        static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
112            vec![
113                // Required reference/range or array
114                ArgSchema {
115                    kinds: smallvec::smallvec![ArgKind::Any],
116                    required: true,
117                    by_ref: false,
118                    shape: ShapeKind::Range,
119                    coercion: CoercionPolicy::None,
120                    max: None,
121                    repeating: None,
122                    default: None,
123                },
124            ]
125        });
126        &SCHEMA
127    }
128
129    fn eval<'a, 'b, 'c>(
130        &self,
131        args: &'c [ArgumentHandle<'a, 'b>],
132        _ctx: &dyn FunctionContext<'b>,
133    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
134        if args.is_empty() {
135            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
136                ExcelError::new(ExcelErrorKind::Value),
137            )));
138        }
139
140        // Try to get reference first, fall back to array literal
141        if let Ok(reference) = args[0].as_reference_or_eval() {
142            // Calculate number of rows
143            let rows = match &reference {
144                ReferenceType::Cell { .. } => 1,
145                ReferenceType::Range {
146                    start_row: Some(sr),
147                    end_row: Some(er),
148                    ..
149                } => {
150                    if *er >= *sr {
151                        (*er - *sr + 1) as i64
152                    } else {
153                        1
154                    }
155                }
156                _ => {
157                    return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
158                        ExcelError::new(ExcelErrorKind::Ref),
159                    )));
160                }
161            };
162            Ok(crate::traits::CalcValue::Scalar(LiteralValue::Int(rows)))
163        } else {
164            // Handle array literal
165            let v = args[0].value()?.into_literal();
166            let rows = match v {
167                LiteralValue::Array(arr) => arr.len() as i64,
168                _ => 1,
169            };
170            Ok(crate::traits::CalcValue::Scalar(LiteralValue::Int(rows)))
171        }
172    }
173}
174
175#[derive(Debug)]
176pub struct ColumnFn;
177
178impl Function for ColumnFn {
179    fn name(&self) -> &'static str {
180        "COLUMN"
181    }
182
183    fn min_args(&self) -> usize {
184        0
185    }
186
187    func_caps!(PURE);
188
189    fn arg_schema(&self) -> &'static [ArgSchema] {
190        use once_cell::sync::Lazy;
191        static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
192            vec![
193                // Optional reference
194                ArgSchema {
195                    kinds: smallvec::smallvec![ArgKind::Range],
196                    required: false,
197                    by_ref: true,
198                    shape: ShapeKind::Range,
199                    coercion: CoercionPolicy::None,
200                    max: None,
201                    repeating: None,
202                    default: None,
203                },
204            ]
205        });
206        &SCHEMA
207    }
208
209    fn eval<'a, 'b, 'c>(
210        &self,
211        args: &'c [ArgumentHandle<'a, 'b>],
212        ctx: &dyn FunctionContext<'b>,
213    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
214        if args.is_empty() {
215            // Return current cell's column if available
216            if let Some(cell_ref) = ctx.current_cell() {
217                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Int(
218                    cell_ref.coord.col() as i64,
219                )));
220            }
221            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
222                ExcelError::new(ExcelErrorKind::Value),
223            )));
224        }
225
226        // Get reference
227        let reference = match args[0].as_reference_or_eval() {
228            Ok(r) => r,
229            Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
230        };
231
232        // Extract column number from reference
233        let col = match &reference {
234            ReferenceType::Cell { col, .. } => *col,
235            ReferenceType::Range {
236                start_col: Some(sc),
237                ..
238            } => *sc,
239            _ => {
240                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
241                    ExcelError::new(ExcelErrorKind::Ref),
242                )));
243            }
244        };
245
246        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Int(
247            col as i64,
248        )))
249    }
250}
251
252#[derive(Debug)]
253pub struct ColumnsFn;
254
255impl Function for ColumnsFn {
256    fn name(&self) -> &'static str {
257        "COLUMNS"
258    }
259
260    fn min_args(&self) -> usize {
261        1
262    }
263
264    func_caps!(PURE);
265
266    fn arg_schema(&self) -> &'static [ArgSchema] {
267        use once_cell::sync::Lazy;
268        static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
269            vec![
270                // Required reference/range or array
271                ArgSchema {
272                    kinds: smallvec::smallvec![ArgKind::Any],
273                    required: true,
274                    by_ref: false,
275                    shape: ShapeKind::Range,
276                    coercion: CoercionPolicy::None,
277                    max: None,
278                    repeating: None,
279                    default: None,
280                },
281            ]
282        });
283        &SCHEMA
284    }
285
286    fn eval<'a, 'b, 'c>(
287        &self,
288        args: &'c [ArgumentHandle<'a, 'b>],
289        _ctx: &dyn FunctionContext<'b>,
290    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
291        if args.is_empty() {
292            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
293                ExcelError::new(ExcelErrorKind::Value),
294            )));
295        }
296
297        // Try to get reference first, fall back to array literal
298        if let Ok(reference) = args[0].as_reference_or_eval() {
299            // Calculate number of columns
300            let cols = match &reference {
301                ReferenceType::Cell { .. } => 1,
302                ReferenceType::Range {
303                    start_col: Some(sc),
304                    end_col: Some(ec),
305                    ..
306                } => {
307                    if *ec >= *sc {
308                        (*ec - *sc + 1) as i64
309                    } else {
310                        1
311                    }
312                }
313                _ => {
314                    return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
315                        ExcelError::new(ExcelErrorKind::Ref),
316                    )));
317                }
318            };
319            Ok(crate::traits::CalcValue::Scalar(LiteralValue::Int(cols)))
320        } else {
321            // Handle array literal
322            let v = args[0].value()?.into_literal();
323            let cols = match v {
324                LiteralValue::Array(arr) => arr.first().map(|r| r.len()).unwrap_or(0) as i64,
325                _ => 1,
326            };
327            Ok(crate::traits::CalcValue::Scalar(LiteralValue::Int(cols)))
328        }
329    }
330}
331
332#[cfg(test)]
333mod tests {
334    use super::*;
335    use crate::test_workbook::TestWorkbook;
336    use formualizer_parse::parser::{ASTNode, ASTNodeType, ReferenceType};
337    use std::sync::Arc;
338
339    #[test]
340    fn row_with_reference() {
341        let wb = TestWorkbook::new().with_function(Arc::new(RowFn));
342        let ctx = wb.interpreter();
343        let f = ctx.context.get_function("", "ROW").unwrap();
344
345        // ROW(B5) -> 5
346        let b5_ref = ASTNode::new(
347            ASTNodeType::Reference {
348                original: "B5".into(),
349                reference: ReferenceType::cell(None, 5, 2),
350            },
351            None,
352        );
353
354        let args = vec![ArgumentHandle::new(&b5_ref, &ctx)];
355        let result = f
356            .dispatch(&args, &ctx.function_context(None))
357            .unwrap()
358            .into_literal();
359        assert_eq!(result, LiteralValue::Int(5));
360
361        // ROW(A1:C3) -> 1 (first row)
362        let range_ref = ASTNode::new(
363            ASTNodeType::Reference {
364                original: "A1:C3".into(),
365                reference: ReferenceType::range(None, Some(1), Some(1), Some(3), Some(3)),
366            },
367            None,
368        );
369
370        let args2 = vec![ArgumentHandle::new(&range_ref, &ctx)];
371        let result2 = f
372            .dispatch(&args2, &ctx.function_context(None))
373            .unwrap()
374            .into_literal();
375        assert_eq!(result2, LiteralValue::Int(1));
376    }
377
378    #[test]
379    fn rows_function() {
380        let wb = TestWorkbook::new().with_function(Arc::new(RowsFn));
381        let ctx = wb.interpreter();
382        let f = ctx.context.get_function("", "ROWS").unwrap();
383
384        // ROWS(A1:A5) -> 5
385        let range_ref = ASTNode::new(
386            ASTNodeType::Reference {
387                original: "A1:A5".into(),
388                reference: ReferenceType::range(None, Some(1), Some(1), Some(5), Some(1)),
389            },
390            None,
391        );
392
393        let args = vec![ArgumentHandle::new(&range_ref, &ctx)];
394        let result = f
395            .dispatch(&args, &ctx.function_context(None))
396            .unwrap()
397            .into_literal();
398        assert_eq!(result, LiteralValue::Int(5));
399
400        // ROWS(B2:D10) -> 9
401        let range_ref2 = ASTNode::new(
402            ASTNodeType::Reference {
403                original: "B2:D10".into(),
404                reference: ReferenceType::range(None, Some(2), Some(2), Some(10), Some(4)),
405            },
406            None,
407        );
408
409        let args2 = vec![ArgumentHandle::new(&range_ref2, &ctx)];
410        let result2 = f
411            .dispatch(&args2, &ctx.function_context(None))
412            .unwrap()
413            .into_literal();
414        assert_eq!(result2, LiteralValue::Int(9));
415
416        // ROWS(A1) -> 1 (single cell)
417        let cell_ref = ASTNode::new(
418            ASTNodeType::Reference {
419                original: "A1".into(),
420                reference: ReferenceType::cell(None, 1, 1),
421            },
422            None,
423        );
424
425        let args3 = vec![ArgumentHandle::new(&cell_ref, &ctx)];
426        let result3 = f
427            .dispatch(&args3, &ctx.function_context(None))
428            .unwrap()
429            .into_literal();
430        assert_eq!(result3, LiteralValue::Int(1));
431    }
432
433    #[test]
434    fn column_with_reference() {
435        let wb = TestWorkbook::new().with_function(Arc::new(ColumnFn));
436        let ctx = wb.interpreter();
437        let f = ctx.context.get_function("", "COLUMN").unwrap();
438
439        // COLUMN(C5) -> 3
440        let c5_ref = ASTNode::new(
441            ASTNodeType::Reference {
442                original: "C5".into(),
443                reference: ReferenceType::cell(None, 5, 3),
444            },
445            None,
446        );
447
448        let args = vec![ArgumentHandle::new(&c5_ref, &ctx)];
449        let result = f
450            .dispatch(&args, &ctx.function_context(None))
451            .unwrap()
452            .into_literal();
453        assert_eq!(result, LiteralValue::Int(3));
454
455        // COLUMN(B2:D4) -> 2 (first column)
456        let range_ref = ASTNode::new(
457            ASTNodeType::Reference {
458                original: "B2:D4".into(),
459                reference: ReferenceType::range(None, Some(2), Some(2), Some(4), Some(4)),
460            },
461            None,
462        );
463
464        let args2 = vec![ArgumentHandle::new(&range_ref, &ctx)];
465        let result2 = f
466            .dispatch(&args2, &ctx.function_context(None))
467            .unwrap()
468            .into_literal();
469        assert_eq!(result2, LiteralValue::Int(2));
470    }
471
472    #[test]
473    fn columns_function() {
474        let wb = TestWorkbook::new().with_function(Arc::new(ColumnsFn));
475        let ctx = wb.interpreter();
476        let f = ctx.context.get_function("", "COLUMNS").unwrap();
477
478        // COLUMNS(A1:E1) -> 5
479        let range_ref = ASTNode::new(
480            ASTNodeType::Reference {
481                original: "A1:E1".into(),
482                reference: ReferenceType::range(None, Some(1), Some(1), Some(1), Some(5)),
483            },
484            None,
485        );
486
487        let args = vec![ArgumentHandle::new(&range_ref, &ctx)];
488        let result = f
489            .dispatch(&args, &ctx.function_context(None))
490            .unwrap()
491            .into_literal();
492        assert_eq!(result, LiteralValue::Int(5));
493
494        // COLUMNS(B2:D10) -> 3
495        let range_ref2 = ASTNode::new(
496            ASTNodeType::Reference {
497                original: "B2:D10".into(),
498                reference: ReferenceType::range(None, Some(2), Some(2), Some(10), Some(4)),
499            },
500            None,
501        );
502
503        let args2 = vec![ArgumentHandle::new(&range_ref2, &ctx)];
504        let result2 = f
505            .dispatch(&args2, &ctx.function_context(None))
506            .unwrap()
507            .into_literal();
508        assert_eq!(result2, LiteralValue::Int(3));
509
510        // COLUMNS(A1) -> 1 (single cell)
511        let cell_ref = ASTNode::new(
512            ASTNodeType::Reference {
513                original: "A1".into(),
514                reference: ReferenceType::cell(None, 1, 1),
515            },
516            None,
517        );
518
519        let args3 = vec![ArgumentHandle::new(&cell_ref, &ctx)];
520        let result3 = f
521            .dispatch(&args3, &ctx.function_context(None))
522            .unwrap()
523            .into_literal();
524        assert_eq!(result3, LiteralValue::Int(1));
525    }
526
527    #[test]
528    fn rows_columns_reversed_range() {
529        // A5:A1 (start_row > end_row) should treat as 1 row / 1 column per current implementation fallback
530        let wb = TestWorkbook::new()
531            .with_function(Arc::new(RowsFn))
532            .with_function(Arc::new(ColumnsFn));
533        let ctx = wb.interpreter();
534        let rows_f = ctx.context.get_function("", "ROWS").unwrap();
535        let cols_f = ctx.context.get_function("", "COLUMNS").unwrap();
536        let rev_range = ASTNode::new(
537            ASTNodeType::Reference {
538                original: "A5:A1".into(),
539                reference: ReferenceType::range(None, Some(5), Some(1), Some(1), Some(1)),
540            },
541            None,
542        );
543        let args = vec![ArgumentHandle::new(&rev_range, &ctx)];
544        let r_count = rows_f
545            .dispatch(&args, &ctx.function_context(None))
546            .unwrap()
547            .into_literal();
548        let c_count = cols_f
549            .dispatch(&args, &ctx.function_context(None))
550            .unwrap()
551            .into_literal();
552        assert_eq!(r_count, LiteralValue::Int(1));
553        assert_eq!(c_count, LiteralValue::Int(1));
554    }
555}