smartcalc_tauri/
types.rs

1/*
2 * smartcalc v1.0.8
3 * Copyright (c) Erhan BARIS (Ruslan Ognyanov Asenov)
4 * Licensed under the GNU General Public License v2.0.
5 */
6
7use alloc::format;
8use alloc::rc::Rc;
9use alloc::string::String;
10use alloc::string::ToString;
11use alloc::vec::Vec;
12use chrono::{NaiveDateTime, TimeZone};
13use core::ops::Deref;
14use core::result::Result;
15
16use crate::compiler::dynamic_type::DynamicTypeItem;
17use crate::compiler::DataItem;
18use crate::config::DynamicType;
19use crate::config::SmartCalcConfig;
20use alloc::collections::btree_map::BTreeMap;
21use chrono::{Duration, NaiveDate};
22use serde_derive::{Deserialize, Serialize};
23
24use crate::tokinizer::TokenInfoStatus;
25use crate::tokinizer::{TokenInfo, Tokinizer};
26use crate::variable::VariableInfo;
27
28pub type ExpressionFunc = fn(
29    config: &SmartCalcConfig,
30    tokinizer: &Tokinizer,
31    fields: &BTreeMap<String, Rc<TokenInfo>>,
32) -> core::result::Result<TokenType, String>;
33pub type AstResult = Result<SmartCalcAstType, (&'static str, u16, u16)>;
34
35pub struct Money(pub f64, pub Rc<CurrencyInfo>);
36impl Money {
37    pub fn get_price(&self) -> f64 {
38        self.0
39    }
40
41    pub fn get_currency(&self) -> Rc<CurrencyInfo> {
42        self.1.clone()
43    }
44}
45
46#[repr(C)]
47#[derive(Clone, Debug)]
48pub enum FieldType {
49    Text(String, Option<String>),
50    DateTime(String),
51    Date(String),
52    Time(String),
53    Money(String),
54    Percent(String),
55    Number(String),
56    Group(String, Vec<String>),
57    TypeGroup(Vec<String>, String),
58    Month(String),
59    Duration(String),
60    Timezone(String),
61    DynamicType(String, Option<String>),
62}
63
64unsafe impl Send for FieldType {}
65unsafe impl Sync for FieldType {}
66
67impl FieldType {
68    pub fn type_name(&self) -> String {
69        match self {
70            FieldType::Text(_, _) => "TEXT".to_string(),
71            FieldType::Date(_) => "DATE".to_string(),
72            FieldType::DateTime(_) => "DATE_TIME".to_string(),
73            FieldType::Time(_) => "TIME".to_string(),
74            FieldType::Money(_) => "MONEY".to_string(),
75            FieldType::Percent(_) => "PERCENT".to_string(),
76            FieldType::Number(_) => "NUMBER".to_string(),
77            FieldType::Group(_, _) => "GROUP".to_string(),
78            FieldType::TypeGroup(_, _) => "TYPE_GROUP".to_string(),
79            FieldType::Month(_) => "MONTH".to_string(),
80            FieldType::Duration(_) => "DURATION".to_string(),
81            FieldType::Timezone(_) => "TIMEZONE".to_string(),
82            FieldType::DynamicType(_, _) => "DYNAMIC_TYPE".to_string(),
83        }
84    }
85}
86
87impl PartialEq for FieldType {
88    fn eq(&self, other: &Self) -> bool {
89        match (self, other) {
90            (FieldType::Timezone(l), FieldType::Timezone(r)) => r == l,
91            (FieldType::Percent(l), FieldType::Percent(r)) => r == l,
92            (FieldType::Number(l), FieldType::Number(r)) => r == l,
93            (FieldType::Text(l, _), FieldType::Text(r, _)) => r.to_lowercase() == l.to_lowercase(),
94            (FieldType::Date(l), FieldType::Date(r)) => r == l,
95            (FieldType::DateTime(l), FieldType::DateTime(r)) => r == l,
96            (FieldType::Time(l), FieldType::Time(r)) => r == l,
97            (FieldType::Money(l), FieldType::Money(r)) => r == l,
98            (FieldType::Month(l), FieldType::Month(r)) => r == l,
99            (FieldType::Duration(l), FieldType::Duration(r)) => r == l,
100            (FieldType::Group(_, l), FieldType::Group(_, r)) => r == l,
101            (FieldType::DynamicType(l, _), FieldType::DynamicType(r, _)) => r == l,
102            (FieldType::TypeGroup(l1, l2), FieldType::TypeGroup(r1, r2)) => r1 == l1 && r2 == l2,
103            (_, _) => false,
104        }
105    }
106}
107
108#[derive(Clone, Debug, Serialize, Deserialize)]
109pub struct CurrencyInfo {
110    pub code: String,
111    pub symbol: String,
112
113    #[serde(alias = "thousandsSeparator")]
114    pub thousands_separator: String,
115
116    #[serde(alias = "decimalSeparator")]
117    pub decimal_separator: String,
118
119    #[serde(alias = "symbolOnLeft")]
120    pub symbol_on_left: bool,
121
122    #[serde(alias = "spaceBetweenAmountAndSymbol")]
123    pub space_between_amount_and_symbol: bool,
124
125    #[serde(alias = "decimalDigits")]
126    pub decimal_digits: u8,
127}
128
129use core::cmp::{Eq, Ord, Ordering, PartialEq};
130
131impl PartialEq for CurrencyInfo {
132    fn eq(&self, other: &Self) -> bool {
133        self.code.eq(&other.code)
134    }
135}
136impl PartialOrd for CurrencyInfo {
137    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
138        self.code.partial_cmp(&other.code)
139    }
140}
141impl Eq for CurrencyInfo {}
142impl Ord for CurrencyInfo {
143    fn cmp(&self, other: &Self) -> Ordering {
144        self.code.cmp(&other.code)
145    }
146}
147
148#[derive(Debug, Clone, PartialEq)]
149pub struct TimeOffset {
150    pub name: String,
151    pub offset: i32,
152}
153
154#[derive(Debug, Copy, Clone, PartialEq)]
155pub enum NumberType {
156    Decimal,
157    Octal,
158    Hexadecimal,
159    Binary,
160    Raw,
161}
162
163#[derive(Debug, Clone)]
164pub enum TokenType {
165    Number(f64, NumberType),
166    Text(String),
167    Time(NaiveDateTime, TimeOffset),
168    Date(NaiveDate, TimeOffset),
169    DateTime(NaiveDateTime, TimeOffset),
170    Operator(char),
171    Field(Rc<FieldType>),
172    Percent(f64),
173    DynamicType(f64, Rc<DynamicType>),
174    Money(f64, Rc<CurrencyInfo>),
175    Variable(Rc<VariableInfo>),
176    Month(u32),
177    Duration(Duration),
178    Timezone(String, i32),
179}
180
181impl PartialEq for TokenType {
182    fn eq(&self, other: &Self) -> bool {
183        match (&self, &other) {
184            (TokenType::Timezone(l_value, l_type), TokenType::Timezone(r_value, r_type)) => {
185                *l_value == *r_value && *l_type == *r_type
186            }
187            (TokenType::Text(l_value), TokenType::Text(r_value)) => {
188                l_value.to_lowercase() == r_value.to_lowercase()
189            }
190            (TokenType::Number(l_value, _), TokenType::Number(r_value, _)) => l_value == r_value,
191            (TokenType::Percent(l_value), TokenType::Percent(r_value)) => l_value == r_value,
192            (TokenType::Operator(l_value), TokenType::Operator(r_value)) => l_value == r_value,
193            (TokenType::Variable(l_value), TokenType::Variable(r_value)) => l_value == r_value,
194            (TokenType::Money(l_value, l_symbol), TokenType::Money(r_value, r_symbol)) => {
195                l_value == r_value && l_symbol == r_symbol
196            }
197            (TokenType::Time(l_value, l_tz), TokenType::Time(r_value, r_tz)) => {
198                l_value == r_value && l_tz == r_tz
199            }
200            (TokenType::Month(l_value), TokenType::Month(r_value)) => l_value == r_value,
201            (TokenType::Duration(l_value), TokenType::Duration(r_value)) => l_value == r_value,
202            (TokenType::Date(l_value, l_tz), TokenType::Date(r_value, r_tz)) => {
203                l_value == r_value && l_tz == r_tz
204            }
205            (TokenType::Field(l_value), TokenType::Field(r_value)) => {
206                l_value.deref() == r_value.deref()
207            }
208            (_, _) => false,
209        }
210    }
211}
212
213#[derive(Serialize, Deserialize)]
214struct MoneyToStringOutput {
215    code: String,
216    value: f64,
217    symbol: String,
218    is_symbol_on_left: bool,
219}
220
221impl ToString for TokenType {
222    fn to_string(&self) -> String {
223        match &self {
224            TokenType::DynamicType(number, dynamic_type) => {
225                dynamic_type.format.replace("{value}", &number.to_string())
226            }
227            TokenType::Number(number, _) => number.to_string(),
228            TokenType::Text(text) => text.to_string(),
229            TokenType::Time(time, tz) => {
230                let tz_offset = chrono::FixedOffset::east(tz.offset * 60);
231                let datetime = tz_offset.from_utc_datetime(time);
232                alloc::format!("{} {}", datetime.format("%H:%M:%S").to_string(), tz.name)
233            }
234            TokenType::Date(date, tz) => {
235                let tz_offset = chrono::FixedOffset::east(tz.offset * 60);
236                let datetime = tz_offset.from_utc_date(date);
237                alloc::format!("{} {}", datetime.format("%d/%m/%Y").to_string(), tz.name)
238            }
239            TokenType::DateTime(datetime, tz) => {
240                let tz_offset = chrono::FixedOffset::east(tz.offset * 60);
241                let datetime = tz_offset.from_utc_datetime(datetime);
242                alloc::format!(
243                    "{} {}",
244                    datetime.format("%d/%m/%Y %H:%M:%S").to_string(),
245                    tz.name
246                )
247            }
248            TokenType::Operator(ch) => ch.to_string(),
249            TokenType::Field(_) => "field".to_string(),
250            TokenType::Percent(number) => format!("{}%", number),
251            TokenType::Money(price, currency) => {
252                let money_output = MoneyToStringOutput {
253                    code: currency.code.to_string(),
254                    value: *price,
255                    symbol: currency.symbol.to_string(),
256                    is_symbol_on_left: currency.symbol_on_left,
257                };
258                let money_output_json = serde_json::to_string(&money_output).unwrap();
259
260                money_output_json
261            }
262            TokenType::Variable(var) => var.to_string(),
263            TokenType::Month(month) => month.to_string(),
264            TokenType::Duration(duration) => duration.to_string(),
265            TokenType::Timezone(timezone, offset) => format!("{} {:?}", timezone, offset),
266        }
267    }
268}
269
270impl TokenType {
271    pub fn type_name(&self) -> String {
272        match self {
273            TokenType::Number(_, _) => "NUMBER".to_string(),
274            TokenType::Text(_) => "TEXT".to_string(),
275            TokenType::Time(_, _) => "TIME".to_string(),
276            TokenType::Date(_, _) => "DATE".to_string(),
277            TokenType::DateTime(_, _) => "DATE_TIME".to_string(),
278            TokenType::Operator(_) => "OPERATOR".to_string(),
279            TokenType::Field(_) => "FIELD".to_string(),
280            TokenType::Percent(_) => "PERCENT".to_string(),
281            TokenType::Money(_, _) => "MONEY".to_string(),
282            TokenType::Variable(_) => "VARIABLE".to_string(),
283            TokenType::Month(_) => "MONTH".to_string(),
284            TokenType::Duration(_) => "DURATION".to_string(),
285            TokenType::Timezone(_, _) => "TIMEZONE".to_string(),
286            TokenType::DynamicType(_, _) => "DYNAMIC_TYPE".to_string(),
287        }
288    }
289
290    pub fn field_compare(&self, field: &FieldType) -> bool {
291        match (field, self) {
292            (FieldType::DynamicType(_, expected), TokenType::DynamicType(_, dynamic_type)) => {
293                expected.as_ref().map_or(true, |v| {
294                    v.to_lowercase() == dynamic_type.group_name.to_lowercase()
295                })
296            }
297            (FieldType::Percent(_), TokenType::Percent(_)) => true,
298            (FieldType::Timezone(_), TokenType::Timezone(_, _)) => true,
299            (FieldType::Number(_), TokenType::Number(_, _)) => true,
300            (FieldType::Text(_, expected), TokenType::Text(text)) => expected
301                .as_ref()
302                .map_or(true, |v| v.to_lowercase() == text.to_lowercase()),
303            (FieldType::Time(_), TokenType::Time(_, _)) => true,
304            (FieldType::DateTime(_), TokenType::DateTime(_, _)) => true,
305            (FieldType::Date(_), TokenType::Date(_, _)) => true,
306            (FieldType::Money(_), TokenType::Money(_, _)) => true,
307            (FieldType::Month(_), TokenType::Month(_)) => true,
308            (FieldType::Duration(_), TokenType::Duration(_)) => true,
309            (FieldType::Group(_, items), TokenType::Text(text)) => items
310                .iter()
311                .any(|item| item.to_lowercase() == text.to_lowercase()),
312            (FieldType::TypeGroup(types, _), right_ast) => types.contains(&right_ast.type_name()),
313            (_, _) => false,
314        }
315    }
316
317    pub fn variable_compare(left: &TokenInfo, right: Rc<SmartCalcAstType>) -> bool {
318        match &left.token_type.borrow().deref() {
319            Some(token) => match (&token, right.deref()) {
320                (TokenType::Text(l_value), SmartCalcAstType::Symbol(r_value)) => {
321                    l_value.deref().to_lowercase() == r_value.to_lowercase()
322                }
323                (TokenType::Timezone(l_value, l_type), SmartCalcAstType::Item(r_value)) => {
324                    r_value.is_same(&(l_value.clone(), *l_type))
325                }
326                (TokenType::Number(l_value, _), SmartCalcAstType::Item(r_value)) => {
327                    r_value.is_same(l_value)
328                }
329                (TokenType::Percent(l_value), SmartCalcAstType::Item(r_value)) => {
330                    r_value.is_same(l_value)
331                }
332                (TokenType::Duration(l_value), SmartCalcAstType::Item(r_value)) => {
333                    r_value.is_same(l_value)
334                }
335                (TokenType::Time(l_value, l_tz), SmartCalcAstType::Item(r_value)) => {
336                    r_value.is_same(&(*l_value, l_tz.clone()))
337                }
338                (TokenType::Money(l_value, l_symbol), SmartCalcAstType::Item(r_value)) => {
339                    r_value.is_same(&(*l_value, l_symbol.clone()))
340                }
341                (TokenType::Date(l_value, l_tz), SmartCalcAstType::Item(r_value)) => {
342                    r_value.is_same(&(*l_value, l_tz.clone()))
343                }
344                (TokenType::Field(l_value), _) => right.field_compare(l_value.deref()),
345                (_, _) => false,
346            },
347            _ => false,
348        }
349    }
350
351    pub fn get_field_name(token: &TokenInfo) -> Option<String> {
352        match &token.token_type.borrow().deref() {
353            Some(TokenType::Field(field)) => match field.deref() {
354                FieldType::Text(field_name, _) => Some(field_name.to_string()),
355                FieldType::DateTime(field_name) => Some(field_name.to_string()),
356                FieldType::Date(field_name) => Some(field_name.to_string()),
357                FieldType::Time(field_name) => Some(field_name.to_string()),
358                FieldType::Money(field_name) => Some(field_name.to_string()),
359                FieldType::Percent(field_name) => Some(field_name.to_string()),
360                FieldType::Number(field_name) => Some(field_name.to_string()),
361                FieldType::Month(field_name) => Some(field_name.to_string()),
362                FieldType::Duration(field_name) => Some(field_name.to_string()),
363                FieldType::Group(field_name, _) => Some(field_name.to_string()),
364                FieldType::TypeGroup(_, field_name) => Some(field_name.to_string()),
365                FieldType::Timezone(field_name) => Some(field_name.to_string()),
366                FieldType::DynamicType(field_name, _) => Some(field_name.to_string()),
367            },
368            _ => None,
369        }
370    }
371}
372
373pub fn find_location<T: PartialEq<U>, U>(tokens: &[Rc<T>], rule_tokens: &[Rc<U>]) -> Option<usize> {
374    let total_rule_token = rule_tokens.len();
375    let mut rule_token_index = 0;
376    let mut target_token_index = 0;
377    let mut start_token_index = 0;
378
379    while let Some(token) = tokens.get(target_token_index) {
380        if token.deref() == rule_tokens[rule_token_index].deref() {
381            rule_token_index += 1;
382            target_token_index += 1;
383        } else {
384            rule_token_index = 0;
385            target_token_index += 1;
386            start_token_index = target_token_index;
387        }
388
389        if total_rule_token == rule_token_index {
390            break;
391        }
392    }
393
394    if total_rule_token == rule_token_index {
395        return Some(start_token_index);
396    }
397    None
398}
399
400impl core::cmp::PartialEq<TokenType> for TokenInfo {
401    fn eq(&self, other: &TokenType) -> bool {
402        if self.token_type.borrow().deref().is_none() {
403            return false;
404        }
405
406        match &self.token_type.borrow().deref() {
407            Some(l_token) => match (&l_token, &other) {
408                (TokenType::Text(l_value), TokenType::Text(r_value)) => {
409                    l_value.to_lowercase() == r_value.to_lowercase()
410                }
411                (TokenType::Number(l_value, _), TokenType::Number(r_value, _)) => {
412                    l_value == r_value
413                }
414                (TokenType::Percent(l_value), TokenType::Percent(r_value)) => l_value == r_value,
415                (TokenType::Operator(l_value), TokenType::Operator(r_value)) => l_value == r_value,
416                (TokenType::Date(l_value, l_tz), TokenType::Date(r_value, r_tz)) => {
417                    l_value == r_value && l_tz == r_tz
418                }
419                (TokenType::Duration(l_value), TokenType::Duration(r_value)) => l_value == r_value,
420                (TokenType::Month(l_value), TokenType::Month(r_value)) => l_value == r_value,
421                (TokenType::Money(l_value, l_symbol), TokenType::Money(r_value, r_symbol)) => {
422                    l_value == r_value && l_symbol == r_symbol
423                }
424                (
425                    TokenType::Timezone(l_value, l_symbol),
426                    TokenType::Timezone(r_value, r_symbol),
427                ) => l_value == r_value && l_symbol == r_symbol,
428                (TokenType::Variable(l_value), TokenType::Variable(r_value)) => l_value == r_value,
429                (TokenType::Field(l_value), _) => other.field_compare(l_value.deref()),
430                (_, TokenType::Field(r_value)) => l_token.field_compare(r_value.deref()),
431                (_, _) => false,
432            },
433            _ => false,
434        }
435    }
436}
437
438impl PartialEq for TokenInfo {
439    fn eq(&self, other: &Self) -> bool {
440        if self.token_type.borrow().deref().is_none() || other.token_type.borrow().deref().is_none()
441        {
442            return false;
443        }
444
445        if self.status.get() == TokenInfoStatus::Removed
446            || other.status.get() == TokenInfoStatus::Removed
447        {
448            return false;
449        }
450
451        match (
452            &self.token_type.borrow().deref(),
453            &other.token_type.borrow().deref(),
454        ) {
455            (Some(l_token), Some(r_token)) => match (&l_token, &r_token) {
456                (TokenType::Text(l_value), TokenType::Text(r_value)) => {
457                    l_value.to_lowercase() == r_value.to_lowercase()
458                }
459                (TokenType::Number(l_value, _), TokenType::Number(r_value, _)) => {
460                    l_value == r_value
461                }
462                (TokenType::Percent(l_value), TokenType::Percent(r_value)) => l_value == r_value,
463                (TokenType::Operator(l_value), TokenType::Operator(r_value)) => l_value == r_value,
464                (TokenType::Date(l_value, l_tz), TokenType::Date(r_value, r_tz)) => {
465                    l_value == r_value && l_tz == r_tz
466                }
467                (TokenType::Duration(l_value), TokenType::Duration(r_value)) => l_value == r_value,
468                (TokenType::Money(l_value, l_symbol), TokenType::Money(r_value, r_symbol)) => {
469                    l_value == r_value && l_symbol == r_symbol
470                }
471                (
472                    TokenType::Timezone(l_value, l_symbol),
473                    TokenType::Timezone(r_value, r_symbol),
474                ) => l_value == r_value && l_symbol == r_symbol,
475                (TokenType::Variable(l_value), TokenType::Variable(r_value)) => l_value == r_value,
476                (TokenType::Field(l_value), _) => r_token.field_compare(l_value.deref()),
477                (_, TokenType::Field(r_value)) => l_token.field_compare(r_value.deref()),
478                (_, _) => false,
479            },
480            (_, _) => false,
481        }
482    }
483}
484
485pub trait CharTraits {
486    fn is_new_line(&self) -> bool;
487    fn is_whitespace(&self) -> bool;
488}
489
490impl CharTraits for char {
491    fn is_new_line(&self) -> bool {
492        *self == '\n'
493    }
494
495    fn is_whitespace(&self) -> bool {
496        matches!(*self, ' ' | '\r' | '\t')
497    }
498}
499
500#[repr(C)]
501#[derive(Clone, Debug)]
502pub enum SmartCalcAstType {
503    None,
504    Field(Rc<FieldType>),
505    Item(Rc<dyn DataItem>),
506    Month(u32),
507    Binary {
508        left: Rc<SmartCalcAstType>,
509        operator: char,
510        right: Rc<SmartCalcAstType>,
511    },
512    PrefixUnary(char, Rc<SmartCalcAstType>),
513    Assignment {
514        variable: Rc<VariableInfo>,
515        expression: Rc<SmartCalcAstType>,
516    },
517    Symbol(String),
518    Variable(Rc<VariableInfo>),
519}
520
521impl SmartCalcAstType {
522    pub fn type_name(&self) -> String {
523        match self {
524            SmartCalcAstType::None => "NONE".to_string(),
525            SmartCalcAstType::Item(item) => item.type_name().to_string(),
526            SmartCalcAstType::Field(field) => field.type_name(),
527            SmartCalcAstType::Month(_) => "MONTH".to_string(),
528            SmartCalcAstType::Binary {
529                left: _,
530                operator: _,
531                right: _,
532            } => "BINARY".to_string(),
533            SmartCalcAstType::PrefixUnary(_, ast) => ast.type_name(),
534            SmartCalcAstType::Assignment {
535                variable: _,
536                expression: _,
537            } => "ASSIGNMENT".to_string(),
538            SmartCalcAstType::Symbol(_) => "SYMBOL".to_string(),
539            SmartCalcAstType::Variable(variable) => variable.data.borrow().type_name(),
540        }
541    }
542
543    pub fn field_compare(&self, field: &FieldType) -> bool {
544        match (field, self) {
545            (FieldType::DynamicType(_, expected), SmartCalcAstType::Item(item)) => {
546                item.type_name() == "DYNAMIC_TYPE"
547                    && expected.as_ref().map_or(true, |v| {
548                        v.to_lowercase()
549                            == match item.as_any().downcast_ref::<DynamicTypeItem>() {
550                                Some(item) => item.get_type().group_name.to_lowercase(),
551                                None => return false,
552                            }
553                    })
554            }
555            (FieldType::Percent(_), SmartCalcAstType::Item(item)) => item.type_name() == "PERCENT",
556            (FieldType::Number(_), SmartCalcAstType::Item(item)) => item.type_name() == "NUMBER",
557            (FieldType::Text(_, expected), SmartCalcAstType::Symbol(symbol)) => expected
558                .as_ref()
559                .map_or(true, |v| v.to_lowercase() == symbol.to_lowercase()),
560            (FieldType::Time(_), SmartCalcAstType::Item(item)) => item.type_name() == "TIME",
561            (FieldType::Money(_), SmartCalcAstType::Item(item)) => item.type_name() == "MONEY",
562            (FieldType::Month(_), SmartCalcAstType::Month(_)) => true,
563            (FieldType::Duration(_), SmartCalcAstType::Item(item)) => {
564                item.type_name() == "DURATION"
565            }
566            (FieldType::Timezone(_), SmartCalcAstType::Item(item)) => {
567                item.type_name() == "TIMEZONE"
568            }
569            (FieldType::DateTime(_), SmartCalcAstType::Item(item)) => {
570                item.type_name() == "DATE_TIME"
571            }
572            (FieldType::Date(_), SmartCalcAstType::Item(item)) => item.type_name() == "DATE",
573            (FieldType::TypeGroup(types, _), right_ast) => types.contains(&right_ast.type_name()),
574            (_, _) => false,
575        }
576    }
577}