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 match ctx.resolve_range_view(&r, "Sheet1") {
165 Ok(rv) => {
166 let (rows, cols) = rv.dims();
167 if rows == 1 && cols == 1 {
168 Ok(rv.as_1x1().unwrap_or(LiteralValue::Empty))
169 } else {
170 let mut rows_out: Vec<Vec<LiteralValue>> = Vec::new();
171 rv.for_each_row(&mut |row| {
172 rows_out.push(row.to_vec());
173 Ok(())
174 })?;
175 Ok(LiteralValue::Array(rows_out))
176 }
177 }
178 Err(e) => Ok(LiteralValue::Error(e)),
179 }
180 } else {
181 Ok(LiteralValue::Error(ExcelError::new(ExcelErrorKind::Ref)))
182 }
183 }
184}
185
186#[derive(Debug)]
187pub struct OffsetFn;
188impl Function for OffsetFn {
189 fn caps(&self) -> FnCaps {
190 FnCaps::PURE | FnCaps::RETURNS_REFERENCE | FnCaps::VOLATILE
192 }
193 fn name(&self) -> &'static str {
194 "OFFSET"
195 }
196 fn min_args(&self) -> usize {
197 3
198 }
199 fn arg_schema(&self) -> &'static [ArgSchema] {
200 use once_cell::sync::Lazy;
201 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(arg_byref_reference);
202 &SCHEMA
203 }
204
205 fn eval_reference<'a, 'b>(
206 &self,
207 args: &'a [ArgumentHandle<'a, 'b>],
208 _ctx: &dyn FunctionContext,
209 ) -> Option<Result<ReferenceType, ExcelError>> {
210 if args.len() < 3 {
211 return Some(Err(ExcelError::new(ExcelErrorKind::Value)));
212 }
213 let base = match args[0].as_reference_or_eval() {
214 Ok(r) => r,
215 Err(e) => return Some(Err(e)),
216 };
217 let dr = match args[1].value() {
218 Ok(v) => match v.as_ref() {
219 LiteralValue::Number(n) => *n as i64,
220 LiteralValue::Int(i) => *i,
221 _ => return Some(Err(ExcelError::new(ExcelErrorKind::Value))),
222 },
223 Err(e) => return Some(Err(e)),
224 };
225 let dc = match args[2].value() {
226 Ok(v) => match v.as_ref() {
227 LiteralValue::Number(n) => *n as i64,
228 LiteralValue::Int(i) => *i,
229 _ => return Some(Err(ExcelError::new(ExcelErrorKind::Value))),
230 },
231 Err(e) => return Some(Err(e)),
232 };
233
234 let (sheet, sr, sc, er, ec) = match base {
235 ReferenceType::Range {
236 sheet,
237 start_row,
238 start_col,
239 end_row,
240 end_col,
241 } => match (start_row, start_col, end_row, end_col) {
242 (Some(sr), Some(sc), Some(er), Some(ec)) => (sheet, sr, sc, er, ec),
243 _ => return Some(Err(ExcelError::new(ExcelErrorKind::Ref))),
244 },
245 ReferenceType::Cell { sheet, row, col } => (sheet, row, col, row, col),
246 _ => return Some(Err(ExcelError::new(ExcelErrorKind::Ref))),
247 };
248
249 let nsr = (sr as i64) + dr;
250 let nsc = (sc as i64) + dc;
251 let height = if args.len() >= 4 {
252 match args[3].value() {
253 Ok(v) => match v.as_ref() {
254 LiteralValue::Number(n) => *n as i64,
255 LiteralValue::Int(i) => *i,
256 _ => return Some(Err(ExcelError::new(ExcelErrorKind::Value))),
257 },
258 Err(e) => return Some(Err(e)),
259 }
260 } else {
261 (er as i64) - (sr as i64) + 1
262 };
263 let width = if args.len() >= 5 {
264 match args[4].value() {
265 Ok(v) => match v.as_ref() {
266 LiteralValue::Number(n) => *n as i64,
267 LiteralValue::Int(i) => *i,
268 _ => return Some(Err(ExcelError::new(ExcelErrorKind::Value))),
269 },
270 Err(e) => return Some(Err(e)),
271 }
272 } else {
273 (ec as i64) - (sc as i64) + 1
274 };
275
276 if nsr <= 0 || nsc <= 0 || height <= 0 || width <= 0 {
277 return Some(Err(ExcelError::new(ExcelErrorKind::Ref)));
278 }
279 let ner = nsr + height - 1;
280 let nec = nsc + width - 1;
281
282 if height == 1 && width == 1 {
283 Some(Ok(ReferenceType::Cell {
284 sheet,
285 row: nsr as u32,
286 col: nsc as u32,
287 }))
288 } else {
289 Some(Ok(ReferenceType::Range {
290 sheet,
291 start_row: Some(nsr as u32),
292 start_col: Some(nsc as u32),
293 end_row: Some(ner as u32),
294 end_col: Some(nec as u32),
295 }))
296 }
297 }
298
299 fn eval_scalar<'a, 'b>(
300 &self,
301 args: &'a [ArgumentHandle<'a, 'b>],
302 ctx: &dyn FunctionContext,
303 ) -> Result<LiteralValue, ExcelError> {
304 if let Some(Ok(r)) = self.eval_reference(args, ctx) {
305 match ctx.resolve_range_view(&r, "Sheet1") {
306 Ok(rv) => {
307 let (rows, cols) = rv.dims();
308 if rows == 1 && cols == 1 {
309 Ok(rv.as_1x1().unwrap_or(LiteralValue::Empty))
310 } else {
311 let mut rows_out: Vec<Vec<LiteralValue>> = Vec::new();
312 rv.for_each_row(&mut |row| {
313 rows_out.push(row.to_vec());
314 Ok(())
315 })?;
316 Ok(LiteralValue::Array(rows_out))
317 }
318 }
319 Err(e) => Ok(LiteralValue::Error(e)),
320 }
321 } else {
322 Ok(LiteralValue::Error(ExcelError::new(ExcelErrorKind::Ref)))
323 }
324 }
325}
326
327pub fn register_builtins() {
328 crate::function_registry::register_function(std::sync::Arc::new(IndexFn));
329 crate::function_registry::register_function(std::sync::Arc::new(OffsetFn));
330}
331
332#[cfg(test)]
333mod tests {
334 use super::*;
335 use crate::test_workbook::TestWorkbook;
336 use crate::traits::ArgumentHandle;
337 use formualizer_parse::parser::{ASTNode, ASTNodeType};
338
339 fn interp(wb: &TestWorkbook) -> crate::interpreter::Interpreter<'_> {
340 wb.interpreter()
341 }
342
343 #[test]
344 fn index_returns_reference_and_materializes_in_value_context() {
345 let wb = TestWorkbook::new()
346 .with_cell_a1("Sheet1", "B2", LiteralValue::Int(42))
347 .with_function(std::sync::Arc::new(IndexFn));
348 let ctx = interp(&wb);
349
350 let array_ref = ASTNode::new(
352 ASTNodeType::Reference {
353 original: "A1:C3".into(),
354 reference: ReferenceType::Range {
355 sheet: None,
356 start_row: Some(1),
357 start_col: Some(1),
358 end_row: Some(3),
359 end_col: Some(3),
360 },
361 },
362 None,
363 );
364 let row = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(2)), None);
365 let col = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(2)), None);
366 let call = ASTNode::new(
367 ASTNodeType::Function {
368 name: "INDEX".into(),
369 args: vec![array_ref.clone(), row.clone(), col.clone()],
370 },
371 None,
372 );
373
374 let r = ctx.evaluate_ast_as_reference(&call).expect("ref ok");
376 match r {
377 ReferenceType::Cell { row, col, .. } => {
378 assert_eq!((row, col), (2, 2));
379 }
380 _ => panic!(),
381 }
382
383 let args = vec![
385 ArgumentHandle::new(&array_ref, &ctx),
386 ArgumentHandle::new(&row, &ctx),
387 ArgumentHandle::new(&col, &ctx),
388 ];
389 let f = ctx.context.get_function("", "INDEX").unwrap();
390 let v = f.dispatch(&args, &ctx.function_context(None)).unwrap();
391 assert_eq!(v, LiteralValue::Int(42));
392 }
393
394 #[test]
395 fn offset_returns_reference_and_materializes() {
396 let wb = TestWorkbook::new()
397 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(1))
398 .with_cell_a1("Sheet1", "B2", LiteralValue::Int(5))
399 .with_function(std::sync::Arc::new(OffsetFn));
400 let ctx = interp(&wb);
401
402 let base = ASTNode::new(
403 ASTNodeType::Reference {
404 original: "A1".into(),
405 reference: ReferenceType::Cell {
406 sheet: None,
407 row: 1,
408 col: 1,
409 },
410 },
411 None,
412 );
413 let dr = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(1)), None);
414 let dc = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(1)), None);
415 let call = ASTNode::new(
416 ASTNodeType::Function {
417 name: "OFFSET".into(),
418 args: vec![base.clone(), dr.clone(), dc.clone()],
419 },
420 None,
421 );
422
423 let r = ctx.evaluate_ast_as_reference(&call).expect("ref ok");
424 match r {
425 ReferenceType::Cell { row, col, .. } => assert_eq!((row, col), (2, 2)),
426 _ => panic!(),
427 }
428
429 let args = vec![
430 ArgumentHandle::new(&base, &ctx),
431 ArgumentHandle::new(&dr, &ctx),
432 ArgumentHandle::new(&dc, &ctx),
433 ];
434 let f = ctx.context.get_function("", "OFFSET").unwrap();
435 let v = f.dispatch(&args, &ctx.function_context(None)).unwrap();
436 assert_eq!(v, LiteralValue::Int(5));
437 }
438}