ironcalc_base/expressions/lexer/
util.rs1use serde::{Deserialize, Serialize};
2
3use crate::expressions::token;
4use crate::language::{get_language, Language};
5use crate::locale::{get_locale, Locale};
6
7use super::{Lexer, LexerMode};
8
9#[derive(Debug, PartialEq, Serialize, Deserialize)]
11pub struct MarkedToken {
12 pub token: token::TokenType,
13 pub start: i32,
14 pub end: i32,
15}
16
17pub fn get_tokens(formula: &str) -> Vec<MarkedToken> {
51 get_tokens_with_locale(
52 formula,
53 #[allow(clippy::expect_used)]
54 get_locale("en").expect(""),
55 #[allow(clippy::expect_used)]
56 get_language("en").expect(""),
57 )
58}
59
60pub fn get_tokens_with_locale(
62 formula: &str,
63 locale: &Locale,
64 language: &Language,
65) -> Vec<MarkedToken> {
66 let mut tokens = Vec::new();
67 let mut lexer = Lexer::new(formula, LexerMode::A1, locale, language);
68 let mut start = lexer.get_position();
69 let mut next_token = lexer.next_token();
70 let mut end = lexer.get_position();
71 loop {
72 match next_token {
73 token::TokenType::EOF => {
74 break;
75 }
76 _ => {
77 tokens.push(MarkedToken {
78 start,
79 end,
80 token: next_token,
81 });
82 start = lexer.get_position();
83 next_token = lexer.next_token();
84 end = lexer.get_position();
85 }
86 }
87 }
88 tokens
89}
90
91fn next_state(absolute_column: bool, absolute_row: bool) -> (bool, bool) {
94 match (absolute_column, absolute_row) {
95 (false, false) => (true, true),
96 (true, true) => (false, true),
97 (false, true) => (true, false),
98 (true, false) => (false, false),
99 }
100}
101
102fn cycle_endpoint(part: &[char]) -> Vec<char> {
106 let n = part.len();
107 let mut i = 0;
108 let mut absolute_column = false;
109 if part.first() == Some(&'$') {
110 absolute_column = true;
111 i += 1;
112 }
113 let column_start = i;
114 while i < n && part[i].is_ascii_alphabetic() {
115 i += 1;
116 }
117 let column = &part[column_start..i];
118 let mut absolute_row = false;
119 if i < n && part[i] == '$' {
120 absolute_row = true;
121 i += 1;
122 }
123 let row_start = i;
124 while i < n && part[i].is_ascii_digit() {
125 i += 1;
126 }
127 let row = &part[row_start..i];
128 if i != n || (column.is_empty() && row.is_empty()) {
129 return part.to_vec();
130 }
131 let (new_column, new_row) = if column.is_empty() {
132 (false, !(absolute_column || absolute_row))
134 } else if row.is_empty() {
135 (!absolute_column, false)
137 } else {
138 next_state(absolute_column, absolute_row)
139 };
140 let mut result = Vec::with_capacity(n + 2);
141 if new_column {
142 result.push('$');
143 }
144 result.extend(column.iter().map(|c| c.to_ascii_uppercase()));
146 if new_row {
147 result.push('$');
148 }
149 result.extend_from_slice(row);
150 result
151}
152
153fn cycle_token_text(text: &[char]) -> Vec<char> {
156 let n = text.len();
157 let mut result = Vec::with_capacity(n + 4);
158 let mut i = 0;
159 while i < n && text[i].is_whitespace() {
161 result.push(text[i]);
162 i += 1;
163 }
164 if i < n && text[i] == '\'' {
165 result.push('\'');
167 i += 1;
168 while i < n {
169 let c = text[i];
170 result.push(c);
171 i += 1;
172 if c == '\'' {
173 if i < n && text[i] == '\'' {
174 result.push('\'');
175 i += 1;
176 } else {
177 break;
178 }
179 }
180 }
181 if i < n && text[i] == '!' {
182 result.push('!');
183 i += 1;
184 }
185 } else if let Some(bang) = text.iter().skip(i).position(|&c| c == '!') {
186 let prefix_end = i + bang + 1;
187 result.extend_from_slice(&text[i..prefix_end]);
188 i = prefix_end;
189 }
190 loop {
192 let part_start = i;
193 while i < n && text[i] != ':' {
194 i += 1;
195 }
196 result.extend(cycle_endpoint(&text[part_start..i]));
197 if i < n {
198 result.push(':');
199 i += 1;
200 } else {
201 break;
202 }
203 }
204 result
205}
206
207pub fn cycle_reference(
217 value: &str,
218 start: usize,
219 end: usize,
220 locale: &Locale,
221 language: &Language,
222) -> Result<(String, i32, i32), String> {
223 let chars: Vec<char> = value.chars().collect();
224 if start > chars.len() || end > chars.len() {
225 return Err("Cursor index out of bounds".to_string());
226 }
227 let (selection_start, selection_end) = if start <= end {
228 (start, end)
229 } else {
230 (end, start)
231 };
232 if chars.first() != Some(&'=') {
233 return Ok((value.to_string(), start as i32, end as i32));
234 }
235 let body = &chars[1..];
236 let body_str: String = body.iter().collect();
237
238 let mut result: Vec<char> = vec!['='];
239 let mut copied = 0;
241 let mut first_cycled_start = None;
242 let mut last_cycled_end = 0;
243 for marked in get_tokens_with_locale(&body_str, locale, language) {
244 if !matches!(
245 marked.token,
246 token::TokenType::Reference { .. } | token::TokenType::Range { .. }
247 ) {
248 continue;
249 }
250 let token_start = marked.start.max(0) as usize + 1;
252 let token_end = marked.end.max(0) as usize + 1;
253 if token_start > selection_end || selection_start > token_end {
254 continue;
255 }
256 result.extend_from_slice(&body[copied..token_start - 1]);
257 let token_text = &body[token_start - 1..token_end - 1];
258 if first_cycled_start.is_none() {
259 let whitespace = token_text.iter().take_while(|c| c.is_whitespace()).count();
260 first_cycled_start = Some(result.len() + whitespace);
261 }
262 result.extend(cycle_token_text(token_text));
263 last_cycled_end = result.len();
264 copied = token_end - 1;
265 }
266 let Some(first_start) = first_cycled_start else {
267 return Ok((value.to_string(), start as i32, end as i32));
269 };
270 result.extend_from_slice(&body[copied..]);
271 let new_value: String = result.iter().collect();
272 if start == end {
273 return Ok((new_value, last_cycled_end as i32, last_cycled_end as i32));
274 }
275 Ok((new_value, first_start as i32, last_cycled_end as i32))
276}