Refactor operators into an enum

(This commit is not formatted properly)

Relates to #46
This commit is contained in:
Sebastian Schmidt 2019-04-14 16:54:35 +02:00
parent 697fbb5d45
commit 6c078c49e5
10 changed files with 589 additions and 962 deletions

View File

@ -18,18 +18,22 @@ impl fmt::Display for EvalexprError {
), ),
ExpectedString { actual } => { ExpectedString { actual } => {
write!(f, "Expected a Value::String, but got {:?}.", actual) write!(f, "Expected a Value::String, but got {:?}.", actual)
}, }
ExpectedInt { actual } => write!(f, "Expected a Value::Int, but got {:?}.", actual), ExpectedInt { actual } => write!(f, "Expected a Value::Int, but got {:?}.", actual),
ExpectedFloat { actual } => write!(f, "Expected a Value::Float, but got {:?}.", actual), ExpectedFloat { actual } => write!(f, "Expected a Value::Float, but got {:?}.", actual),
ExpectedNumber { actual } => { ExpectedNumber { actual } => write!(
write!(f, "Expected a Value::Float or Value::Int, but got {:?}.", actual) f,
}, "Expected a Value::Float or Value::Int, but got {:?}.",
ExpectedNumberOrString { actual } => { actual
write!(f, "Expected a Value::Number or a Value::String, but got {:?}.", actual) ),
}, ExpectedNumberOrString { actual } => write!(
f,
"Expected a Value::Number or a Value::String, but got {:?}.",
actual
),
ExpectedBoolean { actual } => { ExpectedBoolean { actual } => {
write!(f, "Expected a Value::Boolean, but got {:?}.", actual) write!(f, "Expected a Value::Boolean, but got {:?}.", actual)
}, }
ExpectedTuple { actual } => write!(f, "Expected a Value::Tuple, but got {:?}.", actual), ExpectedTuple { actual } => write!(f, "Expected a Value::Tuple, but got {:?}.", actual),
ExpectedEmpty { actual } => write!(f, "Expected a Value::Empty, but got {:?}.", actual), ExpectedEmpty { actual } => write!(f, "Expected a Value::Empty, but got {:?}.", actual),
AppendedToLeafNode => write!(f, "Tried to append a node to a leaf node."), AppendedToLeafNode => write!(f, "Tried to append a node to a leaf node."),
@ -49,7 +53,7 @@ impl fmt::Display for EvalexprError {
), ),
TypeError { expected, actual } => { TypeError { expected, actual } => {
write!(f, "Expected one of {:?}, but got {:?}.", expected, actual) write!(f, "Expected one of {:?}, but got {:?}.", expected, actual)
}, }
UnmatchedLBrace => write!(f, "Found an unmatched opening parenthesis '('."), UnmatchedLBrace => write!(f, "Found an unmatched opening parenthesis '('."),
UnmatchedRBrace => write!(f, "Found an unmatched closing parenthesis ')'."), UnmatchedRBrace => write!(f, "Found an unmatched closing parenthesis ')'."),
UnmatchedPartialToken { first, second } => { UnmatchedPartialToken { first, second } => {
@ -67,7 +71,7 @@ impl fmt::Display for EvalexprError {
first first
) )
} }
}, }
AdditionError { augend, addend } => write!(f, "Error adding {} + {}", augend, addend), AdditionError { augend, addend } => write!(f, "Error adding {} + {}", augend, addend),
SubtractionError { SubtractionError {
minuend, minuend,
@ -80,11 +84,15 @@ impl fmt::Display for EvalexprError {
} => write!(f, "Error multiplying {} * {}", multiplicand, multiplier), } => write!(f, "Error multiplying {} * {}", multiplicand, multiplier),
DivisionError { dividend, divisor } => { DivisionError { dividend, divisor } => {
write!(f, "Error dividing {} / {}", dividend, divisor) write!(f, "Error dividing {} / {}", dividend, divisor)
}, }
ModulationError { dividend, divisor } => { ModulationError { dividend, divisor } => {
write!(f, "Error modulating {} % {}", dividend, divisor) write!(f, "Error modulating {} % {}", dividend, divisor)
}, }
InvalidRegex { regex, message } => write!(f, "Regular expression {:?} is invalid: {:?}", regex, message), InvalidRegex { regex, message } => write!(
f,
"Regular expression {:?} is invalid: {:?}",
regex, message
),
ContextNotManipulable => write!(f, "Cannot manipulate context"), ContextNotManipulable => write!(f, "Cannot manipulate context"),
IllegalEscapeSequence(string) => write!(f, "Illegal escape sequence: {}", string), IllegalEscapeSequence(string) => write!(f, "Illegal escape sequence: {}", string),
CustomMessage(message) => write!(f, "Error: {}", message), CustomMessage(message) => write!(f, "Error: {}", message),

View File

@ -290,7 +290,7 @@ impl EvalexprError {
/// Constructs `EvalexprError::InvalidRegex(regex)` /// Constructs `EvalexprError::InvalidRegex(regex)`
pub fn invalid_regex(regex: String, message: String) -> Self { pub fn invalid_regex(regex: String, message: String) -> Self {
EvalexprError::InvalidRegex{ regex, message } EvalexprError::InvalidRegex { regex, message }
} }
} }
@ -309,10 +309,7 @@ pub(crate) fn expect_operator_argument_amount(
} }
/// Returns `Ok(())` if the actual and expected parameters are equal, and `Err(Error::WrongFunctionArgumentAmount)` otherwise. /// Returns `Ok(())` if the actual and expected parameters are equal, and `Err(Error::WrongFunctionArgumentAmount)` otherwise.
pub fn expect_function_argument_amount( pub fn expect_function_argument_amount(actual: usize, expected: usize) -> EvalexprResult<()> {
actual: usize,
expected: usize,
) -> EvalexprResult<()> {
if actual == expected { if actual == expected {
Ok(()) Ok(())
} else { } else {

View File

@ -67,7 +67,6 @@ pub fn builtin_function(identifier: &str) -> Option<Function> {
)), )),
// string functions // string functions
#[cfg(feature = "regex_support")] #[cfg(feature = "regex_support")]
"str::regex_matches" => Some(Function::new( "str::regex_matches" => Some(Function::new(
Some(2), Some(2),
@ -76,7 +75,10 @@ pub fn builtin_function(identifier: &str) -> Option<Function> {
let re_str = expect_string(&arguments[1])?; let re_str = expect_string(&arguments[1])?;
match Regex::new(re_str) { match Regex::new(re_str) {
Ok(re) => Ok(Value::Boolean(re.is_match(subject))), Ok(re) => Ok(Value::Boolean(re.is_match(subject))),
Err(err) => Err(EvalexprError::invalid_regex(re_str.to_string(), format!("{}", err))) Err(err) => Err(EvalexprError::invalid_regex(
re_str.to_string(),
format!("{}", err),
)),
} }
}), }),
)), )),
@ -89,7 +91,10 @@ pub fn builtin_function(identifier: &str) -> Option<Function> {
let repl = expect_string(&arguments[2])?; let repl = expect_string(&arguments[2])?;
match Regex::new(re_str) { match Regex::new(re_str) {
Ok(re) => Ok(Value::String(re.replace_all(subject, repl).to_string())), Ok(re) => Ok(Value::String(re.replace_all(subject, repl).to_string())),
Err(err) => Err(EvalexprError::invalid_regex(re_str.to_string(), format!("{}", err))), Err(err) => Err(EvalexprError::invalid_regex(
re_str.to_string(),
format!("{}", err),
)),
} }
}), }),
)), )),

