gridline_engine/engine/
preprocess.rs1use regex::Regex;
2
3use super::cell_ref::CellRef;
4
5#[derive(Clone, Copy, Debug)]
7pub enum ShiftOperation {
8 InsertRow(usize),
9 DeleteRow(usize),
10 InsertColumn(usize),
11 DeleteColumn(usize),
12}
13
14pub fn shift_formula_references(formula: &str, op: ShiftOperation) -> String {
22 let with_shifted_ranges = crate::builtins::range_fn_re()
24 .replace_all(formula, |caps: ®ex::Captures| {
25 let func_name = &caps[1];
26 let start_ref = &caps[2];
27 let end_ref = &caps[3];
28 let rest_args = caps.get(4).map(|m| m.as_str()).unwrap_or("");
29
30 let new_start = shift_single_ref(start_ref, op);
31 let new_end = shift_single_ref(end_ref, op);
32
33 if new_start == "#REF!" || new_end == "#REF!" {
35 return "#REF!".to_string();
36 }
37
38 format!("{}({}:{}{}", func_name, new_start, new_end, rest_args)
39 })
40 .to_string();
41
42 shift_cell_refs_outside_strings(&with_shifted_ranges, op)
44}
45
46fn shift_single_ref(cell_ref_str: &str, op: ShiftOperation) -> String {
47 let Some(cr) = CellRef::from_str(cell_ref_str) else {
48 return cell_ref_str.to_string();
49 };
50
51 match op {
52 ShiftOperation::InsertRow(at_row) => {
53 if cr.row >= at_row {
54 CellRef::new(cr.row + 1, cr.col).to_string()
55 } else {
56 cr.to_string()
57 }
58 }
59 ShiftOperation::DeleteRow(at_row) => {
60 if cr.row == at_row {
61 "#REF!".to_string()
62 } else if cr.row > at_row {
63 CellRef::new(cr.row - 1, cr.col).to_string()
64 } else {
65 cr.to_string()
66 }
67 }
68 ShiftOperation::InsertColumn(at_col) => {
69 if cr.col >= at_col {
70 CellRef::new(cr.row, cr.col + 1).to_string()
71 } else {
72 cr.to_string()
73 }
74 }
75 ShiftOperation::DeleteColumn(at_col) => {
76 if cr.col == at_col {
77 "#REF!".to_string()
78 } else if cr.col > at_col {
79 CellRef::new(cr.row, cr.col - 1).to_string()
80 } else {
81 cr.to_string()
82 }
83 }
84 }
85}
86
87fn shift_cell_refs_outside_strings(script: &str, op: ShiftOperation) -> String {
88 let cell_re = Regex::new(r"\b([A-Za-z]+)([0-9]+)\b").unwrap();
89 let value_re = Regex::new(r"@([A-Za-z]+)([0-9]+)\b").unwrap();
90
91 let shift_cells = |seg: &str| {
92 let seg = value_re
94 .replace_all(seg, |caps: ®ex::Captures| {
95 let cell_ref = format!("{}{}", &caps[1], &caps[2]);
96 let shifted = shift_single_ref(&cell_ref, op);
97 if shifted == "#REF!" {
98 shifted
99 } else {
100 format!("@{}", shifted)
101 }
102 })
103 .to_string();
104
105 cell_re
107 .replace_all(&seg, |caps: ®ex::Captures| {
108 let cell_ref = format!("{}{}", &caps[1], &caps[2]);
109 shift_single_ref(&cell_ref, op)
110 })
111 .to_string()
112 };
113
114 let bytes = script.as_bytes();
116 let mut out = String::new();
117 let mut seg_start = 0;
118 let mut in_string = false;
119 let mut backslashes = 0usize;
120 let mut i = 0usize;
121
122 while i < bytes.len() {
123 let b = bytes[i];
124 if in_string {
125 if b == b'\\' {
126 backslashes += 1;
127 i += 1;
128 continue;
129 }
130 if b == b'"' && backslashes % 2 == 0 {
131 out.push_str(&script[seg_start..=i]);
132 in_string = false;
133 seg_start = i + 1;
134 }
135 backslashes = 0;
136 i += 1;
137 continue;
138 }
139
140 if b == b'"' {
141 out.push_str(&shift_cells(&script[seg_start..i]));
142 in_string = true;
143 seg_start = i;
144 backslashes = 0;
145 i += 1;
146 continue;
147 }
148
149 i += 1;
150 }
151
152 if seg_start < script.len() {
153 if in_string {
154 out.push_str(&script[seg_start..]);
155 } else {
156 out.push_str(&shift_cells(&script[seg_start..]));
157 }
158 }
159
160 out
161}
162
163pub fn preprocess_script(script: &str) -> String {
167 let with_ranges = crate::builtins::range_fn_re()
168 .replace_all(script, |caps: ®ex::Captures| {
169 let start_ref = &caps[2];
170 let end_ref = &caps[3];
171 let rest_args = caps.get(4).map(|m| m.as_str()).unwrap_or("");
172
173 let Some(rhai_name) = crate::builtins::range_rhai_name(&caps[1]) else {
174 return caps[0].to_string();
175 };
176
177 if let (Some(start), Some(end)) =
178 (CellRef::from_str(start_ref), CellRef::from_str(end_ref))
179 {
180 format!(
181 "{}({}, {}, {}, {}{})",
182 rhai_name, start.row, start.col, end.row, end.col, rest_args
183 )
184 } else {
185 caps[0].to_string()
186 }
187 })
188 .to_string();
189
190 replace_cell_refs_outside_strings(&with_ranges)
191}
192
193fn replace_cell_refs_outside_strings(script: &str) -> String {
194 let cell_re = Regex::new(r"\b([A-Za-z]+)([0-9]+)\b").unwrap();
195 let value_re = Regex::new(r"@([A-Za-z]+)([0-9]+)\b").unwrap();
196
197 let replace_cells = |seg: &str| {
198 let seg = value_re
199 .replace_all(seg, |caps: ®ex::Captures| {
200 let cell_ref = format!("{}{}", &caps[1], &caps[2]);
201 if let Some(cr) = CellRef::from_str(&cell_ref) {
202 format!("value({}, {})", cr.row, cr.col)
203 } else {
204 caps[0].to_string()
205 }
206 })
207 .to_string();
208
209 cell_re
210 .replace_all(&seg, |caps: ®ex::Captures| {
211 let cell_ref = format!("{}{}", &caps[1], &caps[2]);
212 if let Some(cr) = CellRef::from_str(&cell_ref) {
213 format!("cell({}, {})", cr.row, cr.col)
214 } else {
215 caps[0].to_string()
216 }
217 })
218 .to_string()
219 };
220
221 let bytes = script.as_bytes();
222 let mut out = String::new();
223 let mut seg_start = 0;
224 let mut in_string = false;
225 let mut backslashes = 0usize;
226 let mut i = 0usize;
227
228 while i < bytes.len() {
229 let b = bytes[i];
230 if in_string {
231 if b == b'\\' {
232 backslashes += 1;
233 i += 1;
234 continue;
235 }
236 if b == b'"' && backslashes.is_multiple_of(2) {
237 out.push_str(&script[seg_start..=i]);
238 in_string = false;
239 seg_start = i + 1;
240 }
241 backslashes = 0;
242 i += 1;
243 continue;
244 }
245
246 if b == b'"' {
247 out.push_str(&replace_cells(&script[seg_start..i]));
248 in_string = true;
249 seg_start = i;
250 backslashes = 0;
251 i += 1;
252 continue;
253 }
254
255 i += 1;
256 }
257
258 if seg_start < script.len() {
259 if in_string {
260 out.push_str(&script[seg_start..]);
261 } else {
262 out.push_str(&replace_cells(&script[seg_start..]));
263 }
264 }
265
266 out
267}