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; 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 ## 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. 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 { impl Clone for Dynamic {
fn clone(&self) -> Self { 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 { fn print<T: Display>(x: T) -> String {
format!("{}", x) format!("{}", x)
} }
fn println() -> String {
"\n".to_string()
}
reg_func1!(self, "print", print, String, i32, i64, u32, u64); 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, String, f32, f64, bool, char, String);
reg_func1!(self, "print", print_debug, String, Array); 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, i32, i64, u32, u64);
reg_func1!(self, "debug", print_debug, String, f32, f64, bool, char); 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::any::{Any, AnyExt, Dynamic, Variant};
use crate::call::FunArgs; use crate::call::FunArgs;
use crate::fn_register::RegisterFn; 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 Array = Vec<Dynamic>;
pub type FnCallArgs<'a> = Vec<&'a mut Variant>; pub type FnCallArgs<'a> = Vec<&'a mut Variant>;
@ -41,7 +41,6 @@ impl EvalAltResult {
| Self::ErrorVariableNotFound(s) | Self::ErrorVariableNotFound(s)
| Self::ErrorFunctionNotFound(s) | Self::ErrorFunctionNotFound(s)
| Self::ErrorMismatchOutputType(s) | Self::ErrorMismatchOutputType(s)
| Self::ErrorCantOpenScriptFile(s)
| Self::ErrorArithmetic(s) => s, | Self::ErrorArithmetic(s) => s,
_ => return None, _ => return None,
}) })
@ -159,8 +158,8 @@ impl std::fmt::Display for EvalAltResult {
#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] #[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub struct FnSpec { pub struct FnSpec {
ident: String, pub ident: String,
args: Option<Vec<TypeId>>, pub args: Option<Vec<TypeId>>,
} }
type IteratorFn = dyn Fn(&Dynamic) -> Box<dyn Iterator<Item = Dynamic>>; 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 { pub struct Engine {
/// A hashmap containing all functions known to the engine /// A hashmap containing all compiled functions known to the engine
pub fns: HashMap<FnSpec, Arc<FnIntExt>>, fns: HashMap<FnSpec, Arc<FnIntExt>>,
pub type_iterators: HashMap<TypeId, Arc<IteratorFn>>, /// A hashmap containing all script-defined functions
on_print: Box<dyn Fn(&str)>, pub(crate) script_fns: HashMap<FnSpec, Arc<FnIntExt>>,
on_debug: Box<dyn Fn(&str)>, /// 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 { pub enum FnIntExt {
@ -217,7 +220,7 @@ impl Engine {
A: FunArgs<'a>, A: FunArgs<'a>,
T: Any + Clone, 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| { .and_then(|b| {
b.downcast() b.downcast()
.map(|b| *b) .map(|b| *b)
@ -227,7 +230,12 @@ impl Engine {
/// Universal method for calling functions, that are either /// Universal method for calling functions, that are either
/// registered with the `Engine` or written in Rhai /// 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!( debug_println!(
"Trying to call function {:?} with args {:?}", "Trying to call function {:?} with args {:?}",
ident, ident,
@ -241,28 +249,27 @@ impl Engine {
args: Some(args.iter().map(|a| Any::type_id(&**a)).collect()), 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) .get(&spec)
.or_else(|| self.fns.get(&spec))
.or_else(|| { .or_else(|| {
let spec1 = FnSpec { self.script_fns.get(&FnSpec {
ident: ident.clone(), ident: ident.clone(),
args: None, args: None,
}; })
self.fns.get(&spec1)
}) })
.ok_or_else(|| { .or_else(|| {
let type_names = args self.fns.get(&FnSpec {
.iter() ident: ident.clone(),
.map(|x| (*(&**x).into_dynamic()).type_name()) args: None,
.collect::<Vec<_>>(); })
});
EvalAltResult::ErrorFunctionNotFound(format!( if let Some(f) = fn_def {
"{} ({})", match **f {
ident,
type_names.join(", ")
))
})
.and_then(move |f| match **f {
FnIntExt::Ext(ref f) => { FnIntExt::Ext(ref f) => {
let r = f(args); let r = f(args);
@ -299,7 +306,22 @@ impl Engine {
other => other, 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( pub(crate) fn register_fn_raw(
@ -369,7 +391,7 @@ impl Engine {
use std::iter::once; use std::iter::once;
match dot_rhs { match dot_rhs {
Expr::FunctionCall(fn_name, args) => { Expr::FunctionCall(fn_name, args, def_value) => {
let mut args: Array = args let mut args: Array = args
.iter() .iter()
.map(|arg| self.eval_expr(scope, arg)) .map(|arg| self.eval_expr(scope, arg))
@ -379,13 +401,13 @@ impl Engine {
.chain(args.iter_mut().map(|b| b.as_mut())) .chain(args.iter_mut().map(|b| b.as_mut()))
.collect(); .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) => { Expr::Identifier(id) => {
let get_fn_name = "get$".to_string() + 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) => { Expr::Index(id, idx_raw) => {
@ -397,7 +419,7 @@ impl Engine {
let get_fn_name = "get$".to_string() + id; 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 let Some(arr) = (*val).downcast_mut() as Option<&mut Array> {
if idx >= 0 { if idx >= 0 {
@ -425,7 +447,7 @@ impl Engine {
Expr::Identifier(ref id) => { Expr::Identifier(ref id) => {
let get_fn_name = "get$".to_string() + id; let get_fn_name = "get$".to_string() + id;
let value = self 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))?; .and_then(|mut v| self.get_dot_val_helper(scope, v.as_mut(), inner_rhs))?;
// TODO - Should propagate changes back in this scenario: // TODO - Should propagate changes back in this scenario:
@ -462,7 +484,7 @@ impl Engine {
.enumerate() .enumerate()
.rev() .rev()
.find(|&(_, &mut (ref name, _))| id == name) .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))) .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) => { Expr::Identifier(id) => {
let set_fn_name = "set$".to_string() + 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::Dot(inner_lhs, inner_rhs) => match **inner_lhs {
Expr::Identifier(ref id) => { Expr::Identifier(ref id) => {
let get_fn_name = "get$".to_string() + 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| { .and_then(|mut v| {
self.set_dot_val_helper(v.as_mut(), inner_rhs, source_val) self.set_dot_val_helper(v.as_mut(), inner_rhs, source_val)
.map(|_| v) // Discard Ok return value .map(|_| v) // Discard Ok return value
@ -590,7 +612,7 @@ impl Engine {
.and_then(|mut v| { .and_then(|mut v| {
let set_fn_name = "set$".to_string() + id; 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), _ => Err(EvalAltResult::ErrorDotExpr),
@ -743,14 +765,15 @@ impl Engine {
Ok(Box::new(arr)) Ok(Box::new(arr))
} }
Expr::FunctionCall(fn_name, args) => self.call_fn_raw( Expr::FunctionCall(fn_name, args, def_value) => self.call_fn_raw(
fn_name.to_owned(), fn_name.into(),
args.iter() args.iter()
.map(|expr| self.eval_expr(scope, expr)) .map(|expr| self.eval_expr(scope, expr))
.collect::<Result<Array, _>>()? .collect::<Result<Array, _>>()?
.iter_mut() .iter_mut()
.map(|b| b.as_mut()) .map(|b| b.as_mut())
.collect(), .collect(),
def_value.as_ref(),
), ),
Expr::And(lhs, rhs) => Ok(Box::new( 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 { match stmt {
Stmt::Expr(expr) => self.eval_expr(scope, expr), 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 /// Make a new engine
pub fn new() -> Engine { pub fn new() -> Engine {
let mut engine = Engine { let mut engine = Engine {
fns: HashMap::new(), fns: HashMap::new(),
script_fns: HashMap::new(),
type_iterators: HashMap::new(), type_iterators: HashMap::new(),
on_print: Box::new(|x: &str| println!("{}", x)), on_print: Box::new(|x: &str| println!("{}", x)),
on_debug: Box::new(|x: &str| println!("{}", x)), on_debug: Box::new(|x: &str| println!("{}", x)),
@ -1088,14 +940,4 @@ impl Engine {
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)),*); let r = f($(($clone)($par)),*);
Ok(Box::new(r) as Dynamic) 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. // potentially clone the value, otherwise pass the reference.
Ok(f($(($clone)($par)),*)) 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 any;
mod api;
mod builtin; mod builtin;
mod call; mod call;
mod engine; mod engine;

View File

@ -1,3 +1,4 @@
use crate::Dynamic;
use std::char; use std::char;
use std::error::Error; use std::error::Error;
use std::fmt; use std::fmt;
@ -145,14 +146,14 @@ impl fmt::Display for ParseError {
pub struct AST(pub(crate) Vec<Stmt>, pub(crate) Vec<FnDef>); pub struct AST(pub(crate) Vec<Stmt>, pub(crate) Vec<FnDef>);
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, Clone)]
pub struct FnDef { pub struct FnDef {
pub name: String, pub name: String,
pub params: Vec<String>, pub params: Vec<String>,
pub body: Box<Stmt>, pub body: Box<Stmt>,
} }
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, Clone)]
pub enum Stmt { pub enum Stmt {
If(Box<Expr>, Box<Stmt>), If(Box<Expr>, Box<Stmt>),
IfElse(Box<Expr>, Box<Stmt>, Box<Stmt>), IfElse(Box<Expr>, Box<Stmt>, Box<Stmt>),
@ -167,14 +168,14 @@ pub enum Stmt {
ReturnWithVal(Box<Expr>), ReturnWithVal(Box<Expr>),
} }
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, Clone)]
pub enum Expr { pub enum Expr {
IntegerConstant(i64), IntegerConstant(i64),
FloatConstant(f64), FloatConstant(f64),
Identifier(String), Identifier(String),
CharConstant(char), CharConstant(char),
StringConstant(String), StringConstant(String),
FunctionCall(String, Vec<Expr>), FunctionCall(String, Vec<Expr>, Option<Dynamic>),
Assignment(Box<Expr>, Box<Expr>), Assignment(Box<Expr>, Box<Expr>),
Dot(Box<Expr>, Box<Expr>), Dot(Box<Expr>, Box<Expr>),
Index(String, Box<Expr>), Index(String, Box<Expr>),
@ -997,7 +998,7 @@ fn parse_call_expr<'a>(
if let Some(&(Token::RightParen, _)) = input.peek() { if let Some(&(Token::RightParen, _)) = input.peek() {
input.next(); input.next();
return Ok(Expr::FunctionCall(id, args)); return Ok(Expr::FunctionCall(id, args, None));
} }
loop { loop {
@ -1006,7 +1007,7 @@ fn parse_call_expr<'a>(
match input.peek() { match input.peek() {
Some(&(Token::RightParen, _)) => { Some(&(Token::RightParen, _)) => {
input.next(); input.next();
return Ok(Expr::FunctionCall(id, args)); return Ok(Expr::FunctionCall(id, args, None));
} }
Some(&(Token::Comma, _)) => (), Some(&(Token::Comma, _)) => (),
Some(&(_, pos)) => return Err(ParseError(PERR::MalformedCallExpr, pos)), 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 { match token {
Token::UnaryMinus => { Token::UnaryMinus => {
input.next(); input.next();
Ok(Expr::FunctionCall("-".into(), vec![parse_primary(input)?])) Ok(Expr::FunctionCall(
"-".into(),
vec![parse_primary(input)?],
None,
))
} }
Token::UnaryPlus => { Token::UnaryPlus => {
input.next(); input.next();
@ -1124,7 +1129,11 @@ fn parse_unary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, Pars
} }
Token::Bang => { Token::Bang => {
input.next(); 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), _ => parse_primary(input),
} }
@ -1165,102 +1174,118 @@ fn parse_binop<'a>(
} }
lhs_curr = match op_token { lhs_curr = match op_token {
Token::Plus => 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]), Token::Minus => Expr::FunctionCall("-".into(), vec![lhs_curr, rhs], None),
Token::Multiply => Expr::FunctionCall("*".into(), vec![lhs_curr, rhs]), Token::Multiply => Expr::FunctionCall("*".into(), vec![lhs_curr, rhs], None),
Token::Divide => Expr::FunctionCall("/".into(), vec![lhs_curr, rhs]), Token::Divide => Expr::FunctionCall("/".into(), vec![lhs_curr, rhs], None),
Token::Equals => Expr::Assignment(Box::new(lhs_curr), Box::new(rhs)), Token::Equals => Expr::Assignment(Box::new(lhs_curr), Box::new(rhs)),
Token::PlusAssign => { Token::PlusAssign => {
let lhs_copy = lhs_curr.clone(); let lhs_copy = lhs_curr.clone();
Expr::Assignment( Expr::Assignment(
Box::new(lhs_curr), 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 => { Token::MinusAssign => {
let lhs_copy = lhs_curr.clone(); let lhs_copy = lhs_curr.clone();
Expr::Assignment( Expr::Assignment(
Box::new(lhs_curr), 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::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]), // Comparison operators default to false when passed invalid operands
Token::LessThan => Expr::FunctionCall("<".into(), vec![lhs_curr, rhs]), Token::EqualsTo => {
Token::LessThanEqualsTo => Expr::FunctionCall("<=".into(), vec![lhs_curr, rhs]), Expr::FunctionCall("==".into(), vec![lhs_curr, rhs], Some(Box::new(false)))
Token::GreaterThan => Expr::FunctionCall(">".into(), vec![lhs_curr, rhs]), }
Token::GreaterThanEqualsTo => Expr::FunctionCall(">=".into(), vec![lhs_curr, rhs]), 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::Or => Expr::Or(Box::new(lhs_curr), Box::new(rhs)),
Token::And => Expr::And(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 => { Token::OrAssign => {
let lhs_copy = lhs_curr.clone(); let lhs_copy = lhs_curr.clone();
Expr::Assignment( Expr::Assignment(
Box::new(lhs_curr), 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 => { Token::AndAssign => {
let lhs_copy = lhs_curr.clone(); let lhs_copy = lhs_curr.clone();
Expr::Assignment( Expr::Assignment(
Box::new(lhs_curr), 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 => { Token::XOrAssign => {
let lhs_copy = lhs_curr.clone(); let lhs_copy = lhs_curr.clone();
Expr::Assignment( Expr::Assignment(
Box::new(lhs_curr), 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 => { Token::MultiplyAssign => {
let lhs_copy = lhs_curr.clone(); let lhs_copy = lhs_curr.clone();
Expr::Assignment( Expr::Assignment(
Box::new(lhs_curr), 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 => { Token::DivideAssign => {
let lhs_copy = lhs_curr.clone(); let lhs_copy = lhs_curr.clone();
Expr::Assignment( Expr::Assignment(
Box::new(lhs_curr), 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::Pipe => Expr::FunctionCall("|".into(), vec![lhs_curr, rhs], None),
Token::LeftShift => Expr::FunctionCall("<<".into(), vec![lhs_curr, rhs]), Token::LeftShift => Expr::FunctionCall("<<".into(), vec![lhs_curr, rhs], None),
Token::RightShift => Expr::FunctionCall(">>".into(), vec![lhs_curr, rhs]), Token::RightShift => Expr::FunctionCall(">>".into(), vec![lhs_curr, rhs], None),
Token::LeftShiftAssign => { Token::LeftShiftAssign => {
let lhs_copy = lhs_curr.clone(); let lhs_copy = lhs_curr.clone();
Expr::Assignment( Expr::Assignment(
Box::new(lhs_curr), 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 => { Token::RightShiftAssign => {
let lhs_copy = lhs_curr.clone(); let lhs_copy = lhs_curr.clone();
Expr::Assignment( Expr::Assignment(
Box::new(lhs_curr), 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::Ampersand => Expr::FunctionCall("&".into(), vec![lhs_curr, rhs], None),
Token::Modulo => Expr::FunctionCall("%".into(), vec![lhs_curr, rhs]), Token::Modulo => Expr::FunctionCall("%".into(), vec![lhs_curr, rhs], None),
Token::ModuloAssign => { Token::ModuloAssign => {
let lhs_copy = lhs_curr.clone(); let lhs_copy = lhs_curr.clone();
Expr::Assignment( Expr::Assignment(
Box::new(lhs_curr), 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 => { Token::PowerOfAssign => {
let lhs_copy = lhs_curr.clone(); let lhs_copy = lhs_curr.clone();
Expr::Assignment( Expr::Assignment(
Box::new(lhs_curr), 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)), _ => 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(0));
assert_eq!(engine.eval::<i64>("10 | 4"), Ok(14)); assert_eq!(engine.eval::<i64>("10 | 4"), Ok(14));
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));
} }