use crate::args::{ArgSchema, CoercionPolicy, ShapeKind};
use crate::function::Function;
use crate::traits::{ArgumentHandle, FunctionContext};
use formualizer_common::{ArgKind, ExcelError, ExcelErrorKind, LiteralValue};
use formualizer_macros::func_caps;
use formualizer_parse::parser::ReferenceType;
#[derive(Debug)]
pub struct RowFn;
impl Function for RowFn {
fn name(&self) -> &'static str {
"ROW"
}
fn min_args(&self) -> usize {
0
}
func_caps!(PURE);
fn arg_schema(&self) -> &'static [ArgSchema] {
use once_cell::sync::Lazy;
static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
vec![
ArgSchema {
kinds: smallvec::smallvec![ArgKind::Range],
required: false,
by_ref: true,
shape: ShapeKind::Range,
coercion: CoercionPolicy::None,
max: None,
repeating: None,
default: None,
},
]
});
&SCHEMA
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
if args.is_empty() {
if let Some(cell_ref) = ctx.current_cell() {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Int(
cell_ref.coord.row() as i64 + 1,
)));
}
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new(ExcelErrorKind::Value),
)));
}
let reference = match args[0].as_reference_or_eval() {
Ok(r) => r,
Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
};
let row_1based = match &reference {
ReferenceType::Cell { row, .. } => *row as i64,
ReferenceType::Range {
start_row: Some(sr),
..
} => *sr as i64,
ReferenceType::Range {
start_row: None,
end_row: None,
..
} => 1,
_ => match ctx.resolve_range_view(&reference, ctx.current_sheet()) {
Ok(view) => {
if view.is_empty() {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new(ExcelErrorKind::Ref),
)));
}
view.start_row() as i64 + 1
}
Err(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
},
};
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Int(
row_1based,
)))
}
}
#[derive(Debug)]
pub struct RowsFn;
impl Function for RowsFn {
fn name(&self) -> &'static str {
"ROWS"
}
fn min_args(&self) -> usize {
1
}
func_caps!(PURE);
fn arg_schema(&self) -> &'static [ArgSchema] {
use once_cell::sync::Lazy;
static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
vec![
ArgSchema {
kinds: smallvec::smallvec![ArgKind::Any],
required: true,
by_ref: false,
shape: ShapeKind::Range,
coercion: CoercionPolicy::None,
max: None,
repeating: None,
default: None,
},
]
});
&SCHEMA
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
const EXCEL_MAX_ROWS: i64 = 1_048_576;
if args.is_empty() {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new(ExcelErrorKind::Value),
)));
}
if let Ok(reference) = args[0].as_reference_or_eval() {
let rows = match &reference {
ReferenceType::Cell { .. } => 1,
ReferenceType::Range {
start_row: Some(sr),
end_row: Some(er),
..
} => {
if *er >= *sr {
(*er - *sr + 1) as i64
} else {
1
}
}
ReferenceType::Range {
start_row: None,
end_row: None,
..
} => EXCEL_MAX_ROWS,
ReferenceType::Range {
start_row: Some(sr),
end_row: None,
..
} => EXCEL_MAX_ROWS.saturating_sub(*sr as i64).saturating_add(1),
ReferenceType::Range {
start_row: None,
end_row: Some(er),
..
} => *er as i64,
_ => match ctx.resolve_range_view(&reference, ctx.current_sheet()) {
Ok(view) => view.dims().0 as i64,
Err(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
},
};
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Int(rows)))
} else {
let v = args[0].value()?.into_literal();
let rows = match v {
LiteralValue::Array(arr) => arr.len() as i64,
_ => 1,
};
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Int(rows)))
}
}
}
#[derive(Debug)]
pub struct ColumnFn;
impl Function for ColumnFn {
fn name(&self) -> &'static str {
"COLUMN"
}
fn min_args(&self) -> usize {
0
}
func_caps!(PURE);
fn arg_schema(&self) -> &'static [ArgSchema] {
use once_cell::sync::Lazy;
static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
vec![
ArgSchema {
kinds: smallvec::smallvec![ArgKind::Range],
required: false,
by_ref: true,
shape: ShapeKind::Range,
coercion: CoercionPolicy::None,
max: None,
repeating: None,
default: None,
},
]
});
&SCHEMA
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
if args.is_empty() {
if let Some(cell_ref) = ctx.current_cell() {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Int(
cell_ref.coord.col() as i64 + 1,
)));
}
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new(ExcelErrorKind::Value),
)));
}
let reference = match args[0].as_reference_or_eval() {
Ok(r) => r,
Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
};
let col_1based = match &reference {
ReferenceType::Cell { col, .. } => *col as i64,
ReferenceType::Range {
start_col: Some(sc),
..
} => *sc as i64,
ReferenceType::Range {
start_col: None,
end_col: None,
..
} => 1,
_ => match ctx.resolve_range_view(&reference, ctx.current_sheet()) {
Ok(view) => {
if view.is_empty() {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new(ExcelErrorKind::Ref),
)));
}
view.start_col() as i64 + 1
}
Err(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
},
};
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Int(
col_1based,
)))
}
}
#[derive(Debug)]
pub struct ColumnsFn;
impl Function for ColumnsFn {
fn name(&self) -> &'static str {
"COLUMNS"
}
fn min_args(&self) -> usize {
1
}
func_caps!(PURE);
fn arg_schema(&self) -> &'static [ArgSchema] {
use once_cell::sync::Lazy;
static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
vec![
ArgSchema {
kinds: smallvec::smallvec![ArgKind::Any],
required: true,
by_ref: false,
shape: ShapeKind::Range,
coercion: CoercionPolicy::None,
max: None,
repeating: None,
default: None,
},
]
});
&SCHEMA
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
const EXCEL_MAX_COLS: i64 = 16_384;
if args.is_empty() {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new(ExcelErrorKind::Value),
)));
}
if let Ok(reference) = args[0].as_reference_or_eval() {
let cols = match &reference {
ReferenceType::Cell { .. } => 1,
ReferenceType::Range {
start_col: Some(sc),
end_col: Some(ec),
..
} => {
if *ec >= *sc {
(*ec - *sc + 1) as i64
} else {
1
}
}
ReferenceType::Range {
start_col: None,
end_col: None,
..
} => EXCEL_MAX_COLS,
ReferenceType::Range {
start_col: Some(sc),
end_col: None,
..
} => EXCEL_MAX_COLS.saturating_sub(*sc as i64).saturating_add(1),
ReferenceType::Range {
start_col: None,
end_col: Some(ec),
..
} => *ec as i64,
_ => match ctx.resolve_range_view(&reference, ctx.current_sheet()) {
Ok(view) => view.dims().1 as i64,
Err(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
},
};
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Int(cols)))
} else {
let v = args[0].value()?.into_literal();
let cols = match v {
LiteralValue::Array(arr) => arr.first().map(|r| r.len()).unwrap_or(0) as i64,
_ => 1,
};
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Int(cols)))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_workbook::TestWorkbook;
use crate::{CellRef, Coord};
use formualizer_parse::parser::{ASTNode, ASTNodeType, ReferenceType};
use std::sync::Arc;
#[test]
fn row_with_reference() {
let wb = TestWorkbook::new().with_function(Arc::new(RowFn));
let ctx = wb.interpreter();
let f = ctx.context.get_function("", "ROW").unwrap();
let b5_ref = ASTNode::new(
ASTNodeType::Reference {
original: "B5".into(),
reference: ReferenceType::cell(None, 5, 2),
},
None,
);
let args = vec![ArgumentHandle::new(&b5_ref, &ctx)];
let result = f
.dispatch(&args, &ctx.function_context(None))
.unwrap()
.into_literal();
assert_eq!(result, LiteralValue::Int(5));
let range_ref = ASTNode::new(
ASTNodeType::Reference {
original: "A1:C3".into(),
reference: ReferenceType::range(None, Some(1), Some(1), Some(3), Some(3)),
},
None,
);
let args2 = vec![ArgumentHandle::new(&range_ref, &ctx)];
let result2 = f
.dispatch(&args2, &ctx.function_context(None))
.unwrap()
.into_literal();
assert_eq!(result2, LiteralValue::Int(1));
}
#[test]
fn row_no_arg_uses_current_cell_1_based() {
let wb = TestWorkbook::new().with_function(Arc::new(RowFn));
let ctx = wb.interpreter();
let f = ctx.context.get_function("", "ROW").unwrap();
let current = CellRef::new(0, Coord::from_excel(7, 4, false, false));
let result = f
.dispatch(&[], &ctx.function_context(Some(¤t)))
.unwrap()
.into_literal();
assert_eq!(result, LiteralValue::Int(7));
}
#[test]
fn row_full_column_reference_returns_first_row() {
let wb = TestWorkbook::new().with_function(Arc::new(RowFn));
let ctx = wb.interpreter();
let f = ctx.context.get_function("", "ROW").unwrap();
let col_range_ref = ASTNode::new(
ASTNodeType::Reference {
original: "A:A".into(),
reference: ReferenceType::range(None, None, Some(1), None, Some(1)),
},
None,
);
let args = vec![ArgumentHandle::new(&col_range_ref, &ctx)];
let result = f
.dispatch(&args, &ctx.function_context(None))
.unwrap()
.into_literal();
assert_eq!(result, LiteralValue::Int(1));
}
#[test]
fn row_named_range_falls_back_to_resolved_range_view() {
let wb = TestWorkbook::new()
.with_named_range("MyRow", vec![vec![LiteralValue::Int(42)]])
.with_function(Arc::new(RowFn));
let ctx = wb.interpreter();
let f = ctx.context.get_function("", "ROW").unwrap();
let named_ref = ASTNode::new(
ASTNodeType::Reference {
original: "MyRow".into(),
reference: ReferenceType::NamedRange("MyRow".into()),
},
None,
);
let args = vec![ArgumentHandle::new(&named_ref, &ctx)];
let result = f
.dispatch(&args, &ctx.function_context(None))
.unwrap()
.into_literal();
assert_eq!(result, LiteralValue::Int(1));
}
#[test]
fn rows_function() {
let wb = TestWorkbook::new().with_function(Arc::new(RowsFn));
let ctx = wb.interpreter();
let f = ctx.context.get_function("", "ROWS").unwrap();
let range_ref = ASTNode::new(
ASTNodeType::Reference {
original: "A1:A5".into(),
reference: ReferenceType::range(None, Some(1), Some(1), Some(5), Some(1)),
},
None,
);
let args = vec![ArgumentHandle::new(&range_ref, &ctx)];
let result = f
.dispatch(&args, &ctx.function_context(None))
.unwrap()
.into_literal();
assert_eq!(result, LiteralValue::Int(5));
let range_ref2 = ASTNode::new(
ASTNodeType::Reference {
original: "B2:D10".into(),
reference: ReferenceType::range(None, Some(2), Some(2), Some(10), Some(4)),
},
None,
);
let args2 = vec![ArgumentHandle::new(&range_ref2, &ctx)];
let result2 = f
.dispatch(&args2, &ctx.function_context(None))
.unwrap()
.into_literal();
assert_eq!(result2, LiteralValue::Int(9));
let cell_ref = ASTNode::new(
ASTNodeType::Reference {
original: "A1".into(),
reference: ReferenceType::cell(None, 1, 1),
},
None,
);
let args3 = vec![ArgumentHandle::new(&cell_ref, &ctx)];
let result3 = f
.dispatch(&args3, &ctx.function_context(None))
.unwrap()
.into_literal();
assert_eq!(result3, LiteralValue::Int(1));
}
#[test]
fn rows_full_column_reference_returns_sheet_height() {
let wb = TestWorkbook::new().with_function(Arc::new(RowsFn));
let ctx = wb.interpreter();
let f = ctx.context.get_function("", "ROWS").unwrap();
let col_range_ref = ASTNode::new(
ASTNodeType::Reference {
original: "A:A".into(),
reference: ReferenceType::range(None, None, Some(1), None, Some(1)),
},
None,
);
let args = vec![ArgumentHandle::new(&col_range_ref, &ctx)];
let result = f
.dispatch(&args, &ctx.function_context(None))
.unwrap()
.into_literal();
assert_eq!(result, LiteralValue::Int(1_048_576));
}
#[test]
fn rows_named_range_falls_back_to_resolved_range_view() {
let wb = TestWorkbook::new()
.with_named_range(
"MyRows",
vec![
vec![LiteralValue::Int(1)],
vec![LiteralValue::Int(2)],
vec![LiteralValue::Int(3)],
],
)
.with_function(Arc::new(RowsFn));
let ctx = wb.interpreter();
let f = ctx.context.get_function("", "ROWS").unwrap();
let named_ref = ASTNode::new(
ASTNodeType::Reference {
original: "MyRows".into(),
reference: ReferenceType::NamedRange("MyRows".into()),
},
None,
);
let args = vec![ArgumentHandle::new(&named_ref, &ctx)];
let result = f
.dispatch(&args, &ctx.function_context(None))
.unwrap()
.into_literal();
assert_eq!(result, LiteralValue::Int(3));
}
#[test]
fn column_with_reference() {
let wb = TestWorkbook::new().with_function(Arc::new(ColumnFn));
let ctx = wb.interpreter();
let f = ctx.context.get_function("", "COLUMN").unwrap();
let c5_ref = ASTNode::new(
ASTNodeType::Reference {
original: "C5".into(),
reference: ReferenceType::cell(None, 5, 3),
},
None,
);
let args = vec![ArgumentHandle::new(&c5_ref, &ctx)];
let result = f
.dispatch(&args, &ctx.function_context(None))
.unwrap()
.into_literal();
assert_eq!(result, LiteralValue::Int(3));
let range_ref = ASTNode::new(
ASTNodeType::Reference {
original: "B2:D4".into(),
reference: ReferenceType::range(None, Some(2), Some(2), Some(4), Some(4)),
},
None,
);
let args2 = vec![ArgumentHandle::new(&range_ref, &ctx)];
let result2 = f
.dispatch(&args2, &ctx.function_context(None))
.unwrap()
.into_literal();
assert_eq!(result2, LiteralValue::Int(2));
}
#[test]
fn column_no_arg_uses_current_cell_1_based() {
let wb = TestWorkbook::new().with_function(Arc::new(ColumnFn));
let ctx = wb.interpreter();
let f = ctx.context.get_function("", "COLUMN").unwrap();
let current = CellRef::new(0, Coord::from_excel(7, 4, false, false));
let result = f
.dispatch(&[], &ctx.function_context(Some(¤t)))
.unwrap()
.into_literal();
assert_eq!(result, LiteralValue::Int(4));
}
#[test]
fn column_full_row_reference_returns_first_column() {
let wb = TestWorkbook::new().with_function(Arc::new(ColumnFn));
let ctx = wb.interpreter();
let f = ctx.context.get_function("", "COLUMN").unwrap();
let row_range_ref = ASTNode::new(
ASTNodeType::Reference {
original: "5:5".into(),
reference: ReferenceType::range(None, Some(5), None, Some(5), None),
},
None,
);
let args = vec![ArgumentHandle::new(&row_range_ref, &ctx)];
let result = f
.dispatch(&args, &ctx.function_context(None))
.unwrap()
.into_literal();
assert_eq!(result, LiteralValue::Int(1));
}
#[test]
fn column_named_range_falls_back_to_resolved_range_view() {
let wb = TestWorkbook::new()
.with_named_range("MyRange", vec![vec![LiteralValue::Int(42)]])
.with_function(Arc::new(ColumnFn));
let ctx = wb.interpreter();
let f = ctx.context.get_function("", "COLUMN").unwrap();
let named_ref = ASTNode::new(
ASTNodeType::Reference {
original: "MyRange".into(),
reference: ReferenceType::NamedRange("MyRange".into()),
},
None,
);
let args = vec![ArgumentHandle::new(&named_ref, &ctx)];
let result = f
.dispatch(&args, &ctx.function_context(None))
.unwrap()
.into_literal();
assert_eq!(result, LiteralValue::Int(1));
}
#[test]
fn columns_function() {
let wb = TestWorkbook::new().with_function(Arc::new(ColumnsFn));
let ctx = wb.interpreter();
let f = ctx.context.get_function("", "COLUMNS").unwrap();
let range_ref = ASTNode::new(
ASTNodeType::Reference {
original: "A1:E1".into(),
reference: ReferenceType::range(None, Some(1), Some(1), Some(1), Some(5)),
},
None,
);
let args = vec![ArgumentHandle::new(&range_ref, &ctx)];
let result = f
.dispatch(&args, &ctx.function_context(None))
.unwrap()
.into_literal();
assert_eq!(result, LiteralValue::Int(5));
let range_ref2 = ASTNode::new(
ASTNodeType::Reference {
original: "B2:D10".into(),
reference: ReferenceType::range(None, Some(2), Some(2), Some(10), Some(4)),
},
None,
);
let args2 = vec![ArgumentHandle::new(&range_ref2, &ctx)];
let result2 = f
.dispatch(&args2, &ctx.function_context(None))
.unwrap()
.into_literal();
assert_eq!(result2, LiteralValue::Int(3));
let cell_ref = ASTNode::new(
ASTNodeType::Reference {
original: "A1".into(),
reference: ReferenceType::cell(None, 1, 1),
},
None,
);
let args3 = vec![ArgumentHandle::new(&cell_ref, &ctx)];
let result3 = f
.dispatch(&args3, &ctx.function_context(None))
.unwrap()
.into_literal();
assert_eq!(result3, LiteralValue::Int(1));
}
#[test]
fn columns_full_row_reference_returns_sheet_width() {
let wb = TestWorkbook::new().with_function(Arc::new(ColumnsFn));
let ctx = wb.interpreter();
let f = ctx.context.get_function("", "COLUMNS").unwrap();
let row_range_ref = ASTNode::new(
ASTNodeType::Reference {
original: "1:1".into(),
reference: ReferenceType::range(None, Some(1), None, Some(1), None),
},
None,
);
let args = vec![ArgumentHandle::new(&row_range_ref, &ctx)];
let result = f
.dispatch(&args, &ctx.function_context(None))
.unwrap()
.into_literal();
assert_eq!(result, LiteralValue::Int(16_384));
}
#[test]
fn columns_named_range_falls_back_to_resolved_range_view() {
let wb = TestWorkbook::new()
.with_named_range(
"MyCols",
vec![
vec![
LiteralValue::Int(1),
LiteralValue::Int(2),
LiteralValue::Int(3),
],
vec![
LiteralValue::Int(4),
LiteralValue::Int(5),
LiteralValue::Int(6),
],
],
)
.with_function(Arc::new(ColumnsFn));
let ctx = wb.interpreter();
let f = ctx.context.get_function("", "COLUMNS").unwrap();
let named_ref = ASTNode::new(
ASTNodeType::Reference {
original: "MyCols".into(),
reference: ReferenceType::NamedRange("MyCols".into()),
},
None,
);
let args = vec![ArgumentHandle::new(&named_ref, &ctx)];
let result = f
.dispatch(&args, &ctx.function_context(None))
.unwrap()
.into_literal();
assert_eq!(result, LiteralValue::Int(3));
}
#[test]
fn rows_columns_reversed_range() {
let wb = TestWorkbook::new()
.with_function(Arc::new(RowsFn))
.with_function(Arc::new(ColumnsFn));
let ctx = wb.interpreter();
let rows_f = ctx.context.get_function("", "ROWS").unwrap();
let cols_f = ctx.context.get_function("", "COLUMNS").unwrap();
let rev_range = ASTNode::new(
ASTNodeType::Reference {
original: "A5:A1".into(),
reference: ReferenceType::range(None, Some(5), Some(1), Some(1), Some(1)),
},
None,
);
let args = vec![ArgumentHandle::new(&rev_range, &ctx)];
let r_count = rows_f
.dispatch(&args, &ctx.function_context(None))
.unwrap()
.into_literal();
let c_count = cols_f
.dispatch(&args, &ctx.function_context(None))
.unwrap()
.into_literal();
assert_eq!(r_count, LiteralValue::Int(1));
assert_eq!(c_count, LiteralValue::Int(1));
}
}