• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ISibboI / evalexpr / 18898573087

29 Oct 2025 05:57AM UTC coverage: 91.343% (-0.05%) from 91.395%
18898573087

Pull #189

github

web-flow
Merge 780457253 into e92667e8e
Pull Request #189: Added function remove_value to HashMapContext

2 of 3 new or added lines in 1 file covered. (66.67%)

8 existing lines in 1 file now uncovered.

1319 of 1444 relevant lines covered (91.34%)

9.07 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.71
/src/context/mod.rs
1
//! A context defines methods to retrieve variable values and call functions for literals in an expression tree.
2
//! If mutable, it also allows to assign to variables.
3
//!
4
//! This crate implements two basic variants, the `EmptyContext`, that returns `None` for each identifier and cannot be manipulated, and the `HashMapContext`, that stores its mappings in hash maps.
5
//! The HashMapContext is type-safe and returns an error if the user tries to assign a value of a different type than before to an identifier.
6

7
use std::{collections::HashMap, iter, marker::PhantomData};
8

9
use crate::{
10
    error::EvalexprResultValue,
11
    function::Function,
12
    value::{
13
        numeric_types::{default_numeric_types::DefaultNumericTypes, EvalexprNumericTypes},
14
        value_type::ValueType,
15
        Value,
16
    },
17
    EvalexprError, EvalexprResult,
18
};
19

20
mod predefined;
21

22
/// An immutable context.
23
pub trait Context {
24
    /// The numeric types used for evaluation.
25
    type NumericTypes: EvalexprNumericTypes;
26

27
    /// Returns the value that is linked to the given identifier.
28
    fn get_value(&self, identifier: &str) -> Option<&Value<Self::NumericTypes>>;
29

30
    /// Calls the function that is linked to the given identifier with the given argument.
31
    /// If no function with the given identifier is found, this method returns `EvalexprError::FunctionIdentifierNotFound`.
32
    fn call_function(
33
        &self,
34
        identifier: &str,
35
        argument: &Value<Self::NumericTypes>,
36
    ) -> EvalexprResultValue<Self::NumericTypes>;
37

38
    /// Checks if builtin functions are disabled.
39
    fn are_builtin_functions_disabled(&self) -> bool;
40

41
    /// Disables builtin functions if `disabled` is `true`, and enables them otherwise.
42
    /// If the context does not support enabling or disabling builtin functions, an error is returned.
43
    fn set_builtin_functions_disabled(
44
        &mut self,
45
        disabled: bool,
46
    ) -> EvalexprResult<(), Self::NumericTypes>;
47
}
48

49
/// A context that allows to assign to variables.
50
pub trait ContextWithMutableVariables: Context {
51
    /// Sets the variable with the given identifier to the given value.
52
    fn set_value(
53
        &mut self,
54
        _identifier: String,
55
        _value: Value<Self::NumericTypes>,
56
    ) -> EvalexprResult<(), Self::NumericTypes> {
57
        Err(EvalexprError::ContextNotMutable)
×
58
    }
59
    /// Removes the variable with the given identifier from the context.
60
    fn remove_value(
61
        &mut self,
62
        _identifier: &str,
63
    ) -> EvalexprResult<Option<Value<Self::NumericTypes>>, Self::NumericTypes> {
NEW
64
        Err(EvalexprError::ContextNotMutable)
×
65
    }
66
}
67

68
/// A context that allows to assign to function identifiers.
69
pub trait ContextWithMutableFunctions: Context {
70
    /// Sets the function with the given identifier to the given function.
71
    fn set_function(
72
        &mut self,
73
        _identifier: String,
74
        _function: Function<Self::NumericTypes>,
75
    ) -> EvalexprResult<(), Self::NumericTypes> {
UNCOV
76
        Err(EvalexprError::ContextNotMutable)
×
77
    }
78
}
79

