Skip to main content

rubble_templates/std_fun/
math.rs

1//! Provides standard math functions.
2//!
3//! See [`std_fun`](rubble-templates::std_fun) or [`math_functions`] for more info.
4//!
5use std::collections::HashMap;
6use rubble_templates_core::evaluator::{Function, Context, SyntaxError, EvaluationError};
7use rubble_templates_core::functions::{SimpleFunction, FunctionWithContext};
8use std::num::ParseFloatError;
9use crate::std_fun::strings::EMPTY_STRING;
10
11/// Provides a set of math functions.
12///
13/// Available functions:
14/// * [`+`](plus_function) - Adds the parameters or concatenates them if it fails.
15/// * [`-`](minus_function) - Subtracts the parameters.
16/// * [`*`](multiply_function) - Multiplies the parameters.
17/// * [`/`](divide_function) - Divides the parameters.
18/// * [`mod`](modulo_function) - Calculates the remainder.
19pub fn math_functions() -> HashMap<String, Box<dyn Function>> {
20    let mut functions: HashMap<String, Box<dyn Function>> = HashMap::new();
21    functions.insert("+".to_string(), SimpleFunction::new(plus_function));
22    functions.insert("-".to_string(), FunctionWithContext::new(minus_function));
23    functions.insert("*".to_string(), FunctionWithContext::new(multiply_function));
24    functions.insert("/".to_string(), FunctionWithContext::new(divide_function));
25    functions.insert("mod".to_string(), FunctionWithContext::new(modulo_function));
26    functions
27}
28
29/// Adds (or concatenates) values.
30/// If any of the parameters is not convertible to a number, then the rest will be concatenated.
31///
32/// Eg.
33/// ```text
34/// + 1 2 3.3
35/// + 1 2 "hello" 3.3
36/// ```
37/// Expected output:
38/// ```text
39/// 6.3
40/// 3hello3.3
41/// ```
42pub fn plus_function(parameters: &[String]) -> String {
43    let mut result: String = EMPTY_STRING.to_string();
44    let mut floating_result: Option<f64> = None;
45
46    parameters.iter().for_each(|param| {
47        if result.is_empty() {
48            if let Result::Ok(value) = param.parse::<f64>() {
49                floating_result = Some(floating_result.unwrap_or(0 as f64) + value);
50            } else {
51                if let Some(number) = floating_result
52                    .map(|number| number.to_string()) {
53                    result += &number
54                }
55
56                result.push_str(param);
57            }
58        } else {
59            result.push_str(param);
60        };
61    });
62
63    if result.is_empty() && floating_result.is_some() {
64        floating_result.map(|number| number.to_string()).unwrap()
65    } else {
66        result
67    }
68}
69
70/// Subtracts values.
71/// If any of the parameters is not convertible to a number, then an error will be emitted with the invalid value.
72///
73/// Eg.
74/// ```text
75/// - 8.3 1 1.4
76/// ```
77/// Expected output:
78/// ```text
79/// 5.9
80/// ```
81pub fn minus_function(parameters: &[String], _context: &mut Context) -> Result<String, SyntaxError> {
82    reduce_numbers(parameters, |a, b| a - b)
83}
84
85/// Multiplies values.
86/// If any of the parameters is not convertible to a number, then an error will be emitted with the invalid value.
87///
88/// Eg.
89/// ```text
90/// * 4 2 7.2
91/// ```
92/// Expected output:
93/// ```text
94/// 57.6
95/// ```
96pub fn multiply_function(parameters: &[String], _context: &mut Context) -> Result<String, SyntaxError> {
97    reduce_numbers(parameters, |a, b| a * b)
98}
99
100/// Divides values.
101/// If any of the parameters is not convertible to a number, then an error will be emitted with the invalid value.
102///
103/// Eg.
104/// ```text
105/// / 1440 8 6 4
106/// ```
107/// Expected output:
108/// ```text
109/// 7.5
110/// ```
111pub fn divide_function(parameters: &[String], _context: &mut Context) -> Result<String, SyntaxError> {
112    reduce_numbers(parameters, |a, b| a / b)
113}
114
115/// Calculates the remainder (modulo).
116/// If any of the parameters is not convertible to a number, then an error will be emitted with the invalid value.
117///
118/// Eg.
119/// ```text
120/// mod 7 4
121/// ```
122/// Expected output:
123/// ```text
124/// 3
125/// ```
126pub fn modulo_function(parameters: &[String], _context: &mut Context) -> Result<String, SyntaxError> {
127    reduce_numbers(parameters, |a, b| a % b)
128}
129
130pub fn reduce_numbers<F>(parameters: &[String], f: F) -> Result<String, SyntaxError>
131    where F: Fn(f64, f64) -> f64 {
132    let (index, numbers) = as_numbers(parameters);
133
134    if let Result::Err(error) = numbers {
135        Err(SyntaxError::new(EvaluationError::InvalidValues {
136            description: Some(error.to_string()),
137            values: vec![parameters[index - 1].clone()],
138        })
139        )
140    } else {
141        Ok(numbers.unwrap()
142            .into_iter()
143            .reduce(f)
144            .unwrap_or(0 as f64)
145            .to_string()
146        )
147    }
148}
149
150pub fn as_numbers(parameters: &[String]) -> (usize, Result<Vec<f64>, ParseFloatError>) {
151    let mut index: usize = 0;
152    let numbers: Result<Vec<f64>, ParseFloatError> = parameters.iter()
153        .map(|number| {
154            index += 1;
155            number.parse::<f64>()
156        })
157        .collect();
158
159    (index, numbers)
160}