1use crate::args::{ArgSchema, CoercionPolicy, ShapeKind};
10use crate::function::Function;
11use crate::traits::{ArgumentHandle, FunctionContext};
12use formualizer_common::{
13 ArgKind, ExcelError, ExcelErrorKind, LiteralValue, col_letters_from_1based,
14};
15use formualizer_macros::func_caps;
16
17fn column_to_letters(col: u32) -> String {
19 col_letters_from_1based(col).unwrap_or_default()
20}
21
22#[derive(Debug)]
23pub struct AddressFn;
24
25impl Function for AddressFn {
26 fn name(&self) -> &'static str {
27 "ADDRESS"
28 }
29
30 fn min_args(&self) -> usize {
31 2
32 }
33
34 func_caps!(PURE);
35
36 fn arg_schema(&self) -> &'static [ArgSchema] {
37 use once_cell::sync::Lazy;
38 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
39 vec![
40 ArgSchema {
42 kinds: smallvec::smallvec![ArgKind::Number],
43 required: true,
44 by_ref: false,
45 shape: ShapeKind::Scalar,
46 coercion: CoercionPolicy::NumberStrict,
47 max: None,
48 repeating: None,
49 default: None,
50 },
51 ArgSchema {
53 kinds: smallvec::smallvec![ArgKind::Number],
54 required: true,
55 by_ref: false,
56 shape: ShapeKind::Scalar,
57 coercion: CoercionPolicy::NumberStrict,
58 max: None,
59 repeating: None,
60 default: None,
61 },
62 ArgSchema {
64 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: Some(LiteralValue::Int(1)),
72 },
73 ArgSchema {
75 kinds: smallvec::smallvec![ArgKind::Logical],
76 required: false,
77 by_ref: false,
78 shape: ShapeKind::Scalar,
79 coercion: CoercionPolicy::Logical,
80 max: None,
81 repeating: None,
82 default: Some(LiteralValue::Boolean(true)),
83 },
84 ArgSchema {
86 kinds: smallvec::smallvec![ArgKind::Text],
87 required: false,
88 by_ref: false,
89 shape: ShapeKind::Scalar,
90 coercion: CoercionPolicy::None,
91 max: None,
92 repeating: None,
93 default: None,
94 },
95 ]
96 });
97 &SCHEMA
98 }
99
100 fn eval<'a, 'b, 'c>(
101 &self,
102 args: &'c [ArgumentHandle<'a, 'b>],
103 _ctx: &dyn FunctionContext<'b>,
104 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
105 if args.len() < 2 {
106 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
107 ExcelError::new(ExcelErrorKind::Value),
108 )));
109 }
110
111 let row_val = args[0].value()?.into_literal();
113 if let LiteralValue::Error(e) = row_val {
114 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
115 }
116 let row = match row_val {
117 LiteralValue::Number(n) => n as i64,
118 LiteralValue::Int(i) => i,
119 _ => {
120 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
121 ExcelError::new(ExcelErrorKind::Value),
122 )));
123 }
124 };
125
126 if !(1..=1_048_576).contains(&row) {
127 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
128 ExcelError::new(ExcelErrorKind::Value),
129 )));
130 }
131
132 let col_val = args[1].value()?.into_literal();
134 if let LiteralValue::Error(e) = col_val {
135 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
136 }
137 let col = match col_val {
138 LiteralValue::Number(n) => n as i64,
139 LiteralValue::Int(i) => i,
140 _ => {
141 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
142 ExcelError::new(ExcelErrorKind::Value),
143 )));
144 }
145 };
146
147 if !(1..=16384).contains(&col) {
148 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
149 ExcelError::new(ExcelErrorKind::Value),
150 )));
151 }
152
153 let abs_num = if args.len() > 2 {
155 let abs_val = args[2].value()?.into_literal();
156 if let LiteralValue::Error(e) = abs_val {
157 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
158 }
159 match abs_val {
160 LiteralValue::Number(n) => n as i64,
161 LiteralValue::Int(i) => i,
162 _ => 1,
163 }
164 } else {
165 1
166 };
167
168 if !(1..=4).contains(&abs_num) {
169 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
170 ExcelError::new(ExcelErrorKind::Value),
171 )));
172 }
173
174 let a1_style = if args.len() > 3 {
176 let a1_val = args[3].value()?.into_literal();
177 if let LiteralValue::Error(e) = a1_val {
178 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
179 }
180 match a1_val {
181 LiteralValue::Boolean(b) => b,
182 _ => true,
183 }
184 } else {
185 true
186 };
187
188 let sheet_name = if args.len() > 4 {
190 let sheet_val = args[4].value()?.into_literal();
191 if let LiteralValue::Error(e) = sheet_val {
192 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
193 }
194 match sheet_val {
195 LiteralValue::Text(s) => Some(s),
196 _ => None,
197 }
198 } else {
199 None
200 };
201
202 let address = if a1_style {
204 let col_letters = column_to_letters(col as u32);
206 let (col_abs, row_abs) = match abs_num {
207 1 => (true, true), 2 => (false, true), 3 => (true, false), 4 => (false, false), _ => (true, true),
212 };
213
214 let col_str = if col_abs {
215 format!("${col_letters}")
216 } else {
217 col_letters
218 };
219 let row_str = if row_abs {
220 format!("${row}")
221 } else {
222 row.to_string()
223 };
224 format!("{col_str}{row_str}")
225 } else {
226 let (col_abs, row_abs) = match abs_num {
228 1 => (true, true),
229 2 => (false, true),
230 3 => (true, false),
231 4 => (false, false),
232 _ => (true, true),
233 };
234
235 let row_str = if row_abs {
236 format!("R{row}")
237 } else {
238 format!("R[{row}]")
239 };
240 let col_str = if col_abs {
241 format!("C{col}")
242 } else {
243 format!("C[{col}]")
244 };
245 format!("{row_str}{col_str}")
246 };
247
248 let final_address = if let Some(sheet) = sheet_name {
250 if sheet.contains(' ') || sheet.contains('!') || sheet.contains('\'') {
252 format!("'{}'!{address}", sheet.replace('\'', "''"))
253 } else {
254 format!("{sheet}!{address}")
255 }
256 } else {
257 address
258 };
259
260 Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(
261 final_address,
262 )))
263 }
264}
265
266#[cfg(test)]
267mod tests {
268 use super::*;
269 use crate::test_workbook::TestWorkbook;
270 use formualizer_parse::parser::{ASTNode, ASTNodeType};
271 use std::sync::Arc;
272
273 fn lit(v: LiteralValue) -> ASTNode {
274 ASTNode::new(ASTNodeType::Literal(v), None)
275 }
276
277 #[test]
278 fn test_column_to_letters() {
279 assert_eq!(column_to_letters(1), "A");
280 assert_eq!(column_to_letters(26), "Z");
281 assert_eq!(column_to_letters(27), "AA");
282 assert_eq!(column_to_letters(52), "AZ");
283 assert_eq!(column_to_letters(53), "BA");
284 assert_eq!(column_to_letters(702), "ZZ");
285 assert_eq!(column_to_letters(703), "AAA");
286 }
287
288 #[test]
289 fn address_basic() {
290 let wb = TestWorkbook::new().with_function(Arc::new(AddressFn));
291 let ctx = wb.interpreter();
292 let f = ctx.context.get_function("", "ADDRESS").unwrap();
293
294 let two = lit(LiteralValue::Int(2));
296 let three = lit(LiteralValue::Int(3));
297
298 let args = vec![
299 ArgumentHandle::new(&two, &ctx),
300 ArgumentHandle::new(&three, &ctx),
301 ];
302
303 let result = f
304 .dispatch(&args, &ctx.function_context(None))
305 .unwrap()
306 .into_literal();
307 assert_eq!(result, LiteralValue::Text("$C$2".into()));
308 }
309
310 #[test]
311 fn address_abs_variations() {
312 let wb = TestWorkbook::new().with_function(Arc::new(AddressFn));
313 let ctx = wb.interpreter();
314 let f = ctx.context.get_function("", "ADDRESS").unwrap();
315
316 let row = lit(LiteralValue::Int(5));
317 let col = lit(LiteralValue::Int(4)); let abs1 = lit(LiteralValue::Int(1));
321 let args1 = vec![
322 ArgumentHandle::new(&row, &ctx),
323 ArgumentHandle::new(&col, &ctx),
324 ArgumentHandle::new(&abs1, &ctx),
325 ];
326 assert_eq!(
327 f.dispatch(&args1, &ctx.function_context(None)).unwrap(),
328 LiteralValue::Text("$D$5".into())
329 );
330
331 let abs2 = lit(LiteralValue::Int(2));
333 let args2 = vec![
334 ArgumentHandle::new(&row, &ctx),
335 ArgumentHandle::new(&col, &ctx),
336 ArgumentHandle::new(&abs2, &ctx),
337 ];
338 assert_eq!(
339 f.dispatch(&args2, &ctx.function_context(None)).unwrap(),
340 LiteralValue::Text("D$5".into())
341 );
342
343 let abs3 = lit(LiteralValue::Int(3));
345 let args3 = vec![
346 ArgumentHandle::new(&row, &ctx),
347 ArgumentHandle::new(&col, &ctx),
348 ArgumentHandle::new(&abs3, &ctx),
349 ];
350 assert_eq!(
351 f.dispatch(&args3, &ctx.function_context(None)).unwrap(),
352 LiteralValue::Text("$D5".into())
353 );
354
355 let abs4 = lit(LiteralValue::Int(4));
357 let args4 = vec![
358 ArgumentHandle::new(&row, &ctx),
359 ArgumentHandle::new(&col, &ctx),
360 ArgumentHandle::new(&abs4, &ctx),
361 ];
362 assert_eq!(
363 f.dispatch(&args4, &ctx.function_context(None)).unwrap(),
364 LiteralValue::Text("D5".into())
365 );
366 }
367
368 #[test]
369 fn address_with_sheet() {
370 let wb = TestWorkbook::new().with_function(Arc::new(AddressFn));
371 let ctx = wb.interpreter();
372 let f = ctx.context.get_function("", "ADDRESS").unwrap();
373
374 let row = lit(LiteralValue::Int(1));
375 let col = lit(LiteralValue::Int(1));
376 let abs_num = lit(LiteralValue::Int(1));
377 let a1_style = lit(LiteralValue::Boolean(true));
378
379 let sheet1 = lit(LiteralValue::Text("Sheet1".into()));
381 let args1 = vec![
382 ArgumentHandle::new(&row, &ctx),
383 ArgumentHandle::new(&col, &ctx),
384 ArgumentHandle::new(&abs_num, &ctx),
385 ArgumentHandle::new(&a1_style, &ctx),
386 ArgumentHandle::new(&sheet1, &ctx),
387 ];
388 assert_eq!(
389 f.dispatch(&args1, &ctx.function_context(None)).unwrap(),
390 LiteralValue::Text("Sheet1!$A$1".into())
391 );
392
393 let sheet2 = lit(LiteralValue::Text("My Sheet".into()));
395 let args2 = vec![
396 ArgumentHandle::new(&row, &ctx),
397 ArgumentHandle::new(&col, &ctx),
398 ArgumentHandle::new(&abs_num, &ctx),
399 ArgumentHandle::new(&a1_style, &ctx),
400 ArgumentHandle::new(&sheet2, &ctx),
401 ];
402 assert_eq!(
403 f.dispatch(&args2, &ctx.function_context(None)).unwrap(),
404 LiteralValue::Text("'My Sheet'!$A$1".into())
405 );
406 }
407
408 #[test]
409 fn address_r1c1_style() {
410 let wb = TestWorkbook::new().with_function(Arc::new(AddressFn));
411 let ctx = wb.interpreter();
412 let f = ctx.context.get_function("", "ADDRESS").unwrap();
413
414 let row = lit(LiteralValue::Int(5));
415 let col = lit(LiteralValue::Int(3));
416 let abs1 = lit(LiteralValue::Int(1));
417 let r1c1 = lit(LiteralValue::Boolean(false));
418
419 let args = vec![
421 ArgumentHandle::new(&row, &ctx),
422 ArgumentHandle::new(&col, &ctx),
423 ArgumentHandle::new(&abs1, &ctx),
424 ArgumentHandle::new(&r1c1, &ctx),
425 ];
426 assert_eq!(
427 f.dispatch(&args, &ctx.function_context(None))
428 .unwrap()
429 .into_literal(),
430 LiteralValue::Text("R5C3".into())
431 );
432
433 let abs4 = lit(LiteralValue::Int(4));
435 let args2 = vec![
436 ArgumentHandle::new(&row, &ctx),
437 ArgumentHandle::new(&col, &ctx),
438 ArgumentHandle::new(&abs4, &ctx),
439 ArgumentHandle::new(&r1c1, &ctx),
440 ];
441 assert_eq!(
442 f.dispatch(&args2, &ctx.function_context(None)).unwrap(),
443 LiteralValue::Text("R[5]C[3]".into())
444 );
445 }
446
447 #[test]
448 fn address_edge_cases() {
449 let wb = TestWorkbook::new().with_function(Arc::new(AddressFn));
450 let ctx = wb.interpreter();
451 let f = ctx.context.get_function("", "ADDRESS").unwrap();
452
453 let big_row = lit(LiteralValue::Int(1_048_577));
455 let col = lit(LiteralValue::Int(1));
456 let args = vec![
457 ArgumentHandle::new(&big_row, &ctx),
458 ArgumentHandle::new(&col, &ctx),
459 ];
460 let result = f
461 .dispatch(&args, &ctx.function_context(None))
462 .unwrap()
463 .into_literal();
464 assert!(matches!(result, LiteralValue::Error(e) if e.kind == ExcelErrorKind::Value));
465
466 let row = lit(LiteralValue::Int(1));
468 let big_col = lit(LiteralValue::Int(16385));
469 let args2 = vec![
470 ArgumentHandle::new(&row, &ctx),
471 ArgumentHandle::new(&big_col, &ctx),
472 ];
473 let result2 = f
474 .dispatch(&args2, &ctx.function_context(None))
475 .unwrap()
476 .into_literal();
477 assert!(matches!(result2, LiteralValue::Error(e) if e.kind == ExcelErrorKind::Value));
478
479 let abs5 = lit(LiteralValue::Int(5));
481 let normal_col = lit(LiteralValue::Int(1));
482 let args3 = vec![
483 ArgumentHandle::new(&row, &ctx),
484 ArgumentHandle::new(&normal_col, &ctx),
485 ArgumentHandle::new(&abs5, &ctx),
486 ];
487 let result3 = f
488 .dispatch(&args3, &ctx.function_context(None))
489 .unwrap()
490 .into_literal();
491 assert!(matches!(result3, LiteralValue::Error(e) if e.kind == ExcelErrorKind::Value));
492 }
493}