80
/// A context that allows to iterate over its variable names with their values.
81
pub trait IterateVariablesContext: Context {
82
    /// The iterator type for iterating over variable name-value pairs.
83
    type VariableIterator<'a>: Iterator<Item = (String, Value<Self::NumericTypes>)>
84
    where
85
        Self: 'a;
86
    /// The iterator type for iterating over variable names.
87
    type VariableNameIterator<'a>: Iterator<Item = String>
88
    where
89
        Self: 'a;
90

91
    /// Returns an iterator over pairs of variable names and values.
92
    fn iter_variables(&self) -> Self::VariableIterator<'_>;
93

94
    /// Returns an iterator over variable names.
95
    fn iter_variable_names(&self) -> Self::VariableNameIterator<'_>;
96
}
97

98
/*/// A context that allows to retrieve functions programmatically.
99
pub trait GetFunctionContext: Context {
100
    /// Returns the function that is linked to the given identifier.
101
    ///
102
    /// This might not be possible for all functions, as some might be hard-coded.
103
    /// In this case, a special error variant should be returned (Not yet implemented).
104
    fn get_function(&self, identifier: &str) -> Option<&Function>;
105
}*/
106

107
/// A context that returns `None` for each identifier.
108
/// Builtin functions are disabled and cannot be enabled.
109
#[derive(Debug)]
110
pub struct EmptyContext<NumericTypes>(PhantomData<NumericTypes>);
111

112
impl<NumericTypes: EvalexprNumericTypes> Context for EmptyContext<NumericTypes> {
113
    type NumericTypes = NumericTypes;
114

115
    fn get_value(&self, _identifier: &str) -> Option<&Value<Self::NumericTypes>> {
1✔
UNCOV
116
        None
×
117
    }
118

119
    fn call_function(
1✔
120
        &self,
121
        identifier: &str,
122
        _argument: &Value<Self::NumericTypes>,
123
    ) -> EvalexprResultValue<Self::NumericTypes> {
124
        Err(EvalexprError::FunctionIdentifierNotFound(
1✔
125
            identifier.to_string(),
1✔
126
        ))
127
    }
128

129
    /// Builtin functions are always disabled for `EmptyContext`.
130
    fn are_builtin_functions_disabled(&self) -> bool {
1✔
UNCOV
131
        true
×
132
    }
133

134
    /// Builtin functions can't be enabled for `EmptyContext`.
135
    fn set_builtin_functions_disabled(
1✔
136
        &mut self,
137
        disabled: bool,
138
    ) -> EvalexprResult<(), Self::NumericTypes> {
139
        if disabled {
2✔
140
            Ok(())
1✔
141
        } else {
142
            Err(EvalexprError::BuiltinFunctionsCannotBeEnabled)
1✔
143
        }
144
    }
145
}
146

147
impl<NumericTypes: EvalexprNumericTypes> IterateVariablesContext for EmptyContext<NumericTypes> {
148
    type VariableIterator<'a>
149
        = iter::Empty<(String, Value<Self::NumericTypes>)>
150
    where
151
        Self: 'a;
152
    type VariableNameIterator<'a>
153
        = iter::Empty<String>
154
    where
155
        Self: 'a;
156

157
    fn iter_variables(&self) -> Self::VariableIterator<'_> {
1✔
158
        iter::empty()
1✔
159
    }
160

161
    fn iter_variable_names(&self) -> Self::VariableNameIterator<'_> {
1✔
162
        iter::empty()
1✔
163
    }
164
}
165

166
impl<NumericTypes> Default for EmptyContext<NumericTypes> {
UNCOV
167
    fn default() -> Self {
×
168
        Self(PhantomData)
×
169
    }
170
}
171

172
/// A context that returns `None` for each identifier.
173
/// Builtin functions are enabled and cannot be disabled.
174
#[derive(Debug)]
175
pub struct EmptyContextWithBuiltinFunctions<NumericTypes>(PhantomData<NumericTypes>);
176

177
impl<NumericTypes: EvalexprNumericTypes> Context
178
    for EmptyContextWithBuiltinFunctions<NumericTypes>
