From bb74bee38230aa2c36454aa015446b33b00e07cb Mon Sep 17 00:00:00 2001 From: Sebastian Schmidt Date: Fri, 15 Mar 2019 19:19:59 +0200 Subject: [PATCH] Implemented boolean expressions --- src/error/mod.rs | 24 +++++ src/lib.rs | 14 ++- src/operator/mod.rs | 235 ++++++++++++++++++++++++++++++++++++++++++++ src/token/mod.rs | 115 +++++++++++++++++++--- src/tree/mod.rs | 12 +++ 5 files changed, 383 insertions(+), 17 deletions(-) diff --git a/src/error/mod.rs b/src/error/mod.rs index 9e6933a..ac6a4ab 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -1,4 +1,5 @@ use crate::value::Value; +use token::PartialToken; #[derive(Debug, PartialEq)] pub enum Error { @@ -9,6 +10,9 @@ pub enum Error { ExpectedNumber { actual: Value, }, + ExpectedBoolean { + actual: Value, + }, /// The given expression is empty EmptyExpression, @@ -35,6 +39,11 @@ pub enum Error { /// A closing brace without a matching opening brace was found. UnmatchedRBrace, + + UnmatchedPartialToken { + first: PartialToken, + second: Option, + }, } impl Error { @@ -45,6 +54,14 @@ impl Error { pub fn expected_number(actual: Value) -> Self { Error::ExpectedNumber { actual } } + + pub fn expected_boolean(actual: Value) -> Self { + Error::ExpectedBoolean { actual } + } + + pub fn unmatched_partial_token(first: PartialToken, second: Option) -> Self { + Error::UnmatchedPartialToken {first, second} + } } pub fn expect_argument_amount(actual: usize, expected: usize) -> Result<(), Error> { @@ -61,3 +78,10 @@ pub fn expect_number(actual: &Value) -> Result<(), Error> { _ => Err(Error::expected_number(actual.clone())), } } + +pub fn expect_boolean(actual: &Value) -> Result { + match actual { + Value::Boolean(boolean) => Ok(*boolean), + _ => Err(Error::expected_boolean(actual.clone())), + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 6f90b4c..4af0b7e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,7 +10,7 @@ mod tree; mod value; pub fn eval(string: &str) -> Result { - tree::tokens_to_operator_tree(token::tokenize(string))?.eval(&EmptyConfiguration) + tree::tokens_to_operator_tree(token::tokenize(string)?)?.eval(&EmptyConfiguration) } #[cfg(test)] @@ -90,7 +90,15 @@ mod test { } #[test] - fn test_type_errors() { + fn test_boolean_examples() { + assert_eq!(eval("true && false"), Ok(Value::Boolean(false))); + assert_eq!(eval("true && false || true && true"), Ok(Value::Boolean(true))); + assert_eq!(eval("5 > 4 && 1 <= 1"), Ok(Value::Boolean(true))); + assert_eq!(eval("5.0 <= 4.9 || !(4 > 3.5)"), Ok(Value::Boolean(false))); + } + + #[test] + fn test_errors() { assert_eq!( eval("-true"), Err(Error::expected_number(Value::Boolean(true))) @@ -100,6 +108,6 @@ mod test { Err(Error::expected_number(Value::Boolean(true))) ); assert_eq!(eval("true-"), Err(Error::wrong_argument_amount(1, 2))); + assert_eq!(eval("!(()true)"), Err(Error::AppendedToLeafNode)); } - } diff --git a/src/operator/mod.rs b/src/operator/mod.rs index a029e95..c7e05d7 100644 --- a/src/operator/mod.rs +++ b/src/operator/mod.rs @@ -30,6 +30,16 @@ pub struct Mul; pub struct Div; pub struct Mod; +pub struct Eq; +pub struct Neq; +pub struct Gt; +pub struct Lt; +pub struct Geq; +pub struct Leq; +pub struct And; +pub struct Or; +pub struct Not; + pub struct Const { value: Value, } @@ -216,6 +226,231 @@ impl Operator for Mod { } } +impl Operator for Eq { + fn precedence(&self) -> i32 { + 80 + } + + fn argument_amount(&self) -> usize { + 2 + } + + fn eval(&self, arguments: &[Value], _configuration: &Configuration) -> Result { + expect_argument_amount(arguments.len(), 2)?; + + if arguments[0] == arguments[1] { + Ok(Value::Boolean(true)) + } else { + Ok(Value::Boolean(false)) + } + } +} + +impl Operator for Neq { + fn precedence(&self) -> i32 { + 80 + } + + fn argument_amount(&self) -> usize { + 2 + } + + fn eval(&self, arguments: &[Value], _configuration: &Configuration) -> Result { + expect_argument_amount(arguments.len(), 2)?; + + if arguments[0] != arguments[1] { + Ok(Value::Boolean(true)) + } else { + Ok(Value::Boolean(false)) + } + } +} + +impl Operator for Gt { + fn precedence(&self) -> i32 { + 80 + } + + fn argument_amount(&self) -> usize { + 2 + } + + fn eval(&self, arguments: &[Value], _configuration: &Configuration) -> Result { + expect_argument_amount(arguments.len(), 2)?; + expect_number(&arguments[0])?; + expect_number(&arguments[1])?; + + if arguments[0].is_int() && arguments[1].is_int() { + if arguments[0].as_int().unwrap() > arguments[1].as_int().unwrap() { + Ok(Value::Boolean(true)) + } else { + Ok(Value::Boolean(false)) + } + } else { + if arguments[0].as_float().unwrap() > arguments[1].as_float().unwrap() { + Ok(Value::Boolean(true)) + } else { + Ok(Value::Boolean(false)) + } + } + } +} + +impl Operator for Lt { + fn precedence(&self) -> i32 { + 80 + } + + fn argument_amount(&self) -> usize { + 2 + } + + fn eval(&self, arguments: &[Value], _configuration: &Configuration) -> Result { + expect_argument_amount(arguments.len(), 2)?; + expect_number(&arguments[0])?; + expect_number(&arguments[1])?; + + if arguments[0].is_int() && arguments[1].is_int() { + if arguments[0].as_int().unwrap() < arguments[1].as_int().unwrap() { + Ok(Value::Boolean(true)) + } else { + Ok(Value::Boolean(false)) + } + } else { + if arguments[0].as_float().unwrap() < arguments[1].as_float().unwrap() { + Ok(Value::Boolean(true)) + } else { + Ok(Value::Boolean(false)) + } + } + } +} + +impl Operator for Geq { + fn precedence(&self) -> i32 { + 80 + } + + fn argument_amount(&self) -> usize { + 2 + } + + fn eval(&self, arguments: &[Value], _configuration: &Configuration) -> Result { + expect_argument_amount(arguments.len(), 2)?; + expect_number(&arguments[0])?; + expect_number(&arguments[1])?; + + if arguments[0].is_int() && arguments[1].is_int() { + if arguments[0].as_int().unwrap() >= arguments[1].as_int().unwrap() { + Ok(Value::Boolean(true)) + } else { + Ok(Value::Boolean(false)) + } + } else { + if arguments[0].as_float().unwrap() >= arguments[1].as_float().unwrap() { + Ok(Value::Boolean(true)) + } else { + Ok(Value::Boolean(false)) + } + } + } +} + +impl Operator for Leq { + fn precedence(&self) -> i32 { + 80 + } + + fn argument_amount(&self) -> usize { + 2 + } + + fn eval(&self, arguments: &[Value], _configuration: &Configuration) -> Result { + expect_argument_amount(arguments.len(), 2)?; + expect_number(&arguments[0])?; + expect_number(&arguments[1])?; + + if arguments[0].is_int() && arguments[1].is_int() { + if arguments[0].as_int().unwrap() <= arguments[1].as_int().unwrap() { + Ok(Value::Boolean(true)) + } else { + Ok(Value::Boolean(false)) + } + } else { + if arguments[0].as_float().unwrap() <= arguments[1].as_float().unwrap() { + Ok(Value::Boolean(true)) + } else { + Ok(Value::Boolean(false)) + } + } + } +} + +impl Operator for And { + fn precedence(&self) -> i32 { + 75 + } + + fn argument_amount(&self) -> usize { + 2 + } + + fn eval(&self, arguments: &[Value], _configuration: &Configuration) -> Result { + expect_argument_amount(arguments.len(), 2)?; + let a = expect_boolean(&arguments[0])?; + let b = expect_boolean(&arguments[1])?; + + if a && b { + Ok(Value::Boolean(true)) + } else { + Ok(Value::Boolean(false)) + } + } +} + +impl Operator for Or { + fn precedence(&self) -> i32 { + 70 + } + + fn argument_amount(&self) -> usize { + 2 + } + + fn eval(&self, arguments: &[Value], _configuration: &Configuration) -> Result { + expect_argument_amount(arguments.len(), 2)?; + let a = expect_boolean(&arguments[0])?; + let b = expect_boolean(&arguments[1])?; + + if a || b { + Ok(Value::Boolean(true)) + } else { + Ok(Value::Boolean(false)) + } + } +} + +impl Operator for Not { + fn precedence(&self) -> i32 { + 110 + } + + fn argument_amount(&self) -> usize { + 1 + } + + fn eval(&self, arguments: &[Value], _configuration: &Configuration) -> Result { + expect_argument_amount(arguments.len(), 1)?; + let a = expect_boolean(&arguments[0])?; + + if !a { + Ok(Value::Boolean(true)) + } else { + Ok(Value::Boolean(false)) + } + } +} + impl Operator for Const { fn precedence(&self) -> i32 { 200 diff --git a/src/token/mod.rs b/src/token/mod.rs index ee344eb..ab3d3af 100644 --- a/src/token/mod.rs +++ b/src/token/mod.rs @@ -1,13 +1,28 @@ use value::{FloatType, IntType}; +use error::Error; -#[derive(Clone, PartialEq)] +#[derive(Clone, PartialEq, Debug)] pub enum Token { // Single character tokens + // Arithmetic Plus, Minus, Star, Slash, Percent, + + // Logic + Eq, + Neq, + Gt, + Lt, + Geq, + Leq, + And, + Or, + Not, + + // Precedence LBrace, RBrace, Whitespace, @@ -19,21 +34,37 @@ pub enum Token { Boolean(bool), } -enum PartialToken { +#[derive(Clone, Debug, PartialEq)] +pub enum PartialToken { Token(Token), Literal(String), + Eq, + ExclamationMark, + Gt, + Lt, + Ampersand, + VerticalBar, } // Make this a const fn as soon as match gets stable (issue #57563) -fn char_to_token(c: char) -> PartialToken { +fn char_to_partial_token(c: char) -> PartialToken { match c { '+' => PartialToken::Token(Token::Plus), '-' => PartialToken::Token(Token::Minus), '*' => PartialToken::Token(Token::Star), '/' => PartialToken::Token(Token::Slash), '%' => PartialToken::Token(Token::Percent), + + '=' => PartialToken::Eq, + '!' => PartialToken::ExclamationMark, + '>' => PartialToken::Gt, + '<' => PartialToken::Lt, + '&' => PartialToken::Ampersand, + '|' => PartialToken::VerticalBar, + '(' => PartialToken::Token(Token::LBrace), ')' => PartialToken::Token(Token::RBrace), + c => { if c.is_whitespace() { PartialToken::Token(Token::Whitespace) @@ -53,9 +84,21 @@ impl Token { Token::Star => false, Token::Slash => false, Token::Percent => false, + + Token::Eq => false, + Token::Neq => false, + Token::Gt => false, + Token::Lt => false, + Token::Geq => false, + Token::Leq => false, + Token::And => false, + Token::Or => false, + Token::Not => false, + Token::LBrace => false, Token::RBrace => true, Token::Whitespace => false, + Token::Identifier(_) => true, Token::Float(_) => true, Token::Int(_) => true, @@ -68,7 +111,7 @@ impl Token { fn str_to_tokens(string: &str) -> Vec { let mut result = Vec::new(); for c in string.chars() { - let partial_token = char_to_token(c); + let partial_token = char_to_partial_token(c); let if_let_successful = if let (Some(PartialToken::Literal(last)), PartialToken::Literal(literal)) = @@ -87,13 +130,18 @@ fn str_to_tokens(string: &str) -> Vec { result } -/// Resolves all literals in the given vector of partial tokens by converting them to complex tokens. -fn resolve_literals(tokens: &Vec) -> Vec { - tokens - .iter() - .map(|token| match token { - PartialToken::Token(token) => token.clone(), +/// Resolves all partial tokens by converting them to complex tokens. +fn resolve_literals(mut tokens: &[PartialToken]) -> Result, Error> { + let mut result = Vec::new(); + while tokens.len() > 0 { + let first = tokens[0].clone(); + let second = tokens.get(1).cloned(); + let mut cutoff = 2; + + result.push(match first { + PartialToken::Token(token) => {cutoff = 1; token}, PartialToken::Literal(literal) => { + cutoff = 1; if let Ok(number) = literal.parse::() { Token::Int(number) } else if let Ok(number) = literal.parse::() { @@ -103,11 +151,50 @@ fn resolve_literals(tokens: &Vec) -> Vec { } else { Token::Identifier(literal.to_string()) } - } - }) - .collect() + }, + PartialToken::Eq => { + match second { + Some(PartialToken::Eq) => Token::Eq, + _ => return Err(Error::unmatched_partial_token(first, second)), + } + }, + PartialToken::ExclamationMark => { + match second { + Some(PartialToken::Eq) => Token::Eq, + _ => {cutoff = 1; Token::Not}, + } + }, + PartialToken::Gt => { + match second { + Some(PartialToken::Eq) => Token::Geq, + _ => {cutoff = 1; Token::Gt}, + } + }, + PartialToken::Lt => { + match second { + Some(PartialToken::Eq) => Token::Leq, + _ => {cutoff = 1; Token::Lt}, + } + }, + PartialToken::Ampersand => { + match second { + Some(PartialToken::Ampersand) => Token::And, + _ => return Err(Error::unmatched_partial_token(first, second)), + } + }, + PartialToken::VerticalBar => { + match second { + Some(PartialToken::VerticalBar) => Token::Or, + _ => return Err(Error::unmatched_partial_token(first, second)), + } + }, + }); + + tokens = &tokens[cutoff..]; + } + Ok(result) } -pub fn tokenize(string: &str) -> Vec { +pub fn tokenize(string: &str) -> Result, Error> { resolve_literals(&str_to_tokens(string)) } diff --git a/src/tree/mod.rs b/src/tree/mod.rs index c42c1b5..121668b 100644 --- a/src/tree/mod.rs +++ b/src/tree/mod.rs @@ -88,6 +88,17 @@ pub fn tokens_to_operator_tree(tokens: Vec) -> Result { Token::Star => Some(Node::new(Mul)), Token::Slash => Some(Node::new(Div)), Token::Percent => Some(Node::new(Mod)), + + Token::Eq => Some(Node::new(Eq)), + Token::Neq => Some(Node::new(Neq)), + Token::Gt => Some(Node::new(Gt)), + Token::Lt => Some(Node::new(Lt)), + Token::Geq => Some(Node::new(Geq)), + Token::Leq => Some(Node::new(Leq)), + Token::And => Some(Node::new(And)), + Token::Or => Some(Node::new(Or)), + Token::Not => Some(Node::new(Not)), + Token::LBrace => { root.push(Node::root_node()); None @@ -100,6 +111,7 @@ pub fn tokens_to_operator_tree(tokens: Vec) -> Result { } } Token::Whitespace => None, + Token::Identifier(identifier) => Some(Node::new(Identifier::new(identifier))), Token::Float(number) => Some(Node::new(Const::new(Value::Float(number)))), Token::Int(number) => Some(Node::new(Const::new(Value::Int(number)))),