From a5e09295f87747bf4a43e670c8890164b065472b Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 2 Mar 2020 14:28:42 +0800 Subject: [PATCH] Allow comparisons between different types (returning false). --- README.md | 12 ++ src/any.rs | 2 +- src/api.rs | 173 ++++++++++++++++++++++++++ src/builtin.rs | 5 +- src/engine.rs | 294 ++++++++++---------------------------------- src/fn_register.rs | 4 +- src/lib.rs | 1 + src/parser.rs | 97 +++++++++------ tests/binary_ops.rs | 7 ++ 9 files changed, 329 insertions(+), 266 deletions(-) create mode 100644 src/api.rs diff --git a/README.md b/README.md index cced83b6..1f670281 100644 --- a/README.md +++ b/README.md @@ -393,6 +393,18 @@ let x = 3; let x = (1 + 2) * (6 - 4) / 2; ``` +## Comparison operators + +You can compare most values of the same data type. If you compare two values of _different_ data types, the result is always `false`. + +```rust +42 == 42; // true +42 > 42; // false +"hello" > "foo"; // true +"42" == 42; // false +42 == 42.0; // false - i64 is different from f64 +``` + ## Boolean operators Double boolean operators `&&` and `||` _short-circuit_, meaning that the second operand will not be evaluated if the first one already proves the condition wrong. diff --git a/src/any.rs b/src/any.rs index 3584a5e7..c3c969cb 100644 --- a/src/any.rs +++ b/src/any.rs @@ -73,7 +73,7 @@ impl Variant { impl Clone for Dynamic { fn clone(&self) -> Self { - Any::into_dynamic(self.as_ref() as &Variant) + Any::into_dynamic(self.as_ref()) } } diff --git a/src/api.rs b/src/api.rs new file mode 100644 index 00000000..f8339944 --- /dev/null +++ b/src/api.rs @@ -0,0 +1,173 @@ +use crate::any::{Any, AnyExt}; +use crate::engine::{FnIntExt, FnSpec}; +use crate::parser::{lex, parse}; +use crate::{Dynamic, Engine, EvalAltResult, ParseError, Scope, AST}; +use std::sync::Arc; + +impl Engine { + /// Compile a string into an AST + pub fn compile(input: &str) -> Result { + let tokens = lex(input); + + let mut peekables = tokens.peekable(); + let tree = parse(&mut peekables); + + tree + } + + /// Compile a file into an AST + pub fn compile_file(filename: &str) -> Result { + use std::fs::File; + use std::io::prelude::*; + + let mut f = File::open(filename) + .map_err(|_| EvalAltResult::ErrorCantOpenScriptFile(filename.into()))?; + + let mut contents = String::new(); + + f.read_to_string(&mut contents) + .map_err(|_| EvalAltResult::ErrorCantOpenScriptFile(filename.into())) + .and_then(|_| Self::compile(&contents).map_err(EvalAltResult::ErrorParsing)) + } + + /// Evaluate a file + pub fn eval_file(&mut self, filename: &str) -> Result { + use std::fs::File; + use std::io::prelude::*; + + let mut f = File::open(filename) + .map_err(|_| EvalAltResult::ErrorCantOpenScriptFile(filename.into()))?; + + let mut contents = String::new(); + + f.read_to_string(&mut contents) + .map_err(|_| EvalAltResult::ErrorCantOpenScriptFile(filename.into())) + .and_then(|_| self.eval::(&contents)) + } + + /// Evaluate a string + pub fn eval(&mut self, input: &str) -> Result { + let mut scope = Scope::new(); + self.eval_with_scope(&mut scope, input) + } + + /// Evaluate a string with own scope + pub fn eval_with_scope( + &mut self, + scope: &mut Scope, + input: &str, + ) -> Result { + let ast = Self::compile(input).map_err(EvalAltResult::ErrorParsing)?; + self.eval_ast_with_scope(scope, &ast) + } + + /// Evaluate an AST + pub fn eval_ast(&mut self, ast: &AST) -> Result { + let mut scope = Scope::new(); + self.eval_ast_with_scope(&mut scope, ast) + } + + /// Evaluate an AST with own scope + pub fn eval_ast_with_scope( + &mut self, + scope: &mut Scope, + ast: &AST, + ) -> Result { + let AST(os, fns) = ast; + + for f in fns { + let spec = FnSpec { + ident: f.name.clone(), + args: None, + }; + + self.script_fns + .insert(spec, Arc::new(FnIntExt::Int(f.clone()))); + } + + let result = os + .iter() + .try_fold(Box::new(()) as Dynamic, |_, o| self.eval_stmt(scope, o)); + + self.script_fns.clear(); // Clean up engine + + match result { + Err(EvalAltResult::Return(out)) | Ok(out) => Ok(*out + .downcast::() + .map_err(|err| EvalAltResult::ErrorMismatchOutputType((*err).type_name()))?), + Err(err) => Err(err), + } + } + + /// Evaluate a file, but only return errors, if there are any. + /// Useful for when you don't need the result, but still need + /// to keep track of possible errors + pub fn consume_file(&mut self, filename: &str) -> Result<(), EvalAltResult> { + use std::fs::File; + use std::io::prelude::*; + + let mut f = File::open(filename) + .map_err(|_| EvalAltResult::ErrorCantOpenScriptFile(filename.into()))?; + + let mut contents = String::new(); + + f.read_to_string(&mut contents) + .map_err(|_| EvalAltResult::ErrorCantOpenScriptFile(filename.into())) + .and_then(|_| self.consume(&contents)) + } + + /// Evaluate a string, but only return errors, if there are any. + /// Useful for when you don't need the result, but still need + /// to keep track of possible errors + pub fn consume(&mut self, input: &str) -> Result<(), EvalAltResult> { + self.consume_with_scope(&mut Scope::new(), input) + } + + /// Evaluate a string with own scope, but only return errors, if there are any. + /// Useful for when you don't need the result, but still need + /// to keep track of possible errors + pub fn consume_with_scope( + &mut self, + scope: &mut Scope, + input: &str, + ) -> Result<(), EvalAltResult> { + let tokens = lex(input); + + parse(&mut tokens.peekable()) + .map_err(|err| EvalAltResult::ErrorParsing(err)) + .and_then(|AST(ref os, ref fns)| { + for f in fns { + if f.params.len() > 6 { + return Ok(()); + } + + let spec = FnSpec { + ident: f.name.clone(), + args: None, + }; + + self.script_fns + .insert(spec, Arc::new(FnIntExt::Int(f.clone()))); + } + + let val = os + .iter() + .try_fold(Box::new(()) as Dynamic, |_, o| self.eval_stmt(scope, o)) + .map(|_| ()); + + self.script_fns.clear(); // Clean up engine + + val + }) + } + + /// Overrides `on_print` + pub fn on_print(&mut self, callback: impl Fn(&str) + 'static) { + self.on_print = Box::new(callback); + } + + /// Overrides `on_debug` + pub fn on_debug(&mut self, callback: impl Fn(&str) + 'static) { + self.on_debug = Box::new(callback); + } +} diff --git a/src/builtin.rs b/src/builtin.rs index 7b928754..9e6b033e 100644 --- a/src/builtin.rs +++ b/src/builtin.rs @@ -195,11 +195,14 @@ impl Engine { fn print(x: T) -> String { format!("{}", x) } + fn println() -> String { + "\n".to_string() + } reg_func1!(self, "print", print, String, i32, i64, u32, u64); reg_func1!(self, "print", print, String, f32, f64, bool, char, String); reg_func1!(self, "print", print_debug, String, Array); - self.register_fn("print", |_: ()| println!()); + self.register_fn("print", println); reg_func1!(self, "debug", print_debug, String, i32, i64, u32, u64); reg_func1!(self, "debug", print_debug, String, f32, f64, bool, char); diff --git a/src/engine.rs b/src/engine.rs index 87132e4f..ac8ef1bf 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -7,7 +7,7 @@ use std::sync::Arc; use crate::any::{Any, AnyExt, Dynamic, Variant}; use crate::call::FunArgs; use crate::fn_register::RegisterFn; -use crate::parser::{lex, parse, Expr, FnDef, ParseError, Stmt, AST}; +use crate::parser::{Expr, FnDef, ParseError, Stmt}; pub type Array = Vec; pub type FnCallArgs<'a> = Vec<&'a mut Variant>; @@ -41,7 +41,6 @@ impl EvalAltResult { | Self::ErrorVariableNotFound(s) | Self::ErrorFunctionNotFound(s) | Self::ErrorMismatchOutputType(s) - | Self::ErrorCantOpenScriptFile(s) | Self::ErrorArithmetic(s) => s, _ => return None, }) @@ -159,8 +158,8 @@ impl std::fmt::Display for EvalAltResult { #[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] pub struct FnSpec { - ident: String, - args: Option>, + pub ident: String, + pub args: Option>, } type IteratorFn = dyn Fn(&Dynamic) -> Box>; @@ -180,11 +179,15 @@ type IteratorFn = dyn Fn(&Dynamic) -> Box>; /// } /// ``` pub struct Engine { - /// A hashmap containing all functions known to the engine - pub fns: HashMap>, - pub type_iterators: HashMap>, - on_print: Box, - on_debug: Box, + /// A hashmap containing all compiled functions known to the engine + fns: HashMap>, + /// A hashmap containing all script-defined functions + pub(crate) script_fns: HashMap>, + /// A hashmap containing all iterators known to the engine + type_iterators: HashMap>, + + pub(crate) on_print: Box, + pub(crate) on_debug: Box, } pub enum FnIntExt { @@ -217,7 +220,7 @@ impl Engine { A: FunArgs<'a>, T: Any + Clone, { - self.call_fn_raw(ident.into(), args.into_vec()) + self.call_fn_raw(ident.into(), args.into_vec(), None) .and_then(|b| { b.downcast() .map(|b| *b) @@ -227,7 +230,12 @@ impl Engine { /// Universal method for calling functions, that are either /// registered with the `Engine` or written in Rhai - fn call_fn_raw(&self, ident: String, args: FnCallArgs) -> Result { + fn call_fn_raw( + &self, + ident: String, + args: FnCallArgs, + def_value: Option<&Dynamic>, + ) -> Result { debug_println!( "Trying to call function {:?} with args {:?}", ident, @@ -241,28 +249,27 @@ impl Engine { args: Some(args.iter().map(|a| Any::type_id(&**a)).collect()), }; - self.fns + // First search in script-defined functions (can override built-in), + // then in built-in's, then retry again with no arguments + let fn_def = self + .script_fns .get(&spec) + .or_else(|| self.fns.get(&spec)) .or_else(|| { - let spec1 = FnSpec { + self.script_fns.get(&FnSpec { ident: ident.clone(), args: None, - }; - self.fns.get(&spec1) + }) }) - .ok_or_else(|| { - let type_names = args - .iter() - .map(|x| (*(&**x).into_dynamic()).type_name()) - .collect::>(); + .or_else(|| { + self.fns.get(&FnSpec { + ident: ident.clone(), + args: None, + }) + }); - EvalAltResult::ErrorFunctionNotFound(format!( - "{} ({})", - ident, - type_names.join(", ") - )) - }) - .and_then(move |f| match **f { + if let Some(f) = fn_def { + match **f { FnIntExt::Ext(ref f) => { let r = f(args); @@ -299,7 +306,22 @@ impl Engine { other => other, } } - }) + } + } else if let Some(val) = def_value { + // Return default value + Ok(val.clone()) + } else { + let type_names = args + .iter() + .map(|x| (*(&**x).into_dynamic()).type_name()) + .collect::>(); + + Err(EvalAltResult::ErrorFunctionNotFound(format!( + "{} ({})", + ident, + type_names.join(", ") + ))) + } } pub(crate) fn register_fn_raw( @@ -369,7 +391,7 @@ impl Engine { use std::iter::once; match dot_rhs { - Expr::FunctionCall(fn_name, args) => { + Expr::FunctionCall(fn_name, args, def_value) => { let mut args: Array = args .iter() .map(|arg| self.eval_expr(scope, arg)) @@ -379,13 +401,13 @@ impl Engine { .chain(args.iter_mut().map(|b| b.as_mut())) .collect(); - self.call_fn_raw(fn_name.to_owned(), args) + self.call_fn_raw(fn_name.into(), args, def_value.as_ref()) } Expr::Identifier(id) => { let get_fn_name = "get$".to_string() + id; - self.call_fn_raw(get_fn_name, vec![this_ptr]) + self.call_fn_raw(get_fn_name, vec![this_ptr], None) } Expr::Index(id, idx_raw) => { @@ -397,7 +419,7 @@ impl Engine { let get_fn_name = "get$".to_string() + id; - let mut val = self.call_fn_raw(get_fn_name, vec![this_ptr])?; + let mut val = self.call_fn_raw(get_fn_name, vec![this_ptr], None)?; if let Some(arr) = (*val).downcast_mut() as Option<&mut Array> { if idx >= 0 { @@ -425,7 +447,7 @@ impl Engine { Expr::Identifier(ref id) => { let get_fn_name = "get$".to_string() + id; let value = self - .call_fn_raw(get_fn_name, vec![this_ptr]) + .call_fn_raw(get_fn_name, vec![this_ptr], None) .and_then(|mut v| self.get_dot_val_helper(scope, v.as_mut(), inner_rhs))?; // TODO - Should propagate changes back in this scenario: @@ -462,7 +484,7 @@ impl Engine { .enumerate() .rev() .find(|&(_, &mut (ref name, _))| id == name) - .ok_or_else(|| EvalAltResult::ErrorVariableNotFound(id.to_owned())) + .ok_or_else(|| EvalAltResult::ErrorVariableNotFound(id.into())) .and_then(move |(idx, &mut (_, ref mut val))| map(val.as_mut()).map(|val| (idx, val))) } @@ -575,14 +597,14 @@ impl Engine { Expr::Identifier(id) => { let set_fn_name = "set$".to_string() + id; - self.call_fn_raw(set_fn_name, vec![this_ptr, source_val.as_mut()]) + self.call_fn_raw(set_fn_name, vec![this_ptr, source_val.as_mut()], None) } Expr::Dot(inner_lhs, inner_rhs) => match **inner_lhs { Expr::Identifier(ref id) => { let get_fn_name = "get$".to_string() + id; - self.call_fn_raw(get_fn_name, vec![this_ptr]) + self.call_fn_raw(get_fn_name, vec![this_ptr], None) .and_then(|mut v| { self.set_dot_val_helper(v.as_mut(), inner_rhs, source_val) .map(|_| v) // Discard Ok return value @@ -590,7 +612,7 @@ impl Engine { .and_then(|mut v| { let set_fn_name = "set$".to_string() + id; - self.call_fn_raw(set_fn_name, vec![this_ptr, v.as_mut()]) + self.call_fn_raw(set_fn_name, vec![this_ptr, v.as_mut()], None) }) } _ => Err(EvalAltResult::ErrorDotExpr), @@ -743,14 +765,15 @@ impl Engine { Ok(Box::new(arr)) } - Expr::FunctionCall(fn_name, args) => self.call_fn_raw( - fn_name.to_owned(), + Expr::FunctionCall(fn_name, args, def_value) => self.call_fn_raw( + fn_name.into(), args.iter() .map(|expr| self.eval_expr(scope, expr)) .collect::>()? .iter_mut() .map(|b| b.as_mut()) .collect(), + def_value.as_ref(), ), Expr::And(lhs, rhs) => Ok(Box::new( @@ -781,7 +804,11 @@ impl Engine { } } - fn eval_stmt(&self, scope: &mut Scope, stmt: &Stmt) -> Result { + pub(crate) fn eval_stmt( + &self, + scope: &mut Scope, + stmt: &Stmt, + ) -> Result { match stmt { Stmt::Expr(expr) => self.eval_expr(scope, expr), @@ -899,186 +926,11 @@ impl Engine { } } - /// Compile a string into an AST - pub fn compile(input: &str) -> Result { - let tokens = lex(input); - - let mut peekables = tokens.peekable(); - let tree = parse(&mut peekables); - - tree - } - - /// Compile a file into an AST - pub fn compile_file(filename: &str) -> Result { - use std::fs::File; - use std::io::prelude::*; - - if let Ok(mut f) = File::open(filename) { - let mut contents = String::new(); - - if f.read_to_string(&mut contents).is_ok() { - Self::compile(&contents).map_err(|err| EvalAltResult::ErrorParsing(err)) - } else { - Err(EvalAltResult::ErrorCantOpenScriptFile(filename.to_owned())) - } - } else { - Err(EvalAltResult::ErrorCantOpenScriptFile(filename.to_owned())) - } - } - - /// Evaluate a file - pub fn eval_file(&mut self, filename: &str) -> Result { - use std::fs::File; - use std::io::prelude::*; - - if let Ok(mut f) = File::open(filename) { - let mut contents = String::new(); - - if f.read_to_string(&mut contents).is_ok() { - self.eval::(&contents) - } else { - Err(EvalAltResult::ErrorCantOpenScriptFile(filename.to_owned())) - } - } else { - Err(EvalAltResult::ErrorCantOpenScriptFile(filename.to_owned())) - } - } - - /// Evaluate a string - pub fn eval(&mut self, input: &str) -> Result { - let mut scope = Scope::new(); - self.eval_with_scope(&mut scope, input) - } - - /// Evaluate a string with own scope - pub fn eval_with_scope( - &mut self, - scope: &mut Scope, - input: &str, - ) -> Result { - let ast = Self::compile(input).map_err(|err| EvalAltResult::ErrorParsing(err))?; - self.eval_ast_with_scope(scope, &ast) - } - - /// Evaluate an AST - pub fn eval_ast(&mut self, ast: &AST) -> Result { - let mut scope = Scope::new(); - self.eval_ast_with_scope(&mut scope, ast) - } - - /// Evaluate an AST with own scope - pub fn eval_ast_with_scope( - &mut self, - scope: &mut Scope, - ast: &AST, - ) -> Result { - let AST(os, fns) = ast; - let mut x: Result = Ok(Box::new(())); - - for f in fns { - let name = f.name.clone(); - let local_f = f.clone(); - - let spec = FnSpec { - ident: name, - args: None, - }; - - self.fns.insert(spec, Arc::new(FnIntExt::Int(local_f))); - } - - for o in os { - x = match self.eval_stmt(scope, o) { - Ok(v) => Ok(v), - Err(e) => return Err(e), - } - } - - let x = x?; - - match x.downcast::() { - Ok(out) => Ok(*out), - Err(a) => Err(EvalAltResult::ErrorMismatchOutputType((*a).type_name())), - } - } - - /// Evaluate a file, but only return errors, if there are any. - /// Useful for when you don't need the result, but still need - /// to keep track of possible errors - pub fn consume_file(&mut self, filename: &str) -> Result<(), EvalAltResult> { - use std::fs::File; - use std::io::prelude::*; - - if let Ok(mut f) = File::open(filename) { - let mut contents = String::new(); - - if f.read_to_string(&mut contents).is_ok() { - if let e @ Err(_) = self.consume(&contents) { - e - } else { - Ok(()) - } - } else { - Err(EvalAltResult::ErrorCantOpenScriptFile(filename.to_owned())) - } - } else { - Err(EvalAltResult::ErrorCantOpenScriptFile(filename.to_owned())) - } - } - - /// Evaluate a string, but only return errors, if there are any. - /// Useful for when you don't need the result, but still need - /// to keep track of possible errors - pub fn consume(&mut self, input: &str) -> Result<(), EvalAltResult> { - self.consume_with_scope(&mut Scope::new(), input) - } - - /// Evaluate a string with own scope, but only return errors, if there are any. - /// Useful for when you don't need the result, but still need - /// to keep track of possible errors - pub fn consume_with_scope( - &mut self, - scope: &mut Scope, - input: &str, - ) -> Result<(), EvalAltResult> { - let tokens = lex(input); - - let mut peekables = tokens.peekable(); - - match parse(&mut peekables) { - Ok(AST(ref os, ref fns)) => { - for f in fns { - if f.params.len() > 6 { - return Ok(()); - } - let name = f.name.clone(); - let local_f = f.clone(); - - let spec = FnSpec { - ident: name, - args: None, - }; - - self.fns.insert(spec, Arc::new(FnIntExt::Int(local_f))); - } - - for o in os { - if let Err(e) = self.eval_stmt(scope, o) { - return Err(e); - } - } - - Ok(()) - } - Err(err) => Err(EvalAltResult::ErrorParsing(err)), - } - } - /// Make a new engine pub fn new() -> Engine { let mut engine = Engine { fns: HashMap::new(), + script_fns: HashMap::new(), type_iterators: HashMap::new(), on_print: Box::new(|x: &str| println!("{}", x)), on_debug: Box::new(|x: &str| println!("{}", x)), @@ -1088,14 +940,4 @@ impl Engine { engine } - - /// Overrides `on_print` - pub fn on_print(&mut self, callback: impl Fn(&str) + 'static) { - self.on_print = Box::new(callback); - } - - /// Overrides `on_debug` - pub fn on_debug(&mut self, callback: impl Fn(&str) + 'static) { - self.on_debug = Box::new(callback); - } } diff --git a/src/fn_register.rs b/src/fn_register.rs index c23794de..8a65952f 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -53,7 +53,7 @@ macro_rules! def_register { let r = f($(($clone)($par)),*); Ok(Box::new(r) as Dynamic) }; - self.register_fn_raw(name.to_owned(), Some(vec![$(TypeId::of::<$par>()),*]), Box::new(fun)); + self.register_fn_raw(name.into(), Some(vec![$(TypeId::of::<$par>()),*]), Box::new(fun)); } } @@ -85,7 +85,7 @@ macro_rules! def_register { // potentially clone the value, otherwise pass the reference. Ok(f($(($clone)($par)),*)) }; - self.register_fn_raw(name.to_owned(), Some(vec![$(TypeId::of::<$par>()),*]), Box::new(fun)); + self.register_fn_raw(name.into(), Some(vec![$(TypeId::of::<$par>()),*]), Box::new(fun)); } } diff --git a/src/lib.rs b/src/lib.rs index 443b8a1d..f7a3907e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,6 +40,7 @@ macro_rules! debug_println { } mod any; +mod api; mod builtin; mod call; mod engine; diff --git a/src/parser.rs b/src/parser.rs index fccd1b30..8f9db5f0 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,3 +1,4 @@ +use crate::Dynamic; use std::char; use std::error::Error; use std::fmt; @@ -145,14 +146,14 @@ impl fmt::Display for ParseError { pub struct AST(pub(crate) Vec, pub(crate) Vec); -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, Clone)] pub struct FnDef { pub name: String, pub params: Vec, pub body: Box, } -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, Clone)] pub enum Stmt { If(Box, Box), IfElse(Box, Box, Box), @@ -167,14 +168,14 @@ pub enum Stmt { ReturnWithVal(Box), } -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, Clone)] pub enum Expr { IntegerConstant(i64), FloatConstant(f64), Identifier(String), CharConstant(char), StringConstant(String), - FunctionCall(String, Vec), + FunctionCall(String, Vec, Option), Assignment(Box, Box), Dot(Box, Box), Index(String, Box), @@ -997,7 +998,7 @@ fn parse_call_expr<'a>( if let Some(&(Token::RightParen, _)) = input.peek() { input.next(); - return Ok(Expr::FunctionCall(id, args)); + return Ok(Expr::FunctionCall(id, args, None)); } loop { @@ -1006,7 +1007,7 @@ fn parse_call_expr<'a>( match input.peek() { Some(&(Token::RightParen, _)) => { input.next(); - return Ok(Expr::FunctionCall(id, args)); + return Ok(Expr::FunctionCall(id, args, None)); } Some(&(Token::Comma, _)) => (), Some(&(_, pos)) => return Err(ParseError(PERR::MalformedCallExpr, pos)), @@ -1116,7 +1117,11 @@ fn parse_unary<'a>(input: &mut Peekable>) -> Result { input.next(); - Ok(Expr::FunctionCall("-".into(), vec![parse_primary(input)?])) + Ok(Expr::FunctionCall( + "-".into(), + vec![parse_primary(input)?], + None, + )) } Token::UnaryPlus => { input.next(); @@ -1124,7 +1129,11 @@ fn parse_unary<'a>(input: &mut Peekable>) -> Result { input.next(); - Ok(Expr::FunctionCall("!".into(), vec![parse_primary(input)?])) + Ok(Expr::FunctionCall( + "!".into(), + vec![parse_primary(input)?], + Some(Box::new(false)), // NOT operator, when operating on invalid operand, defaults to false + )) } _ => parse_primary(input), } @@ -1165,102 +1174,118 @@ fn parse_binop<'a>( } lhs_curr = match op_token { - Token::Plus => Expr::FunctionCall("+".into(), vec![lhs_curr, rhs]), - Token::Minus => Expr::FunctionCall("-".into(), vec![lhs_curr, rhs]), - Token::Multiply => Expr::FunctionCall("*".into(), vec![lhs_curr, rhs]), - Token::Divide => Expr::FunctionCall("/".into(), vec![lhs_curr, rhs]), + Token::Plus => Expr::FunctionCall("+".into(), vec![lhs_curr, rhs], None), + Token::Minus => Expr::FunctionCall("-".into(), vec![lhs_curr, rhs], None), + Token::Multiply => Expr::FunctionCall("*".into(), vec![lhs_curr, rhs], None), + Token::Divide => Expr::FunctionCall("/".into(), vec![lhs_curr, rhs], None), + Token::Equals => Expr::Assignment(Box::new(lhs_curr), Box::new(rhs)), Token::PlusAssign => { let lhs_copy = lhs_curr.clone(); Expr::Assignment( Box::new(lhs_curr), - Box::new(Expr::FunctionCall("+".into(), vec![lhs_copy, rhs])), + Box::new(Expr::FunctionCall("+".into(), vec![lhs_copy, rhs], None)), ) } Token::MinusAssign => { let lhs_copy = lhs_curr.clone(); Expr::Assignment( Box::new(lhs_curr), - Box::new(Expr::FunctionCall("-".into(), vec![lhs_copy, rhs])), + Box::new(Expr::FunctionCall("-".into(), vec![lhs_copy, rhs], None)), ) } Token::Period => Expr::Dot(Box::new(lhs_curr), Box::new(rhs)), - Token::EqualsTo => Expr::FunctionCall("==".into(), vec![lhs_curr, rhs]), - Token::NotEqualsTo => Expr::FunctionCall("!=".into(), vec![lhs_curr, rhs]), - Token::LessThan => Expr::FunctionCall("<".into(), vec![lhs_curr, rhs]), - Token::LessThanEqualsTo => Expr::FunctionCall("<=".into(), vec![lhs_curr, rhs]), - Token::GreaterThan => Expr::FunctionCall(">".into(), vec![lhs_curr, rhs]), - Token::GreaterThanEqualsTo => Expr::FunctionCall(">=".into(), vec![lhs_curr, rhs]), + + // Comparison operators default to false when passed invalid operands + Token::EqualsTo => { + Expr::FunctionCall("==".into(), vec![lhs_curr, rhs], Some(Box::new(false))) + } + Token::NotEqualsTo => { + Expr::FunctionCall("!=".into(), vec![lhs_curr, rhs], Some(Box::new(false))) + } + Token::LessThan => { + Expr::FunctionCall("<".into(), vec![lhs_curr, rhs], Some(Box::new(false))) + } + Token::LessThanEqualsTo => { + Expr::FunctionCall("<=".into(), vec![lhs_curr, rhs], Some(Box::new(false))) + } + Token::GreaterThan => { + Expr::FunctionCall(">".into(), vec![lhs_curr, rhs], Some(Box::new(false))) + } + Token::GreaterThanEqualsTo => { + Expr::FunctionCall(">=".into(), vec![lhs_curr, rhs], Some(Box::new(false))) + } + Token::Or => Expr::Or(Box::new(lhs_curr), Box::new(rhs)), Token::And => Expr::And(Box::new(lhs_curr), Box::new(rhs)), - Token::XOr => Expr::FunctionCall("^".into(), vec![lhs_curr, rhs]), + Token::XOr => Expr::FunctionCall("^".into(), vec![lhs_curr, rhs], None), Token::OrAssign => { let lhs_copy = lhs_curr.clone(); Expr::Assignment( Box::new(lhs_curr), - Box::new(Expr::FunctionCall("|".into(), vec![lhs_copy, rhs])), + Box::new(Expr::FunctionCall("|".into(), vec![lhs_copy, rhs], None)), ) } Token::AndAssign => { let lhs_copy = lhs_curr.clone(); Expr::Assignment( Box::new(lhs_curr), - Box::new(Expr::FunctionCall("&".into(), vec![lhs_copy, rhs])), + Box::new(Expr::FunctionCall("&".into(), vec![lhs_copy, rhs], None)), ) } Token::XOrAssign => { let lhs_copy = lhs_curr.clone(); Expr::Assignment( Box::new(lhs_curr), - Box::new(Expr::FunctionCall("^".into(), vec![lhs_copy, rhs])), + Box::new(Expr::FunctionCall("^".into(), vec![lhs_copy, rhs], None)), ) } Token::MultiplyAssign => { let lhs_copy = lhs_curr.clone(); Expr::Assignment( Box::new(lhs_curr), - Box::new(Expr::FunctionCall("*".into(), vec![lhs_copy, rhs])), + Box::new(Expr::FunctionCall("*".into(), vec![lhs_copy, rhs], None)), ) } Token::DivideAssign => { let lhs_copy = lhs_curr.clone(); Expr::Assignment( Box::new(lhs_curr), - Box::new(Expr::FunctionCall("/".into(), vec![lhs_copy, rhs])), + Box::new(Expr::FunctionCall("/".into(), vec![lhs_copy, rhs], None)), ) } - Token::Pipe => Expr::FunctionCall("|".into(), vec![lhs_curr, rhs]), - Token::LeftShift => Expr::FunctionCall("<<".into(), vec![lhs_curr, rhs]), - Token::RightShift => Expr::FunctionCall(">>".into(), vec![lhs_curr, rhs]), + Token::Pipe => Expr::FunctionCall("|".into(), vec![lhs_curr, rhs], None), + Token::LeftShift => Expr::FunctionCall("<<".into(), vec![lhs_curr, rhs], None), + Token::RightShift => Expr::FunctionCall(">>".into(), vec![lhs_curr, rhs], None), Token::LeftShiftAssign => { let lhs_copy = lhs_curr.clone(); Expr::Assignment( Box::new(lhs_curr), - Box::new(Expr::FunctionCall("<<".into(), vec![lhs_copy, rhs])), + Box::new(Expr::FunctionCall("<<".into(), vec![lhs_copy, rhs], None)), ) } Token::RightShiftAssign => { let lhs_copy = lhs_curr.clone(); Expr::Assignment( Box::new(lhs_curr), - Box::new(Expr::FunctionCall(">>".into(), vec![lhs_copy, rhs])), + Box::new(Expr::FunctionCall(">>".into(), vec![lhs_copy, rhs], None)), ) } - Token::Ampersand => Expr::FunctionCall("&".into(), vec![lhs_curr, rhs]), - Token::Modulo => Expr::FunctionCall("%".into(), vec![lhs_curr, rhs]), + Token::Ampersand => Expr::FunctionCall("&".into(), vec![lhs_curr, rhs], None), + Token::Modulo => Expr::FunctionCall("%".into(), vec![lhs_curr, rhs], None), Token::ModuloAssign => { let lhs_copy = lhs_curr.clone(); Expr::Assignment( Box::new(lhs_curr), - Box::new(Expr::FunctionCall("%".into(), vec![lhs_copy, rhs])), + Box::new(Expr::FunctionCall("%".into(), vec![lhs_copy, rhs], None)), ) } - Token::PowerOf => Expr::FunctionCall("~".into(), vec![lhs_curr, rhs]), + Token::PowerOf => Expr::FunctionCall("~".into(), vec![lhs_curr, rhs], None), Token::PowerOfAssign => { let lhs_copy = lhs_curr.clone(); Expr::Assignment( Box::new(lhs_curr), - Box::new(Expr::FunctionCall("~".into(), vec![lhs_copy, rhs])), + Box::new(Expr::FunctionCall("~".into(), vec![lhs_copy, rhs], None)), ) } _ => return Err(ParseError(PERR::UnknownOperator, pos)), diff --git a/tests/binary_ops.rs b/tests/binary_ops.rs index 4978b34d..a0f7c451 100644 --- a/tests/binary_ops.rs +++ b/tests/binary_ops.rs @@ -10,4 +10,11 @@ fn test_binary_ops() { assert_eq!(engine.eval::("10 & 4"), Ok(0)); assert_eq!(engine.eval::("10 | 4"), Ok(14)); assert_eq!(engine.eval::("10 ^ 4"), Ok(14)); + + assert_eq!(engine.eval::("42 == 42"), Ok(true)); + assert_eq!(engine.eval::("42 > 42"), Ok(false)); + + // Incompatible types compare to false + assert_eq!(engine.eval::("true == 42"), Ok(false)); + assert_eq!(engine.eval::(r#""42" == 42"#), Ok(false)); }