parent
cdcd24e7b6
commit
38c4c35a0b
@ -2,12 +2,19 @@ use crate::value::Value;
|
||||
use function::Function;
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// A configuration for an expression tree.
|
||||
///
|
||||
/// A configuration defines methods to retrieve values and functions for literals in an expression tree.
|
||||
/// This crate implements two basic variants, the `EmptyConfiguration`, that returns `None` for each identifier, and the `HashMapConfiguration`, that stores its mappings in hash maps.
|
||||
pub trait Configuration {
|
||||
/// Returns the value that is linked to the given identifier.
|
||||
fn get_value(&self, identifier: &str) -> Option<&Value>;
|
||||
|
||||
/// Returns the function that is linked to the given identifier.
|
||||
fn get_function(&self, identifier: &str) -> Option<&Function>;
|
||||
}
|
||||
|
||||
/// A configuration that returns `None` for each identifier.
|
||||
pub struct EmptyConfiguration;
|
||||
|
||||
impl Configuration for EmptyConfiguration {
|
||||
@ -20,12 +27,16 @@ impl Configuration for EmptyConfiguration {
|
||||
}
|
||||
}
|
||||
|
||||
/// A configuration 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.*
|
||||
pub struct HashMapConfiguration {
|
||||
variables: HashMap<String, Value>,
|
||||
functions: HashMap<String, Function>,
|
||||
}
|
||||
|
||||
impl HashMapConfiguration {
|
||||
/// Constructs a `HashMapConfiguration` with no mappings.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
variables: Default::default(),
|
||||
@ -33,10 +44,16 @@ impl HashMapConfiguration {
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a variable mapping to the configuration.
|
||||
///
|
||||
/// *Value and function mappings are stored independently, meaning that there can be a function and a variable with the same identifier.*
|
||||
pub fn insert_variable<S: Into<String>, V: Into<Value>>(&mut self, identifier: S, value: V) {
|
||||
self.variables.insert(identifier.into(), value.into());
|
||||
}
|
||||
|
||||
/// Adds a function mappign to the configuration.
|
||||
///
|
||||
/// *Value and function mappings are stored independently, meaning that there can be a function and a variable with the same identifier.*
|
||||
pub fn insert_function<S: Into<String>>(&mut self, identifier: S, function: Function) {
|
||||
self.functions.insert(identifier.into(), function);
|
||||
}
|
||||
|
@ -1,20 +1,42 @@
|
||||
//! The `error` module contains the `Error` enum that contains all error types used by this crate.
|
||||
//!
|
||||
//! The `Error` enum implements constructors for its struct variants, because those are ugly to construct.
|
||||
//!
|
||||
//! The module also contains some helper functions starting with `expect_` that check for a condition and return `Err(_)` if the condition is not fulfilled.
|
||||
//! They are meant as shortcuts to not write the same error checking code everywhere.
|
||||
|
||||
use crate::value::Value;
|
||||
use token::PartialToken;
|
||||
|
||||
/// Errors used in this crate.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Error {
|
||||
/// An operator was called with a wrong amount of arguments.
|
||||
WrongOperatorArgumentAmount {
|
||||
/// Expected amount of arguments.
|
||||
expected: usize,
|
||||
/// Actual amount of arguments.
|
||||
actual: usize,
|
||||
},
|
||||
|
||||
/// A function was called with a wrong amount of arguments.
|
||||
WrongFunctionArgumentAmount {
|
||||
/// Expected amount of arguments.
|
||||
expected: usize,
|
||||
/// Actual amount of arguments.
|
||||
actual: usize,
|
||||
},
|
||||
|
||||
/// A numeric value was expected.
|
||||
/// Numeric values are the variants `Value::Int` and `Value::Float`.
|
||||
ExpectedNumber {
|
||||
/// Actual value.
|
||||
actual: Value,
|
||||
},
|
||||
|
||||
/// A boolean value was expected.
|
||||
ExpectedBoolean {
|
||||
/// Actual value.
|
||||
actual: Value,
|
||||
},
|
||||
|
||||
@ -47,35 +69,46 @@ pub enum Error {
|
||||
/// A closing brace without a matching opening brace was found.
|
||||
UnmatchedRBrace,
|
||||
|
||||
/// A `PartialToken` is unmatched, such that it cannot be combined into a full `Token`.
|
||||
/// This happens if for example a single `=` is found, surrounded by whitespace.
|
||||
/// It is not a token, but it is part of the string representation of some tokens.
|
||||
UnmatchedPartialToken {
|
||||
/// The unmatched partial token.
|
||||
first: PartialToken,
|
||||
/// The token that follows the unmatched partial token and that cannot be matched to the partial token, or `None`, if `first` is the last partial token in the stream.
|
||||
second: Option<PartialToken>,
|
||||
},
|
||||
}
|
||||
|
||||
impl Error {
|
||||
pub fn wrong_operator_argument_amount(actual: usize, expected: usize) -> Self {
|
||||
pub(crate) fn wrong_operator_argument_amount(actual: usize, expected: usize) -> Self {
|
||||
Error::WrongOperatorArgumentAmount { actual, expected }
|
||||
}
|
||||
|
||||
pub fn wrong_function_argument_amount(actual: usize, expected: usize) -> Self {
|
||||
pub(crate) fn wrong_function_argument_amount(actual: usize, expected: usize) -> Self {
|
||||
Error::WrongFunctionArgumentAmount { actual, expected }
|
||||
}
|
||||
|
||||
/// Constructs `Error::ExpectedNumber(actual)`.
|
||||
pub fn expected_number(actual: Value) -> Self {
|
||||
Error::ExpectedNumber { actual }
|
||||
}
|
||||
|
||||
/// Constructs `Error::ExpectedBoolean(actual)`.
|
||||
pub fn expected_boolean(actual: Value) -> Self {
|
||||
Error::ExpectedBoolean { actual }
|
||||
}
|
||||
|
||||
pub fn unmatched_partial_token(first: PartialToken, second: Option<PartialToken>) -> Self {
|
||||
pub(crate) fn unmatched_partial_token(
|
||||
first: PartialToken,
|
||||
second: Option<PartialToken>,
|
||||
) -> Self {
|
||||
Error::UnmatchedPartialToken { first, second }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expect_operator_argument_amount(actual: usize, expected: usize) -> Result<(), Error> {
|
||||
/// Returns `Ok(())` if the actual and expected parameters are equal, and `Err(Error::WrongOperatorArgumentAmount)` otherwise.
|
||||
pub(crate) fn expect_operator_argument_amount(actual: usize, expected: usize) -> Result<(), Error> {
|
||||
if actual == expected {
|
||||
Ok(())
|
||||
} else {
|
||||
@ -83,7 +116,8 @@ pub fn expect_operator_argument_amount(actual: usize, expected: usize) -> Result
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expect_function_argument_amount(actual: usize, expected: usize) -> Result<(), Error> {
|
||||
/// Returns `Ok(())` if the actual and expected parameters are equal, and `Err(Error::WrongFunctionArgumentAmount)` otherwise.
|
||||
pub(crate) fn expect_function_argument_amount(actual: usize, expected: usize) -> Result<(), Error> {
|
||||
if actual == expected {
|
||||
Ok(())
|
||||
} else {
|
||||
@ -91,6 +125,9 @@ pub fn expect_function_argument_amount(actual: usize, expected: usize) -> Result
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `Ok(())` if the given value is numeric.
|
||||
/// Numeric types are `Value::Int` and `Value::Float`.
|
||||
/// Otherwise, `Err(Error::ExpectedNumber)` is returned.
|
||||
pub fn expect_number(actual: &Value) -> Result<(), Error> {
|
||||
match actual {
|
||||
Value::Float(_) | Value::Int(_) => Ok(()),
|
||||
@ -98,6 +135,7 @@ pub fn expect_number(actual: &Value) -> Result<(), Error> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `Ok(())` if the given value is a `Value::Boolean`, or `Err(Error::ExpectedBoolean)` otherwise.
|
||||
pub fn expect_boolean(actual: &Value) -> Result<bool, Error> {
|
||||
match actual {
|
||||
Value::Boolean(boolean) => Ok(*boolean),
|
||||
|
@ -1,14 +1,34 @@
|
||||
use error::{self, Error};
|
||||
use value::Value;
|
||||
|
||||
pub mod builtin;
|
||||
pub(crate) mod builtin;
|
||||
|
||||
/// A user-defined function.
|
||||
/// Functions can be used in expressions by storing them in a `Configuration`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use evalexpr::*;
|
||||
///
|
||||
/// let mut configuration = HashMapConfiguration::new();
|
||||
/// configuration.insert_function("id", Function::new(Some(1), Box::new(|arguments| {
|
||||
/// Ok(arguments[0].clone())
|
||||
/// })));
|
||||
/// assert_eq!(eval_with_configuration("id(4)", &configuration), Ok(Value::from(4)));
|
||||
/// ```
|
||||
pub struct Function {
|
||||
argument_amount: Option<usize>,
|
||||
function: Box<Fn(&[Value]) -> Result<Value, Error>>,
|
||||
}
|
||||
|
||||
impl Function {
|
||||
/// Creates a user-defined function.
|
||||
///
|
||||
/// The `argument_amount` is the amount of arguments this function takes.
|
||||
/// It is verified before the actual function is executed, assuming it is not `None`.
|
||||
///
|
||||
/// The `function` is a boxed function that takes a slice of values and returns a `Result<Value, Error>`.
|
||||
pub fn new(
|
||||
argument_amount: Option<usize>,
|
||||
function: Box<Fn(&[Value]) -> Result<Value, Error>>,
|
||||
@ -19,7 +39,7 @@ impl Function {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn call(&self, arguments: &[Value]) -> Result<Value, Error> {
|
||||
pub(crate) fn call(&self, arguments: &[Value]) -> Result<Value, Error> {
|
||||
if let Some(argument_amount) = self.argument_amount {
|
||||
error::expect_function_argument_amount(arguments.len(), argument_amount)?;
|
||||
}
|
||||
|
@ -83,7 +83,7 @@ fn char_to_partial_token(c: char) -> PartialToken {
|
||||
|
||||
impl Token {
|
||||
// Make this a const fn as soon as match gets stable (issue #57563)
|
||||
pub fn is_leftsided_value(&self) -> bool {
|
||||
pub(crate) fn is_leftsided_value(&self) -> bool {
|
||||
match self {
|
||||
Token::Plus => false,
|
||||
Token::Minus => false,
|
||||
@ -115,7 +115,7 @@ impl Token {
|
||||
}
|
||||
|
||||
// Make this a const fn as soon as match gets stable (issue #57563)
|
||||
pub fn is_rightsided_value(&self) -> bool {
|
||||
pub(crate) fn is_rightsided_value(&self) -> bool {
|
||||
match self {
|
||||
Token::Plus => false,
|
||||
Token::Minus => false,
|
||||
@ -242,6 +242,6 @@ fn resolve_literals(mut tokens: &[PartialToken]) -> Result<Vec<Token>, Error> {
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn tokenize(string: &str) -> Result<Vec<Token>, Error> {
|
||||
pub(crate) fn tokenize(string: &str) -> Result<Vec<Token>, Error> {
|
||||
resolve_literals(&str_to_tokens(string))
|
||||
}
|
||||
|
@ -3,6 +3,19 @@ use token::Token;
|
||||
|
||||
mod display;
|
||||
|
||||
/// A node in the operator tree.
|
||||
/// The operator tree is created by the crate-level `build_operator_tree` method.
|
||||
/// It can be evaluated for a given configuration with the `Node::eval` method.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use evalexpr::*;
|
||||
///
|
||||
/// let node = build_operator_tree("1 + 2").unwrap();
|
||||
/// assert_eq!(node.eval(&EmptyConfiguration), Ok(Value::from(3)));
|
||||
/// ```
|
||||
///
|
||||
#[derive(Debug)]
|
||||
pub struct Node {
|
||||
children: Vec<Node>,
|
||||
@ -21,6 +34,9 @@ impl Node {
|
||||
Self::new(RootNode)
|
||||
}
|
||||
|
||||
/// Evaluates the operator tree rooted at this node.
|
||||
///
|
||||
/// Fails, if and operator is used with a wrong number of arguments or a wrong type.
|
||||
pub fn eval(&self, configuration: &Configuration) -> Result<Value, Error> {
|
||||
let mut arguments = Vec::new();
|
||||
for child in self.children() {
|
||||
@ -29,11 +45,11 @@ impl Node {
|
||||
self.operator().eval(&arguments, configuration)
|
||||
}
|
||||
|
||||
pub fn children(&self) -> &[Node] {
|
||||
fn children(&self) -> &[Node] {
|
||||
&self.children
|
||||
}
|
||||
|
||||
pub fn operator(&self) -> &Box<dyn Operator> {
|
||||
fn operator(&self) -> &Box<dyn Operator> {
|
||||
&self.operator
|
||||
}
|
||||
|
||||
@ -81,7 +97,7 @@ impl Node {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tokens_to_operator_tree(tokens: Vec<Token>) -> Result<Node, Error> {
|
||||
pub(crate) fn tokens_to_operator_tree(tokens: Vec<Token>) -> Result<Node, Error> {
|
||||
let mut root = vec![Node::root_node()];
|
||||
let mut last_token_is_rightsided_value = false;
|
||||
let mut token_iter = tokens.iter().peekable();
|
||||
|
Loading…
Reference in New Issue
Block a user