1use 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}