Allow comparisons between different types (returning false).

This commit is contained in:
Stephen Chung 2020-03-02 14:28:42 +08:00
parent 22a505b57b
commit a5e09295f8
9 changed files with 329 additions and 266 deletions

View File

@ -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.

View File

@ -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())
}
}

173
src/api.rs Normal file
View File

@ -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<AST, ParseError> {
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<AST, 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::compile(&contents).map_err(EvalAltResult::ErrorParsing))
}
/// Evaluate a file
pub fn eval_file<T: Any + Clone>(&mut self, filename: &str) -> Result<T, 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.eval::<T>(&contents))
}
/// Evaluate a string
pub fn eval<T: Any + Clone>(&mut self, input: &str) -> Result<T, EvalAltResult> {
let mut scope = Scope::new();
self.eval_with_scope(&mut scope, input)
}
/// Evaluate a string with own scope
pub fn eval_with_scope<T: Any + Clone>(
&mut self,
scope: &mut Scope,
input: &str,
) -> Result<T, EvalAltResult> {
let ast = Self::compile(input).map_err(EvalAltResult::ErrorParsing)?;
self.eval_ast_with_scope(scope, &ast)
}
/// Evaluate an AST
pub fn eval_ast<T: Any + Clone>(&mut self, ast: &AST) -> Result<T, EvalAltResult> {
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<T: Any + Clone>(
&mut self,
scope: &mut Scope,
ast: &AST,
) -> Result<T, EvalAltResult> {
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::<T>()
.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);
}
}

View File

@ -195,11 +195,14 @@ impl Engine {
fn print<T: Display>(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);

View File

@ -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<Dynamic>;
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<Vec<TypeId>>,
pub ident: String,
pub args: Option<Vec<TypeId>>,
}
type IteratorFn = dyn Fn(&Dynamic) -> Box<dyn Iterator<Item = Dynamic>>;
@ -180,11 +179,15 @@ type IteratorFn = dyn Fn(&Dynamic) -> Box<dyn Iterator<Item = Dynamic>>;
/// }
/// ```
pub struct Engine {
/// A hashmap containing all functions known to the engine
pub fns: HashMap<FnSpec, Arc<FnIntExt>>,
pub type_iterators: HashMap<TypeId, Arc<IteratorFn>>,
on_print: Box<dyn Fn(&str)>,
on_debug: Box<dyn Fn(&str)>,
/// A hashmap containing all compiled functions known to the engine
fns: HashMap<FnSpec, Arc<FnIntExt>>,
/// A hashmap containing all script-defined functions
pub(crate) script_fns: HashMap<FnSpec, Arc<FnIntExt>>,
/// A hashmap containing all iterators known to the engine
type_iterators: HashMap<TypeId, Arc<IteratorFn>>,
pub(crate) on_print: Box<dyn Fn(&str)>,
pub(crate) on_debug: Box<dyn Fn(&str)>,
}
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<Dynamic, EvalAltResult> {
fn call_fn_raw(
&self,
ident: String,
args: FnCallArgs,
def_value: Option<&Dynamic>,
) -> Result<Dynamic, EvalAltResult> {
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::<Vec<_>>();
})
.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::<Vec<_>>();
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::<Result<Array, _>>()?
.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<Dynamic, EvalAltResult> {
pub(crate) fn eval_stmt(
&self,
scope: &mut Scope,
stmt: &Stmt,
) -> Result<Dynamic, EvalAltResult> {
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<AST, ParseError> {
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<AST, 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() {
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<T: Any + Clone>(&mut self, filename: &str) -> Result<T, 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() {
self.eval::<T>(&contents)
} else {
Err(EvalAltResult::ErrorCantOpenScriptFile(filename.to_owned()))
}
} else {
Err(EvalAltResult::ErrorCantOpenScriptFile(filename.to_owned()))
}
}
/// Evaluate a string
pub fn eval<T: Any + Clone>(&mut self, input: &str) -> Result<T, EvalAltResult> {
let mut scope = Scope::new();
self.eval_with_scope(&mut scope, input)
}
/// Evaluate a string with own scope
pub fn eval_with_scope<T: Any + Clone>(
&mut self,
scope: &mut Scope,
input: &str,
) -> Result<T, EvalAltResult> {
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<T: Any + Clone>(&mut self, ast: &AST) -> Result<T, EvalAltResult> {
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<T: Any + Clone>(
&mut self,
scope: &mut Scope,
ast: &AST,
) -> Result<T, EvalAltResult> {
let AST(os, fns) = ast;
let mut x: Result<Dynamic, EvalAltResult> = 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::<T>() {
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);
}
}

View File

@ -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));
}
}

View File

@ -40,6 +40,7 @@ macro_rules! debug_println {
}
mod any;
mod api;
mod builtin;
mod call;
mod engine;

View File

@ -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<Stmt>, pub(crate) Vec<FnDef>);
#[derive(Debug, PartialEq, Clone)]
#[derive(Debug, Clone)]
pub struct FnDef {
pub name: String,
pub params: Vec<String>,
pub body: Box<Stmt>,
}
#[derive(Debug, PartialEq, Clone)]
#[derive(Debug, Clone)]
pub enum Stmt {
If(Box<Expr>, Box<Stmt>),
IfElse(Box<Expr>, Box<Stmt>, Box<Stmt>),
@ -167,14 +168,14 @@ pub enum Stmt {
ReturnWithVal(Box<Expr>),
}
#[derive(Debug, PartialEq, Clone)]
#[derive(Debug, Clone)]
pub enum Expr {
IntegerConstant(i64),
FloatConstant(f64),
Identifier(String),
CharConstant(char),
StringConstant(String),
FunctionCall(String, Vec<Expr>),
FunctionCall(String, Vec<Expr>, Option<Dynamic>),
Assignment(Box<Expr>, Box<Expr>),
Dot(Box<Expr>, Box<Expr>),
Index(String, Box<Expr>),
@ -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<TokenIterator<'a>>) -> Result<Expr, Pars
match token {
Token::UnaryMinus => {
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<TokenIterator<'a>>) -> Result<Expr, Pars
}
Token::Bang => {
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)),

View File

@ -10,4 +10,11 @@ fn test_binary_ops() {
assert_eq!(engine.eval::<i64>("10 & 4"), Ok(0));
assert_eq!(engine.eval::<i64>("10 | 4"), Ok(14));
assert_eq!(engine.eval::<i64>("10 ^ 4"), Ok(14));
assert_eq!(engine.eval::<bool>("42 == 42"), Ok(true));
assert_eq!(engine.eval::<bool>("42 > 42"), Ok(false));
// Incompatible types compare to false
assert_eq!(engine.eval::<bool>("true == 42"), Ok(false));
assert_eq!(engine.eval::<bool>(r#""42" == 42"#), Ok(false));
}