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}