1use crate::reference::{CellRef, Coord};
2use formualizer_parse::parser::{ASTNode, ASTNodeType};
3
4pub struct ReferenceAdjuster;
6
7#[derive(Debug, Clone)]
8pub enum ShiftOperation {
9 InsertRows {
10 sheet_id: u16,
11 before: u32,
12 count: u32,
13 },
14 DeleteRows {
15 sheet_id: u16,
16 start: u32,
17 count: u32,
18 },
19 InsertColumns {
20 sheet_id: u16,
21 before: u32,
22 count: u32,
23 },
24 DeleteColumns {
25 sheet_id: u16,
26 start: u32,
27 count: u32,
28 },
29}
30
31impl ReferenceAdjuster {
32 pub fn new() -> Self {
33 Self
34 }
35
36 pub fn adjust_ast(&self, ast: &ASTNode, op: &ShiftOperation) -> ASTNode {
38 match &ast.node_type {
39 ASTNodeType::Reference {
40 original,
41 reference,
42 } => {
43 let adjusted = self.adjust_reference(reference, op);
44 ASTNode {
45 node_type: ASTNodeType::Reference {
46 original: original.clone(),
47 reference: adjusted,
48 },
49 source_token: ast.source_token.clone(),
50 contains_volatile: ast.contains_volatile,
51 }
52 }
53 ASTNodeType::BinaryOp {
54 op: bin_op,
55 left,
56 right,
57 } => ASTNode {
58 node_type: ASTNodeType::BinaryOp {
59 op: bin_op.clone(),
60 left: Box::new(self.adjust_ast(left, op)),
61 right: Box::new(self.adjust_ast(right, op)),
62 },
63 source_token: ast.source_token.clone(),
64 contains_volatile: ast.contains_volatile,
65 },
66 ASTNodeType::UnaryOp { op: un_op, expr } => ASTNode {
67 node_type: ASTNodeType::UnaryOp {
68 op: un_op.clone(),
69 expr: Box::new(self.adjust_ast(expr, op)),
70 },
71 source_token: ast.source_token.clone(),
72 contains_volatile: ast.contains_volatile,
73 },
74 ASTNodeType::Function { name, args } => ASTNode {
75 node_type: ASTNodeType::Function {
76 name: name.clone(),
77 args: args.iter().map(|arg| self.adjust_ast(arg, op)).collect(),
78 },
79 source_token: ast.source_token.clone(),
80 contains_volatile: ast.contains_volatile,
81 },
82 _ => ast.clone(),
83 }
84 }
85
86 pub fn adjust_cell_ref(&self, cell_ref: &CellRef, op: &ShiftOperation) -> Option<CellRef> {
89 let coord = cell_ref.coord;
90 let adjusted_coord = match op {
91 ShiftOperation::InsertRows {
92 sheet_id,
93 before,
94 count,
95 } if cell_ref.sheet_id == *sheet_id => {
96 if coord.row_abs() || coord.row < *before {
97 coord
99 } else {
100 Coord::new(
102 coord.row + count,
103 coord.col,
104 coord.row_abs(),
105 coord.col_abs(),
106 )
107 }
108 }
109 ShiftOperation::DeleteRows {
110 sheet_id,
111 start,
112 count,
113 } if cell_ref.sheet_id == *sheet_id => {
114 if coord.row_abs() {
115 coord
117 } else if coord.row >= *start && coord.row < start + count {
118 return None;
120 } else if coord.row >= start + count {
121 Coord::new(
123 coord.row - count,
124 coord.col,
125 coord.row_abs(),
126 coord.col_abs(),
127 )
128 } else {
129 coord
131 }
132 }
133 ShiftOperation::InsertColumns {
134 sheet_id,
135 before,
136 count,
137 } if cell_ref.sheet_id == *sheet_id => {
138 if coord.col_abs() || coord.col < *before {
139 coord
141 } else {
142 Coord::new(
144 coord.row,
145 coord.col + count,
146 coord.row_abs(),
147 coord.col_abs(),
148 )
149 }
150 }
151 ShiftOperation::DeleteColumns {
152 sheet_id,
153 start,
154 count,
155 } if cell_ref.sheet_id == *sheet_id => {
156 if coord.col_abs() {
157 coord
159 } else if coord.col >= *start && coord.col < start + count {
160 return None;
162 } else if coord.col >= start + count {
163 Coord::new(
165 coord.row,
166 coord.col - count,
167 coord.row_abs(),
168 coord.col_abs(),
169 )
170 } else {
171 coord
173 }
174 }
175 _ => coord,
176 };
177
178 Some(CellRef::new(cell_ref.sheet_id, adjusted_coord))
179 }
180
181 fn adjust_reference(
183 &self,
184 reference: &formualizer_parse::parser::ReferenceType,
185 op: &ShiftOperation,
186 ) -> formualizer_parse::parser::ReferenceType {
187 use formualizer_parse::parser::ReferenceType;
188
189 match reference {
190 ReferenceType::Cell { sheet, row, col } => {
191 let temp_ref = CellRef::new(
194 match op {
195 ShiftOperation::InsertRows { sheet_id, .. }
196 | ShiftOperation::DeleteRows { sheet_id, .. }
197 | ShiftOperation::InsertColumns { sheet_id, .. }
198 | ShiftOperation::DeleteColumns { sheet_id, .. } => *sheet_id,
199 },
200 Coord::new(*row, *col, false, false), );
202
203 match self.adjust_cell_ref(&temp_ref, op) {
204 None => {
205 ReferenceType::Cell {
208 sheet: Some("#REF".to_string()),
209 row: 0,
210 col: 0,
211 }
212 }
213 Some(adjusted) => ReferenceType::Cell {
214 sheet: sheet.clone(),
215 row: adjusted.coord.row,
216 col: adjusted.coord.col,
217 },
218 }
219 }
220 ReferenceType::Range {
221 sheet,
222 start_row,
223 start_col,
224 end_row,
225 end_col,
226 } => {
227 let is_unbounded_column = start_row.is_none() && end_row.is_none();
231 let is_unbounded_row = start_col.is_none() && end_col.is_none();
232
233 if is_unbounded_column || is_unbounded_row {
236 return reference.clone();
237 }
238
239 let (adj_start_row, adj_end_row) = match op {
241 ShiftOperation::InsertRows { before, count, .. } => {
242 match (start_row, end_row) {
244 (Some(start), Some(end)) => {
245 let adj_start = if *start >= *before {
246 start + count
247 } else {
248 *start
249 };
250 let adj_end = if *end >= *before { end + count } else { *end };
251 (Some(adj_start), Some(adj_end))
252 }
253 _ => (*start_row, *end_row),
255 }
256 }
257 ShiftOperation::DeleteRows { start, count, .. } => {
258 match (start_row, end_row) {
260 (Some(range_start), Some(range_end)) => {
261 if *range_end < *start || *range_start >= start + count {
262 let adj_start = if *range_start >= start + count {
264 range_start - count
265 } else {
266 *range_start
267 };
268 let adj_end = if *range_end >= start + count {
269 range_end - count
270 } else {
271 *range_end
272 };
273 (Some(adj_start), Some(adj_end))
274 } else if *range_start >= *start && *range_end < start + count {
275 return ReferenceType::Range {
277 sheet: Some("#REF".to_string()),
278 start_row: Some(0),
279 start_col: Some(0),
280 end_row: Some(0),
281 end_col: Some(0),
282 };
283 } else {
284 let adj_start = if *range_start < *start {
286 *range_start
287 } else {
288 *start
289 };
290 let adj_end = if *range_end >= start + count {
291 range_end - count
292 } else {
293 start - 1
294 };
295 (Some(adj_start), Some(adj_end))
296 }
297 }
298 _ => (*start_row, *end_row),
300 }
301 }
302 _ => (*start_row, *end_row),
303 };
304
305 let (adj_start_col, adj_end_col) = match op {
307 ShiftOperation::InsertColumns { before, count, .. } => {
308 match (start_col, end_col) {
310 (Some(start), Some(end)) => {
311 let adj_start = if *start >= *before {
312 start + count
313 } else {
314 *start
315 };
316 let adj_end = if *end >= *before { end + count } else { *end };
317 (Some(adj_start), Some(adj_end))
318 }
319 _ => (*start_col, *end_col),
321 }
322 }
323 ShiftOperation::DeleteColumns { start, count, .. } => {
324 match (start_col, end_col) {
326 (Some(range_start), Some(range_end)) => {
327 if *range_end < *start || *range_start >= start + count {
328 let adj_start = if *range_start >= start + count {
330 range_start - count
331 } else {
332 *range_start
333 };
334 let adj_end = if *range_end >= start + count {
335 range_end - count
336 } else {
337 *range_end
338 };
339 (Some(adj_start), Some(adj_end))
340 } else if *range_start >= *start && *range_end < start + count {
341 return ReferenceType::Range {
343 sheet: Some("#REF".to_string()),
344 start_row: Some(0),
345 start_col: Some(0),
346 end_row: Some(0),
347 end_col: Some(0),
348 };
349 } else {
350 let adj_start = if *range_start < *start {
352 *range_start
353 } else {
354 *start
355 };
356 let adj_end = if *range_end >= start + count {
357 range_end - count
358 } else {
359 start - 1
360 };
361 (Some(adj_start), Some(adj_end))
362 }
363 }
364 _ => (*start_col, *end_col),
366 }
367 }
368 _ => (*start_col, *end_col),
369 };
370
371 ReferenceType::Range {
372 sheet: sheet.clone(),
373 start_row: adj_start_row,
374 start_col: adj_start_col,
375 end_row: adj_end_row,
376 end_col: adj_end_col,
377 }
378 }
379 _ => reference.clone(),
380 }
381 }
382}
383
384impl Default for ReferenceAdjuster {
385 fn default() -> Self {
386 Self::new()
387 }
388}
389
390pub struct RelativeReferenceAdjuster {
392 row_offset: i32,
393 col_offset: i32,
394}
395
396impl RelativeReferenceAdjuster {
397 pub fn new(row_offset: i32, col_offset: i32) -> Self {
398 Self {
399 row_offset,
400 col_offset,
401 }
402 }
403
404 pub fn adjust_formula(&self, ast: &ASTNode) -> ASTNode {
405 match &ast.node_type {
406 ASTNodeType::Reference {
407 original,
408 reference,
409 } => {
410 let adjusted = self.adjust_reference(reference);
411 ASTNode {
412 node_type: ASTNodeType::Reference {
413 original: original.clone(),
414 reference: adjusted,
415 },
416 source_token: ast.source_token.clone(),
417 contains_volatile: ast.contains_volatile,
418 }
419 }
420 ASTNodeType::BinaryOp { op, left, right } => ASTNode {
421 node_type: ASTNodeType::BinaryOp {
422 op: op.clone(),
423 left: Box::new(self.adjust_formula(left)),
424 right: Box::new(self.adjust_formula(right)),
425 },
426 source_token: ast.source_token.clone(),
427 contains_volatile: ast.contains_volatile,
428 },
429 ASTNodeType::UnaryOp { op, expr } => ASTNode {
430 node_type: ASTNodeType::UnaryOp {
431 op: op.clone(),
432 expr: Box::new(self.adjust_formula(expr)),
433 },
434 source_token: ast.source_token.clone(),
435 contains_volatile: ast.contains_volatile,
436 },
437 ASTNodeType::Function { name, args } => ASTNode {
438 node_type: ASTNodeType::Function {
439 name: name.clone(),
440 args: args.iter().map(|arg| self.adjust_formula(arg)).collect(),
441 },
442 source_token: ast.source_token.clone(),
443 contains_volatile: ast.contains_volatile,
444 },
445 _ => ast.clone(),
446 }
447 }
448
449 fn adjust_reference(
450 &self,
451 reference: &formualizer_parse::parser::ReferenceType,
452 ) -> formualizer_parse::parser::ReferenceType {
453 use formualizer_parse::parser::ReferenceType;
454
455 match reference {
456 ReferenceType::Cell { sheet, row, col } => {
457 let new_row = (*row as i32 + self.row_offset).max(1) as u32;
460 let new_col = (*col as i32 + self.col_offset).max(1) as u32;
461
462 ReferenceType::Cell {
463 sheet: sheet.clone(),
464 row: new_row,
465 col: new_col,
466 }
467 }
468 ReferenceType::Range {
469 sheet,
470 start_row,
471 start_col,
472 end_row,
473 end_col,
474 } => {
475 let adj_start_row = start_row.map(|r| (r as i32 + self.row_offset).max(1) as u32);
477 let adj_start_col = start_col.map(|c| (c as i32 + self.col_offset).max(1) as u32);
478 let adj_end_row = end_row.map(|r| (r as i32 + self.row_offset).max(1) as u32);
479 let adj_end_col = end_col.map(|c| (c as i32 + self.col_offset).max(1) as u32);
480
481 ReferenceType::Range {
482 sheet: sheet.clone(),
483 start_row: adj_start_row,
484 start_col: adj_start_col,
485 end_row: adj_end_row,
486 end_col: adj_end_col,
487 }
488 }
489 _ => reference.clone(),
490 }
491 }
492}
493
494#[cfg(test)]
495mod tests {
496 use super::*;
497 use formualizer_parse::parser::parse;
498
499 fn format_formula(ast: &ASTNode) -> String {
500 format!("{ast:?}")
503 }
504
505 #[test]
506 fn test_reference_adjustment_on_row_insert() {
507 let adjuster = ReferenceAdjuster::new();
508
509 let ast = parse("=A5+B10").unwrap();
511
512 let adjusted = adjuster.adjust_ast(
514 &ast,
515 &ShiftOperation::InsertRows {
516 sheet_id: 0,
517 before: 7,
518 count: 2,
519 },
520 );
521
522 if let ASTNodeType::BinaryOp { left, right, .. } = &adjusted.node_type {
525 if let ASTNodeType::Reference {
526 reference: left_ref,
527 ..
528 } = &left.node_type
529 {
530 if let formualizer_parse::parser::ReferenceType::Cell { row, col, .. } = left_ref {
531 assert_eq!(*row, 5); assert_eq!(*col, 1);
533 }
534 }
535 if let ASTNodeType::Reference {
536 reference: right_ref,
537 ..
538 } = &right.node_type
539 {
540 if let formualizer_parse::parser::ReferenceType::Cell { row, col, .. } = right_ref {
541 assert_eq!(*row, 12); assert_eq!(*col, 2);
543 }
544 }
545 }
546 }
547
548 #[test]
549 fn test_reference_adjustment_on_column_delete() {
550 let adjuster = ReferenceAdjuster::new();
551
552 let ast = parse("=C1+F1").unwrap();
554
555 let adjusted = adjuster.adjust_ast(
557 &ast,
558 &ShiftOperation::DeleteColumns {
559 sheet_id: 0,
560 start: 2, count: 2,
562 },
563 );
564
565 if let ASTNodeType::BinaryOp { left, right, .. } = &adjusted.node_type {
567 if let ASTNodeType::Reference {
568 reference: left_ref,
569 ..
570 } = &left.node_type
571 {
572 if let formualizer_parse::parser::ReferenceType::Cell { sheet, row, col } = left_ref
574 {
575 assert_eq!(sheet.as_deref(), Some("#REF"));
576 assert_eq!(*row, 0);
577 assert_eq!(*col, 0);
578 }
579 }
580 if let ASTNodeType::Reference {
581 reference: right_ref,
582 ..
583 } = &right.node_type
584 {
585 if let formualizer_parse::parser::ReferenceType::Cell { row, col, .. } = right_ref {
586 assert_eq!(*row, 1); assert_eq!(*col, 4); }
589 }
590 }
591 }
592
593 #[test]
594 fn test_range_reference_adjustment() {
595 let adjuster = ReferenceAdjuster::new();
596
597 let ast = parse("=SUM(A1:A10)").unwrap();
599
600 let adjusted = adjuster.adjust_ast(
602 &ast,
603 &ShiftOperation::InsertRows {
604 sheet_id: 0,
605 before: 5,
606 count: 3,
607 },
608 );
609
610 if let ASTNodeType::Function { args, .. } = &adjusted.node_type {
612 if let Some(first_arg) = args.first() {
613 if let ASTNodeType::Reference { reference, .. } = &first_arg.node_type {
614 if let formualizer_parse::parser::ReferenceType::Range {
615 start_row,
616 end_row,
617 ..
618 } = reference
619 {
620 assert_eq!(start_row.unwrap_or(0), 1); assert_eq!(end_row.unwrap_or(0), 13); }
623 }
624 }
625 }
626 }
627
628 #[test]
629 fn test_relative_reference_copy() {
630 let adjuster = RelativeReferenceAdjuster::new(2, 3); let ast = parse("=A1+B2").unwrap();
634 let adjusted = adjuster.adjust_formula(&ast);
635
636 if let ASTNodeType::BinaryOp { left, right, .. } = &adjusted.node_type {
638 if let ASTNodeType::Reference {
639 reference: left_ref,
640 ..
641 } = &left.node_type
642 {
643 if let formualizer_parse::parser::ReferenceType::Cell { row, col, .. } = left_ref {
644 assert_eq!(*row, 3); assert_eq!(*col, 4);
646 }
647 }
648 if let ASTNodeType::Reference {
649 reference: right_ref,
650 ..
651 } = &right.node_type
652 {
653 if let formualizer_parse::parser::ReferenceType::Cell { row, col, .. } = right_ref {
654 assert_eq!(*row, 4); assert_eq!(*col, 5);
656 }
657 }
658 }
659 }
660
661 #[test]
662 fn test_absolute_reference_preservation() {
663 let adjuster = ReferenceAdjuster::new();
664
665 let cell_abs_row = CellRef::new(
667 0,
668 Coord::new(5, 2, true, false), );
670
671 let result = adjuster.adjust_cell_ref(
673 &cell_abs_row,
674 &ShiftOperation::InsertRows {
675 sheet_id: 0,
676 before: 3,
677 count: 2,
678 },
679 );
680
681 assert!(result.is_some());
683 let adjusted = result.unwrap();
684 assert_eq!(adjusted.coord.row, 5); assert_eq!(adjusted.coord.col, 2); assert!(adjusted.coord.row_abs());
687 assert!(!adjusted.coord.col_abs());
688 }
689
690 #[test]
691 fn test_absolute_column_preservation() {
692 let adjuster = ReferenceAdjuster::new();
693
694 let cell_abs_col = CellRef::new(
696 0,
697 Coord::new(5, 2, false, true), );
699
700 let result = adjuster.adjust_cell_ref(
702 &cell_abs_col,
703 &ShiftOperation::DeleteColumns {
704 sheet_id: 0,
705 start: 1,
706 count: 1,
707 },
708 );
709
710 assert!(result.is_some());
712 let adjusted = result.unwrap();
713 assert_eq!(adjusted.coord.row, 5); assert_eq!(adjusted.coord.col, 2); assert!(!adjusted.coord.row_abs());
716 assert!(adjusted.coord.col_abs());
717 }
718
719 #[test]
720 fn test_mixed_absolute_relative_references() {
721 let adjuster = ReferenceAdjuster::new();
722
723 let mixed1 = CellRef::new(
725 0,
726 Coord::new(5, 1, false, true), );
728
729 let result1 = adjuster.adjust_cell_ref(
730 &mixed1,
731 &ShiftOperation::InsertRows {
732 sheet_id: 0,
733 before: 3,
734 count: 2,
735 },
736 );
737
738 assert!(result1.is_some());
739 let adj1 = result1.unwrap();
740 assert_eq!(adj1.coord.row, 7); assert_eq!(adj1.coord.col, 1); let mixed2 = CellRef::new(
745 0,
746 Coord::new(10, 3, true, false), );
748
749 let result2 = adjuster.adjust_cell_ref(
750 &mixed2,
751 &ShiftOperation::DeleteColumns {
752 sheet_id: 0,
753 start: 1,
754 count: 1,
755 },
756 );
757
758 assert!(result2.is_some());
759 let adj2 = result2.unwrap();
760 assert_eq!(adj2.coord.row, 10); assert_eq!(adj2.coord.col, 2); }
763
764 #[test]
765 fn test_fully_absolute_reference() {
766 let adjuster = ReferenceAdjuster::new();
767
768 let fully_abs = CellRef::new(
770 0,
771 Coord::new(1, 1, true, true), );
773
774 let result1 = adjuster.adjust_cell_ref(
778 &fully_abs,
779 &ShiftOperation::InsertRows {
780 sheet_id: 0,
781 before: 1,
782 count: 5,
783 },
784 );
785 assert!(result1.is_some());
786 assert_eq!(result1.unwrap().coord.row, 1);
787 assert_eq!(result1.unwrap().coord.col, 1);
788
789 let result2 = adjuster.adjust_cell_ref(
791 &fully_abs,
792 &ShiftOperation::DeleteColumns {
793 sheet_id: 0,
794 start: 0,
795 count: 1,
796 },
797 );
798 assert!(result2.is_some());
799 assert_eq!(result2.unwrap().coord.row, 1);
800 assert_eq!(result2.unwrap().coord.col, 1);
801 }
802
803 #[test]
804 fn test_deleted_reference_becomes_ref_error() {
805 let adjuster = ReferenceAdjuster::new();
806
807 let cell = CellRef::new(
809 0,
810 Coord::new(5, 3, false, false), );
812
813 let result = adjuster.adjust_cell_ref(
815 &cell,
816 &ShiftOperation::DeleteRows {
817 sheet_id: 0,
818 start: 5,
819 count: 1,
820 },
821 );
822
823 assert!(result.is_none());
825
826 let result2 = adjuster.adjust_cell_ref(
828 &cell,
829 &ShiftOperation::DeleteColumns {
830 sheet_id: 0,
831 start: 3,
832 count: 1,
833 },
834 );
835
836 assert!(result2.is_none());
838 }
839
840 #[test]
841 fn test_range_expansion_on_insert() {
842 let adjuster = ReferenceAdjuster::new();
843
844 let ast = parse("=SUM(B2:D10)").unwrap();
846
847 let adjusted = adjuster.adjust_ast(
849 &ast,
850 &ShiftOperation::InsertRows {
851 sheet_id: 0,
852 before: 5,
853 count: 3,
854 },
855 );
856
857 if let ASTNodeType::Function { args, .. } = &adjusted.node_type {
859 if let Some(first_arg) = args.first() {
860 if let ASTNodeType::Reference { reference, .. } = &first_arg.node_type {
861 if let formualizer_parse::parser::ReferenceType::Range {
862 start_row,
863 end_row,
864 start_col,
865 end_col,
866 ..
867 } = reference
868 {
869 assert_eq!(*start_row, Some(2)); assert_eq!(*end_row, Some(13)); assert_eq!(*start_col, Some(2)); assert_eq!(*end_col, Some(4)); }
874 }
875 }
876 }
877 }
878
879 #[test]
880 fn test_range_contraction_on_delete() {
881 let adjuster = ReferenceAdjuster::new();
882
883 let ast = parse("=SUM(A5:A20)").unwrap();
885
886 let adjusted = adjuster.adjust_ast(
888 &ast,
889 &ShiftOperation::DeleteRows {
890 sheet_id: 0,
891 start: 10,
892 count: 5,
893 },
894 );
895
896 if let ASTNodeType::Function { args, .. } = &adjusted.node_type {
898 if let Some(first_arg) = args.first() {
899 if let ASTNodeType::Reference { reference, .. } = &first_arg.node_type {
900 if let formualizer_parse::parser::ReferenceType::Range {
901 start_row,
902 end_row,
903 ..
904 } = reference
905 {
906 assert_eq!(*start_row, Some(5)); assert_eq!(*end_row, Some(15)); }
909 }
910 }
911 }
912 }
913}