1use super::lexer::{Compare, Lexer, Token};
2
3pub struct Digit {
4 pub kind: char, pub index: i32,
6 pub number: NumberState, }
8
9pub enum TextToken {
10 Literal(char),
11 Text(String),
12 Ghost(char),
13 Spacer(char),
14 Raw,
16 Digit(Digit),
17 Period,
18 Day,
20 DayPadded,
21 DayNameShort,
22 DayName,
23 Month,
24 MonthPadded,
25 MonthNameShort,
26 MonthName,
27 MonthLetter,
28 YearShort,
29 Year,
30 Hour,
31 HourPadded,
32 Minute,
33 MinutePadded,
34 Second,
35 SecondPadded,
36 ElapsedHour,
37 ElapsedHourPadded,
38 ElapsedMinute,
39 ElapsedMinutePadded,
40 ElapsedSecond,
41 ElapsedSecondPadded,
42 AMPM,
43}
44pub struct NumberPart {
45 pub color: Option<i32>,
46 pub condition: Option<(Compare, f64)>,
47 pub use_thousands: bool,
48 pub percent: i32, pub comma: i32, pub tokens: Vec<TextToken>,
51 pub digit_count: i32, pub precision: i32, pub is_scientific: bool,
54 pub scientific_minus: bool,
55 pub exponent_digit_count: i32,
56 pub currency: Option<char>,
57}
58
59pub struct DatePart {
60 pub color: Option<i32>,
61 pub use_ampm: bool,
62 pub tokens: Vec<TextToken>,
63}
64
65pub struct ErrorPart {}
66
67pub struct GeneralPart {}
68
69pub enum ParsePart {
70 Number(NumberPart),
71 Date(DatePart),
72 Error(ErrorPart),
73 General(GeneralPart),
74}
75
76pub struct Parser {
77 pub parts: Vec<ParsePart>,
78 lexer: Lexer,
79}
80
81#[derive(PartialEq, Copy, Clone)]
82pub enum NumberState {
83 Integer,
84 Decimal,
85 Exponent,
86}
87
88impl NumberState {
89 pub fn is_integer(&self) -> bool {
90 matches!(self, NumberState::Integer)
91 }
92
93 pub fn is_decimal(&self) -> bool {
94 matches!(self, NumberState::Decimal)
95 }
96
97 pub fn is_exponent(&self) -> bool {
98 matches!(self, NumberState::Exponent)
99 }
100}
101
102impl ParsePart {
103 pub fn is_error(&self) -> bool {
104 match &self {
105 ParsePart::Date(..) => false,
106 ParsePart::Number(..) => false,
107 ParsePart::Error(..) => true,
108 ParsePart::General(..) => false,
109 }
110 }
111 pub fn is_date(&self) -> bool {
112 match &self {
113 ParsePart::Date(..) => true,
114 ParsePart::Number(..) => false,
115 ParsePart::Error(..) => false,
116 ParsePart::General(..) => false,
117 }
118 }
119}
120
121impl Parser {
126 pub fn new(format: &str) -> Self {
127 let lexer = Lexer::new(format);
128 let parts = vec![];
129 Parser { parts, lexer }
130 }
131 pub fn parse(&mut self) {
132 while self.lexer.peek_token() != Token::EOF {
133 let part = self.parse_part();
134 self.parts.push(part);
135 }
136 }
137
138 fn parse_part(&mut self) -> ParsePart {
139 let mut token = self.lexer.next_token();
140 let mut digit_count = 0;
141 let mut precision = 0;
142 let mut is_date = false;
143 let mut use_ampm = false;
144 let mut is_number = false;
145 let mut found_decimal_dot = false;
146 let mut use_thousands = false;
147 let mut comma = 0;
148 let mut percent = 0;
149 let mut last_token_is_digit = false;
150 let mut color = None;
151 let mut condition = None;
152 let mut tokens = vec![];
153 let mut is_scientific = false;
154 let mut scientific_minus = false;
155 let mut exponent_digit_count = 0;
156 let mut number = NumberState::Integer;
157 let mut index = 0;
158 let mut currency = None;
159 let mut is_time = false;
160
161 while token != Token::EOF && token != Token::Separator {
162 let next_token = self.lexer.next_token();
163 let token_is_digit = token.is_digit();
164 is_number = is_number || token_is_digit;
165 let next_token_is_digit = next_token.is_digit();
166 if token_is_digit {
167 if is_scientific {
168 exponent_digit_count += 1;
169 } else if found_decimal_dot {
170 precision += 1;
171 } else {
172 digit_count += 1;
173 }
174 }
175 match token {
176 Token::General => {
177 if tokens.is_empty() {
178 return ParsePart::General(GeneralPart {});
179 } else {
180 return ParsePart::Error(ErrorPart {});
181 }
182 }
183 Token::Comma => {
184 if last_token_is_digit && next_token_is_digit {
186 use_thousands = true;
187 } else if digit_count > 0 {
188 comma += 1;
189 } else {
190 tokens.push(TextToken::Literal(','));
192 }
193 }
194 Token::Percent => {
195 tokens.push(TextToken::Literal('%'));
196 percent += 1;
197 }
198 Token::Period => {
199 if is_number && !found_decimal_dot {
200 tokens.push(TextToken::Period);
201 found_decimal_dot = true;
202 if number.is_integer() {
203 number = NumberState::Decimal;
204 index = 0;
205 }
206 } else {
207 tokens.push(TextToken::Literal('.'));
208 }
209 }
210 Token::Color(index) => {
211 color = Some(index);
212 }
213 Token::Condition(cmp, value) => {
214 condition = Some((cmp, value));
215 }
216 Token::Currency(c) => {
217 currency = Some(c);
218 }
219 Token::QuestionMark => {
220 tokens.push(TextToken::Digit(Digit {
221 kind: '?',
222 index,
223 number,
224 }));
225 index += 1;
226 }
227 Token::Sharp => {
228 tokens.push(TextToken::Digit(Digit {
229 kind: '#',
230 index,
231 number,
232 }));
233 index += 1;
234 }
235 Token::Zero => {
236 tokens.push(TextToken::Digit(Digit {
237 kind: '0',
238 index,
239 number,
240 }));
241 index += 1;
242 }
243 Token::Literal(value) => {
244 if value == ':' {
245 is_time = true;
246 }
247 tokens.push(TextToken::Literal(value));
248 }
249 Token::Text(value) => {
250 tokens.push(TextToken::Text(value));
251 }
252 Token::Ghost(value) => {
253 tokens.push(TextToken::Ghost(value));
254 }
255 Token::Spacer(value) => {
256 tokens.push(TextToken::Spacer(value));
257 }
258 Token::Day => {
259 is_date = true;
260 tokens.push(TextToken::Day);
261 }
262 Token::DayPadded => {
263 is_date = true;
264 tokens.push(TextToken::DayPadded);
265 }
266 Token::DayNameShort => {
267 is_date = true;
268 tokens.push(TextToken::DayNameShort);
269 }
270 Token::DayName => {
271 is_date = true;
272 tokens.push(TextToken::DayName);
273 }
274 Token::MonthNameShort => {
275 is_date = true;
276 tokens.push(TextToken::MonthNameShort);
277 }
278 Token::MonthName => {
279 is_date = true;
280 tokens.push(TextToken::MonthName);
281 }
282 Token::Month => {
283 if is_time {
284 tokens.push(TextToken::Minute);
286 } else {
287 is_date = true;
288 tokens.push(TextToken::Month);
289 }
290 }
291 Token::MonthPadded => {
292 if is_time {
293 tokens.push(TextToken::MinutePadded);
295 } else {
296 is_date = true;
297 tokens.push(TextToken::MonthPadded);
298 }
299 }
300 Token::MonthLetter => {
301 is_date = true;
302 tokens.push(TextToken::MonthLetter);
303 }
304 Token::YearShort => {
305 is_date = true;
306 tokens.push(TextToken::YearShort);
307 }
308 Token::Year => {
309 is_date = true;
310 tokens.push(TextToken::Year);
311 }
312 Token::Hour => {
313 is_date = true;
314 is_time = true;
315 tokens.push(TextToken::Hour);
316 }
317 Token::HourPadded => {
318 is_date = true;
319 is_time = true;
320 tokens.push(TextToken::HourPadded);
321 }
322 Token::Second => {
323 is_date = true;
324 is_time = true;
325 tokens.push(TextToken::Second);
326 }
327 Token::SecondPadded => {
328 is_date = true;
329 is_time = true;
330 tokens.push(TextToken::SecondPadded);
331 }
332 Token::AMPM => {
333 is_date = true;
334 use_ampm = true;
335 tokens.push(TextToken::AMPM);
336 }
337 Token::Scientific => {
338 if !is_scientific {
339 index = 0;
340 number = NumberState::Exponent;
341 }
342 is_scientific = true;
343 }
344 Token::ScientificMinus => {
345 if !is_scientific {
346 index = 0;
347 number = NumberState::Exponent;
348 }
349 is_scientific = true;
350 scientific_minus = true;
351 }
352 Token::Separator => {}
353 Token::Raw => {
354 tokens.push(TextToken::Raw);
355 }
356 Token::ILLEGAL => {
357 return ParsePart::Error(ErrorPart {});
358 }
359 Token::EOF => {}
360 Token::ElapsedHour => {
361 is_date = true;
362 is_time = true;
363 tokens.push(TextToken::ElapsedHour);
364 }
365 Token::ElapsedMinute => {
366 is_date = true;
367 is_time = true;
368 tokens.push(TextToken::ElapsedMinute);
369 }
370 Token::ElapsedSecond => {
371 is_date = true;
372 is_time = true;
373 tokens.push(TextToken::ElapsedSecond);
374 }
375 Token::ElapsedHourPadded => {
376 is_date = true;
377 is_time = true;
378 tokens.push(TextToken::ElapsedHourPadded);
379 }
380 Token::ElapsedMinutePadded => {
381 is_date = true;
382 is_time = true;
383 tokens.push(TextToken::ElapsedMinutePadded);
384 }
385 Token::ElapsedSecondPadded => {
386 is_date = true;
387 is_time = true;
388 tokens.push(TextToken::ElapsedSecondPadded);
389 }
390 }
391 last_token_is_digit = token_is_digit;
392 token = next_token;
393 }
394 if is_date {
395 if is_number {
396 return ParsePart::Error(ErrorPart {});
397 }
398 ParsePart::Date(DatePart {
399 color,
400 use_ampm,
401 tokens,
402 })
403 } else {
404 ParsePart::Number(NumberPart {
405 color,
406 condition,
407 use_thousands,
408 percent,
409 comma,
410 tokens,
411 digit_count,
412 precision,
413 is_scientific,
414 scientific_minus,
415 exponent_digit_count,
416 currency,
417 })
418 }
419 }
420}