sbrd_gen/
eval.rs

1#![deny(missing_debug_implementations)]
2//! Module for evaluator for `script` and `format`
3
4use crate::value::{DataValueMap, SbrdBool, SbrdInt, SbrdReal, SbrdString};
5use evalexpr::{
6    eval_boolean_with_context, eval_int_with_context, eval_number_with_context,
7    eval_string_with_context, EvalexprError, HashMapContext,
8};
9use human_string_filler::StrExt;
10use std::fmt::Write;
11
12/// Evaluator for `script` and `format`.
13/// Script and format is processed by replacing a replace-key-syntax for the key with value based on each entry `(key, value)` of context.
14/// Replace-key-syntax is "{key}" and "{key:\<format-option\>}". It specified by Rust format syntax with the key as name position. But not support index position, variable, padding with character and [`Pointer`] format (`{:p}`).
15/// [`Debug`] format is not supported in release build.
16/// If you want to know, you will see [`Rust-format syntax`] and [`DataValue::format`].
17///
18/// All values, variables and functions are available as described in the [`evalexpr`] except the regex functions.
19/// If you'll know syntax and available them more, you can see [`this document`].
20///
21/// # Examples
22/// ```
23/// fn main(){
24///     use sbrd_gen::eval::Evaluator;
25///     use sbrd_gen::value::{DataValue, DataValueMap};
26///
27///     let mut value_context = DataValueMap::new();
28///     value_context.insert("Key-Int", DataValue::Int(12));
29///     value_context.insert("キー Real", DataValue::Real(12.345));
30///     value_context.insert("Key:String", DataValue::String("aiueoあいうえお".to_string()));
31///     value_context.insert("Key Bool:", DataValue::Bool(true));
32///     value_context.insert("key Null ", DataValue::Null);
33///     let evaluator = Evaluator::new(&value_context);
34///
35///     assert_eq!(Ok("no key".to_string()), evaluator.format_script("no key"));
36///     assert_eq!(Ok("12".to_string()), evaluator.format_script("{Key-Int}"));
37///     assert_eq!(Ok("{Key-Int}".to_string()), evaluator.format_script("{{Key-Int}}"));
38///     assert_eq!(Ok("Rate= +12.35".to_string()), evaluator.format_script("Rate={キー Real:+7.2}"));
39///     assert_eq!(Ok("Rate=+012.35".to_string()), evaluator.format_script("Rate={キー Real:+07.2}"));
40///     assert_eq!(Ok(" aiueoあいうえお ".to_string()), evaluator.format_script("{Key:String:^12}"));
41///     assert_eq!(Ok("true    ".to_string()), evaluator.format_script("{Key Bool::<8}"));
42///     assert_eq!(Ok("null".to_string()), evaluator.format_script("{key Null :<10}"));
43/// }
44/// ```
45///
46/// [`Rust-format syntax`]: https://doc.rust-lang.org/std/fmt/index.html#syntax
47/// [`Pointer`]: https://doc.rust-lang.org/std/fmt/trait.Pointer.html
48/// [`Debug`]: https://doc.rust-lang.org/std/fmt/trait.Debug.html
49/// [`DataValue::format`]: ../value/enum.DataValue.html#method.format
50/// [`evalexpr`]: https://crates.io/crates/evalexpr/7.0.1
51/// [`this document`]: https://docs.rs/evalexpr/7.0.1/evalexpr/index.html#features
52#[derive(Debug, PartialEq, Clone)]
53pub struct Evaluator<'a> {
54    value_context: &'a DataValueMap<&'a str>,
55}
56
57/// Context for evaluator
58pub type EvalContext = HashMapContext;
59/// Error while evaluate
60#[derive(Debug, PartialEq)]
61pub enum EvalError {
62    /// Fail evaluate
63    FailEval(EvalexprError),
64    /// Fail apply value context
65    FailApplyValueContext(String),
66}
67
68impl std::fmt::Display for EvalError {
69    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
70        match self {
71            EvalError::FailEval(e) => write!(f, "Fail eval with error: {}", e),
72            EvalError::FailApplyValueContext(e) => {
73                write!(f, "Fail apply value context with error: {}", e)
74            }
75        }
76    }
77}
78
79impl std::error::Error for EvalError {}
80
81/// Alias of [`Result`] for [`Evaluator`]
82///
83/// [`Result`]: https://doc.rust-lang.org/std/result/enum.Result.html
84/// [`Evaluator`]: ./struct.Evaluator.html
85pub type EvalResult<T> = Result<T, EvalError>;
86
87impl<'a> Evaluator<'a> {
88    /// Create from script and a value context
89    pub fn new(value_context: &'a DataValueMap<&str>) -> Self {
90        Self { value_context }
91    }
92
93    /// Create context when use evaluate
94    fn create_eval_context() -> EvalResult<EvalContext> {
95        #[allow(unused_mut)]
96        let mut context = EvalContext::new();
97
98        // @todo If you need impl custom function, do impl. e.g. function for get value at the index
99        /*
100        context
101            .set_function(
102                "get".to_string(),
103                Function::new(move |argument| {
104                    let arg_tuple = argument.as_fixed_len_tuple(2)?;
105                    let (values, index) = (arg_tuple[0].as_tuple()?, arg_tuple[1].as_int()?);
106
107                    if index < 0 {
108                        return Err(EvalexprError::CustomMessage(
109                            "Invalid index in a script.".to_string(),
110                        ));
111                    }
112                    match values.get(index as usize) {
113                        None => Err(EvalexprError::CustomMessage(format!(
114                            "Not found value in {} at tuple index {}",
115                            argument, index
116                        ))),
117                        Some(value) => Ok(value.clone()),
118                    }
119                }),
120            )
121            .map_err(EvalError::FailEval)?;
122         */
123
124        Ok(context)
125    }
126
127    /// Apply value-context to the script
128    fn apply_value_context(&self, script: &str) -> EvalResult<String> {
129        let mut result = String::new();
130        script
131            .fill_into::<_, _, String>(&mut result, |output: &mut String, key: &str| {
132                match self.value_context.get(key) {
133                    Some(v) => {
134                        let formatted = v
135                            .format("{}")
136                            .ok_or_else(|| format!("Fail apply key \"{}\".", key))?;
137
138                        output.write_str(&formatted).map_err(|e| e.to_string())?;
139                    }
140                    None => {
141                        let split_index: usize = key.rfind(':').ok_or_else(|| {
142                            format!("Not found key \"{}\" at the value context.", key)
143                        })?;
144                        let _key = &key[0..split_index];
145                        match self.value_context.get(_key) {
146                            Some(v) => {
147                                let formatted = v
148                                    .format(&format!("{{{}}}", &key[split_index..key.len()]))
149                                    .ok_or_else(|| format!("Fail apply key \"{}\".", _key))?;
150
151                                output.write_str(&formatted).map_err(|e| e.to_string())?;
152                            }
153                            None => {
154                                return Err(format!(
155                                    "Not found key \"{}\" at the value context.",
156                                    _key
157                                ));
158                            }
159                        }
160                    }
161                }
162                Ok(())
163            })
164            .map(|_| result)
165            .map_err(|e| EvalError::FailApplyValueContext(e.to_string()))
166    }
167
168    /// Get format applied value-context to the script.
169    ///
170    /// If you want to know syntax, you will see [`Evaluator`]'s document.
171    ///
172    /// [`Evaluator`]: ./struct.Evaluator.html
173    pub fn format_script(&self, script: &str) -> EvalResult<String> {
174        self.apply_value_context(script)
175    }
176
177    /// Evaluate the script applied the context, as [`SbrdInt`]
178    ///
179    /// [`SbrdInt`]: ../value/type.SbrdInt.html
180    pub fn eval_int(&self, script: &str) -> EvalResult<SbrdInt> {
181        eval_int_with_context(&self.format_script(script)?, &Self::create_eval_context()?)
182            .map(|v| v as SbrdInt)
183            .map_err(EvalError::FailEval)
184    }
185
186    /// Evaluate the script applied the context, as [`SbrdReal`]
187    ///
188    /// [`SbrdReal`]: ../value/type.SbrdReal.html
189    pub fn eval_real(&self, script: &str) -> EvalResult<SbrdReal> {
190        eval_number_with_context(&self.format_script(script)?, &Self::create_eval_context()?)
191            .map(|v| v as SbrdReal)
192            .map_err(EvalError::FailEval)
193    }
194
195    /// Evaluate the script applied the context, as [`SbrdBool`]
196    ///
197    /// [`SbrdBool`]: ../value/type.SbrdBool.html
198    pub fn eval_bool(&self, script: &str) -> EvalResult<SbrdBool> {
199        eval_boolean_with_context(&self.format_script(script)?, &Self::create_eval_context()?)
200            .map(|v| v as SbrdBool)
201            .map_err(EvalError::FailEval)
202    }
203
204    /// Evaluate the script applied the context, as [`SbrdString`]
205    ///
206    /// [`SbrdString`]: ../value/type.SbrdString.html
207    pub fn eval_string(&self, script: &str) -> EvalResult<SbrdString> {
208        eval_string_with_context(&self.format_script(script)?, &Self::create_eval_context()?)
209            .map_err(EvalError::FailEval)
210    }
211}