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 {
24 kinds: smallvec::smallvec![ArgKind::Any],
25 required: true,
26 by_ref: false,
27 shape: ShapeKind::Range,
28 coercion: CoercionPolicy::None,
29 max: None,
30 repeating: None,
31 default: None,
32 },
33 number_strict_scalar(),
34 ArgSchema {
36 kinds: smallvec::smallvec![ArgKind::Number],
37 required: false,
38 by_ref: false,
39 shape: ShapeKind::Scalar,
40 coercion: CoercionPolicy::NumberStrict,
41 max: None,
42 repeating: None,
43 default: None,
44 },
45 ]
46}
47
48fn arg_byref_reference() -> Vec<ArgSchema> {
49 vec![
50 ArgSchema {
51 kinds: smallvec::smallvec![ArgKind::Range],
52 required: true,
53 by_ref: true,
54 shape: ShapeKind::Range,
55 coercion: CoercionPolicy::None,
56 max: None,
57 repeating: None,
58 default: None,
59 },
60 number_strict_scalar(),
61 number_strict_scalar(),
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 ArgSchema {
74 kinds: smallvec::smallvec![ArgKind::Number],
76 required: false,
77 by_ref: false,
78 shape: ShapeKind::Scalar,
79 coercion: CoercionPolicy::NumberStrict,
80 max: None,
81 repeating: None,
82 default: None,
83 },
84 ]
85}
86
87#[derive(Debug)]
88pub struct IndexFn;
89
90impl Function for IndexFn {
148 fn caps(&self) -> FnCaps {
149 FnCaps::PURE | FnCaps::RETURNS_REFERENCE
150 }
151 fn name(&self) -> &'static str {
152 "INDEX"
153 }
154 fn min_args(&self) -> usize {
155 2
156 }
157 fn arg_schema(&self) -> &'static [ArgSchema] {
158 use once_cell::sync::Lazy;
159 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(arg_byref_array);
160 &SCHEMA
161 }
162
163 fn eval_reference<'a, 'b, 'c>(
164 &self,
165 args: &'c [ArgumentHandle<'a, 'b>],
166 _ctx: &dyn FunctionContext<'b>,
167 ) -> Option<Result<ReferenceType, ExcelError>> {
168 if args.len() < 2 {
170 return Some(Err(ExcelError::new(ExcelErrorKind::Value)));
171 }
172 let base = match args[0].as_reference_or_eval() {
174 Ok(r) => r,
175 Err(_) => return None,
176 };
177 let row = match args[1].value() {
178 Ok(cv) => match cv.into_literal() {
179 LiteralValue::Number(n) => n as i64,
180 LiteralValue::Int(i) => i,
181 _ => return Some(Err(ExcelError::new(ExcelErrorKind::Value))),
182 },
183 Err(e) => return Some(Err(e)),
184 };
185 let col = if args.len() >= 3 {
186 match args[2].value() {
187 Ok(cv) => match cv.into_literal() {
188 LiteralValue::Number(n) => n as i64,
189 LiteralValue::Int(i) => i,
190 _ => return Some(Err(ExcelError::new(ExcelErrorKind::Value))),
191 },
192 Err(e) => return Some(Err(e)),
193 }
194 } else {
195 1
197 };
198
199 let (sheet, sr, sc, er, ec) = match base {
201 ReferenceType::Range {
202 sheet,
203 start_row,
204 start_col,
205 end_row,
206 end_col,
207 ..
208 } => match (start_row, start_col, end_row, end_col) {
209 (Some(sr), Some(sc), Some(er), Some(ec)) => (sheet, sr, sc, er, ec),
210 _ => return Some(Err(ExcelError::new(ExcelErrorKind::Ref))),
211 },
212 ReferenceType::Cell {
213 sheet, row, col, ..
214 } => (sheet, row, col, row, col),
215 _ => return Some(Err(ExcelError::new(ExcelErrorKind::Ref))),
216 };
217
218 if row <= 0 || col <= 0 {
220 return Some(Err(ExcelError::new(ExcelErrorKind::Ref)));
221 }
222 let r = sr + (row as u32) - 1;
223 let c = sc + (col as u32) - 1;
224 if r > er || c > ec {
225 return Some(Err(ExcelError::new(ExcelErrorKind::Ref)));
226 }
227
228 Some(Ok(ReferenceType::cell(sheet, r, c)))
229 }
230
231 fn eval<'a, 'b, 'c>(
232 &self,
233 args: &'c [ArgumentHandle<'a, 'b>],
234 ctx: &dyn FunctionContext<'b>,
235 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
236 if let Some(result) = self.eval_reference(args, ctx) {
238 match result {
239 Ok(r) => {
240 let current_sheet = ctx.current_sheet();
242 match ctx.resolve_range_view(&r, current_sheet) {
243 Ok(rv) => {
244 let (rows, cols) = rv.dims();
245 if rows == 1 && cols == 1 {
246 Ok(crate::traits::CalcValue::Scalar(
247 rv.as_1x1().unwrap_or(LiteralValue::Empty),
248 ))
249 } else {
250 Ok(crate::traits::CalcValue::Range(rv))
251 }
252 }
253 Err(e) => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
254 }
255 }
256 Err(e) => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
257 }
258 } else {
259 if args.len() < 2 {
261 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
262 ExcelError::new(ExcelErrorKind::Value),
263 )));
264 }
265 let v = args[0].value()?.into_literal();
266 let table: Vec<Vec<LiteralValue>> = match v {
267 LiteralValue::Array(rows) => rows,
268 other => vec![vec![other]],
269 };
270 let index = match args[1].value()?.into_literal() {
271 LiteralValue::Number(n) => n as i64,
272 LiteralValue::Int(i) => i,
273 _ => {
274 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
275 ExcelError::new(ExcelErrorKind::Value),
276 )));
277 }
278 };
279
280 let is_single_row = table.len() == 1;
282 let is_single_col = table.iter().all(|r| r.len() == 1);
283
284 if args.len() == 2 && (is_single_row || is_single_col) {
286 if index <= 0 {
288 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
289 ExcelError::new(ExcelErrorKind::Ref),
290 )));
291 }
292 let idx = (index - 1) as usize;
293 let val = if is_single_row {
294 table[0].get(idx).cloned()
295 } else {
296 table.get(idx).and_then(|r| r.first()).cloned()
297 };
298 return Ok(crate::traits::CalcValue::Scalar(val.unwrap_or_else(|| {
299 LiteralValue::Error(ExcelError::new(ExcelErrorKind::Ref))
300 })));
301 }
302
303 let row = index as usize;
305 let col = if args.len() >= 3 {
306 match args[2].value()?.into_literal() {
307 LiteralValue::Number(n) => n as usize,
308 LiteralValue::Int(i) => i as usize,
309 _ => {
310 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
311 ExcelError::new(ExcelErrorKind::Value),
312 )));
313 }
314 }
315 } else {
316 1
317 };
318
319 if row == 0 || col == 0 {
321 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
322 ExcelError::new(ExcelErrorKind::Ref),
323 )));
324 }
325 let val = table
326 .get(row - 1)
327 .and_then(|r| r.get(col - 1))
328 .cloned()
329 .unwrap_or_else(|| LiteralValue::Error(ExcelError::new(ExcelErrorKind::Ref)));
330 Ok(crate::traits::CalcValue::Scalar(val))
331 }
332 }
333}
334
335#[derive(Debug)]
336pub struct OffsetFn;
337
338impl Function for OffsetFn {
395 fn caps(&self) -> FnCaps {
396 FnCaps::PURE | FnCaps::RETURNS_REFERENCE | FnCaps::VOLATILE | FnCaps::DYNAMIC_DEPENDENCY
398 }
399 fn name(&self) -> &'static str {
400 "OFFSET"
401 }
402 fn min_args(&self) -> usize {
403 3
404 }
405 fn arg_schema(&self) -> &'static [ArgSchema] {
406 use once_cell::sync::Lazy;
407 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(arg_byref_reference);
408 &SCHEMA
409 }
410
411 fn eval_reference<'a, 'b, 'c>(
412 &self,
413 args: &'c [ArgumentHandle<'a, 'b>],
414 _ctx: &dyn FunctionContext<'b>,
415 ) -> Option<Result<ReferenceType, ExcelError>> {
416 if args.len() < 3 {
417 return Some(Err(ExcelError::new(ExcelErrorKind::Value)));
418 }
419 let base = match args[0].as_reference_or_eval() {
420 Ok(r) => r,
421 Err(e) => return Some(Err(e)),
422 };
423 let dr = match args[1].value() {
424 Ok(cv) => match cv.into_literal() {
425 LiteralValue::Number(n) => n as i64,
426 LiteralValue::Int(i) => i,
427 _ => return Some(Err(ExcelError::new(ExcelErrorKind::Value))),
428 },
429 Err(e) => return Some(Err(e)),
430 };
431 let dc = match args[2].value() {
432 Ok(cv) => match cv.into_literal() {
433 LiteralValue::Number(n) => n as i64,
434 LiteralValue::Int(i) => i,
435 _ => return Some(Err(ExcelError::new(ExcelErrorKind::Value))),
436 },
437 Err(e) => return Some(Err(e)),
438 };
439
440 let (sheet, sr, sc, er, ec) = match base {
441 ReferenceType::Range {
442 sheet,
443 start_row,
444 start_col,
445 end_row,
446 end_col,
447 ..
448 } => match (start_row, start_col, end_row, end_col) {
449 (Some(sr), Some(sc), Some(er), Some(ec)) => (sheet, sr, sc, er, ec),
450 _ => return Some(Err(ExcelError::new(ExcelErrorKind::Ref))),
451 },
452 ReferenceType::Cell {
453 sheet, row, col, ..
454 } => (sheet, row, col, row, col),
455 _ => return Some(Err(ExcelError::new(ExcelErrorKind::Ref))),
456 };
457
458 let nsr = (sr as i64) + dr;
459 let nsc = (sc as i64) + dc;
460 let height = if args.len() >= 4 {
461 match args[3].value() {
462 Ok(cv) => match cv.into_literal() {
463 LiteralValue::Number(n) => n as i64,
464 LiteralValue::Int(i) => i,
465 _ => return Some(Err(ExcelError::new(ExcelErrorKind::Value))),
466 },
467 Err(e) => return Some(Err(e)),
468 }
469 } else {
470 (er as i64) - (sr as i64) + 1
471 };
472 let width = if args.len() >= 5 {
473 match args[4].value() {
474 Ok(cv) => match cv.into_literal() {
475 LiteralValue::Number(n) => n as i64,
476 LiteralValue::Int(i) => i,
477 _ => return Some(Err(ExcelError::new(ExcelErrorKind::Value))),
478 },
479 Err(e) => return Some(Err(e)),
480 }
481 } else {
482 (ec as i64) - (sc as i64) + 1
483 };
484
485 if nsr <= 0 || nsc <= 0 || height <= 0 || width <= 0 {
486 return Some(Err(ExcelError::new(ExcelErrorKind::Ref)));
487 }
488 let ner = nsr + height - 1;
489 let nec = nsc + width - 1;
490
491 if height == 1 && width == 1 {
492 Some(Ok(ReferenceType::cell(sheet, nsr as u32, nsc as u32)))
493 } else {
494 Some(Ok(ReferenceType::range(
495 sheet,
496 Some(nsr as u32),
497 Some(nsc as u32),
498 Some(ner as u32),
499 Some(nec as u32),
500 )))
501 }
502 }
503
504 fn eval<'a, 'b, 'c>(
505 &self,
506 args: &'c [ArgumentHandle<'a, 'b>],
507 ctx: &dyn FunctionContext<'b>,
508 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
509 if let Some(Ok(r)) = self.eval_reference(args, ctx) {
510 let current_sheet = ctx.current_sheet();
511 match ctx.resolve_range_view(&r, current_sheet) {
512 Ok(rv) => {
513 let (rows, cols) = rv.dims();
514 if rows == 1 && cols == 1 {
515 Ok(crate::traits::CalcValue::Scalar(
516 rv.as_1x1().unwrap_or(LiteralValue::Empty),
517 ))
518 } else {
519 Ok(crate::traits::CalcValue::Range(rv))
520 }
521 }
522 Err(e) => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
523 }
524 } else {
525 Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
526 ExcelError::new(ExcelErrorKind::Ref),
527 )))
528 }
529 }
530}
531
532fn arg_indirect() -> Vec<ArgSchema> {
533 vec![
534 ArgSchema {
535 kinds: smallvec::smallvec![ArgKind::Text],
536 required: true,
537 by_ref: false,
538 shape: ShapeKind::Scalar,
539 coercion: CoercionPolicy::None,
540 max: None,
541 repeating: None,
542 default: None,
543 },
544 ArgSchema {
545 kinds: smallvec::smallvec![ArgKind::Logical, ArgKind::Number],
546 required: false,
547 by_ref: false,
548 shape: ShapeKind::Scalar,
549 coercion: CoercionPolicy::Logical,
550 max: None,
551 repeating: None,
552 default: Some(LiteralValue::Boolean(true)),
553 },
554 ]
555}
556
557#[derive(Debug)]
558pub struct IndirectFn;
559
560impl Function for IndirectFn {
612 fn caps(&self) -> FnCaps {
613 FnCaps::PURE | FnCaps::RETURNS_REFERENCE | FnCaps::VOLATILE | FnCaps::DYNAMIC_DEPENDENCY
614 }
615 fn name(&self) -> &'static str {
616 "INDIRECT"
617 }
618 fn min_args(&self) -> usize {
619 1
620 }
621 fn arg_schema(&self) -> &'static [ArgSchema] {
622 use once_cell::sync::Lazy;
623 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(arg_indirect);
624 &SCHEMA
625 }
626
627 fn eval_reference<'a, 'b, 'c>(
628 &self,
629 args: &'c [ArgumentHandle<'a, 'b>],
630 _ctx: &dyn FunctionContext<'b>,
631 ) -> Option<Result<ReferenceType, ExcelError>> {
632 if args.is_empty() {
633 return Some(Err(ExcelError::new(ExcelErrorKind::Value)));
634 }
635
636 let ref_text = match args[0].value() {
637 Ok(cv) => match cv.into_literal() {
638 LiteralValue::Text(s) => s.to_string(),
639 _ => return Some(Err(ExcelError::new(ExcelErrorKind::Value))),
640 },
641 Err(e) => return Some(Err(e)),
642 };
643
644 let a1_style = if args.len() >= 2 {
645 match args[1].value() {
646 Ok(cv) => match cv.into_literal() {
647 LiteralValue::Boolean(b) => b,
648 LiteralValue::Int(i) => i != 0,
649 LiteralValue::Number(n) => n != 0.0,
650 _ => return Some(Err(ExcelError::new(ExcelErrorKind::Value))),
651 },
652 Err(e) => return Some(Err(e)),
653 }
654 } else {
655 true
656 };
657
658 if !a1_style {
659 return Some(Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
660 "INDIRECT with R1C1 style (second argument FALSE) is not yet supported",
661 )));
662 }
663
664 let parsed = formualizer_parse::parser::ReferenceType::parse_sheet_ref(&ref_text);
665
666 match parsed {
667 Ok(formualizer_common::SheetRef::Cell(cell)) => {
668 let sheet = match cell.sheet {
669 formualizer_common::SheetLocator::Current => None,
670 formualizer_common::SheetLocator::Name(name) => Some(name.to_string()),
671 formualizer_common::SheetLocator::Id(_) => None,
672 };
673 Some(Ok(ReferenceType::Cell {
674 sheet,
675 row: cell.coord.row() + 1,
676 col: cell.coord.col() + 1,
677 row_abs: cell.coord.row_abs(),
678 col_abs: cell.coord.col_abs(),
679 }))
680 }
681 Ok(formualizer_common::SheetRef::Range(range)) => {
682 let sheet = match range.sheet {
683 formualizer_common::SheetLocator::Current => None,
684 formualizer_common::SheetLocator::Name(name) => Some(name.to_string()),
685 formualizer_common::SheetLocator::Id(_) => None,
686 };
687 Some(Ok(ReferenceType::Range {
688 sheet,
689 start_row: range.start_row.map(|b| b.index + 1),
690 start_col: range.start_col.map(|b| b.index + 1),
691 end_row: range.end_row.map(|b| b.index + 1),
692 end_col: range.end_col.map(|b| b.index + 1),
693 start_row_abs: range.start_row.map(|b| b.abs).unwrap_or(false),
694 start_col_abs: range.start_col.map(|b| b.abs).unwrap_or(false),
695 end_row_abs: range.end_row.map(|b| b.abs).unwrap_or(false),
696 end_col_abs: range.end_col.map(|b| b.abs).unwrap_or(false),
697 }))
698 }
699 Err(_) => match formualizer_parse::parser::ReferenceType::from_string(&ref_text) {
700 Ok(ReferenceType::NamedRange(name)) => Some(Ok(ReferenceType::NamedRange(name))),
701 Ok(ReferenceType::Table(tref)) => Some(Ok(ReferenceType::Table(tref))),
702 _ => Some(Err(ExcelError::new(ExcelErrorKind::Ref))),
703 },
704 }
705 }
706
707 fn eval<'a, 'b, 'c>(
708 &self,
709 args: &'c [ArgumentHandle<'a, 'b>],
710 ctx: &dyn FunctionContext<'b>,
711 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
712 match self.eval_reference(args, ctx) {
713 Some(Ok(r)) => {
714 let current_sheet = ctx.current_sheet();
715 match ctx.resolve_range_view(&r, current_sheet) {
716 Ok(rv) => {
717 let (rows, cols) = rv.dims();
718 if rows == 1 && cols == 1 {
719 Ok(crate::traits::CalcValue::Scalar(
720 rv.as_1x1().unwrap_or(LiteralValue::Empty),
721 ))
722 } else {
723 Ok(crate::traits::CalcValue::Range(rv))
724 }
725 }
726 Err(e) => {
727 let mapped = if e.kind == ExcelErrorKind::Name {
728 ExcelError::new(ExcelErrorKind::Ref)
729 } else {
730 e
731 };
732 Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
733 mapped,
734 )))
735 }
736 }
737 }
738 Some(Err(e)) => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
739 None => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
740 ExcelError::new(ExcelErrorKind::Ref),
741 ))),
742 }
743 }
744}
745
746pub fn register_builtins() {
747 crate::function_registry::register_function(std::sync::Arc::new(IndexFn));
748 crate::function_registry::register_function(std::sync::Arc::new(OffsetFn));
749 crate::function_registry::register_function(std::sync::Arc::new(IndirectFn));
750}
751
752#[cfg(test)]
753mod tests {
754 use super::*;
755 use crate::test_workbook::TestWorkbook;
756 use crate::traits::ArgumentHandle;
757 use formualizer_parse::parser::{ASTNode, ASTNodeType};
758
759 fn interp(wb: &TestWorkbook) -> crate::interpreter::Interpreter<'_> {
760 wb.interpreter()
761 }
762
763 #[test]
764 fn index_returns_reference_and_materializes_in_value_context() {
765 let wb = TestWorkbook::new()
766 .with_cell_a1("Sheet1", "B2", LiteralValue::Int(42))
767 .with_function(std::sync::Arc::new(IndexFn));
768 let ctx = interp(&wb);
769
770 let array_ref = ASTNode::new(
772 ASTNodeType::Reference {
773 original: "A1:C3".into(),
774 reference: ReferenceType::Range {
775 sheet: None,
776 start_row: Some(1),
777 start_col: Some(1),
778 end_row: Some(3),
779 end_col: Some(3),
780 start_row_abs: false,
781 start_col_abs: false,
782 end_row_abs: false,
783 end_col_abs: false,
784 },
785 },
786 None,
787 );
788 let row = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(2)), None);
789 let col = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(2)), None);
790 let call = ASTNode::new(
791 ASTNodeType::Function {
792 name: "INDEX".into(),
793 args: vec![array_ref.clone(), row.clone(), col.clone()],
794 },
795 None,
796 );
797
798 let r = ctx.evaluate_ast_as_reference(&call).expect("ref ok");
800 match r {
801 ReferenceType::Cell { row, col, .. } => {
802 assert_eq!((row, col), (2, 2));
803 }
804 _ => panic!(),
805 }
806
807 let args = vec![
809 ArgumentHandle::new(&array_ref, &ctx),
810 ArgumentHandle::new(&row, &ctx),
811 ArgumentHandle::new(&col, &ctx),
812 ];
813 let f = ctx.context.get_function("", "INDEX").unwrap();
814 let v = f
815 .dispatch(&args, &ctx.function_context(None))
816 .unwrap()
817 .into_literal();
818 assert_eq!(v, LiteralValue::Number(42.0));
819 }
820
821 #[test]
822 fn offset_returns_reference_and_materializes() {
823 let wb = TestWorkbook::new()
824 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(1))
825 .with_cell_a1("Sheet1", "B2", LiteralValue::Int(5))
826 .with_function(std::sync::Arc::new(OffsetFn));
827 let ctx = interp(&wb);
828
829 let base = ASTNode::new(
830 ASTNodeType::Reference {
831 original: "A1".into(),
832 reference: ReferenceType::Cell {
833 sheet: None,
834 row: 1,
835 col: 1,
836 row_abs: false,
837 col_abs: false,
838 },
839 },
840 None,
841 );
842 let dr = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(1)), None);
843 let dc = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(1)), None);
844 let call = ASTNode::new(
845 ASTNodeType::Function {
846 name: "OFFSET".into(),
847 args: vec![base.clone(), dr.clone(), dc.clone()],
848 },
849 None,
850 );
851
852 let r = ctx.evaluate_ast_as_reference(&call).expect("ref ok");
853 match r {
854 ReferenceType::Cell { row, col, .. } => assert_eq!((row, col), (2, 2)),
855 _ => panic!(),
856 }
857
858 let args = vec![
859 ArgumentHandle::new(&base, &ctx),
860 ArgumentHandle::new(&dr, &ctx),
861 ArgumentHandle::new(&dc, &ctx),
862 ];
863 let f = ctx.context.get_function("", "OFFSET").unwrap();
864 let v = f
865 .dispatch(&args, &ctx.function_context(None))
866 .unwrap()
867 .into_literal();
868 assert_eq!(v, LiteralValue::Number(5.0));
869 }
870}