179
{
180
    type NumericTypes = NumericTypes;
181

182
    fn get_value(&self, _identifier: &str) -> Option<&Value<Self::NumericTypes>> {
1✔
UNCOV
183
        None
×
184
    }
185

186
    fn call_function(
1✔
187
        &self,
188
        identifier: &str,
189
        _argument: &Value<Self::NumericTypes>,
190
    ) -> EvalexprResultValue<Self::NumericTypes> {
191
        Err(EvalexprError::FunctionIdentifierNotFound(
1✔
192
            identifier.to_string(),
1✔
193
        ))
194
    }
195

196
    /// Builtin functions are always enabled for EmptyContextWithBuiltinFunctions.
197
    fn are_builtin_functions_disabled(&self) -> bool {
1✔
UNCOV
198
        false
×
199
    }
200

201
    /// Builtin functions can't be disabled for EmptyContextWithBuiltinFunctions.
202
    fn set_builtin_functions_disabled(
1✔
203
        &mut self,
204
        disabled: bool,
205
    ) -> EvalexprResult<(), Self::NumericTypes> {
206
        if disabled {
2✔
207
            Err(EvalexprError::BuiltinFunctionsCannotBeDisabled)
1✔
208
        } else {
209
            Ok(())
1✔
210
        }
211
    }
212
}
213

214
impl<NumericTypes: EvalexprNumericTypes> IterateVariablesContext
215
    for EmptyContextWithBuiltinFunctions<NumericTypes>
216
{
217
    type VariableIterator<'a>
218
        = iter::Empty<(String, Value<Self::NumericTypes>)>
219
    where
220
        Self: 'a;
221
    type VariableNameIterator<'a>
222
        = iter::Empty<String>
223
    where
224
        Self: 'a;
225

226
    fn iter_variables(&self) -> Self::VariableIterator<'_> {
1✔
227
        iter::empty()
1✔
228
    }
229

230
    fn iter_variable_names(&self) -> Self::VariableNameIterator<'_> {
1✔
231
        iter::empty()
1✔
232
    }
233
}
234

235
impl<NumericTypes> Default for EmptyContextWithBuiltinFunctions<NumericTypes> {
UNCOV
236
    fn default() -> Self {
×
237
        Self(PhantomData)
×
238
    }
239
}
240

241
/// A context that stores its mappings in hash maps.
242
///
243
/// *Value and function mappings are stored independently, meaning that there can be a function and a value with the same identifier.*
244
///
245
/// This context is type-safe, meaning that an identifier that is assigned a value of some type once cannot be assigned a value of another type.
246
#[derive(Clone, Debug)]
247
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
248
pub struct HashMapContext<NumericTypes: EvalexprNumericTypes = DefaultNumericTypes> {
249
    variables: HashMap<String, Value<NumericTypes>>,
250
    #[cfg_attr(feature = "serde", serde(skip))]
251
    functions: HashMap<String, Function<NumericTypes>>,
252

253
    /// True if builtin functions are disabled.
254
    without_builtin_functions: bool,
255
}
256

257
impl<NumericTypes: EvalexprNumericTypes> HashMapContext<NumericTypes> {
258
    /// Constructs a `HashMapContext` with no mappings.
259
    pub fn new() -> Self {
29✔
260
        Default::default()
29✔
261
    }
262

263
    /// Removes all variables from the context.
264
    /// This allows to reuse the context without allocating a new HashMap.
265
    ///
266
    /// # Example
267
    ///
268
    /// ```rust
269
    /// # use evalexpr::*;
270
    ///
271
    /// let mut context = HashMapContext::<DefaultNumericTypes>::new();
272
    /// context.set_value("abc".into(), "def".into()).unwrap();
273
    /// assert_eq!(context.get_value("abc"), Some(&("def".into())));
274
    /// context.clear_variables();
275
    /// assert_eq!(context.get_value("abc"), None);
276
    /// ```
277
    pub fn clear_variables(&mut self) {
3✔
278
        self.variables.clear()
3✔
279
    }
280

281
    /// Removes all functions from the context.
282
    /// This allows to reuse the context without allocating a new HashMap.
283
    pub fn clear_functions(&mut self) {
2✔
284
        self.functions.clear()
2✔
285
    }
286

287
    /// Removes all variables and functions from the context.
288
    /// This allows to reuse the context without allocating a new HashMap.
289
    ///
290
    /// # Example
291
    ///
292
    /// ```rust
293
    /// # use evalexpr::*;
294
    ///
295
    /// let mut context = HashMapContext::<DefaultNumericTypes>::new();
296
    /// context.set_value("abc".into(), "def".into()).unwrap();
297
    /// assert_eq!(context.get_value("abc"), Some(&("def".into())));
298
    /// context.clear();
299
    /// assert_eq!(context.get_value("abc"), None);
300
    /// ```
301
    pub fn clear(&mut self) {
2✔
302
        self.clear_variables();
2✔
303
        self.clear_functions();
2✔
304
    }
305
}
306

