1use crate::tokinizer::{read_currency, small_date, RuleType};
8use crate::{Session, TimeOffset};
9use alloc::collections::BTreeMap;
10use alloc::rc::Rc;
11use alloc::string::{String, ToString};
12use alloc::vec;
13use alloc::vec::Vec;
14use anyhow::anyhow;
15use core::borrow::Borrow;
16use core::ops::Deref;
17
18use crate::compiler::Interpreter;
19use crate::config::{DynamicType, SmartCalcConfig};
20use crate::formatter::format_result;
21use crate::logger::{initialize_logger, LOGGER};
22use crate::syntax::SyntaxParser;
23use crate::token::ui_token::UiToken;
24use crate::tokinizer::TokenInfo;
25use crate::tokinizer::Tokinizer;
26use crate::tools::parse_timezone;
27use crate::types::SmartCalcAstType;
28use crate::types::{ExpressionFunc, TokenType};
29
30pub type ExecutionLine = Option<ExecuteLine>;
31
32pub trait RuleTrait {
33 fn name(&self) -> String;
34 fn call(
35 &self,
36 smartcalc: &SmartCalcConfig,
37 fields: &BTreeMap<String, TokenType>,
38 ) -> Option<TokenType>;
39}
40
41#[derive(Debug, Default)]
42pub struct ExecuteResult {
43 pub status: bool,
44 pub lines: Vec<ExecutionLine>,
45}
46
47#[derive(Debug, Clone)]
48pub struct ExecuteLineResult {
49 pub output: String,
50 pub ast: Rc<SmartCalcAstType>,
51}
52
53impl ExecuteLineResult {
54 pub fn new(output: String, ast: Rc<SmartCalcAstType>) -> Self {
55 ExecuteLineResult { output, ast }
56 }
57}
58
59#[derive(Debug)]
60pub struct ExecuteLine {
61 pub result: Result<ExecuteLineResult, String>,
62 pub raw_tokens: Vec<Rc<TokenType>>,
63 pub ui_tokens: Vec<UiToken>,
64 pub calculated_tokens: Vec<Rc<TokenInfo>>,
65}
66
67impl ExecuteLine {
68 pub fn new(
69 result: Result<ExecuteLineResult, String>,
70 ui_tokens: Vec<UiToken>,
71 raw_tokens: Vec<Rc<TokenType>>,
72 calculated_tokens: Vec<Rc<TokenInfo>>,
73 ) -> Self {
74 ExecuteLine {
75 result,
76 ui_tokens,
77 raw_tokens,
78 calculated_tokens,
79 }
80 }
81}
82
83pub struct SmartCalc {
84 config: SmartCalcConfig,
85}
86
87unsafe impl Send for SmartCalc {}
88
89impl Default for SmartCalc {
90 fn default() -> Self {
91 initialize_logger();
92 let mut smartcalc = SmartCalc {
93 config: SmartCalcConfig::default(),
94 };
95 smartcalc.set_date_rule(
96 "en",
97 vec![
98 "{MONTH:month} {NUMBER:day}, {NUMBER:year}".to_string(),
99 "{MONTH:month} {NUMBER:day} {NUMBER:year}".to_string(),
100 "{NUMBER:day}/{NUMBER:month}/{NUMBER:year}".to_string(),
101 "{NUMBER:day} {MONTH:month} {NUMBER:year}".to_string(),
102 "{NUMBER:day} {MONTH:month}".to_string(),
103 ],
104 );
105 smartcalc.set_date_rule(
106 "tr",
107 vec![
108 "{NUMBER:day}/{NUMBER:month}/{NUMBER:year}".to_string(),
109 "{NUMBER:day} {MONTH:month} {NUMBER:year}".to_string(),
110 "{NUMBER:day} {MONTH:month}".to_string(),
111 ],
112 );
113 smartcalc
114 }
115}
116
117impl SmartCalc {
118 pub fn add_dynamic_type<T: Borrow<str>>(&mut self, name: T) -> bool {
119 match self.config.types.get(name.borrow()) {
120 Some(_) => false,
121 None => {
122 self.config
123 .types
124 .insert(name.borrow().to_string(), BTreeMap::new());
125 true
126 }
127 }
128 }
129
130 pub fn add_dynamic_type_item<T: Borrow<str>>(
131 &mut self,
132 name: T,
133 index: usize,
134 format: T,
135 parse: Vec<T>,
136 upgrade_code: T,
137 downgrade_code: T,
138 names: Vec<String>,
139 decimal_digits: Option<u8>,
140 use_fract_rounding: Option<bool>,
141 remove_fract_if_zero: Option<bool>,
142 ) -> bool {
143 match self.config.types.get(name.borrow()) {
144 Some(dynamic_type) => {
145 if dynamic_type.contains_key(&index) {
146 return false;
147 }
148 }
149 None => return false,
150 };
151
152 let mut parse_tokens = Vec::new();
153 for type_parse_item in parse.iter() {
154 let mut session = Session::new();
155 session.set_language("en".to_string());
156 session.set_text(type_parse_item.borrow().to_string());
157
158 let tokens = Tokinizer::token_infos(&self.config, &session);
159 parse_tokens.push(tokens);
160 }
161
162 if let Some(dynamic_type) = self.config.types.get_mut(name.borrow()) {
163 dynamic_type.insert(
164 index,
165 Rc::new(DynamicType::new(
166 name.borrow().to_string(),
167 index,
168 format.borrow().to_string(),
169 parse_tokens,
170 upgrade_code.borrow().to_string(),
171 downgrade_code.borrow().to_string(),
172 names,
173 decimal_digits,
174 use_fract_rounding,
175 remove_fract_if_zero,
176 )),
177 );
178 }
179 true
180 }
181
182 pub fn set_money_configuration(
183 &mut self,
184 remove_fract_if_zero: bool,
185 use_fract_rounding: bool,
186 ) {
187 self.config.money_config.remove_fract_if_zero = remove_fract_if_zero;
188 self.config.money_config.use_fract_rounding = use_fract_rounding;
189 }
190
191 pub fn set_number_configuration(
192 &mut self,
193 decimal_digits: u8,
194 remove_fract_if_zero: bool,
195 use_fract_rounding: bool,
196 ) {
197 self.config.number_config.decimal_digits = decimal_digits;
198 self.config.number_config.remove_fract_if_zero = remove_fract_if_zero;
199 self.config.number_config.use_fract_rounding = use_fract_rounding;
200 }
201
202 pub fn set_percentage_configuration(
203 &mut self,
204 decimal_digits: u8,
205 remove_fract_if_zero: bool,
206 use_fract_rounding: bool,
207 ) {
208 self.config.percentage_config.decimal_digits = decimal_digits;
209 self.config.percentage_config.remove_fract_if_zero = remove_fract_if_zero;
210 self.config.percentage_config.use_fract_rounding = use_fract_rounding;
211 }
212
213 pub fn set_decimal_seperator(&mut self, decimal_seperator: String) {
214 self.config.decimal_seperator = decimal_seperator;
215 }
216
217 pub fn set_thousand_separator(&mut self, thousand_separator: String) {
218 self.config.thousand_separator = thousand_separator;
219 }
220
221 pub fn set_date_rule(&mut self, language: &str, rules: Vec<String>) {
222 let mut function_items = Vec::new();
223 for rule_item in rules {
224 let mut session = Session::new();
225 session.set_language(language.to_string());
226 session.set_text(rule_item.to_string());
227 function_items.push(Tokinizer::token_infos(&self.config, &session));
228 }
229
230 let current_rules = match self.config.rule.get_mut(language) {
231 Some(current_rules) => current_rules,
232 None => return,
233 };
234
235 current_rules.retain(|rule| {
237 let is_small_date = if let RuleType::Internal { function_name, .. } = rule {
238 function_name == "small_date"
239 } else {
240 false
241 };
242
243 !is_small_date
244 });
245
246 current_rules.push(RuleType::Internal {
247 function_name: "small_date".to_string(),
248 function: small_date as ExpressionFunc,
249 tokens_list: function_items,
250 });
251 }
252
253 pub fn set_timezone(&mut self, timezone: String) -> Result<(), String> {
254 let timezone = match self.config.token_parse_regex.get("timezone") {
255 Some(regexes) => {
256 let capture = match regexes[0].captures(&timezone) {
257 Some(capture) => capture,
258 None => return Err("Timezone information not found".to_string()),
259 };
260 match capture.name("timezone") {
261 Some(_) => parse_timezone(&self.config, &capture),
262 None => None,
263 }
264 }
265 _ => None,
266 };
267
268 match timezone {
269 Some((timezone, offset)) => {
270 self.config.timezone = timezone.to_uppercase();
271 self.config.timezone_offset = offset;
272 Ok(())
273 }
274 None => Err("Timezone information not found".to_string()),
275 }
276 }
277
278 pub fn get_time_offset(&self) -> TimeOffset {
279 self.config.get_time_offset()
280 }
281
282 pub fn load_from_json(json_data: &str) -> Self {
283 SmartCalc {
284 config: SmartCalcConfig::load_from_json(json_data),
285 }
286 }
287
288 pub fn update_currency(&mut self, currency: &str, rate: f64) -> bool {
289 match read_currency(&self.config, currency) {
290 Some(real_currency) => {
291 self.config.currency_rate.insert(real_currency, rate);
292 true
293 }
294 _ => false,
295 }
296 }
297
298 pub fn delete_rule(&mut self, language: String, rule_name: String) -> bool {
299 match self.config.rule.get_mut(&language) {
300 Some(language_collection) => {
301 let position = language_collection.iter().position(|item| match item {
302 RuleType::API {
303 tokens_list: _,
304 rule: rule_item,
305 } => rule_name == rule_item.name(),
306 _ => false,
307 });
308
309 match position {
310 Some(location) => {
311 language_collection.remove(location);
312 true
313 }
314 _ => false,
315 }
316 }
317 None => false,
318 }
319 }
320
321 pub fn add_rule(
322 &mut self,
323 language: String,
324 rules: Vec<String>,
325 rule: Rc<dyn RuleTrait>,
326 ) -> bool {
327 let mut rule_tokens = Vec::new();
328
329 for rule_item in rules.iter() {
330 let mut session = Session::new();
331 session.set_language(language.to_string());
332 session.set_text(rule_item.to_string());
333 let tokens = Tokinizer::token_infos(&self.config, &session);
334 rule_tokens.push(tokens);
335 }
336
337 let language_data = match self.config.rule.get_mut(&language) {
338 Some(language) => language,
339 None => return false,
340 };
341
342 language_data.push(RuleType::API {
343 tokens_list: rule_tokens,
344 rule,
345 });
346 true
347 }
348
349 pub fn format_result(&self, session: &Session, result: Rc<SmartCalcAstType>) -> String {
350 format_result(&self.config, session, result)
351 }
352
353 pub fn initialize() {
354 if log::set_logger(&LOGGER).is_ok() {
355 if cfg!(debug_assertions) {
356 log::set_max_level(log::LevelFilter::Debug)
357 } else {
358 log::set_max_level(log::LevelFilter::Info)
359 }
360 }
361 }
362
363 pub(crate) fn execute_text(&self, session: &Session) -> ExecutionLine {
364 log::debug!("> {}", session.current_line());
365 if session.current_line().is_empty() {
366 return None;
367 }
368
369 let mut tokinizer = Tokinizer::new(&self.config, session);
370 if !tokinizer.tokinize() {
371 return None;
372 }
373
374 let mut syntax = SyntaxParser::new(session, &tokinizer);
375 log::debug!(" > parse starting");
376
377 let execution_result = match syntax.parse() {
378 Ok(ast) => {
379 log::debug!(" > parse Ok {:?}", ast);
380 let ast_rc = Rc::new(ast);
381
382 match Interpreter::execute(&self.config, ast_rc, session) {
383 Ok(ast) => Ok(ExecuteLineResult::new(
384 self.format_result(session, ast.clone()),
385 ast,
386 )),
387 Err(error) => Err(error),
388 }
389 }
390 Err((error, _, _)) => {
391 log::debug!(" > parse Err");
392 log::info!("Syntax parse error, {}", error);
393 Err(error.to_string())
394 }
395 };
396
397 Some(ExecuteLine::new(
398 execution_result,
399 tokinizer.ui_tokens.get_tokens(),
400 tokinizer.tokens,
401 tokinizer.token_infos.clone(),
402 ))
403 }
404
405 pub fn execute<Tlan: Borrow<str>, Tdata: Borrow<str>>(
406 &self,
407 language: Tlan,
408 data: Tdata,
409 ) -> ExecuteResult {
410 let mut session = Session::new();
411
412 session.set_text(data.borrow().to_string());
413 session.set_language(language.borrow().to_string());
414 self.execute_session(&session)
415 }
416
417 pub fn basic_execute<T: Borrow<str>>(data: T, config: &SmartCalcConfig) -> anyhow::Result<f64> {
418 let mut session = Session::new();
419
420 session.set_text(data.borrow().to_string());
421 session.set_language("en".borrow().to_string());
422
423 if session.line_count() != 1 {
424 return Err(anyhow!("Multiline calculation not supported"));
425 }
426
427 if !session.has_value() {
428 return Err(anyhow!("No data found"));
429 }
430
431 log::debug!("> {}", session.current_line());
432 if session.current_line().is_empty() {
433 return Err(anyhow!("Calculation empty"));
434 }
435
436 let mut tokinizer = Tokinizer::new(config, &session);
437 if !tokinizer.basic_tokinize() {
438 return Err(anyhow!("Syntax error"));
439 }
440
441 let mut syntax = SyntaxParser::new(&session, &tokinizer);
442 log::debug!(" > parse starting");
443
444 match syntax.parse() {
445 Ok(ast) => {
446 log::debug!(" > parse Ok {:?}", ast);
447 let ast_rc = Rc::new(ast);
448
449 match Interpreter::execute(config, ast_rc, &session) {
450 Ok(ast) => match ast.deref() {
451 SmartCalcAstType::Item(item) => Ok(item.get_underlying_number()),
452 _ => Err(anyhow!("Number not found")),
453 },
454 Err(error) => Err(anyhow!(error)),
455 }
456 }
457 Err((error, _, _)) => {
458 log::debug!(" > parse Err");
459 log::info!("Syntax parse error, {}", error);
460 Err(anyhow!(error))
461 }
462 }
463 }
464
465 pub fn execute_session(&self, session: &Session) -> ExecuteResult {
466 let mut results = ExecuteResult::default();
467
468 if session.has_value() {
469 results.status = true;
470 loop {
471 let line_result = self.execute_text(session);
472 results.lines.push(line_result);
473 if session.next_line().is_none() {
474 break;
475 }
476 }
477 }
478
479 results
480 }
481}
482
483#[cfg(test)]
484mod test {
485 use alloc::{
486 collections::BTreeMap,
487 rc::Rc,
488 string::{String, ToString},
489 vec,
490 };
491 use core::ops::Deref;
492
493 use crate::{
494 types::{NumberType, TokenType},
495 RuleTrait, SmartCalc, SmartCalcConfig,
496 };
497
498 #[derive(Default)]
499 pub struct Test1;
500 impl RuleTrait for Test1 {
501 fn name(&self) -> String {
502 "test1".to_string()
503 }
504 fn call(
505 &self,
506 _: &SmartCalcConfig,
507 fields: &BTreeMap<String, TokenType>,
508 ) -> Option<TokenType> {
509 match fields.get("surname") {
510 Some(TokenType::Text(surname)) => {
511 assert_eq!(surname, &"baris".to_string());
512 Some(TokenType::Number(2022.0, NumberType::Decimal))
513 }
514 _ => None,
515 }
516 }
517 }
518
519 macro_rules! check_basic_rule_output {
520 ($result:ident, $expected:expr) => {
521 assert!($result.status);
522 assert_eq!($result.lines.len(), 1);
523 assert_eq!($result.lines[0].is_some(), true);
524 match $result.lines.get(0) {
525 Some(line) => match line {
526 Some(item) => match item.calculated_tokens.get(0) {
527 Some(calculated_token) => assert_eq!(
528 calculated_token
529 .token_type
530 .borrow()
531 .deref()
532 .as_ref()
533 .unwrap(),
534 &$expected
535 ),
536 None => assert!(false, "Calculated token not found"),
537 },
538 None => assert!(false, "Result line does not have value"),
539 },
540 None => assert!(false, "Result line not found"),
541 };
542 };
543 }
544
545 macro_rules! check_dynamic_type_output {
546 ($result:ident, $type_name:literal, $expected:literal) => {
547 assert!($result.status);
548 assert_eq!($result.lines.len(), 1);
549 assert_eq!($result.lines[0].is_some(), true);
550
551 match $result.lines.get(0) {
552 Some(line) => match line {
553 Some(item) => match item.calculated_tokens.get(0) {
554 Some(calculated_token) => {
555 match calculated_token.token_type.borrow().deref() {
556 Some(TokenType::DynamicType(number, item)) => {
557 assert_eq!(*number, $expected);
558 assert_eq!(item.deref().group_name, $type_name.to_string());
559 }
560 _ => assert!(
561 false,
562 "Expected token wrong. Token: {:?}",
563 calculated_token.token_type.borrow().deref()
564 ),
565 }
566 }
567 None => assert!(false, "Calculated token not found"),
568 },
569 None => assert!(false, "Result line does not have value"),
570 },
571 None => assert!(false, "Result line not found"),
572 };
573 };
574 }
575
576 #[test]
577 fn add_rule_1() -> Result<(), ()> {
578 let mut calculater = SmartCalc::default();
579 let test1 = Rc::new(Test1::default());
580 calculater.add_rule(
581 "en".to_string(),
582 vec![
583 "erhan {TEXT:surname}".to_string(),
584 "{TEXT:surname} erhan".to_string(),
585 ],
586 test1.clone(),
587 );
588 let result = calculater.execute("en".to_string(), "erhan baris");
589 check_basic_rule_output!(result, TokenType::Number(2022.0, NumberType::Decimal));
590
591 let result = calculater.execute("en".to_string(), "baris erhan");
592 check_basic_rule_output!(result, TokenType::Number(2022.0, NumberType::Decimal));
593
594 Ok(())
595 }
596
597 #[test]
598 fn add_rule_2() -> Result<(), ()> {
599 let mut calculater = SmartCalc::default();
600 let test1 = Rc::new(Test1::default());
601 calculater.add_rule(
602 "en".to_string(),
603 vec![
604 "erhan {TEXT:surname:baris}".to_string(),
605 "{TEXT:surname:baris} erhan".to_string(),
606 ],
607 test1.clone(),
608 );
609 let result = calculater.execute("en".to_string(), "erhan baris");
610 check_basic_rule_output!(result, TokenType::Number(2022.0, NumberType::Decimal));
611
612 let result = calculater.execute("en".to_string(), "baris erhan");
613 check_basic_rule_output!(result, TokenType::Number(2022.0, NumberType::Decimal));
614
615 Ok(())
616 }
617
618 #[test]
619 fn delete_rule_2() -> Result<(), ()> {
620 let mut calculater = SmartCalc::default();
621 let test1 = Rc::new(Test1::default());
622 assert!(calculater.add_rule(
623 "en".to_string(),
624 vec![
625 "erhan {TEXT:surname:baris}".to_string(),
626 "{TEXT:surname:baris} erhan".to_string()
627 ],
628 test1.clone()
629 ));
630 assert!(calculater.delete_rule("en".to_string(), test1.name().clone()));
631
632 Ok(())
633 }
634
635 #[test]
636 fn delete_rule_3() -> Result<(), ()> {
637 let mut calculater = SmartCalc::default();
638 let test1 = Rc::new(Test1::default());
639 assert!(!calculater.delete_rule("en".to_string(), test1.name().clone()));
640
641 Ok(())
642 }
643
644 #[test]
645 fn dynamic_type_1() -> Result<(), ()> {
646 let mut calculater = SmartCalc::default();
647 assert!(calculater.add_dynamic_type("test1"));
648 assert!(calculater.add_dynamic_type_item(
649 "test1",
650 1,
651 "{value} a",
652 vec!["{NUMBER:value} {TEXT:type:a}"],
653 "{value} / 2",
654 "{value} 2",
655 vec!["a".to_string()],
656 None,
657 None,
658 None
659 ));
660 assert!(calculater.add_dynamic_type_item(
661 "test1",
662 2,
663 "{value} b",
664 vec!["{NUMBER:value} {TEXT:type:b}"],
665 "{value} / 2",
666 "{value} * 2",
667 vec!["b".to_string()],
668 None,
669 None,
670 None
671 ));
672 assert!(calculater.add_dynamic_type_item(
673 "test1",
674 3,
675 "{value} c",
676 vec!["{NUMBER:value} {TEXT:type:c}"],
677 "{value} / 2",
678 "{value} * 2",
679 vec!["c".to_string()],
680 None,
681 None,
682 None
683 ));
684 assert!(calculater.add_dynamic_type_item(
685 "test1",
686 4,
687 "{value} d",
688 vec!["{NUMBER:value} {TEXT:type:d}"],
689 "{value}",
690 "{value} * 2",
691 vec!["d".to_string()],
692 None,
693 None,
694 None
695 ));
696
697 let result = calculater.execute("en".to_string(), "10 a to b");
698 check_dynamic_type_output!(result, "test1", 5.0);
699
700 let result = calculater.execute("en".to_string(), "1011 b to a");
701 check_dynamic_type_output!(result, "test1", 2022.0);
702
703 let result = calculater.execute("en".to_string(), "1 d to a");
704 check_dynamic_type_output!(result, "test1", 8.0);
705
706 let result = calculater.execute("en".to_string(), "8 a to d");
707 check_dynamic_type_output!(result, "test1", 1.0);
708 Ok(())
709 }
710
711 #[test]
712 fn dynamic_type_2() -> Result<(), ()> {
713 let mut calculater = SmartCalc::default();
714 assert!(calculater.add_dynamic_type("test1"));
715 assert!(!calculater.add_dynamic_type("test1"));
716
717 assert!(calculater.add_dynamic_type_item(
718 "test1",
719 1,
720 "{value} a",
721 vec!["{NUMBER:value} {TEXT:type:a}"],
722 "{value} / 2",
723 "{value} 2",
724 vec!["a".to_string()],
725 None,
726 None,
727 None
728 ));
729 assert!(!calculater.add_dynamic_type_item(
730 "test1",
731 1,
732 "{value} a",
733 vec!["{NUMBER:value} {TEXT:type:a}"],
734 "{value} / 2",
735 "{value} 2",
736 vec!["a".to_string()],
737 None,
738 None,
739 None
740 ));
741 Ok(())
742 }
743
744 #[test]
745 fn basic_test_1() -> anyhow::Result<()> {
746 let config = SmartCalcConfig::default();
747 let result = SmartCalc::basic_execute("1024", &config)?;
748 assert_eq!(result, 1024.0);
749 Ok(())
750 }
751
752 #[test]
753 fn basic_test_2() -> anyhow::Result<()> {
754 let config = SmartCalcConfig::default();
755 let result = SmartCalc::basic_execute("1024 * 2", &config)?;
756 assert_eq!(result, 2048.0);
757 Ok(())
758 }
759
760 #[test]
761 fn basic_test_3() -> anyhow::Result<()> {
762 let config = SmartCalcConfig::default();
763 let error = match SmartCalc::basic_execute("a + 1024 * 2", &config) {
764 Ok(_) => return Ok(()),
765 Err(error) => error,
766 };
767 assert_eq!(error.to_string(), "Number not found".to_string());
768 Ok(())
769 }
770
771 #[test]
772 fn basic_test_4() -> anyhow::Result<()> {
773 let config = SmartCalcConfig::default();
774 let error = match SmartCalc::basic_execute("+ 1024 * 2", &config) {
775 Ok(_) => return Ok(()),
776 Err(error) => error,
777 };
778 assert_eq!(error.to_string(), "No more token".to_string());
779 Ok(())
780 }
781
782 #[test]
783 fn basic_test_5() -> anyhow::Result<()> {
784 let config = SmartCalcConfig::default();
785 let error = match SmartCalc::basic_execute(
786 r#"1+ 1024 * 2
787"#,
788 &config,
789 ) {
790 Ok(_) => return Ok(()),
791 Err(error) => error,
792 };
793 assert_eq!(
794 error.to_string(),
795 "Multiline calculation not supported".to_string()
796 );
797 Ok(())
798 }
799
800 #[test]
801 fn basic_test_6() -> anyhow::Result<()> {
802 let config = SmartCalcConfig::default();
803 let error = match SmartCalc::basic_execute(r#""#, &config) {
804 Ok(_) => return Ok(()),
805 Err(error) => error,
806 };
807 assert_eq!(error.to_string(), "Calculation empty".to_string());
808 Ok(())
809 }
810
811 #[derive(Default)]
812 pub struct Coin;
813
814 impl RuleTrait for Coin {
815 fn name(&self) -> String {
816 "Coin".to_string()
817 }
818
819 fn call(
820 &self,
821 smartcalc: &SmartCalcConfig,
822 fields: &BTreeMap<String, TokenType>,
823 ) -> Option<TokenType> {
824 let count = match fields.get("count") {
825 Some(TokenType::Number(number, NumberType::Decimal)) => *number,
826 _ => return None,
827 };
828 let coin = match fields.get("coin") {
829 Some(TokenType::Text(text)) => text.clone(),
830 _ => return None,
831 };
832
833 let price = match &coin[..] {
834 "btc" => 1000.0 * count,
835 "eth" => 800.0 * count,
836 _ => return None,
837 };
838
839 return Some(TokenType::Money(
840 price,
841 smartcalc.get_currency("usd".to_string())?,
842 ));
843 }
844 }
845
846 #[test]
847 fn add_rule_3() -> Result<(), ()> {
848 let mut calculater = SmartCalc::default();
849 let test1 = Rc::new(Coin::default());
850 calculater.add_rule(
851 "en".to_string(),
852 vec!["{NUMBER:count} {TEXT:coin}".to_string()],
853 test1.clone(),
854 );
855 let result = calculater.execute("en".to_string(), "10 btc to usd");
856 check_basic_rule_output!(
857 result,
858 TokenType::Money(
859 10000.0,
860 calculater.config.get_currency("usd".to_string()).unwrap()
861 )
862 );
863 Ok(())
864 }
865
866 #[test]
867 fn add_rule_4() -> Result<(), ()> {
868 let mut calculater = SmartCalc::default();
869 let test1 = Rc::new(Coin::default());
870 calculater.add_rule(
871 "en".to_string(),
872 vec!["{NUMBER:count} {TEXT:coin}".to_string()],
873 test1.clone(),
874 );
875 let result = calculater.execute("en".to_string(), "10 eth to usd");
876 check_basic_rule_output!(
877 result,
878 TokenType::Money(
879 8000.0,
880 calculater.config.get_currency("usd".to_string()).unwrap()
881 )
882 );
883 Ok(())
884 }
885
886 #[test]
887 fn add_rule_5() -> Result<(), ()> {
888 let mut calculater = SmartCalc::default();
889 let test1 = Rc::new(Coin::default());
890 calculater.add_rule(
891 "en".to_string(),
892 vec!["{NUMBER:count} {TEXT:coin}".to_string()],
893 test1.clone(),
894 );
895 let result = calculater.execute("en".to_string(), "10 eth to dkk");
896 check_basic_rule_output!(
897 result,
898 TokenType::Money(
899 49644.9970792,
900 calculater.config.get_currency("dkk".to_string()).unwrap()
901 )
902 );
903 Ok(())
904 }
905}