parent
cdcd24e7b6
commit
38c4c35a0b
@ -2,12 +2,19 @@ use crate::value::Value;
|
|||||||
use function::Function;
|
use function::Function;
|
||||||
use std::collections::HashMap;
|
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 {
|
pub trait Configuration {
|
||||||
|
/// Returns the value that is linked to the given identifier.
|
||||||
fn get_value(&self, identifier: &str) -> Option<&Value>;
|
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>;
|
fn get_function(&self, identifier: &str) -> Option<&Function>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A configuration that returns `None` for each identifier.
|
||||||
pub struct EmptyConfiguration;
|
pub struct EmptyConfiguration;
|
||||||
|
|
||||||
impl Configuration for 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 {
|
pub struct HashMapConfiguration {
|
||||||
variables: HashMap<String, Value>,
|
variables: HashMap<String, Value>,
|
||||||
functions: HashMap<String, Function>,
|
functions: HashMap<String, Function>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HashMapConfiguration {
|
impl HashMapConfiguration {
|
||||||
|
/// Constructs a `HashMapConfiguration` with no mappings.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
variables: Default::default(),
|
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) {
|
pub fn insert_variable<S: Into<String>, V: Into<Value>>(&mut self, identifier: S, value: V) {
|
||||||
self.variables.insert(identifier.into(), value.into());
|
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) {
|
pub fn insert_function<S: Into<String>>(&mut self, identifier: S, function: Function) {
|
||||||
self.functions.insert(identifier.into(), 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 crate::value::Value;
|
||||||
use token::PartialToken;
|
use token::PartialToken;
|
||||||
|
|
||||||
|
/// Errors used in this crate.
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
|
/// An operator was called with a wrong amount of arguments.
|
||||||
WrongOperatorArgumentAmount {
|
WrongOperatorArgumentAmount {
|
||||||
|
/// Expected amount of arguments.
|
||||||
expected: usize,
|
expected: usize,
|
||||||
|
/// Actual amount of arguments.
|
||||||
actual: usize,
|
actual: usize,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// A function was called with a wrong amount of arguments.
|
||||||
WrongFunctionArgumentAmount {
|
WrongFunctionArgumentAmount {
|
||||||
|
/// Expected amount of arguments.
|
||||||
expected: usize,
|
expected: usize,
|
||||||
|
/// Actual amount of arguments.
|
||||||
actual: usize,
|
actual: usize,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// A numeric value was expected.
|
||||||
|
/// Numeric values are the variants `Value::Int` and `Value::Float`.
|
||||||
ExpectedNumber {
|
ExpectedNumber {
|
||||||
|
/// Actual value.
|
||||||
actual: Value,
|
actual: Value,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// A boolean value was expected.
|
||||||
ExpectedBoolean {
|
ExpectedBoolean {
|
||||||
|
/// Actual value.
|
||||||
actual: Value,
|
actual: Value,
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -47,35 +69,46 @@ pub enum Error {
|
|||||||
/// A closing brace without a matching opening brace was found.
|
/// A closing brace without a matching opening brace was found.
|
||||||
UnmatchedRBrace,
|
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 {
|
UnmatchedPartialToken {
|
||||||
|
/// The unmatched partial token.
|
||||||
first: PartialToken,
|
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>,
|
second: Option<PartialToken>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Error {
|
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 }
|
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 }
|
Error::WrongFunctionArgumentAmount { actual, expected }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Constructs `Error::ExpectedNumber(actual)`.
|
||||||
pub fn expected_number(actual: Value) -> Self {
|
pub fn expected_number(actual: Value) -> Self {
|
||||||
Error::ExpectedNumber { actual }
|
Error::ExpectedNumber { actual }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Constructs `Error::ExpectedBoolean(actual)`.
|
||||||
pub fn expected_boolean(actual: Value) -> Self {
|
pub fn expected_boolean(actual: Value) -> Self {
|
||||||
Error::ExpectedBoolean { actual }
|
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 }
|
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 {
|
if actual == expected {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} 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 {
|
if actual == expected {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} 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> {
|
pub fn expect_number(actual: &Value) -> Result<(), Error> {
|
||||||
match actual {
|
match actual {
|
||||||
Value::Float(_) | Value::Int(_) => Ok(()),
|
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> {
|
pub fn expect_boolean(actual: &Value) -> Result<bool, Error> {
|
||||||
match actual {
|
match actual {
|
||||||
Value::Boolean(boolean) => Ok(*boolean),
|
Value::Boolean(boolean) => Ok(*boolean),
|
||||||
|
@ -1,14 +1,34 @@
|
|||||||
use error::{self, Error};
|
use error::{self, Error};
|
||||||
use value::Value;
|
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 {
|
pub struct Function {
|
||||||
argument_amount: Option<usize>,
|
argument_amount: Option<usize>,
|
||||||
function: Box<Fn(&[Value]) -> Result<Value, Error>>,
|
function: Box<Fn(&[Value]) -> Result<Value, Error>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Function {
|
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(
|
pub fn new(
|
||||||
argument_amount: Option<usize>,
|
argument_amount: Option<usize>,
|
||||||
function: Box<Fn(&[Value]) -> Result<Value, Error>>,
|
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 {
|
if let Some(argument_amount) = self.argument_amount {
|
||||||
error::expect_function_argument_amount(arguments.len(), 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 {
|
impl Token {
|
||||||
// Make this a const fn as soon as match gets stable (issue #57563)
|
// 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 {
|
match self {
|
||||||
Token::Plus => false,
|
Token::Plus => false,
|
||||||
Token::Minus => false,
|
Token::Minus => false,
|
||||||
@ -115,7 +115,7 @@ impl Token {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Make this a const fn as soon as match gets stable (issue #57563)
|
// 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 {
|
match self {
|
||||||
Token::Plus => false,
|
Token::Plus => false,
|
||||||
Token::Minus => false,
|
Token::Minus => false,
|
||||||
@ -242,6 +242,6 @@ fn resolve_literals(mut tokens: &[PartialToken]) -> Result<Vec<Token>, Error> {
|
|||||||
Ok(result)
|
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))
|
resolve_literals(&str_to_tokens(string))
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,19 @@ use token::Token;
|
|||||||
|
|
||||||
mod display;
|
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)]
|
#[derive(Debug)]
|
||||||
pub struct Node {
|
pub struct Node {
|
||||||
children: Vec<Node>,
|
children: Vec<Node>,
|
||||||
@ -21,6 +34,9 @@ impl Node {
|
|||||||
Self::new(RootNode)
|
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> {
|
pub fn eval(&self, configuration: &Configuration) -> Result<Value, Error> {
|
||||||
let mut arguments = Vec::new();
|
let mut arguments = Vec::new();
|
||||||
for child in self.children() {
|
for child in self.children() {
|
||||||
@ -29,11 +45,11 @@ impl Node {
|
|||||||
self.operator().eval(&arguments, configuration)
|
self.operator().eval(&arguments, configuration)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn children(&self) -> &[Node] {
|
fn children(&self) -> &[Node] {
|
||||||
&self.children
|
&self.children
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn operator(&self) -> &Box<dyn Operator> {
|
fn operator(&self) -> &Box<dyn Operator> {
|
||||||
&self.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 root = vec![Node::root_node()];
|
||||||
let mut last_token_is_rightsided_value = false;
|
let mut last_token_is_rightsided_value = false;
|
||||||
let mut token_iter = tokens.iter().peekable();
|
let mut token_iter = tokens.iter().peekable();
|
||||||
|
Loading…
Reference in New Issue
Block a user