Implemented boolean expressions
This commit is contained in:
parent
a1ba054609
commit
bb74bee382
@ -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())),
|
||||
}
|
||||
}
|
14
src/lib.rs
14
src/lib.rs
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
|
115
src/token/mod.rs
115
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<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))
|
||||
}
|
||||
|
@ -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)))),
|
||||
|
Loading…
Reference in New Issue
Block a user