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