Add map type functionality

This commit is contained in:
Jeff 2023-06-23 21:44:40 -04:00
parent 44170697cb
commit f2ae0d952f
9 changed files with 105 additions and 116 deletions

7
src/bin/expressive.rs Normal file
View File

@ -0,0 +1,7 @@
pub fn main() -> Result<(), Box<dyn std::error::Error>> {
let args = std::env::args().skip(1).collect::<Vec<String>>().join(" ");
println!("{}", evalexpr::eval(&args)?);
Ok(())
}

View File

@ -4,7 +4,7 @@
//! 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.
//! 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.
use std::{collections::HashMap, iter};
use std::collections::BTreeMap;
use crate::{
function::Function,
@ -27,7 +27,7 @@ pub trait Context {
/// A context that allows to assign to variables.
pub trait ContextWithMutableVariables: Context {
/// Sets the variable with the given identifier to the given value.
fn set_value(&mut self, _identifier: String, _value: Value) -> EvalexprResult<()> {
fn set_value(&mut self, _identifier: &str, _value: Value) -> EvalexprResult<()> {
Err(EvalexprError::ContextNotMutable)
}
}
@ -40,105 +40,81 @@ pub trait ContextWithMutableFunctions: Context {
}
}
/// A context that allows to iterate over its variable names with their values.
///
/// **Note:** this trait will change after GATs are stabilised, because then we can get rid of the lifetime in the trait definition.
pub trait IterateVariablesContext {
/// The iterator type for iterating over variable name-value pairs.
type VariableIterator<'a>: Iterator<Item = (String, Value)>
where
Self: 'a;
/// The iterator type for iterating over variable names.
type VariableNameIterator<'a>: Iterator<Item = String>
where
Self: 'a;
/// Returns an iterator over pairs of variable names and values.
fn iter_variables(&self) -> Self::VariableIterator<'_>;
/// Returns an iterator over variable names.
fn iter_variable_names(&self) -> Self::VariableNameIterator<'_>;
}
/// A context that stores its mappings in hash maps.
///
/// *Value and function mappings are stored independently, meaning that there can be a function and a value with the same identifier.*
///
/// 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.
#[derive(Clone, Debug, Default)]
pub struct ContextData {
map_name: String,
variables: HashMap<String, Value>,
functions: HashMap<String, Function>,
pub struct VariableMap {
variables: BTreeMap<String, Value>,
}
impl ContextData {
/// Constructs a `HashMapContext` with no mappings.
impl PartialEq for VariableMap {
fn eq(&self, other: &Self) -> bool {
if self.variables.len() != other.variables.len() {
return false;
}
for variable in &self.variables {
for other in &other.variables {
if variable != other {
return false;
}
}
}
true
}
}
impl VariableMap {
/// Create a new instace.
pub fn new() -> Self {
Default::default()
}
}
impl Context for ContextData {
impl Context for VariableMap {
fn get_value(&self, identifier: &str) -> Option<&Value> {
self.variables.get(identifier)
}
fn call_function(&self, identifier: &str, argument: &Value) -> EvalexprResult<Value> {
match identifier {
"map" => {
let map = Value::Map(self.variables.clone());
self.variables.insert(identifier.to_string(), map);
Ok(Value::Empty)
},
_ => todo!(),
}
}
}
impl ContextWithMutableVariables for ContextData {
fn set_value(&mut self, identifier: String, value: Value) -> EvalexprResult<()> {
if let Some(existing_value) = self.variables.get_mut(&identifier) {
if ValueType::from(&existing_value) == ValueType::from(&value) {
*existing_value = value;
return Ok(());
impl ContextWithMutableVariables for VariableMap {
fn set_value(&mut self, identifier: &str, value: Value) -> EvalexprResult<()> {
println!("{:#?}", self);
println!("{}", identifier);
let split = identifier.split_once(".");
if let Some((map_name, next_identifier)) = split {
if let Some(map_value) = self.variables.get_mut(map_name) {
if let Value::Map(map) = map_value {
map.set_value(next_identifier, value)?;
} else {
return Err(EvalexprError::ExpectedMap {
actual: map_value.clone(),
});
}
} else {
return Err(EvalexprError::expected_type(existing_value, value));
let mut new_map = BTreeMap::new();
new_map.insert(next_identifier.to_string(), value);
let map_var = Value::Map(VariableMap { variables: new_map });
self.variables.insert(map_name.to_string(), map_var);
}
} else {
self.variables.insert(identifier.to_string(), value);
}
// Implicit else, because `self.variables` and `identifier` are not unborrowed in else
self.variables.insert(identifier, value);
Ok(())
}
}
impl ContextWithMutableFunctions for ContextData {
impl ContextWithMutableFunctions for VariableMap {
fn set_function(&mut self, identifier: String, function: Function) -> EvalexprResult<()> {
self.functions.insert(identifier, function);
Ok(())
}
}
impl IterateVariablesContext for ContextData {
type VariableIterator<'a> = std::iter::Map<
std::collections::hash_map::Iter<'a, String, Value>,
fn((&String, &Value)) -> (String, Value),
>;
type VariableNameIterator<'a> =
std::iter::Cloned<std::collections::hash_map::Keys<'a, String, Value>>;
fn iter_variables(&self) -> Self::VariableIterator<'_> {
self.variables
.iter()
.map(|(string, value)| (string.clone(), value.clone()))
}
fn iter_variable_names(&self) -> Self::VariableNameIterator<'_> {
self.variables.keys().cloned()
todo!()
}
}
@ -179,7 +155,7 @@ macro_rules! context_map {
// Create a context, then recurse to add the values in it
( $($tt:tt)* ) => {{
let mut context = $crate::HashMapContext::new();
let mut context = $crate::VariableMap::new();
$crate::context_map!((&mut context) $($tt)*)
.map(|_| context)
}};

View File

@ -5,7 +5,10 @@ use crate::{
value::{FloatType, IntType},
EvalexprError, Function, Value, ValueType,
};
use std::ops::{BitAnd, BitOr, BitXor, Not, Shl, Shr};
use std::{
collections::HashSet,
ops::{BitAnd, BitOr, BitXor, Not, Shl, Shr},
};
macro_rules! simple_math {
($func:ident) => {

View File

@ -1,6 +1,6 @@
use crate::{
token, tree, value::TupleType, Context, ContextWithMutableVariables, EmptyType, EvalexprError,
EvalexprResult, FloatType, ContextData, IntType, Node, Value, EMPTY_VALUE,
EvalexprResult, FloatType, IntType, Node, Value, VariableMap, EMPTY_VALUE,
};
/// Evaluate the given expression string.
@ -15,7 +15,7 @@ use crate::{
///
/// *See the [crate doc](index.html) for more examples and explanations of the expression format.*
pub fn eval(string: &str) -> EvalexprResult<Value> {
eval_with_context_mut(string, &mut ContextData::new())
eval_with_context_mut(string, &mut VariableMap::new())
}
/// Evaluate the given expression string with the given context.
@ -91,21 +91,21 @@ pub fn build_operator_tree(string: &str) -> EvalexprResult<Node> {
///
/// *See the [crate doc](index.html) for more examples and explanations of the expression format.*
pub fn eval_string(string: &str) -> EvalexprResult<String> {
eval_string_with_context_mut(string, &mut ContextData::new())
eval_string_with_context_mut(string, &mut VariableMap::new())
}
/// Evaluate the given expression string into an integer.
///
/// *See the [crate doc](index.html) for more examples and explanations of the expression format.*
pub fn eval_int(string: &str) -> EvalexprResult<IntType> {
eval_int_with_context_mut(string, &mut ContextData::new())
eval_int_with_context_mut(string, &mut VariableMap::new())
}
/// Evaluate the given expression string into a float.
///
/// *See the [crate doc](index.html) for more examples and explanations of the expression format.*
pub fn eval_float(string: &str) -> EvalexprResult<FloatType> {
eval_float_with_context_mut(string, &mut ContextData::new())
eval_float_with_context_mut(string, &mut VariableMap::new())
}
/// Evaluate the given expression string into a float.
@ -113,28 +113,28 @@ pub fn eval_float(string: &str) -> EvalexprResult<FloatType> {
///
/// *See the [crate doc](index.html) for more examples and explanations of the expression format.*
pub fn eval_number(string: &str) -> EvalexprResult<FloatType> {
eval_number_with_context_mut(string, &mut ContextData::new())
eval_number_with_context_mut(string, &mut VariableMap::new())
}
/// Evaluate the given expression string into a boolean.
///
/// *See the [crate doc](index.html) for more examples and explanations of the expression format.*
pub fn eval_boolean(string: &str) -> EvalexprResult<bool> {
eval_boolean_with_context_mut(string, &mut ContextData::new())
eval_boolean_with_context_mut(string, &mut VariableMap::new())
}
/// Evaluate the given expression string into a tuple.
///
/// *See the [crate doc](index.html) for more examples and explanations of the expression format.*
pub fn eval_tuple(string: &str) -> EvalexprResult<TupleType> {
eval_tuple_with_context_mut(string, &mut ContextData::new())
eval_tuple_with_context_mut(string, &mut VariableMap::new())
}
/// Evaluate the given expression string into an empty value.
///
/// *See the [crate doc](index.html) for more examples and explanations of the expression format.*
pub fn eval_empty(string: &str) -> EvalexprResult<EmptyType> {
eval_empty_with_context_mut(string, &mut ContextData::new())
eval_empty_with_context_mut(string, &mut VariableMap::new())
}
/// Evaluate the given expression string into a string with the given context.

View File

@ -560,9 +560,7 @@ extern crate serde;
extern crate serde_derive;
pub use crate::{
context::{
Context, ContextWithMutableFunctions, ContextWithMutableVariables, ContextData, IterateVariablesContext,
},
context::{Context, ContextWithMutableFunctions, ContextWithMutableVariables, VariableMap},
error::{EvalexprError, EvalexprResult},
function::Function,
interface::*,

View File

@ -459,8 +459,7 @@ impl Operator {
let arguments = &arguments[0];
match context.call_function(identifier, arguments) {
Err(EvalexprError::FunctionIdentifierNotFound(_)) =>
{
Err(EvalexprError::FunctionIdentifierNotFound(_)) => {
if let Some(builtin_function) = builtin_function(identifier) {
builtin_function.call(arguments)
} else {
@ -486,7 +485,7 @@ impl Operator {
Assign => {
expect_operator_argument_amount(arguments.len(), 2)?;
let target = arguments[0].as_string()?;
context.set_value(target, arguments[1].clone())?;
context.set_value(&target, arguments[1].clone())?;
Ok(Value::Empty)
},
@ -515,7 +514,7 @@ impl Operator {
self
),
}?;
context.set_value(target, result)?;
context.set_value(&target, result)?;
Ok(Value::Empty)
},

View File

@ -1,7 +1,7 @@
use crate::{
token::Token,
value::{TupleType, EMPTY_VALUE},
Context, ContextWithMutableVariables, EmptyType, FloatType, ContextData, IntType,
Context, ContextWithMutableVariables, EmptyType, FloatType, IntType, VariableMap,
};
use crate::{
@ -346,7 +346,7 @@ impl Node {
///
/// Fails, if one of the operators in the expression tree fails.
pub fn eval(&self) -> EvalexprResult<Value> {
self.eval_with_context_mut(&mut ContextData::new())
self.eval_with_context_mut(&mut VariableMap::new())
}
/// Evaluates the operator tree rooted at this node into a string with an the given context.
@ -532,21 +532,21 @@ impl Node {
///
/// Fails, if one of the operators in the expression tree fails.
pub fn eval_string(&self) -> EvalexprResult<String> {
self.eval_string_with_context_mut(&mut ContextData::new())
self.eval_string_with_context_mut(&mut VariableMap::new())
}
/// Evaluates the operator tree rooted at this node into a float.
///
/// Fails, if one of the operators in the expression tree fails.
pub fn eval_float(&self) -> EvalexprResult<FloatType> {
self.eval_float_with_context_mut(&mut ContextData::new())
self.eval_float_with_context_mut(&mut VariableMap::new())
}
/// Evaluates the operator tree rooted at this node into an integer.
///
/// Fails, if one of the operators in the expression tree fails.
pub fn eval_int(&self) -> EvalexprResult<IntType> {
self.eval_int_with_context_mut(&mut ContextData::new())
self.eval_int_with_context_mut(&mut VariableMap::new())
}
/// Evaluates the operator tree rooted at this node into a float.
@ -554,28 +554,28 @@ impl Node {
///
/// Fails, if one of the operators in the expression tree fails.
pub fn eval_number(&self) -> EvalexprResult<FloatType> {
self.eval_number_with_context_mut(&mut ContextData::new())
self.eval_number_with_context_mut(&mut VariableMap::new())
}
/// Evaluates the operator tree rooted at this node into a boolean.
///
/// Fails, if one of the operators in the expression tree fails.
pub fn eval_boolean(&self) -> EvalexprResult<bool> {
self.eval_boolean_with_context_mut(&mut ContextData::new())
self.eval_boolean_with_context_mut(&mut VariableMap::new())
}
/// Evaluates the operator tree rooted at this node into a tuple.
///
/// Fails, if one of the operators in the expression tree fails.
pub fn eval_tuple(&self) -> EvalexprResult<TupleType> {
self.eval_tuple_with_context_mut(&mut ContextData::new())
self.eval_tuple_with_context_mut(&mut VariableMap::new())
}
/// Evaluates the operator tree rooted at this node into an empty value.
///
/// Fails, if one of the operators in the expression tree fails.
pub fn eval_empty(&self) -> EvalexprResult<EmptyType> {
self.eval_empty_with_context_mut(&mut ContextData::new())
self.eval_empty_with_context_mut(&mut VariableMap::new())
}
/// Returns the children of this node as a slice.

View File

@ -1,5 +1,11 @@
use crate::error::{EvalexprError, EvalexprResult};
use std::{convert::TryFrom, collections::HashMap};
use crate::{
error::{EvalexprError, EvalexprResult},
VariableMap,
};
use std::{
collections::{BTreeMap, HashMap},
convert::TryFrom,
};
mod display;
pub mod value_type;
@ -37,7 +43,7 @@ pub enum Value {
/// An empty value.
Empty,
/// Collection of key-value pairs.
Map(HashMap<String, Value>),
Map(VariableMap),
}
impl Value {

View File

@ -110,7 +110,7 @@ fn test_boolean_examples() {
#[test]
fn test_with_context() {
let mut context = ContextData::new();
let mut context = VariableMap::new();
context
.set_value("tr".into(), Value::Boolean(true))
.unwrap();
@ -144,7 +144,7 @@ fn test_with_context() {
#[test]
fn test_functions() {
let mut context = ContextData::new();
let mut context = VariableMap::new();
context
.set_function(
"sub2".to_string(),
@ -175,7 +175,7 @@ fn test_functions() {
#[test]
fn test_n_ary_functions() {
let mut context = ContextData::new();
let mut context = VariableMap::new();
context
.set_function(
"sub2".into(),
@ -282,7 +282,7 @@ fn test_n_ary_functions() {
#[test]
fn test_capturing_functions() {
let mut context = ContextData::new();
let mut context = VariableMap::new();
// this variable is captured by the function
let three = 3;
context
@ -601,7 +601,7 @@ fn test_no_panic() {
#[test]
fn test_shortcut_functions() {
let mut context = ContextData::new();
let mut context = VariableMap::new();
context
.set_value("string".into(), Value::from("a string"))
.unwrap();
@ -1284,7 +1284,7 @@ fn test_whitespace() {
#[test]
fn test_assignment() {
let mut context = ContextData::new();
let mut context = VariableMap::new();
assert_eq!(
eval_empty_with_context_mut("int = 3", &mut context),
Ok(EMPTY_VALUE)
@ -1324,7 +1324,7 @@ fn test_assignment() {
#[test]
fn test_expression_chaining() {
let mut context = ContextData::new();
let mut context = VariableMap::new();
assert_eq!(
eval_int_with_context_mut("a = 5; a = a + 2; a", &mut context),
Ok(7)
@ -1333,7 +1333,7 @@ fn test_expression_chaining() {
#[test]
fn test_strings() {
let mut context = ContextData::new();
let mut context = VariableMap::new();
assert_eq!(eval("\"string\""), Ok(Value::from("string")));
assert_eq!(
eval_with_context_mut("a = \"a string\"", &mut context),
@ -1441,7 +1441,7 @@ fn test_implicit_context() {
#[test]
fn test_operator_assignments() {
let mut context = ContextData::new();
let mut context = VariableMap::new();
assert_eq!(eval_empty_with_context_mut("a = 5", &mut context), Ok(()));
assert_eq!(eval_empty_with_context_mut("a += 5", &mut context), Ok(()));
assert_eq!(eval_empty_with_context_mut("a -= 5", &mut context), Ok(()));
@ -1463,7 +1463,7 @@ fn test_operator_assignments() {
Ok(())
);
let mut context = ContextData::new();
let mut context = VariableMap::new();
assert_eq!(eval_int_with_context_mut("a = 5; a", &mut context), Ok(5));
assert_eq!(eval_int_with_context_mut("a += 3; a", &mut context), Ok(8));
assert_eq!(eval_int_with_context_mut("a -= 5; a", &mut context), Ok(3));
@ -1643,7 +1643,7 @@ fn test_hashmap_context_type_safety() {
#[test]
fn test_hashmap_context_clone_debug() {
let mut context = ContextData::new();
let mut context = VariableMap::new();
// this variable is captured by the function
let three = 3;
context
@ -2228,7 +2228,7 @@ fn assignment_lhs_is_identifier() {
let tree = build_operator_tree("a = 1").unwrap();
let operators: Vec<_> = tree.iter().map(|node| node.operator().clone()).collect();
let mut context = ContextData::new();
let mut context = VariableMap::new();
tree.eval_with_context_mut(&mut context).unwrap();
assert_eq!(context.get_value("a"), Some(&Value::Int(1)));
@ -2250,7 +2250,7 @@ fn assignment_lhs_is_identifier() {
#[test]
fn test_variable_assignment_and_iteration() {
let mut context = ContextData::new();
let mut context = VariableMap::new();
eval_with_context_mut("a = 5; b = 5.0", &mut context).unwrap();
let mut variables: Vec<_> = context.iter_variables().collect();
@ -2278,7 +2278,7 @@ fn test_negative_power() {
#[test]
fn test_builtin_functions_context() {
let mut context = ContextData::new();
let mut context = VariableMap::new();
// Builtin functions are enabled by default for HashMapContext.
assert_eq!(eval_with_context("max(1,3)", &context), Ok(Value::from(3)));
// Disabling builtin function in Context.