1use crate::args::{ArgSchema, CoercionPolicy, ShapeKind};
2use crate::function::{FnCaps, Function};
3use crate::traits::{ArgumentHandle, FunctionContext};
4use formualizer_common::{ArgKind, ExcelError, ExcelErrorKind, LiteralValue};
5use formualizer_parse::parser::ReferenceType;
6
7fn number_strict_scalar() -> ArgSchema {
8 ArgSchema {
9 kinds: smallvec::smallvec![ArgKind::Number],
10 required: true,
11 by_ref: false,
12 shape: ShapeKind::Scalar,
13 coercion: CoercionPolicy::NumberStrict,
14 max: None,
15 repeating: None,
16 default: None,
17 }
18}
19
20fn arg_byref_array() -> Vec<ArgSchema> {
21 vec![
22 ArgSchema {
23 kinds: smallvec::smallvec![ArgKind::Range],
24 required: true,
25 by_ref: true,
26 shape: ShapeKind::Range,
27 coercion: CoercionPolicy::None,
28 max: None,
29 repeating: None,
30 default: None,
31 },
32 number_strict_scalar(),
33 number_strict_scalar(),
34 ]
35}
36
37fn arg_byref_reference() -> Vec<ArgSchema> {
38 vec![
39 ArgSchema {
40 kinds: smallvec::smallvec![ArgKind::Range],
41 required: true,
42 by_ref: true,
43 shape: ShapeKind::Range,
44 coercion: CoercionPolicy::None,
45 max: None,
46 repeating: None,
47 default: None,
48 },
49 number_strict_scalar(),
50 number_strict_scalar(),
51 ArgSchema {
52 kinds: smallvec::smallvec![ArgKind::Number],
54 required: false,
55 by_ref: false,
56 shape: ShapeKind::Scalar,
57 coercion: CoercionPolicy::NumberStrict,
58 max: None,
59 repeating: None,
60 default: None,
61 },
62 ArgSchema {
63 kinds: smallvec::smallvec![ArgKind::Number],
65 required: false,
66 by_ref: false,
67 shape: ShapeKind::Scalar,
68 coercion: CoercionPolicy::NumberStrict,
69 max: None,
70 repeating: None,
71 default: None,
72 },
73 ]
74}
75
76#[derive(Debug)]
77pub struct IndexFn;
78impl Function for IndexFn {
79 fn caps(&self) -> FnCaps {
80 FnCaps::PURE | FnCaps::RETURNS_REFERENCE
81 }
82 fn name(&self) -> &'static str {
83 "INDEX"
84 }
85 fn min_args(&self) -> usize {
86 3
87 }
88 fn arg_schema(&self) -> &'static [ArgSchema] {
89 use once_cell::sync::Lazy;
90 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(arg_byref_array);
91 &SCHEMA
92 }
93
94 fn eval_reference<'a, 'b>(
95 &self,
96 args: &'a [ArgumentHandle<'a, 'b>],
97 _ctx: &dyn FunctionContext,
98 ) -> Option<Result<ReferenceType, ExcelError>> {
99 if args.len() < 3 {
101 return Some(Err(ExcelError::new(ExcelErrorKind::Value)));
102 }
103 let base = match args[0].as_reference_or_eval() {
104 Ok(r) => r,
105 Err(e) => return Some(Err(e)),
106 };
107 let row = match args[1].value() {
108 Ok(v) => match v.as_ref() {
109 LiteralValue::Number(n) => *n as i64,
110 LiteralValue::Int(i) => *i,
111 _ => return Some(Err(ExcelError::new(ExcelErrorKind::Value))),
112 },
113 Err(e) => return Some(Err(e)),
114 };
115 let col = match args[2].value() {
116 Ok(v) => match v.as_ref() {
117 LiteralValue::Number(n) => *n as i64,
118 LiteralValue::Int(i) => *i,
119 _ => return Some(Err(ExcelError::new(ExcelErrorKind::Value))),
120 },
121 Err(e) => return Some(Err(e)),
122 };
123
124 let (sheet, sr, sc, er, ec) = match base {
126 ReferenceType::Range {
127 sheet,
128 start_row,
129 start_col,
130 end_row,
131 end_col,
132 } => match (start_row, start_col, end_row, end_col) {
133 (Some(sr), Some(sc), Some(er), Some(ec)) => (sheet, sr, sc, er, ec),
134 _ => return Some(Err(ExcelError::new(ExcelErrorKind::Ref))),
135 },
136 ReferenceType::Cell { sheet, row, col } => (sheet, row, col, row, col),
137 _ => return Some(Err(ExcelError::new(ExcelErrorKind::Ref))),
138 };
139
140 if row <= 0 || col <= 0 {
142 return Some(Err(ExcelError::new(ExcelErrorKind::Ref)));
143 }
144 let r = sr + (row as u32) - 1;
145 let c = sc + (col as u32) - 1;
146 if r > er || c > ec {
147 return Some(Err(ExcelError::new(ExcelErrorKind::Ref)));
148 }
149
150 Some(Ok(ReferenceType::Cell {
151 sheet,
152 row: r,
153 col: c,
154 }))
155 }
156
157 fn eval_scalar<'a, 'b>(
158 &self,
159 args: &'a [ArgumentHandle<'a, 'b>],
160 ctx: &dyn FunctionContext,
161 ) -> Result<LiteralValue, ExcelError> {
162 if let Some(Ok(r)) = self.eval_reference(args, ctx) {
163 let current_sheet = ctx.current_sheet();
165 match ctx.resolve_range_view(&r, current_sheet) {
166 Ok(rv) => {
167 let (rows, cols) = rv.dims();
168 if rows == 1 && cols == 1 {
169 Ok(rv.as_1x1().unwrap_or(LiteralValue::Empty))
170 } else {
171 let mut rows_out: Vec<Vec<LiteralValue>> = Vec::new();
172 rv.for_each_row(&mut |row| {
173 rows_out.push(row.to_vec());
174 Ok(())
175 })?;
176 Ok(LiteralValue::Array(rows_out))
177 }
178 }
179 Err(e) => Ok(LiteralValue::Error(e)),
180 }
181 } else {
182 Ok(LiteralValue::Error(ExcelError::new(ExcelErrorKind::Ref)))
183 }
184 }
185}
186
187#[derive(Debug)]
188pub struct OffsetFn;
189impl Function for OffsetFn {
190 fn caps(&self) -> FnCaps {
191 FnCaps::PURE | FnCaps::RETURNS_REFERENCE | FnCaps::VOLATILE
193 }
194 fn name(&self) -> &'static str {
195 "OFFSET"
196 }
197 fn min_args(&self) -> usize {
198 3
199 }
200 fn arg_schema(&self) -> &'static [ArgSchema] {
201 use once_cell::sync::Lazy;
202 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(arg_byref_reference);
203 &SCHEMA
204 }
205
206 fn eval_reference<'a, 'b>(
207 &self,
208 args: &'a [ArgumentHandle<'a, 'b>],
209 _ctx: &dyn FunctionContext,
210 ) -> Option<Result<ReferenceType, ExcelError>> {
211 if args.len() < 3 {
212 return Some(Err(ExcelError::new(ExcelErrorKind::Value)));
213 }
214 let base = match args[0].as_reference_or_eval() {
215 Ok(r) => r,
216 Err(e) => return Some(Err(e)),
217 };
218 let dr = match args[1].value() {
219 Ok(v) => match v.as_ref() {
220 LiteralValue::Number(n) => *n as i64,
221 LiteralValue::Int(i) => *i,
222 _ => return Some(Err(ExcelError::new(ExcelErrorKind::Value))),
223 },
224 Err(e) => return Some(Err(e)),
225 };
226 let dc = match args[2].value() {
227 Ok(v) => match v.as_ref() {
228 LiteralValue::Number(n) => *n as i64,
229 LiteralValue::Int(i) => *i,
230 _ => return Some(Err(ExcelError::new(ExcelErrorKind::Value))),
231 },
232 Err(e) => return Some(Err(e)),
233 };
234
235 let (sheet, sr, sc, er, ec) = match base {
236 ReferenceType::Range {
237 sheet,
238 start_row,
239 start_col,
240 end_row,
241 end_col,
242 } => match (start_row, start_col, end_row, end_col) {
243 (Some(sr), Some(sc), Some(er), Some(ec)) => (sheet, sr, sc, er, ec),
244 _ => return Some(Err(ExcelError::new(ExcelErrorKind::Ref))),
245 },
246 ReferenceType::Cell { sheet, row, col } => (sheet, row, col, row, col),
247 _ => return Some(Err(ExcelError::new(ExcelErrorKind::Ref))),
248 };
249
250 let nsr = (sr as i64) + dr;
251 let nsc = (sc as i64) + dc;
252 let height = if args.len() >= 4 {
253 match args[3].value() {
254 Ok(v) => match v.as_ref() {
255 LiteralValue::Number(n) => *n as i64,
256 LiteralValue::Int(i) => *i,
257 _ => return Some(Err(ExcelError::new(ExcelErrorKind::Value))),
258 },
259 Err(e) => return Some(Err(e)),
260 }
261 } else {
262 (er as i64) - (sr as i64) + 1
263 };
264 let width = if args.len() >= 5 {
265 match args[4].value() {
266 Ok(v) => match v.as_ref() {
267 LiteralValue::Number(n) => *n as i64,
268 LiteralValue::Int(i) => *i,
269 _ => return Some(Err(ExcelError::new(ExcelErrorKind::Value))),
270 },
271 Err(e) => return Some(Err(e)),
272 }
273 } else {
274 (ec as i64) - (sc as i64) + 1
275 };
276
277 if nsr <= 0 || nsc <= 0 || height <= 0 || width <= 0 {
278 return Some(Err(ExcelError::new(ExcelErrorKind::Ref)));
279 }
280 let ner = nsr + height - 1;
281 let nec = nsc + width - 1;
282
283 if height == 1 && width == 1 {
284 Some(Ok(ReferenceType::Cell {
285 sheet,
286 row: nsr as u32,
287 col: nsc as u32,
288 }))
289 } else {
290 Some(Ok(ReferenceType::Range {
291 sheet,
292 start_row: Some(nsr as u32),
293 start_col: Some(nsc as u32),
294 end_row: Some(ner as u32),
295 end_col: Some(nec as u32),
296 }))
297 }
298 }
299
300 fn eval_scalar<'a, 'b>(
301 &self,
302 args: &'a [ArgumentHandle<'a, 'b>],
303 ctx: &dyn FunctionContext,
304 ) -> Result<LiteralValue, ExcelError> {
305 if let Some(Ok(r)) = self.eval_reference(args, ctx) {
306 let current_sheet = ctx.current_sheet();
307 match ctx.resolve_range_view(&r, current_sheet) {
308 Ok(rv) => {
309 let (rows, cols) = rv.dims();
310 if rows == 1 && cols == 1 {
311 Ok(rv.as_1x1().unwrap_or(LiteralValue::Empty))
312 } else {
313 let mut rows_out: Vec<Vec<LiteralValue>> = Vec::new();
314 rv.for_each_row(&mut |row| {
315 rows_out.push(row.to_vec());
316 Ok(())
317 })?;
318 Ok(LiteralValue::Array(rows_out))
319 }
320 }
321 Err(e) => Ok(LiteralValue::Error(e)),
322 }
323 } else {
324 Ok(LiteralValue::Error(ExcelError::new(ExcelErrorKind::Ref)))
325 }
326 }
327}
328
329pub fn register_builtins() {
330 crate::function_registry::register_function(std::sync::Arc::new(IndexFn));
331 crate::function_registry::register_function(std::sync::Arc::new(OffsetFn));
332}
333
334#[cfg(test)]
335mod tests {
336 use super::*;
337 use crate::test_workbook::TestWorkbook;
338 use crate::traits::ArgumentHandle;
339 use formualizer_parse::parser::{ASTNode, ASTNodeType};
340
341 fn interp(wb: &TestWorkbook) -> crate::interpreter::Interpreter<'_> {
342 wb.interpreter()
343 }
344
345 #[test]
346 fn index_returns_reference_and_materializes_in_value_context() {
347 let wb = TestWorkbook::new()
348 .with_cell_a1("Sheet1", "B2", LiteralValue::Int(42))
349 .with_function(std::sync::Arc::new(IndexFn));
350 let ctx = interp(&wb);
351
352 let array_ref = ASTNode::new(
354 ASTNodeType::Reference {
355 original: "A1:C3".into(),
356 reference: ReferenceType::Range {
357 sheet: None,
358 start_row: Some(1),
359 start_col: Some(1),
360 end_row: Some(3),
361 end_col: Some(3),
362 },
363 },
364 None,
365 );
366 let row = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(2)), None);
367 let col = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(2)), None);
368 let call = ASTNode::new(
369 ASTNodeType::Function {
370 name: "INDEX".into(),
371 args: vec![array_ref.clone(), row.clone(), col.clone()],
372 },
373 None,
374 );
375
376 let r = ctx.evaluate_ast_as_reference(&call).expect("ref ok");
378 match r {
379 ReferenceType::Cell { row, col, .. } => {
380 assert_eq!((row, col), (2, 2));
381 }
382 _ => panic!(),
383 }
384
385 let args = vec![
387 ArgumentHandle::new(&array_ref, &ctx),
388 ArgumentHandle::new(&row, &ctx),
389 ArgumentHandle::new(&col, &ctx),
390 ];
391 let f = ctx.context.get_function("", "INDEX").unwrap();
392 let v = f.dispatch(&args, &ctx.function_context(None)).unwrap();
393 assert_eq!(v, LiteralValue::Int(42));
394 }
395
396 #[test]
397 fn offset_returns_reference_and_materializes() {
398 let wb = TestWorkbook::new()
399 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(1))
400 .with_cell_a1("Sheet1", "B2", LiteralValue::Int(5))
401 .with_function(std::sync::Arc::new(OffsetFn));
402 let ctx = interp(&wb);
403
404 let base = ASTNode::new(
405 ASTNodeType::Reference {
406 original: "A1".into(),
407 reference: ReferenceType::Cell {
408 sheet: None,
409 row: 1,
410 col: 1,
411 },
412 },
413 None,
414 );
415 let dr = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(1)), None);
416 let dc = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(1)), None);
417 let call = ASTNode::new(
418 ASTNodeType::Function {
419 name: "OFFSET".into(),
420 args: vec![base.clone(), dr.clone(), dc.clone()],
421 },
422 None,
423 );
424
425 let r = ctx.evaluate_ast_as_reference(&call).expect("ref ok");
426 match r {
427 ReferenceType::Cell { row, col, .. } => assert_eq!((row, col), (2, 2)),
428 _ => panic!(),
429 }
430
431 let args = vec![
432 ArgumentHandle::new(&base, &ctx),
433 ArgumentHandle::new(&dr, &ctx),
434 ArgumentHandle::new(&dc, &ctx),
435 ];
436 let f = ctx.context.get_function("", "OFFSET").unwrap();
437 let v = f.dispatch(&args, &ctx.function_context(None)).unwrap();
438 assert_eq!(v, LiteralValue::Int(5));
439 }
440}