Allow comparisons between different types (returning false).
This commit is contained in:
parent
22a505b57b
commit
a5e09295f8
12
README.md
12
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.
|
||||
|
@ -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
173
src/api.rs
Normal 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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
294
src/engine.rs
294
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<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);
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -40,6 +40,7 @@ macro_rules! debug_println {
|
||||
}
|
||||
|
||||
mod any;
|
||||
mod api;
|
||||
mod builtin;
|
||||
mod call;
|
||||
mod engine;
|
||||
|
@ -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)),
|
||||
|
@ -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));
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user