use super::lexer::{Compare, Lexer, Token}; pub struct Digit { pub kind: char, // '#' | '?' | '0' pub index: i32, pub number: char, // 'i' | 'd' | 'e' (integer, decimal or exponent) } pub enum TextToken { Literal(char), Text(String), Ghost(char), Spacer(char), // Text Raw, Digit(Digit), Period, // Dates Day, DayPadded, DayNameShort, DayName, Month, MonthPadded, MonthNameShort, MonthName, MonthLetter, YearShort, Year, } pub struct NumberPart { pub color: Option, pub condition: Option<(Compare, f64)>, pub use_thousands: bool, pub percent: i32, // multiply number by 100^percent pub comma: i32, // divide number by 1000^comma pub tokens: Vec, pub digit_count: i32, // number of digit tokens (#, 0 or ?) to the left of the decimal point pub precision: i32, // number of digits to the right of the decimal point pub is_scientific: bool, pub scientific_minus: bool, pub exponent_digit_count: i32, } pub struct DatePart { pub color: Option, pub tokens: Vec, } pub struct ErrorPart {} pub struct GeneralPart {} pub enum ParsePart { Number(NumberPart), Date(DatePart), Error(ErrorPart), General(GeneralPart), } pub struct Parser { pub parts: Vec, lexer: Lexer, } impl ParsePart { pub fn is_error(&self) -> bool { match &self { ParsePart::Date(..) => false, ParsePart::Number(..) => false, ParsePart::Error(..) => true, ParsePart::General(..) => false, } } pub fn is_date(&self) -> bool { match &self { ParsePart::Date(..) => true, ParsePart::Number(..) => false, ParsePart::Error(..) => false, ParsePart::General(..) => false, } } } impl Parser { pub fn new(format: &str) -> Self { let lexer = Lexer::new(format); let parts = vec![]; Parser { parts, lexer } } pub fn parse(&mut self) { while self.lexer.peek_token() != Token::EOF { let part = self.parse_part(); self.parts.push(part); } } fn parse_part(&mut self) -> ParsePart { let mut token = self.lexer.next_token(); let mut digit_count = 0; let mut precision = 0; let mut is_date = false; let mut is_number = false; let mut found_decimal_dot = false; let mut use_thousands = false; let mut comma = 0; let mut percent = 0; let mut last_token_is_digit = false; let mut color = None; let mut condition = None; let mut tokens = vec![]; let mut is_scientific = false; let mut scientific_minus = false; let mut exponent_digit_count = 0; let mut number = 'i'; let mut index = 0; while token != Token::EOF && token != Token::Separator { let next_token = self.lexer.next_token(); let token_is_digit = token.is_digit(); is_number = is_number || token_is_digit; let next_token_is_digit = next_token.is_digit(); if token_is_digit { if is_scientific { exponent_digit_count += 1; } else if found_decimal_dot { precision += 1; } else { digit_count += 1; } } match token { Token::General => { if tokens.is_empty() { return ParsePart::General(GeneralPart {}); } else { return ParsePart::Error(ErrorPart {}); } } Token::Comma => { // If it is in between digit token then we use the thousand separator if last_token_is_digit && next_token_is_digit { use_thousands = true; } else if digit_count > 0 { comma += 1; } else { // Before the number is just a literal. tokens.push(TextToken::Literal(',')); } } Token::Percent => { tokens.push(TextToken::Literal('%')); percent += 1; } Token::Period => { if !found_decimal_dot { tokens.push(TextToken::Period); found_decimal_dot = true; if number == 'i' { number = 'd'; index = 0; } } else { tokens.push(TextToken::Literal('.')); } } Token::Color(index) => { color = Some(index); } Token::Condition(cmp, value) => { condition = Some((cmp, value)); } Token::QuestionMark => { tokens.push(TextToken::Digit(Digit { kind: '?', index, number, })); index += 1; } Token::Sharp => { tokens.push(TextToken::Digit(Digit { kind: '#', index, number, })); index += 1; } Token::Zero => { tokens.push(TextToken::Digit(Digit { kind: '0', index, number, })); index += 1; } Token::Literal(value) => { tokens.push(TextToken::Literal(value)); } Token::Text(value) => { tokens.push(TextToken::Text(value)); } Token::Ghost(value) => { tokens.push(TextToken::Ghost(value)); } Token::Spacer(value) => { tokens.push(TextToken::Spacer(value)); } Token::Day => { is_date = true; tokens.push(TextToken::Day); } Token::DayPadded => { is_date = true; tokens.push(TextToken::DayPadded); } Token::DayNameShort => { is_date = true; tokens.push(TextToken::DayNameShort); } Token::DayName => { is_date = true; tokens.push(TextToken::DayName); } Token::MonthNameShort => { is_date = true; tokens.push(TextToken::MonthNameShort); } Token::MonthName => { is_date = true; tokens.push(TextToken::MonthName); } Token::Month => { is_date = true; tokens.push(TextToken::Month); } Token::MonthPadded => { is_date = true; tokens.push(TextToken::MonthPadded); } Token::MonthLetter => { is_date = true; tokens.push(TextToken::MonthLetter); } Token::YearShort => { is_date = true; tokens.push(TextToken::YearShort); } Token::Year => { is_date = true; tokens.push(TextToken::Year); } Token::Scientific => { if !is_scientific { index = 0; number = 'e'; } is_scientific = true; } Token::ScientificMinus => { is_scientific = true; scientific_minus = true; } Token::Separator => {} Token::Raw => { tokens.push(TextToken::Raw); } Token::ILLEGAL => { return ParsePart::Error(ErrorPart {}); } Token::EOF => {} } last_token_is_digit = token_is_digit; token = next_token; } if is_date { if is_number { return ParsePart::Error(ErrorPart {}); } ParsePart::Date(DatePart { color, tokens }) } else { ParsePart::Number(NumberPart { color, condition, use_thousands, percent, comma, tokens, digit_count, precision, is_scientific, scientific_minus, exponent_digit_count, }) } } }