Implemented boolean expressions

This commit is contained in:
Sebastian Schmidt 2019-03-15 19:19:59 +02:00
parent a1ba054609
commit bb74bee382
5 changed files with 383 additions and 17 deletions

View File

@ -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<PartialToken>,
},
}
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<PartialToken>) -> 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<bool, Error> {
match actual {
Value::Boolean(boolean) => Ok(*boolean),
_ => Err(Error::expected_boolean(actual.clone())),
}
}

View File

@ -10,7 +10,7 @@ mod tree;
mod value;
pub fn eval(string: &str) -> Result<Value, Error> {
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));
}
}

View File

@ -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<Value, Error> {
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<Value, Error> {
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<Value, Error> {
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<Value, Error> {
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<Value, Error> {
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<Value, Error> {
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<Value, Error> {
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<Value, Error> {
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<Value, Error> {
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

View File

@ -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<PartialToken> {
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<PartialToken> {
result
}
/// Resolves all literals in the given vector of partial tokens by converting them to complex tokens.
fn resolve_literals(tokens: &Vec<PartialToken>) -> Vec<Token> {
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<Vec<Token>, 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::<IntType>() {
Token::Int(number)
} else if let Ok(number) = literal.parse::<FloatType>() {
@ -103,11 +151,50 @@ fn resolve_literals(tokens: &Vec<PartialToken>) -> Vec<Token> {
} 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<Token> {
pub fn tokenize(string: &str) -> Result<Vec<Token>, Error> {
resolve_literals(&str_to_tokens(string))
}

View File

@ -88,6 +88,17 @@ pub fn tokens_to_operator_tree(tokens: Vec<Token>) -> Result<Node, Error> {
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<Token>) -> Result<Node, Error> {
}
}
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)))),