Skip to main content

gridline_engine/engine/
preprocess.rs

1use regex::Regex;
2
3use super::cell_ref::CellRef;
4
5/// Replace cell references like "A1" with Rhai function calls like "cell(0, 0)".
6/// Typed refs like "@A1" become "value(0, 0)" (returns Dynamic).
7/// Also transforms range functions like SUM(A1:B5, ...) into sum_range(0, 0, 4, 1, ...).
8pub fn preprocess_script(script: &str) -> String {
9    let with_ranges = crate::builtins::range_fn_re()
10        .replace_all(script, |caps: &regex::Captures| {
11            let start_ref = &caps[2];
12            let end_ref = &caps[3];
13            let rest_args = caps.get(4).map(|m| m.as_str()).unwrap_or("");
14
15            let Some(rhai_name) = crate::builtins::range_rhai_name(&caps[1]) else {
16                return caps[0].to_string();
17            };
18
19            if let (Some(start), Some(end)) =
20                (CellRef::from_str(start_ref), CellRef::from_str(end_ref))
21            {
22                format!(
23                    "{}({}, {}, {}, {}{})",
24                    rhai_name, start.row, start.col, end.row, end.col, rest_args
25                )
26            } else {
27                caps[0].to_string()
28            }
29        })
30        .to_string();
31
32    replace_cell_refs_outside_strings(&with_ranges)
33}
34
35fn replace_cell_refs_outside_strings(script: &str) -> String {
36    let cell_re = Regex::new(r"\b([A-Za-z]+)([0-9]+)\b").unwrap();
37    let value_re = Regex::new(r"@([A-Za-z]+)([0-9]+)\b").unwrap();
38
39    let replace_cells = |seg: &str| {
40        let seg = value_re
41            .replace_all(seg, |caps: &regex::Captures| {
42                let cell_ref = format!("{}{}", &caps[1], &caps[2]);
43                if let Some(cr) = CellRef::from_str(&cell_ref) {
44                    format!("value({}, {})", cr.row, cr.col)
45                } else {
46                    caps[0].to_string()
47                }
48            })
49            .to_string();
50
51        cell_re
52            .replace_all(&seg, |caps: &regex::Captures| {
53                let cell_ref = format!("{}{}", &caps[1], &caps[2]);
54                if let Some(cr) = CellRef::from_str(&cell_ref) {
55                    format!("cell({}, {})", cr.row, cr.col)
56                } else {
57                    caps[0].to_string()
58                }
59            })
60            .to_string()
61    };
62
63    let bytes = script.as_bytes();
64    let mut out = String::new();
65    let mut seg_start = 0;
66    let mut in_string = false;
67    let mut backslashes = 0usize;
68    let mut i = 0usize;
69
70    while i < bytes.len() {
71        let b = bytes[i];
72        if in_string {
73            if b == b'\\' {
74                backslashes += 1;
75                i += 1;
76                continue;
77            }
78            if b == b'"' && backslashes.is_multiple_of(2) {
79                out.push_str(&script[seg_start..=i]);
80                in_string = false;
81                seg_start = i + 1;
82            }
83            backslashes = 0;
84            i += 1;
85            continue;
86        }
87
88        if b == b'"' {
89            out.push_str(&replace_cells(&script[seg_start..i]));
90            in_string = true;
91            seg_start = i;
92            backslashes = 0;
93            i += 1;
94            continue;
95        }
96
97        i += 1;
98    }
99
100    if seg_start < script.len() {
101        if in_string {
102            out.push_str(&script[seg_start..]);
103        } else {
104            out.push_str(&replace_cells(&script[seg_start..]));
105        }
106    }
107
108    out
109}