307
impl<NumericTypes: EvalexprNumericTypes> Context for HashMapContext<NumericTypes> {
308
    type NumericTypes = NumericTypes;
309

310
    fn get_value(&self, identifier: &str) -> Option<&Value<Self::NumericTypes>> {
18✔
311
        self.variables.get(identifier)
18✔
312
    }
313

314
    fn call_function(
9✔
315
        &self,
316
        identifier: &str,
317
        argument: &Value<Self::NumericTypes>,
318
    ) -> EvalexprResultValue<Self::NumericTypes> {
319
        if let Some(function) = self.functions.get(identifier) {
9✔
320
            function.call(argument)
5✔
321
        } else {
322
            Err(EvalexprError::FunctionIdentifierNotFound(
5✔
323
                identifier.to_string(),
5✔
324
            ))
325
        }
326
    }
327

328
    fn are_builtin_functions_disabled(&self) -> bool {
5✔
329
        self.without_builtin_functions
5✔
330
    }
331

332
    fn set_builtin_functions_disabled(
2✔
333
        &mut self,
334
        disabled: bool,
335
    ) -> EvalexprResult<(), NumericTypes> {
336
        self.without_builtin_functions = disabled;
2✔
337
        Ok(())
2✔
338
    }
339
}
340

341
impl<NumericTypes: EvalexprNumericTypes> ContextWithMutableVariables
342
    for HashMapContext<NumericTypes>
343
{
344
    fn set_value(
18✔
345
        &mut self,
346
        identifier: String,
347
        value: Value<Self::NumericTypes>,
348
    ) -> EvalexprResult<(), NumericTypes> {
349
        if let Some(existing_value) = self.variables.get_mut(&identifier) {
36✔
350
            if ValueType::from(&existing_value) == ValueType::from(&value) {
18✔
351
                *existing_value = value;
7✔
352
                return Ok(());
7✔
353
            } else {
354
                return Err(EvalexprError::expected_type(existing_value, value));
8✔
355
            }
356
        }
357

358
        // Implicit else, because `self.variables` and `identifier` are not unborrowed in else
359
        self.variables.insert(identifier, value);
36✔
360
        Ok(())
18✔
361
    }
362
    fn remove_value(
2✔
363
        &mut self,
364
        identifier: &str,
365
    ) -> EvalexprResult<Option<Value<Self::NumericTypes>>, Self::NumericTypes> {
366
        // Removes a value from the `self.variables`, returning the value at the key if the key was previously in the map.
367
        Ok(self.variables.remove(identifier))
2✔
368
    }
369
}
370

371
impl<NumericTypes: EvalexprNumericTypes> ContextWithMutableFunctions
372
    for HashMapContext<NumericTypes>
373
{
374
    fn set_function(
5✔
375
        &mut self,
376
        identifier: String,
377
        function: Function<NumericTypes>,
378
    ) -> EvalexprResult<(), Self::NumericTypes> {
379
        self.functions.insert(identifier, function);
5✔
380
        Ok(())
5✔
381
    }
382
}
383

384
impl<NumericTypes: EvalexprNumericTypes> IterateVariablesContext for HashMapContext<NumericTypes> {
385
    type VariableIterator<'a>
386
        = std::iter::Map<
387
        std::collections::hash_map::Iter<'a, String, Value<NumericTypes>>,
388
        fn((&String, &Value<NumericTypes>)) -> (String, Value<NumericTypes>),
389
    >
390
    where
391
        Self: 'a;
392
    type VariableNameIterator<'a>
393
        = std::iter::Cloned<std::collections::hash_map::Keys<'a, String, Value<NumericTypes>>>
394
    where
395
        Self: 'a;
396

397
    fn iter_variables(&self) -> Self::VariableIterator<'_> {
1✔
UNCOV
398
        self.variables
×
399
            .iter()
400
            .map(|(string, value)| (string.clone(), value.clone()))
3✔
401
    }
