1use super::string_interner::{StringId, StringInterner};
4use super::value_ref::ValueRef;
5use formualizer_parse::parser::{ExternalRefKind, TableSpecifier};
6use rustc_hash::FxHashMap;
7use std::collections::hash_map::DefaultHasher;
8use std::fmt;
9use std::hash::{Hash, Hasher};
10
11#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
13pub struct AstNodeId(u32);
14
15impl AstNodeId {
16 pub fn as_u32(self) -> u32 {
17 self.0
18 }
19
20 pub(crate) const fn from_u32(raw: u32) -> Self {
21 Self(raw)
22 }
23}
24
25impl fmt::Display for AstNodeId {
26 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27 write!(f, "AstNode({})", self.0)
28 }
29}
30
31#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
32pub struct TableSpecId(u32);
33
34impl TableSpecId {
35 pub fn as_u32(self) -> u32 {
36 self.0
37 }
38}
39
40#[derive(Debug, Clone, PartialEq, Eq, Hash)]
42pub enum AstNodeData {
43 Literal(ValueRef),
45
46 Omitted,
48
49 Reference {
51 original_id: StringId, ref_type: CompactRefType, },
54
55 UnaryOp { op_id: StringId, expr_id: AstNodeId },
57
58 BinaryOp {
60 op_id: StringId,
61 left_id: AstNodeId,
62 right_id: AstNodeId,
63 },
64
65 Function {
67 name_id: StringId,
68 args_offset: u32, args_count: u16, },
71
72 Array {
74 rows: u16,
75 cols: u16,
76 elements_offset: u32, },
78}
79
80#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
82pub enum SheetKey {
83 Id(u16),
84 Name(StringId),
85}
86
87#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
89pub enum CompactRefType {
90 Cell {
91 sheet: Option<SheetKey>,
92 row: u32,
93 col: u32,
94 row_abs: bool,
95 col_abs: bool,
96 },
97 Range {
98 sheet: Option<SheetKey>,
99 start_row: u32,
100 start_col: u32,
101 end_row: u32,
102 end_col: u32,
103 start_row_abs: bool,
104 start_col_abs: bool,
105 end_row_abs: bool,
106 end_col_abs: bool,
107 },
108 External {
109 raw_id: StringId,
110 book_id: StringId,
111 sheet_id: StringId,
112 kind: ExternalRefKind,
113 },
114 NamedRange(StringId),
115 Table {
116 name_id: StringId,
117 specifier_id: Option<TableSpecId>,
118 },
119 Cell3D {
121 sheet_first: StringId,
122 sheet_last: StringId,
123 row: u32,
124 col: u32,
125 row_abs: bool,
126 col_abs: bool,
127 },
128 Range3D {
130 sheet_first: StringId,
131 sheet_last: StringId,
132 start_row: u32,
133 start_col: u32,
134 end_row: u32,
135 end_col: u32,
136 start_row_abs: bool,
137 start_col_abs: bool,
138 end_row_abs: bool,
139 end_col_abs: bool,
140 },
141}
142
143#[derive(Debug, Clone, PartialEq, Eq, Hash)]
149pub(crate) struct AstNodeEntry {
150 pub(crate) data: AstNodeData,
151 pub(crate) meta: AstNodeMetadata,
152}
153
154#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
156pub(crate) struct AstNodeMetadata {
157 pub(crate) canonical_hash: u64,
158 pub(crate) labels: CanonicalLabels,
159}
160
161#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
163pub(crate) struct CanonicalLabels {
164 pub(crate) flags: u64,
165 pub(crate) rejects: u64,
166}
167
168#[allow(dead_code)]
169impl CanonicalLabels {
170 pub(crate) const FLAG_RELATIVE_ONLY: u64 = 1 << 0;
171 pub(crate) const FLAG_ABSOLUTE_ONLY: u64 = 1 << 1;
172 pub(crate) const FLAG_MIXED_ANCHORS: u64 = 1 << 2;
173 pub(crate) const FLAG_VOLATILE: u64 = 1 << 3;
174 pub(crate) const FLAG_DYNAMIC: u64 = 1 << 4;
175 pub(crate) const FLAG_CONTAINS_STRUCTURED_REF: u64 = 1 << 5;
176 pub(crate) const FLAG_NEEDS_PLACEMENT_REWRITE: u64 = 1 << 6;
177 pub(crate) const FLAG_CONTAINS_NAME: u64 = 1 << 7;
178 pub(crate) const FLAG_CONTAINS_TABLE: u64 = 1 << 8;
179 pub(crate) const FLAG_CONTAINS_RANGE: u64 = 1 << 9;
180 pub(crate) const FLAG_CONTAINS_ARRAY: u64 = 1 << 10;
181 pub(crate) const FLAG_CONTAINS_LET_LAMBDA: u64 = 1 << 11;
182 pub(crate) const FLAG_CONTAINS_FUNCTION: u64 = 1 << 12;
183 pub(crate) const FLAG_EXPLICIT_SHEET: u64 = 1 << 13;
184 pub(crate) const FLAG_CURRENT_SHEET: u64 = 1 << 14;
185
186 pub(crate) const REJECT_INVALID_PLACEMENT_ANCHOR: u64 = 1 << 0;
189 pub(crate) const REJECT_DYNAMIC_REFERENCE: u64 = 1 << 1;
190 pub(crate) const REJECT_UNKNOWN_OR_CUSTOM_FUNCTION: u64 = 1 << 2;
191 pub(crate) const REJECT_LOCAL_ENVIRONMENT: u64 = 1 << 3;
192 pub(crate) const REJECT_PARSER_VOLATILE_FLAG: u64 = 1 << 4;
193 pub(crate) const REJECT_VOLATILE_FUNCTION: u64 = 1 << 5;
194 pub(crate) const REJECT_REFERENCE_RETURNING_FUNCTION: u64 = 1 << 6;
195 pub(crate) const REJECT_ARRAY_OR_SPILL_FUNCTION: u64 = 1 << 7;
196 pub(crate) const REJECT_ARRAY_LITERAL: u64 = 1 << 8;
197 pub(crate) const REJECT_SPILL_REFERENCE: u64 = 1 << 9;
198 pub(crate) const REJECT_SPILL_RESULT_REGION_OPERATOR: u64 = 1 << 10;
199 pub(crate) const REJECT_IMPLICIT_INTERSECTION_OPERATOR: u64 = 1 << 11;
200 pub(crate) const REJECT_CALL_EXPRESSION: u64 = 1 << 12;
201 pub(crate) const REJECT_STRUCTURED_REFERENCE: u64 = 1 << 14;
204 pub(crate) const REJECT_STRUCTURED_REFERENCE_CURRENT_ROW: u64 = 1 << 15;
205 pub(crate) const REJECT_THREE_D_REFERENCE: u64 = 1 << 16;
206 pub(crate) const REJECT_EXTERNAL_REFERENCE: u64 = 1 << 17;
207 pub(crate) const REJECT_OPEN_RANGE_REFERENCE: u64 = 1 << 18;
208 pub(crate) const REJECT_WHOLE_AXIS_REFERENCE: u64 = 1 << 19;
209 pub(crate) const REJECT_UNSUPPORTED_REFERENCE: u64 = 1 << 20;
210
211 pub(crate) fn has_flag(self, flag: u64) -> bool {
212 self.flags & flag != 0
213 }
214
215 pub(crate) fn has_reject(self, reject: u64) -> bool {
216 self.rejects & reject != 0
217 }
218}
219
220pub struct AstArena {
222 nodes: Vec<AstNodeEntry>,
224
225 dedup_map: FxHashMap<u64, AstNodeId>,
227
228 function_args: Vec<AstNodeId>,
230
231 array_elements: Vec<AstNodeId>,
233
234 strings: StringInterner,
236
237 table_specs: Vec<TableSpecifier>,
239 table_spec_dedup: FxHashMap<u64, TableSpecId>,
240
241 dedup_hits: usize,
243}
244
245impl AstArena {
246 pub fn new() -> Self {
247 Self {
248 nodes: Vec::new(),
249 dedup_map: FxHashMap::default(),
250 function_args: Vec::new(),
251 array_elements: Vec::new(),
252 strings: StringInterner::new(),
253 table_specs: Vec::new(),
254 table_spec_dedup: FxHashMap::default(),
255 dedup_hits: 0,
256 }
257 }
258
259 pub fn with_capacity(node_cap: usize) -> Self {
260 Self {
261 nodes: Vec::with_capacity(node_cap),
262 dedup_map: FxHashMap::with_capacity_and_hasher(node_cap, Default::default()),
263 function_args: Vec::with_capacity(node_cap * 2), array_elements: Vec::with_capacity(node_cap),
265 strings: StringInterner::with_capacity(node_cap / 10),
266 table_specs: Vec::new(),
267 table_spec_dedup: FxHashMap::default(),
268 dedup_hits: 0,
269 }
270 }
271
272 pub fn insert(&mut self, node: AstNodeData) -> AstNodeId {
277 self.insert_entry(node, AstNodeMetadata::default())
278 }
279
280 #[allow(dead_code)]
287 pub(crate) fn insert_with_meta(
288 &mut self,
289 node: AstNodeData,
290 meta: AstNodeMetadata,
291 ) -> AstNodeId {
292 self.insert_entry(node, meta)
293 }
294
295 fn insert_entry(&mut self, node: AstNodeData, meta: AstNodeMetadata) -> AstNodeId {
296 let hash = self.hash_node(&node);
298
299 if let Some(&id) = self.dedup_map.get(&hash) {
301 if self.nodes[id.0 as usize].data == node {
303 self.dedup_hits += 1;
304 return id;
305 }
306 }
307
308 let id = AstNodeId(self.nodes.len() as u32);
310 self.nodes.push(AstNodeEntry { data: node, meta });
311 self.dedup_map.insert(hash, id);
312 id
313 }
314
315 pub fn insert_literal(&mut self, value: ValueRef) -> AstNodeId {
317 self.insert(AstNodeData::Literal(value))
318 }
319
320 pub(crate) fn insert_omitted(&mut self) -> AstNodeId {
322 self.insert(AstNodeData::Omitted)
323 }
324
325 pub fn insert_reference(&mut self, original: &str, ref_type: CompactRefType) -> AstNodeId {
327 let original_id = self.strings.intern(original);
328 self.insert(AstNodeData::Reference {
329 original_id,
330 ref_type,
331 })
332 }
333
334 pub fn insert_unary_op(&mut self, op: &str, expr: AstNodeId) -> AstNodeId {
336 let op_id = self.strings.intern(op);
337 self.insert(AstNodeData::UnaryOp {
338 op_id,
339 expr_id: expr,
340 })
341 }
342
343 pub fn insert_binary_op(&mut self, op: &str, left: AstNodeId, right: AstNodeId) -> AstNodeId {
345 let op_id = self.strings.intern(op);
346 self.insert(AstNodeData::BinaryOp {
347 op_id,
348 left_id: left,
349 right_id: right,
350 })
351 }
352
353 pub fn insert_function(&mut self, name: &str, args: Vec<AstNodeId>) -> AstNodeId {
355 let name_id = self.strings.intern(name);
356 let args_offset = self.function_args.len() as u32;
357 let args_count = args.len() as u16;
358
359 self.function_args.extend(args);
360
361 self.insert(AstNodeData::Function {
362 name_id,
363 args_offset,
364 args_count,
365 })
366 }
367
368 pub fn insert_array(&mut self, rows: u16, cols: u16, elements: Vec<AstNodeId>) -> AstNodeId {
370 assert_eq!(
371 elements.len(),
372 (rows * cols) as usize,
373 "Array dimensions don't match element count"
374 );
375
376 let elements_offset = self.array_elements.len() as u32;
377 self.array_elements.extend(elements);
378
379 self.insert(AstNodeData::Array {
380 rows,
381 cols,
382 elements_offset,
383 })
384 }
385
386 pub fn get(&self, id: AstNodeId) -> Option<&AstNodeData> {
388 self.nodes.get(id.0 as usize).map(|entry| &entry.data)
389 }
390
391 #[allow(dead_code)]
393 pub(crate) fn entry(&self, id: AstNodeId) -> Option<&AstNodeEntry> {
394 self.nodes.get(id.0 as usize)
395 }
396
397 #[allow(dead_code)]
399 pub(crate) fn metadata(&self, id: AstNodeId) -> Option<AstNodeMetadata> {
400 self.entry(id).map(|entry| entry.meta)
401 }
402
403 pub fn get_function_args(&self, id: AstNodeId) -> Option<&[AstNodeId]> {
405 match self.get(id)? {
406 AstNodeData::Function {
407 args_offset,
408 args_count,
409 ..
410 } => {
411 let start = *args_offset as usize;
412 let end = start + *args_count as usize;
413 Some(&self.function_args[start..end])
414 }
415 _ => None,
416 }
417 }
418
419 pub fn get_array_elements(&self, id: AstNodeId) -> Option<&[AstNodeId]> {
421 match self.get(id)? {
422 AstNodeData::Array {
423 rows,
424 cols,
425 elements_offset,
426 } => {
427 let start = *elements_offset as usize;
428 let count = (*rows * *cols) as usize;
429 let end = start + count;
430 Some(&self.array_elements[start..end])
431 }
432 _ => None,
433 }
434 }
435
436 pub fn get_array_elements_info(&self, id: AstNodeId) -> Option<(u16, u16, &[AstNodeId])> {
437 match self.get(id)? {
438 AstNodeData::Array { rows, cols, .. } => {
439 let elements = self.get_array_elements(id)?;
440 Some((*rows, *cols, elements))
441 }
442 _ => None,
443 }
444 }
445
446 pub fn resolve_string(&self, id: StringId) -> &str {
448 self.strings.resolve(id)
449 }
450
451 pub fn strings(&self) -> &StringInterner {
453 &self.strings
454 }
455
456 pub fn strings_mut(&mut self) -> &mut StringInterner {
458 &mut self.strings
459 }
460
461 pub fn intern_table_specifier(&mut self, specifier: &TableSpecifier) -> TableSpecId {
462 let hash = {
463 let mut hasher = DefaultHasher::new();
464 specifier.hash(&mut hasher);
465 hasher.finish()
466 };
467
468 if let Some(&id) = self.table_spec_dedup.get(&hash)
469 && self
470 .table_specs
471 .get(id.0 as usize)
472 .is_some_and(|existing| existing == specifier)
473 {
474 return id;
475 }
476
477 let id = TableSpecId(self.table_specs.len() as u32);
478 self.table_specs.push(specifier.clone());
479 self.table_spec_dedup.insert(hash, id);
480 id
481 }
482
483 pub fn resolve_table_specifier(&self, id: TableSpecId) -> Option<&TableSpecifier> {
484 self.table_specs.get(id.0 as usize)
485 }
486
487 fn hash_node(&self, node: &AstNodeData) -> u64 {
489 let mut hasher = DefaultHasher::new();
490 node.hash(&mut hasher);
491 hasher.finish()
492 }
493
494 pub fn stats(&self) -> AstArenaStats {
496 AstArenaStats {
497 node_count: self.nodes.len(),
498 dedup_hits: self.dedup_hits,
499 string_count: self.strings.len(),
500 table_spec_count: self.table_specs.len(),
501 total_args: self.function_args.len(),
502 total_array_elements: self.array_elements.len(),
503 }
504 }
505
506 pub fn memory_usage(&self) -> usize {
508 self.nodes.capacity() * std::mem::size_of::<AstNodeEntry>()
509 + self.dedup_map.capacity() * (8 + 4) + self.function_args.capacity() * 4
511 + self.array_elements.capacity() * 4
512 + self.strings.memory_usage()
513 + self.table_specs.capacity() * std::mem::size_of::<TableSpecifier>()
514 + self.table_spec_dedup.capacity() * (8 + 4)
515 }
516
517 pub fn clear(&mut self) {
519 self.nodes.clear();
520 self.dedup_map.clear();
521 self.function_args.clear();
522 self.array_elements.clear();
523 self.strings.clear();
524 self.table_specs.clear();
525 self.table_spec_dedup.clear();
526 self.dedup_hits = 0;
527 }
528}
529
530impl Default for AstArena {
531 fn default() -> Self {
532 Self::new()
533 }
534}
535
536impl fmt::Debug for AstArena {
537 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
538 f.debug_struct("AstArena")
539 .field("nodes", &self.nodes.len())
540 .field("dedup_hits", &self.dedup_hits)
541 .field("strings", &self.strings.len())
542 .finish()
543 }
544}
545
546#[derive(Debug, Clone)]
548pub struct AstArenaStats {
549 pub node_count: usize,
550 pub dedup_hits: usize,
551 pub string_count: usize,
552 pub table_spec_count: usize,
553 pub total_args: usize,
554 pub total_array_elements: usize,
555}
556
557#[cfg(test)]
558mod tests {
559 use super::*;
560
561 #[test]
562 fn test_ast_arena_literal() {
563 let mut arena = AstArena::new();
564
565 let lit1 = arena.insert_literal(ValueRef::small_int(42).unwrap());
566 let lit2 = arena.insert_literal(ValueRef::boolean(true));
567
568 assert_ne!(lit1, lit2);
569
570 match arena.get(lit1) {
571 Some(AstNodeData::Literal(v)) => {
572 assert_eq!(v.as_small_int(), Some(42));
573 }
574 _ => panic!("Expected literal node"),
575 }
576 }
577
578 #[test]
579 fn test_ast_arena_deduplication() {
580 let mut arena = AstArena::new();
581
582 let lit1 = arena.insert_literal(ValueRef::small_int(42).unwrap());
584 let lit2 = arena.insert_literal(ValueRef::small_int(42).unwrap());
585
586 assert_eq!(lit1, lit2); assert_eq!(arena.stats().dedup_hits, 1);
588 }
589
590 #[test]
591 fn test_ast_arena_binary_op() {
592 let mut arena = AstArena::new();
593
594 let left = arena.insert_literal(ValueRef::small_int(1).unwrap());
595 let right = arena.insert_literal(ValueRef::small_int(2).unwrap());
596 let add = arena.insert_binary_op("+", left, right);
597
598 match arena.get(add) {
599 Some(AstNodeData::BinaryOp {
600 op_id,
601 left_id,
602 right_id,
603 }) => {
604 assert_eq!(arena.resolve_string(*op_id), "+");
605 assert_eq!(*left_id, left);
606 assert_eq!(*right_id, right);
607 }
608 _ => panic!("Expected binary op node"),
609 }
610 }
611
612 #[test]
613 fn test_ast_arena_function() {
614 let mut arena = AstArena::new();
615
616 let arg1 = arena.insert_literal(ValueRef::small_int(10).unwrap());
617 let arg2 = arena.insert_literal(ValueRef::small_int(20).unwrap());
618 let arg3 = arena.insert_literal(ValueRef::small_int(30).unwrap());
619
620 let func = arena.insert_function("SUM", vec![arg1, arg2, arg3]);
621
622 match arena.get(func) {
623 Some(AstNodeData::Function {
624 name_id,
625 args_count,
626 ..
627 }) => {
628 assert_eq!(arena.resolve_string(*name_id), "SUM");
629 assert_eq!(*args_count, 3);
630 }
631 _ => panic!("Expected function node"),
632 }
633
634 let args = arena.get_function_args(func).unwrap();
635 assert_eq!(args, &[arg1, arg2, arg3]);
636 }
637
638 #[test]
639 fn test_ast_arena_structural_sharing() {
640 let mut arena = AstArena::new();
641
642 let a1_ref = arena.insert_reference(
644 "A1",
645 CompactRefType::Cell {
646 sheet: None,
647 row: 1,
648 col: 1,
649 row_abs: false,
650 col_abs: false,
651 },
652 );
653
654 let one = arena.insert_literal(ValueRef::small_int(1).unwrap());
656 let expr1 = arena.insert_binary_op("+", a1_ref, one);
657
658 let two = arena.insert_literal(ValueRef::small_int(2).unwrap());
660 let expr2 = arena.insert_binary_op("*", a1_ref, two);
661
662 assert_eq!(arena.stats().node_count, 5); let a1_ref2 = arena.insert_reference(
667 "A1",
668 CompactRefType::Cell {
669 sheet: None,
670 row: 1,
671 col: 1,
672 row_abs: false,
673 col_abs: false,
674 },
675 );
676 assert_eq!(a1_ref, a1_ref2);
677 }
678
679 #[test]
680 fn test_ast_arena_array() {
681 let mut arena = AstArena::new();
682
683 let elements = vec![
684 arena.insert_literal(ValueRef::small_int(1).unwrap()),
685 arena.insert_literal(ValueRef::small_int(2).unwrap()),
686 arena.insert_literal(ValueRef::small_int(3).unwrap()),
687 arena.insert_literal(ValueRef::small_int(4).unwrap()),
688 ];
689
690 let array = arena.insert_array(2, 2, elements.clone());
691
692 match arena.get(array) {
693 Some(AstNodeData::Array { rows, cols, .. }) => {
694 assert_eq!(*rows, 2);
695 assert_eq!(*cols, 2);
696 }
697 _ => panic!("Expected array node"),
698 }
699
700 let stored_elements = arena.get_array_elements(array).unwrap();
701 assert_eq!(stored_elements, &elements[..]);
702 }
703
704 #[test]
705 fn test_ast_arena_complex_expression() {
706 let mut arena = AstArena::new();
707
708 let range = arena.insert_reference(
712 "A1:A10",
713 CompactRefType::Range {
714 sheet: None,
715 start_row: 1,
716 start_col: 1,
717 end_row: 10,
718 end_col: 1,
719 start_row_abs: false,
720 start_col_abs: false,
721 end_row_abs: false,
722 end_col_abs: false,
723 },
724 );
725
726 let sum = arena.insert_function("SUM", vec![range]);
728
729 let b1 = arena.insert_reference(
731 "B1",
732 CompactRefType::Cell {
733 sheet: None,
734 row: 1,
735 col: 2,
736 row_abs: false,
737 col_abs: false,
738 },
739 );
740
741 let zero = arena.insert_literal(ValueRef::small_int(0).unwrap());
743
744 let condition = arena.insert_binary_op(">", b1, zero);
746
747 let c1 = arena.insert_reference(
749 "C1",
750 CompactRefType::Cell {
751 sheet: None,
752 row: 1,
753 col: 3,
754 row_abs: false,
755 col_abs: false,
756 },
757 );
758 let d1 = arena.insert_reference(
759 "D1",
760 CompactRefType::Cell {
761 sheet: None,
762 row: 1,
763 col: 4,
764 row_abs: false,
765 col_abs: false,
766 },
767 );
768
769 let if_expr = arena.insert_function("IF", vec![condition, c1, d1]);
771
772 let final_expr = arena.insert_binary_op("+", sum, if_expr);
774
775 assert!(arena.get(final_expr).is_some());
777 assert_eq!(arena.stats().node_count, 9); }
782
783 #[test]
784 fn test_ast_arena_string_deduplication() {
785 let mut arena = AstArena::new();
786
787 let one = arena.insert_literal(ValueRef::small_int(1).unwrap());
789 let two = arena.insert_literal(ValueRef::small_int(2).unwrap());
790 let three = arena.insert_literal(ValueRef::small_int(3).unwrap());
791
792 let add1 = arena.insert_binary_op("+", one, two);
793 let add2 = arena.insert_binary_op("+", two, three);
794 let add3 = arena.insert_binary_op("+", one, three);
795
796 assert_eq!(arena.strings().len(), 1);
798 }
799
800 #[test]
801 fn test_ast_arena_clear() {
802 let mut arena = AstArena::new();
803
804 arena.insert_literal(ValueRef::small_int(1).unwrap());
805 arena.insert_literal(ValueRef::small_int(2).unwrap());
806 let left = arena.insert_literal(ValueRef::small_int(3).unwrap());
807 let right = arena.insert_literal(ValueRef::small_int(4).unwrap());
808 arena.insert_binary_op("+", left, right);
809
810 assert_eq!(arena.stats().node_count, 5);
811
812 arena.clear();
813
814 assert_eq!(arena.stats().node_count, 0);
815 assert_eq!(arena.strings().len(), 0);
816 }
817}