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 {
72 fn name(&self) -> &'static str {
73 "ADDRESS"
74 }
75
76 fn min_args(&self) -> usize {
77 2
78 }
79
80 func_caps!(PURE);
81
82 fn arg_schema(&self) -> &'static [ArgSchema] {
83 use once_cell::sync::Lazy;
84 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
85 vec![
86 ArgSchema {
88 kinds: smallvec::smallvec![ArgKind::Number],
89 required: true,
90 by_ref: false,
91 shape: ShapeKind::Scalar,
92 coercion: CoercionPolicy::NumberStrict,
93 max: None,
94 repeating: None,
95 default: None,
96 },
97 ArgSchema {
99 kinds: smallvec::smallvec![ArgKind::Number],
100 required: true,
101 by_ref: false,
102 shape: ShapeKind::Scalar,
103 coercion: CoercionPolicy::NumberStrict,
104 max: None,
105 repeating: None,
106 default: None,
107 },
108 ArgSchema {
110 kinds: smallvec::smallvec![ArgKind::Number],
111 required: false,
112 by_ref: false,
113 shape: ShapeKind::Scalar,
114 coercion: CoercionPolicy::NumberStrict,
115 max: None,
116 repeating: None,
117 default: Some(LiteralValue::Int(1)),
118 },
119 ArgSchema {
121 kinds: smallvec::smallvec![ArgKind::Logical],
122 required: false,
123 by_ref: false,
124 shape: ShapeKind::Scalar,
125 coercion: CoercionPolicy::Logical,
126 max: None,
127 repeating: None,
128 default: Some(LiteralValue::Boolean(true)),
129 },
130 ArgSchema {
132 kinds: smallvec::smallvec![ArgKind::Text],
133 required: false,
134 by_ref: false,
135 shape: ShapeKind::Scalar,
136 coercion: CoercionPolicy::None,
137 max: None,
138 repeating: None,
139 default: None,
140 },
141 ]
142 });
143 &SCHEMA
144 }
145
146 fn eval<'a, 'b, 'c>(
147 &self,
148 args: &'c [ArgumentHandle<'a, 'b>],
149 _ctx: &dyn FunctionContext<'b>,
150 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
151 if args.len() < 2 {
152 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
153 ExcelError::new(ExcelErrorKind::Value),
154 )));
155 }
156
157 let row_val = args[0].value()?.into_literal();
159 if let LiteralValue::Error(e) = row_val {
160 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
161 }
162 let row = match row_val {
163 LiteralValue::Number(n) => n as i64,
164 LiteralValue::Int(i) => i,
165 _ => {
166 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
167 ExcelError::new(ExcelErrorKind::Value),
168 )));
169 }
170 };
171
172 if !(1..=1_048_576).contains(&row) {
173 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
174 ExcelError::new(ExcelErrorKind::Value),
175 )));
176 }
177
178 let col_val = args[1].value()?.into_literal();
180 if let LiteralValue::Error(e) = col_val {
181 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
182 }
183 let col = match col_val {
184 LiteralValue::Number(n) => n as i64,
185 LiteralValue::Int(i) => i,
186 _ => {
187 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
188 ExcelError::new(ExcelErrorKind::Value),
189 )));
190 }
191 };
192
193 if !(1..=16384).contains(&col) {
194 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
195 ExcelError::new(ExcelErrorKind::Value),
196 )));
197 }
198
199 let abs_num = if args.len() > 2 {
201 let abs_val = args[2].value()?.into_literal();
202 if let LiteralValue::Error(e) = abs_val {
203 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
204 }
205 match abs_val {
206 LiteralValue::Number(n) => n as i64,
207 LiteralValue::Int(i) => i,
208 _ => 1,
209 }
210 } else {
211 1
212 };
213
214 if !(1..=4).contains(&abs_num) {
215 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
216 ExcelError::new(ExcelErrorKind::Value),
217 )));
218 }
219
220 let a1_style = if args.len() > 3 {
222 let a1_val = args[3].value()?.into_literal();
223 if let LiteralValue::Error(e) = a1_val {
224 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
225 }
226 match a1_val {
227 LiteralValue::Boolean(b) => b,
228 _ => true,
229 }
230 } else {
231 true
232 };
233
234 let sheet_name = if args.len() > 4 {
236 let sheet_val = args[4].value()?.into_literal();
237 if let LiteralValue::Error(e) = sheet_val {
238 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
239 }
240 match sheet_val {
241 LiteralValue::Text(s) => Some(s),
242 _ => None,
243 }
244 } else {
245 None
246 };
247
248 let address = if a1_style {
250 let col_letters = column_to_letters(col as u32);
252 let (col_abs, row_abs) = match abs_num {
253 1 => (true, true), 2 => (false, true), 3 => (true, false), 4 => (false, false), _ => (true, true),
258 };
259
260 let col_str = if col_abs {
261 format!("${col_letters}")
262 } else {
263 col_letters
264 };
265 let row_str = if row_abs {
266 format!("${row}")
267 } else {
268 row.to_string()
269 };
270 format!("{col_str}{row_str}")
271 } else {
272 let (col_abs, row_abs) = match abs_num {
274 1 => (true, true),
275 2 => (false, true),
276 3 => (true, false),
277 4 => (false, false),
278 _ => (true, true),
279 };
280
281 let row_str = if row_abs {
282 format!("R{row}")
283 } else {
284 format!("R[{row}]")
285 };
286 let col_str = if col_abs {
287 format!("C{col}")
288 } else {
289 format!("C[{col}]")
290 };
291 format!("{row_str}{col_str}")
292 };
293
294 let final_address = if let Some(sheet) = sheet_name {
296 if sheet.contains(' ') || sheet.contains('!') || sheet.contains('\'') {
298 format!("'{}'!{address}", sheet.replace('\'', "''"))
299 } else {
300 format!("{sheet}!{address}")
301 }
302 } else {
303 address
304 };
305
306 Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(
307 final_address,
308 )))
309 }
310}
311
312#[cfg(test)]
313mod tests {
314 use super::*;
315 use crate::test_workbook::TestWorkbook;
316 use formualizer_parse::parser::{ASTNode, ASTNodeType};
317 use std::sync::Arc;
318
319 fn lit(v: LiteralValue) -> ASTNode {
320 ASTNode::new(ASTNodeType::Literal(v), None)
321 }
322
323 #[test]
324 fn test_column_to_letters() {
325 assert_eq!(column_to_letters(1), "A");
326 assert_eq!(column_to_letters(26), "Z");
327 assert_eq!(column_to_letters(27), "AA");
328 assert_eq!(column_to_letters(52), "AZ");
329 assert_eq!(column_to_letters(53), "BA");
330 assert_eq!(column_to_letters(702), "ZZ");
331 assert_eq!(column_to_letters(703), "AAA");
332 }
333
334 #[test]
335 fn address_basic() {
336 let wb = TestWorkbook::new().with_function(Arc::new(AddressFn));
337 let ctx = wb.interpreter();
338 let f = ctx.context.get_function("", "ADDRESS").unwrap();
339
340 let two = lit(LiteralValue::Int(2));
342 let three = lit(LiteralValue::Int(3));
343
344 let args = vec![
345 ArgumentHandle::new(&two, &ctx),
346 ArgumentHandle::new(&three, &ctx),
347 ];
348
349 let result = f
350 .dispatch(&args, &ctx.function_context(None))
351 .unwrap()
352 .into_literal();
353 assert_eq!(result, LiteralValue::Text("$C$2".into()));
354 }
355
356 #[test]
357 fn address_abs_variations() {
358 let wb = TestWorkbook::new().with_function(Arc::new(AddressFn));
359 let ctx = wb.interpreter();
360 let f = ctx.context.get_function("", "ADDRESS").unwrap();
361
362 let row = lit(LiteralValue::Int(5));
363 let col = lit(LiteralValue::Int(4)); let abs1 = lit(LiteralValue::Int(1));
367 let args1 = vec![
368 ArgumentHandle::new(&row, &ctx),
369 ArgumentHandle::new(&col, &ctx),
370 ArgumentHandle::new(&abs1, &ctx),
371 ];
372 assert_eq!(
373 f.dispatch(&args1, &ctx.function_context(None)).unwrap(),
374 LiteralValue::Text("$D$5".into())
375 );
376
377 let abs2 = lit(LiteralValue::Int(2));
379 let args2 = vec![
380 ArgumentHandle::new(&row, &ctx),
381 ArgumentHandle::new(&col, &ctx),
382 ArgumentHandle::new(&abs2, &ctx),
383 ];
384 assert_eq!(
385 f.dispatch(&args2, &ctx.function_context(None)).unwrap(),
386 LiteralValue::Text("D$5".into())
387 );
388
389 let abs3 = lit(LiteralValue::Int(3));
391 let args3 = vec![
392 ArgumentHandle::new(&row, &ctx),
393 ArgumentHandle::new(&col, &ctx),
394 ArgumentHandle::new(&abs3, &ctx),
395 ];
396 assert_eq!(
397 f.dispatch(&args3, &ctx.function_context(None)).unwrap(),
398 LiteralValue::Text("$D5".into())
399 );
400
401 let abs4 = lit(LiteralValue::Int(4));
403 let args4 = vec![
404 ArgumentHandle::new(&row, &ctx),
405 ArgumentHandle::new(&col, &ctx),
406 ArgumentHandle::new(&abs4, &ctx),
407 ];
408 assert_eq!(
409 f.dispatch(&args4, &ctx.function_context(None)).unwrap(),
410 LiteralValue::Text("D5".into())
411 );
412 }
413
414 #[test]
415 fn address_with_sheet() {
416 let wb = TestWorkbook::new().with_function(Arc::new(AddressFn));
417 let ctx = wb.interpreter();
418 let f = ctx.context.get_function("", "ADDRESS").unwrap();
419
420 let row = lit(LiteralValue::Int(1));
421 let col = lit(LiteralValue::Int(1));
422 let abs_num = lit(LiteralValue::Int(1));
423 let a1_style = lit(LiteralValue::Boolean(true));
424
425 let sheet1 = lit(LiteralValue::Text("Sheet1".into()));
427 let args1 = vec![
428 ArgumentHandle::new(&row, &ctx),
429 ArgumentHandle::new(&col, &ctx),
430 ArgumentHandle::new(&abs_num, &ctx),
431 ArgumentHandle::new(&a1_style, &ctx),
432 ArgumentHandle::new(&sheet1, &ctx),
433 ];
434 assert_eq!(
435 f.dispatch(&args1, &ctx.function_context(None)).unwrap(),
436 LiteralValue::Text("Sheet1!$A$1".into())
437 );
438
439 let sheet2 = lit(LiteralValue::Text("My Sheet".into()));
441 let args2 = vec![
442 ArgumentHandle::new(&row, &ctx),
443 ArgumentHandle::new(&col, &ctx),
444 ArgumentHandle::new(&abs_num, &ctx),
445 ArgumentHandle::new(&a1_style, &ctx),
446 ArgumentHandle::new(&sheet2, &ctx),
447 ];
448 assert_eq!(
449 f.dispatch(&args2, &ctx.function_context(None)).unwrap(),
450 LiteralValue::Text("'My Sheet'!$A$1".into())
451 );
452 }
453
454 #[test]
455 fn address_r1c1_style() {
456 let wb = TestWorkbook::new().with_function(Arc::new(AddressFn));
457 let ctx = wb.interpreter();
458 let f = ctx.context.get_function("", "ADDRESS").unwrap();
459
460 let row = lit(LiteralValue::Int(5));
461 let col = lit(LiteralValue::Int(3));
462 let abs1 = lit(LiteralValue::Int(1));
463 let r1c1 = lit(LiteralValue::Boolean(false));
464
465 let args = vec![
467 ArgumentHandle::new(&row, &ctx),
468 ArgumentHandle::new(&col, &ctx),
469 ArgumentHandle::new(&abs1, &ctx),
470 ArgumentHandle::new(&r1c1, &ctx),
471 ];
472 assert_eq!(
473 f.dispatch(&args, &ctx.function_context(None))
474 .unwrap()
475 .into_literal(),
476 LiteralValue::Text("R5C3".into())
477 );
478
479 let abs4 = lit(LiteralValue::Int(4));
481 let args2 = vec![
482 ArgumentHandle::new(&row, &ctx),
483 ArgumentHandle::new(&col, &ctx),
484 ArgumentHandle::new(&abs4, &ctx),
485 ArgumentHandle::new(&r1c1, &ctx),
486 ];
487 assert_eq!(
488 f.dispatch(&args2, &ctx.function_context(None)).unwrap(),
489 LiteralValue::Text("R[5]C[3]".into())
490 );
491 }
492
493 #[test]
494 fn address_edge_cases() {
495 let wb = TestWorkbook::new().with_function(Arc::new(AddressFn));
496 let ctx = wb.interpreter();
497 let f = ctx.context.get_function("", "ADDRESS").unwrap();
498
499 let big_row = lit(LiteralValue::Int(1_048_577));
501 let col = lit(LiteralValue::Int(1));
502 let args = vec![
503 ArgumentHandle::new(&big_row, &ctx),
504 ArgumentHandle::new(&col, &ctx),
505 ];
506 let result = f
507 .dispatch(&args, &ctx.function_context(None))
508 .unwrap()
509 .into_literal();
510 assert!(matches!(result, LiteralValue::Error(e) if e.kind == ExcelErrorKind::Value));
511
512 let row = lit(LiteralValue::Int(1));
514 let big_col = lit(LiteralValue::Int(16385));
515 let args2 = vec![
516 ArgumentHandle::new(&row, &ctx),
517 ArgumentHandle::new(&big_col, &ctx),
518 ];
519 let result2 = f
520 .dispatch(&args2, &ctx.function_context(None))
521 .unwrap()
522 .into_literal();
523 assert!(matches!(result2, LiteralValue::Error(e) if e.kind == ExcelErrorKind::Value));
524
525 let abs5 = lit(LiteralValue::Int(5));
527 let normal_col = lit(LiteralValue::Int(1));
528 let args3 = vec![
529 ArgumentHandle::new(&row, &ctx),
530 ArgumentHandle::new(&normal_col, &ctx),
531 ArgumentHandle::new(&abs5, &ctx),
532 ];
533 let result3 = f
534 .dispatch(&args3, &ctx.function_context(None))
535 .unwrap()
536 .into_literal();
537 assert!(matches!(result3, LiteralValue::Error(e) if e.kind == ExcelErrorKind::Value));
538 }
539}