View File

@ -2,140 +2,37 @@ use std::fmt::{Display, Error, Formatter};
use operator::*; use operator::*;
impl Display for RootNode { impl Display for Operator {
fn fmt(&self, _f: &mut Formatter) -> Result<(), Error> {
Ok(())
}
}
impl Display for Add {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
write!(f, "+") use crate::operator::Operator::*;
} match self {
} RootNode => Ok(()),
Add => write!(f, "+"),
Sub => write!(f, "-"),
Neg => write!(f, "-"),
Mul => write!(f, "*"),
Div => write!(f, "/"),
Mod => write!(f, "%"),
Exp => write!(f, "^"),
impl Display for Sub { Eq => write!(f, "=="),
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { Neq => write!(f, "!="),
write!(f, "-") Gt => write!(f, ">"),
} Lt => write!(f, "<"),
} Geq => write!(f, ">="),
Leq => write!(f, "<="),
And => write!(f, "&&"),
Or => write!(f, "||"),
Not => write!(f, "!"),
impl Display for Neg { Tuple => write!(f, ", "),
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { Assign => write!(f, " = "),
write!(f, "-")
}
}
impl Display for Mul { Chain => write!(f, "; "),
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
write!(f, "*")
}
}
impl Display for Div { Const { value } => write!(f, "{}", value),
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { VariableIdentifier { identifier } => write!(f, "{}", identifier),
write!(f, "/") FunctionIdentifier { identifier } => write!(f, "{}", identifier),
} }
}
impl Display for Mod {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
write!(f, "%")
}
}
impl Display for Exp {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
write!(f, "^")
}
}
impl Display for Eq {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
write!(f, "==")
}
}
impl Display for Neq {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
write!(f, "!=")
}
}
impl Display for Gt {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
write!(f, ">")
}
}
impl Display for Lt {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
write!(f, "<")
}
}
impl Display for Geq {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
write!(f, ">=")
}
}
impl Display for Leq {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
write!(f, "<=")
}
}
impl Display for And {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
write!(f, "&&")
}
}
impl Display for Or {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
write!(f, "||")
}
}
impl Display for Not {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
write!(f, "!")
}
}
impl Display for Tuple {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
write!(f, ", ")
}
}
impl Display for Assign {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
write!(f, "; ")
}
}
impl Display for Chain {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
write!(f, " = ")
}
}
impl Display for Const {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
write!(f, "{}", self.value)
}
}
impl Display for VariableIdentifier {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
write!(f, "{}", self.identifier)
}
}
impl Display for FunctionIdentifier {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
write!(f, "{}", self.identifier)
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -83,7 +83,7 @@ fn char_to_partial_token(c: char) -> PartialToken {
} else { } else {
PartialToken::Literal(c.to_string()) PartialToken::Literal(c.to_string())
} }
}, }
} }
} }
@ -233,7 +233,7 @@ fn partial_tokens_to_tokens(mut tokens: &[PartialToken]) -> EvalexprResult<Vec<T
PartialToken::Token(token) => { PartialToken::Token(token) => {
cutoff = 1; cutoff = 1;
Some(token) Some(token)
}, }
PartialToken::Literal(literal) => { PartialToken::Literal(literal) => {
cutoff = 1; cutoff = 1;
if let Ok(number) = literal.parse::<IntType>() { if let Ok(number) = literal.parse::<IntType>() {
@ -245,38 +245,38 @@ fn partial_tokens_to_tokens(mut tokens: &[PartialToken]) -> EvalexprResult<Vec<T
} else { } else {
Some(Token::Identifier(literal.to_string())) Some(Token::Identifier(literal.to_string()))
} }
}, }
PartialToken::Whitespace => { PartialToken::Whitespace => {
cutoff = 1; cutoff = 1;
None None
}, }
PartialToken::Eq => match second { PartialToken::Eq => match second {
Some(PartialToken::Eq) => Some(Token::Eq), Some(PartialToken::Eq) => Some(Token::Eq),
_ => { _ => {
cutoff = 1; cutoff = 1;
Some(Token::Assign) Some(Token::Assign)
}, }
}, },
PartialToken::ExclamationMark => match second { PartialToken::ExclamationMark => match second {
Some(PartialToken::Eq) => Some(Token::Eq), Some(PartialToken::Eq) => Some(Token::Eq),
_ => { _ => {
cutoff = 1; cutoff = 1;
Some(Token::Not) Some(Token::Not)
}, }
}, },
PartialToken::Gt => match second { PartialToken::Gt => match second {
Some(PartialToken::Eq) => Some(Token::Geq), Some(PartialToken::Eq) => Some(Token::Geq),
_ => { _ => {
cutoff = 1; cutoff = 1;
Some(Token::Gt) Some(Token::Gt)
}, }
}, },
PartialToken::Lt => match second { PartialToken::Lt => match second {
Some(PartialToken::Eq) => Some(Token::Leq), Some(PartialToken::Eq) => Some(Token::Leq),
_ => { _ => {
cutoff = 1; cutoff = 1;
Some(Token::Lt) Some(Token::Lt)
}, }
}, },
PartialToken::Ampersand => match second { PartialToken::Ampersand => match second {
Some(PartialToken::Ampersand) => Some(Token::And), Some(PartialToken::Ampersand) => Some(Token::And),

View File

@ -1,5 +1,5 @@
use Node;
use std::slice::Iter; use std::slice::Iter;
use Node;
/// An iterator that traverses an operator tree in pre-order. /// An iterator that traverses an operator tree in pre-order.
pub struct NodeIter<'a> { pub struct NodeIter<'a> {
@ -47,7 +47,7 @@ impl<'a> Iterator for NodeIter<'a> {
impl Node { impl Node {
/// Returns an iterator over all nodes in this tree. /// Returns an iterator over all nodes in this tree.
pub fn iter(&self) -> impl Iterator<Item=&Node> { pub fn iter(&self) -> impl Iterator<Item = &Node> {
NodeIter::new(self) NodeIter::new(self)
} }
} }

View File

@ -35,19 +35,19 @@ mod iter;
#[derive(Debug)] #[derive(Debug)]
pub struct Node { pub struct Node {
children: Vec<Node>, children: Vec<Node>,
operator: Box<dyn Operator>, operator: Operator,
} }
impl Node { impl Node {
fn new<T: Operator + 'static>(operator: T) -> Self { fn new(operator: Operator) -> Self {
Self { Self {
children: Vec::new(), children: Vec::new(),
operator: Box::new(operator), operator,
} }
} }
fn root_node() -> Self { fn root_node() -> Self {
Self::new(RootNode) Self::new(Operator::RootNode)
} }
/// Returns an iterator over all identifiers in this expression. /// Returns an iterator over all identifiers in this expression.
@ -67,7 +67,11 @@ impl Node {
/// assert_eq!(iter.next(), None); /// assert_eq!(iter.next(), None);
/// ``` /// ```
pub fn iter_identifiers(&self) -> impl Iterator<Item = &str> { pub fn iter_identifiers(&self) -> impl Iterator<Item = &str> {
self.iter().filter_map(|node| node.operator.identifier()) self.iter().filter_map(|node| match node.operator() {
Operator::VariableIdentifier { identifier }
| Operator::FunctionIdentifier { identifier } => Some(identifier.as_str()),
_ => None,
})
} }
/// Returns an iterator over all variable identifiers in this expression. /// Returns an iterator over all variable identifiers in this expression.
@ -86,7 +90,10 @@ impl Node {
/// assert_eq!(iter.next(), None); /// assert_eq!(iter.next(), None);
/// ``` /// ```
pub fn iter_variable_identifiers(&self) -> impl Iterator<Item = &str> { pub fn iter_variable_identifiers(&self) -> impl Iterator<Item = &str> {
self.iter().filter_map(|node| node.operator.variable_identifier()) self.iter().filter_map(|node| match node.operator() {
Operator::VariableIdentifier { identifier } => Some(identifier.as_str()),
_ => None,
})
} }
/// Returns an iterator over all function identifiers in this expression. /// Returns an iterator over all function identifiers in this expression.
@ -103,7 +110,10 @@ impl Node {
/// assert_eq!(iter.next(), None); /// assert_eq!(iter.next(), None);
/// ``` /// ```
pub fn iter_function_identifiers(&self) -> impl Iterator<Item = &str> { pub fn iter_function_identifiers(&self) -> impl Iterator<Item = &str> {
self.iter().filter_map(|node| node.operator.function_identifier()) self.iter().filter_map(|node| match node.operator() {
Operator::FunctionIdentifier { identifier } => Some(identifier.as_str()),
_ => None,
})
} }
/// Evaluates the operator tree rooted at this node with the given context. /// Evaluates the operator tree rooted at this node with the given context.
@ -347,7 +357,7 @@ impl Node {
&self.children &self.children
} }
fn operator(&self) -> &Box<dyn Operator> { fn operator(&self) -> &Operator {
&self.operator &self.operator
} }
@ -404,60 +414,60 @@ pub(crate) fn tokens_to_operator_tree(tokens: Vec<Token>) -> EvalexprResult<Node
let next = token_iter.peek().cloned(); let next = token_iter.peek().cloned();
let node = match token.clone() { let node = match token.clone() {
Token::Plus => Some(Node::new(Add)), Token::Plus => Some(Node::new(Operator::Add)),
Token::Minus => { Token::Minus => {
if last_token_is_rightsided_value { if last_token_is_rightsided_value {
Some(Node::new(Sub)) Some(Node::new(Operator::Sub))
} else { } else {
Some(Node::new(Neg)) Some(Node::new(Operator::Neg))
} }
}, }
Token::Star => Some(Node::new(Mul)), Token::Star => Some(Node::new(Operator::Mul)),
Token::Slash => Some(Node::new(Div)), Token::Slash => Some(Node::new(Operator::Div)),
Token::Percent => Some(Node::new(Mod)), Token::Percent => Some(Node::new(Operator::Mod)),
Token::Hat => Some(Node::new(Exp)), Token::Hat => Some(Node::new(Operator::Exp)),
Token::Eq => Some(Node::new(Eq)), Token::Eq => Some(Node::new(Operator::Eq)),
Token::Neq => Some(Node::new(Neq)), Token::Neq => Some(Node::new(Operator::Neq)),
Token::Gt => Some(Node::new(Gt)), Token::Gt => Some(Node::new(Operator::Gt)),
Token::Lt => Some(Node::new(Lt)), Token::Lt => Some(Node::new(Operator::Lt)),
Token::Geq => Some(Node::new(Geq)), Token::Geq => Some(Node::new(Operator::Geq)),
Token::Leq => Some(Node::new(Leq)), Token::Leq => Some(Node::new(Operator::Leq)),
Token::And => Some(Node::new(And)), Token::And => Some(Node::new(Operator::And)),
Token::Or => Some(Node::new(Or)), Token::Or => Some(Node::new(Operator::Or)),
Token::Not => Some(Node::new(Not)), Token::Not => Some(Node::new(Operator::Not)),
Token::LBrace => { Token::LBrace => {
root.push(Node::root_node()); root.push(Node::root_node());
None None
}, }
Token::RBrace => { Token::RBrace => {
if root.len() < 2 { if root.len() < 2 {
return Err(EvalexprError::UnmatchedRBrace); return Err(EvalexprError::UnmatchedRBrace);
} else { } else {
root.pop() root.pop()
} }
}, }
Token::Comma => Some(Node::new(Tuple)), Token::Comma => Some(Node::new(Operator::Tuple)),
Token::Assign => Some(Node::new(Assign)), Token::Assign => Some(Node::new(Operator::Assign)),
Token::Semicolon => Some(Node::new(Chain)), Token::Semicolon => Some(Node::new(Operator::Chain)),
Token::Identifier(identifier) => { Token::Identifier(identifier) => {
let mut result = Some(Node::new(VariableIdentifier::new(identifier.clone()))); let mut result = Some(Node::new(Operator::variable_identifier(identifier.clone())));
if let Some(next) = next { if let Some(next) = next {
if next == &Token::Assign { if next == &Token::Assign {
result = Some(Node::new(Const::new(identifier.clone().into()))); result = Some(Node::new(Operator::value(identifier.clone().into())));
} else if next.is_leftsided_value() { } else if next.is_leftsided_value() {
result = Some(Node::new(FunctionIdentifier::new(identifier))); result = Some(Node::new(Operator::function_identifier(identifier)));
} }
} }
result result
}, }
Token::Float(float) => Some(Node::new(Const::new(Value::Float(float)))), Token::Float(float) => Some(Node::new(Operator::value(Value::Float(float)))),
Token::Int(int) => Some(Node::new(Const::new(Value::Int(int)))), Token::Int(int) => Some(Node::new(Operator::value(Value::Int(int)))),
Token::Boolean(boolean) => Some(Node::new(Const::new(Value::Boolean(boolean)))), Token::Boolean(boolean) => Some(Node::new(Operator::value(Value::Boolean(boolean)))),
Token::String(string) => Some(Node::new(Const::new(Value::String(string)))), Token::String(string) => Some(Node::new(Operator::value(Value::String(string)))),
}; };
if let Some(node) = node { if let Some(node) = node {

View File

@ -21,7 +21,7 @@ impl Display for Value {
value.fmt(f)?; value.fmt(f)?;
} }
write!(f, ")") write!(f, ")")
}, }
Value::Empty => write!(f, "()"), Value::Empty => write!(f, "()"),
} }
} }

View File

@ -279,18 +279,9 @@ fn test_n_ary_functions() {
#[test] #[test]
fn test_builtin_functions() { fn test_builtin_functions() {
assert_eq!( assert_eq!(eval("min(4.0, 3)"), Ok(Value::Int(3)));
eval("min(4.0, 3)"), assert_eq!(eval("max(4.0, 3)"), Ok(Value::Float(4.0)));
Ok(Value::Int(3)) assert_eq!(eval("len(\"foobar\")"), Ok(Value::Int(6)));
);
assert_eq!(
eval("max(4.0, 3)"),
Ok(Value::Float(4.0))
);
assert_eq!(
eval("len(\"foobar\")"),
Ok(Value::Int(6))
);
assert_eq!( assert_eq!(
eval("str::to_lowercase(\"FOOBAR\")"), eval("str::to_lowercase(\"FOOBAR\")"),
Ok(Value::from("foobar")) Ok(Value::from("foobar"))
@ -317,10 +308,10 @@ fn test_regex_functions() {
Ok(Value::Boolean(false)) Ok(Value::Boolean(false))
); );
match eval("str::regex_matches(\"foo\", \"[\")") { match eval("str::regex_matches(\"foo\", \"[\")") {
Err(EvalexprError::InvalidRegex{ regex, message }) => { Err(EvalexprError::InvalidRegex { regex, message }) => {
assert_eq!(regex, "["); assert_eq!(regex, "[");
assert!(message.contains("unclosed character class")); assert!(message.contains("unclosed character class"));
}, }
v => panic!(v), v => panic!(v),
}; };
assert_eq!( assert_eq!(