402

403
    fn iter_variable_names(&self) -> Self::VariableNameIterator<'_> {
1✔
404
        self.variables.keys().cloned()
1✔
405
    }
406
}
407

408
impl<NumericTypes: EvalexprNumericTypes> Default for HashMapContext<NumericTypes> {
409
    fn default() -> Self {
29✔
410
        Self {
411
            variables: Default::default(),
29✔
412
            functions: Default::default(),
29✔
413
            without_builtin_functions: false,
414
        }
415
    }
416
}
417

418
/// This macro provides a convenient syntax for creating a static context.
419
///
420
/// # Examples
421
///
422
/// ```rust
423
/// use evalexpr::*;
424
///
425
/// let ctx: HashMapContext<DefaultNumericTypes> = context_map! {
426
///     "x" => int 8,
427
///     "f" => Function::new(|_| Ok(Value::from_int(42)))
428
/// }.unwrap(); // Do proper error handling here
429
///
430
/// assert_eq!(eval_with_context("x + f()", &ctx), Ok(Value::from_int(50)));
431
/// ```
432
#[macro_export]
433
macro_rules! context_map {
434
    // Termination (allow missing comma at the end of the argument list)
435
    ( ($ctx:expr) $k:expr => Function::new($($v:tt)*) ) =>
436
        { $crate::context_map!(($ctx) $k => Function::new($($v)*),) };
437
    ( ($ctx:expr) $k:expr => int $v:expr ) =>
438
        { $crate::context_map!(($ctx) $k => int $v,)  };
439
    ( ($ctx:expr) $k:expr => float $v:expr ) =>
440
        { $crate::context_map!(($ctx) $k => float $v,)  };
441
    ( ($ctx:expr) $k:expr => $v:expr ) =>
442
        { $crate::context_map!(($ctx) $k => $v,)  };
443
    // Termination
444
    ( ($ctx:expr) ) => { Ok(()) };
1✔
445

446
    // The user has to specify a literal 'Function::new' in order to create a function
447
    ( ($ctx:expr) $k:expr => Function::new($($v:tt)*) , $($tt:tt)*) => {{
448
        $crate::ContextWithMutableFunctions::set_function($ctx, $k.into(), $crate::Function::new($($v)*))
449
            .and($crate::context_map!(($ctx) $($tt)*))
450
    }};
451
    // add an integer value, and chain the eventual error with the ones in the next values
452
    ( ($ctx:expr) $k:expr => int $v:expr , $($tt:tt)*) => {{
453
        $crate::ContextWithMutableVariables::set_value($ctx, $k.into(), $crate::Value::from_int($v.into()))
454
            .and($crate::context_map!(($ctx) $($tt)*))
455
    }};
456
    // add a float value, and chain the eventual error with the ones in the next values
457
    ( ($ctx:expr) $k:expr => float $v:expr , $($tt:tt)*) => {{
458
        $crate::ContextWithMutableVariables::set_value($ctx, $k.into(), $crate::Value::from_float($v.into()))
38✔
459
            .and($crate::context_map!(($ctx) $($tt)*))
19✔
460
    }};
461
    // add a value, and chain the eventual error with the ones in the next values
462
    ( ($ctx:expr) $k:expr => $v:expr , $($tt:tt)*) => {{
463
        $crate::ContextWithMutableVariables::set_value($ctx, $k.into(), $v.into())
464
            .and($crate::context_map!(($ctx) $($tt)*))
465
    }};
466

467
    // Create a context, then recurse to add the values in it
468
    ( $($tt:tt)* ) => {{
469
        let mut context = $crate::HashMapContext::new();
1✔
470
        $crate::context_map!((&mut context) $($tt)*)
471
            .map(|_| context)
17✔
472
    }};
473
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc