From f3bcb2a10dbb3baca67d816cc4fa234ad9769d35 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 10 Mar 2020 14:09:05 +0800 Subject: [PATCH 01/24] Simplify call_fn API, no need to pass &mut references. --- README.md | 22 ++++++++++++++++------ src/api.rs | 39 ++++++++++++++++++++++++++------------- src/call.rs | 28 +++++++++++----------------- tests/engine.rs | 10 ++++++++-- 4 files changed, 61 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 71f33028..70555b38 100644 --- a/README.md +++ b/README.md @@ -120,6 +120,8 @@ fn main() -> Result<(), EvalAltResult> let result = engine.eval::("40 + 2")?; println!("Answer: {}", result); // prints 42 + + Ok(()) } ``` @@ -153,7 +155,7 @@ use rhai::Engine; let mut engine = Engine::new(); -let ast = engine.compile_file("hello_world.rhai").unwrap(); +let ast = engine.compile_file("hello_world.rhai")?; ``` Rhai also allows you to work _backwards_ from the other direction - i.e. calling a Rhai-scripted function from Rust. @@ -166,11 +168,19 @@ use rhai::Engine; let mut engine = Engine::new(); // Define a function in a script and compile to AST -let ast = engine.compile("fn hello(x, y) { x.len() + y }")?; +let ast = engine.compile( + r" + fn hello(x, y) { // a function with two arguments: String and i64 + x.len() + y // returning i64 + } + ")?; -// Evaluate the function in the AST, passing arguments into the script as a tuple -// (beware, arguments must be of the correct types because Rhai does not have built-in type conversions) -let result: i64 = engine.call_fn("hello", &ast, (&mut String::from("abc"), &mut 123_i64))?; +// Evaluate the function in the AST, passing arguments into the script as a tuple. +// Beware, arguments must be of the correct types because Rhai does not have built-in type conversions. +// If you pass in arguments of the wrong type, the Engine will not find the function. + +let result: i64 = engine.call_fn("hello", &ast, ( String::from("abc"), 123_i64 ) )?; +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ put arguments in a tuple ``` Values and types @@ -292,7 +302,7 @@ fn main() } ``` -You can also see in this example how you can register multiple functions (or in this case multiple instances of the same function) to the same name in script. This gives you a way to overload functions and call the correct one, based on the types of the arguments, from your script. +You can also see in this example how you can register multiple functions (or in this case multiple instances of the same function) to the same name in script. This gives you a way to overload functions the correct one, based on the types of the arguments, from your script. Fallible functions ------------------ diff --git a/src/api.rs b/src/api.rs index 5e89c5ca..9989af7b 100644 --- a/src/api.rs +++ b/src/api.rs @@ -2,7 +2,7 @@ use crate::any::{Any, AnyExt, Dynamic}; use crate::call::FuncArgs; -use crate::engine::{Engine, FnAny, FnIntExt, FnSpec}; +use crate::engine::{Engine, FnAny, FnCallArgs, FnIntExt, FnSpec}; use crate::error::ParseError; use crate::fn_register::RegisterFn; use crate::parser::{lex, parse, Position, AST}; @@ -273,17 +273,32 @@ impl<'e> Engine<'e> { /// /// let ast = engine.compile("fn add(x, y) { x.len() + y }")?; /// - /// let result: i64 = engine.call_fn("add", &ast, (&mut String::from("abc"), &mut 123_i64))?; + /// let result: i64 = engine.call_fn("add", &ast, (String::from("abc"), 123_i64))?; /// /// assert_eq!(result, 126); /// # Ok(()) /// # } /// ``` - pub fn call_fn<'f, A: FuncArgs<'f>, T: Any + Clone>( + pub fn call_fn( &mut self, name: &str, ast: &AST, args: A, + ) -> Result { + let mut arg_values = args.into_vec(); + + self.call_fn_internal( + name, + ast, + arg_values.iter_mut().map(|v| v.as_mut()).collect(), + ) + } + + pub(crate) fn call_fn_internal( + &mut self, + name: &str, + ast: &AST, + args: FnCallArgs, ) -> Result { let pos = Default::default(); @@ -297,16 +312,14 @@ impl<'e> Engine<'e> { ); }); - let result = self - .call_fn_raw(name, args.into_vec(), None, pos) - .and_then(|b| { - b.downcast().map(|b| *b).map_err(|a| { - EvalAltResult::ErrorMismatchOutputType( - self.map_type_name((*a).type_name()).into(), - pos, - ) - }) - }); + let result = self.call_fn_raw(name, args, None, pos).and_then(|b| { + b.downcast().map(|b| *b).map_err(|a| { + EvalAltResult::ErrorMismatchOutputType( + self.map_type_name((*a).type_name()).into(), + pos, + ) + }) + }); self.clear_functions(); diff --git a/src/call.rs b/src/call.rs index 065cc8d5..f9faf563 100644 --- a/src/call.rs +++ b/src/call.rs @@ -1,35 +1,29 @@ //! Helper module which defines `FnArgs` to make function calling easier. -use crate::any::{Any, Variant}; +use crate::any::{Any, Dynamic}; /// Trait that represent arguments to a function call. -pub trait FuncArgs<'a> { - /// Convert to a `Vec` of `Variant` arguments. - fn into_vec(self) -> Vec<&'a mut Variant>; +pub trait FuncArgs { + /// Convert to a `Vec` of `Dynamic` arguments. + fn into_vec(self) -> Vec; } -impl<'a> FuncArgs<'a> for Vec<&'a mut Variant> { - fn into_vec(self) -> Self { - self - } -} - -impl<'a, T: Any> FuncArgs<'a> for &'a mut Vec { - fn into_vec(self) -> Vec<&'a mut Variant> { - self.iter_mut().map(|x| x as &mut Variant).collect() +impl FuncArgs for &mut Vec { + fn into_vec(self) -> Vec { + self.iter_mut().map(|x| x.into_dynamic()).collect() } } macro_rules! impl_args { ($($p:ident),*) => { - impl<'a, $($p: Any + Clone),*> FuncArgs<'a> for ($(&'a mut $p,)*) + impl<$($p: Any + Clone),*> FuncArgs for ($($p,)*) { - fn into_vec(self) -> Vec<&'a mut Variant> { + fn into_vec(self) -> Vec { let ($($p,)*) = self; #[allow(unused_variables, unused_mut)] let mut v = Vec::new(); - $(v.push($p as &mut Variant);)* + $(v.push($p.into_dynamic());)* v } @@ -45,4 +39,4 @@ macro_rules! impl_args { } #[cfg_attr(rustfmt, rustfmt_skip)] -impl_args!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S); +impl_args!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T); diff --git a/tests/engine.rs b/tests/engine.rs index b55a7c44..b049d107 100644 --- a/tests/engine.rs +++ b/tests/engine.rs @@ -4,9 +4,15 @@ use rhai::{Engine, EvalAltResult}; fn test_engine_call_fn() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - let ast = engine.compile("fn hello(x, y) { x.len() + y }")?; + let ast = engine.compile( + r" + fn hello(x, y) { + x.len() + y + } + ", + )?; - let result: i64 = engine.call_fn("hello", &ast, (&mut String::from("abc"), &mut 123_i64))?; + let result: i64 = engine.call_fn("hello", &ast, (String::from("abc"), 123_i64))?; assert_eq!(result, 126); From 52b5732bcb3462238ab8faa0f44645d255daa8cd Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 10 Mar 2020 17:10:33 +0800 Subject: [PATCH 02/24] Add no_inidex feature to disable arrays and indexing. --- Cargo.toml | 8 +++- README.md | 13 +++--- src/api.rs | 117 +++++++++++++++++++++++++----------------------- src/builtin.rs | 116 ++++++++++++++++++++++++++--------------------- src/engine.rs | 24 ++++++++-- src/lib.rs | 5 ++- src/optimize.rs | 23 +++++++--- src/parser.rs | 51 +++++++++++++-------- src/result.rs | 12 ++--- src/scope.rs | 1 + 10 files changed, 225 insertions(+), 145 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index fe543553..61609161 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,12 @@ include = [ num-traits = "*" [features] +default = [] debug_msgs = [] -no_stdlib = [] unchecked = [] +no_stdlib = [] +no_index = [] + +[profile.release] +lto = "fat" +codegen-units = 1 diff --git a/README.md b/README.md index 70555b38..6cf47caf 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,7 @@ Optional features | `debug_msgs` | Print debug messages to stdout (using `println!`) related to function registrations and function calls. | | `no_stdlib` | Exclude the standard library of utility functions in the build, and only include the minimum necessary functionalities. | | `unchecked` | Exclude arithmetic checking in the standard library. Beware that a bad script may panic the entire system! | +| `no_index` | Disable arrays and indexing features | Related ------- @@ -606,7 +607,7 @@ let booly = !true; Numeric functions ----------------- -The following standard functions (defined in the standard library but excluded if `no_stdlib`) operate on `i8`, `i16`, `i32`, `i64`, `f32` and `f64` only: +The following standard functions (defined in the standard library but excluded if [`no_stdlib`](#optional-features)) operate on `i8`, `i16`, `i32`, `i64`, `f32` and `f64` only: | Function | Description | | ---------- | ----------------------------------- | @@ -617,7 +618,7 @@ The following standard functions (defined in the standard library but excluded i Floating-point functions ------------------------ -The following standard functions (defined in the standard library but excluded if `no_stdlib`) operate on `f64` only: +The following standard functions (defined in the standard library but excluded if [`no_stdlib`](#optional-features)) operate on `f64` only: | Category | Functions | | ---------------- | ------------------------------------------------------------ | @@ -645,7 +646,7 @@ let age = 42; let record = full_name + ": age " + age; record == "Bob C. Davis: age 42"; -// Strings can be indexed to get a character +// Strings can be indexed to get a character (disabled with the 'no_index' feature) let c = record[4]; c == 'C'; @@ -669,7 +670,7 @@ record[4] = '\x58'; // 0x58 = 'X' record == "Bob X. Davis: age 42 ❤\n"; ``` -The following standard functions (defined in the standard library but excluded if `no_stdlib`) operate on strings: +The following standard functions (defined in the standard library but excluded if [`no_stdlib`](#optional-features)) operate on strings: | Function | Description | | ---------- | ------------------------------------------------------------------------ | @@ -716,7 +717,7 @@ Arrays You can create arrays of values, and then access them with numeric indices. -The following functions (defined in the standard library but excluded if `no_stdlib`) operate on arrays: +The following functions (defined in the standard library but excluded if [`no_stdlib`](#optional-features)) operate on arrays: | Function | Description | | ---------- | ------------------------------------------------------------------------------------- | @@ -788,6 +789,8 @@ engine.register_fn("push", The type of a Rhai array is `rhai::Array`. `type_of()` returns `"array"`. +Arrays are disabled via the [`no_index`](#optional-features) feature. + Comparison operators -------------------- diff --git a/src/api.rs b/src/api.rs index 9989af7b..ac9914d1 100644 --- a/src/api.rs +++ b/src/api.rs @@ -104,8 +104,7 @@ impl<'e> Engine<'e> { parse(&mut tokens.peekable(), self.optimize) } - /// Compile a file into an AST. - pub fn compile_file(&self, filename: &str) -> Result { + fn read_file(filename: &str) -> Result { let mut f = File::open(filename) .map_err(|err| EvalAltResult::ErrorReadingScriptFile(filename.into(), err))?; @@ -113,19 +112,18 @@ impl<'e> Engine<'e> { f.read_to_string(&mut contents) .map_err(|err| EvalAltResult::ErrorReadingScriptFile(filename.into(), err)) - .and_then(|_| self.compile(&contents).map_err(EvalAltResult::ErrorParsing)) + .map(|_| contents) + } + + /// Compile a file into an AST. + pub fn compile_file(&self, filename: &str) -> Result { + Self::read_file(filename) + .and_then(|contents| self.compile(&contents).map_err(|err| err.into())) } /// Evaluate a file. pub fn eval_file(&mut self, filename: &str) -> Result { - let mut f = File::open(filename) - .map_err(|err| EvalAltResult::ErrorReadingScriptFile(filename.into(), err))?; - - let mut contents = String::new(); - - f.read_to_string(&mut contents) - .map_err(|err| EvalAltResult::ErrorReadingScriptFile(filename.into(), err)) - .and_then(|_| self.eval::(&contents)) + Self::read_file(filename).and_then(|contents| self.eval::(&contents)) } /// Evaluate a string. @@ -164,27 +162,36 @@ impl<'e> Engine<'e> { retain_functions: bool, ast: &AST, ) -> Result { - let AST(statements, functions) = ast; + fn eval_ast_internal( + engine: &mut Engine, + scope: &mut Scope, + retain_functions: bool, + ast: &AST, + ) -> Result { + let AST(statements, functions) = ast; - functions.iter().for_each(|f| { - self.script_functions.insert( - FnSpec { - name: f.name.clone().into(), - args: None, - }, - Arc::new(FnIntExt::Int(f.clone())), - ); - }); + functions.iter().for_each(|f| { + engine.script_functions.insert( + FnSpec { + name: f.name.clone().into(), + args: None, + }, + Arc::new(FnIntExt::Int(f.clone())), + ); + }); - let result = statements - .iter() - .try_fold(().into_dynamic(), |_, stmt| self.eval_stmt(scope, stmt)); + let result = statements + .iter() + .try_fold(().into_dynamic(), |_, stmt| engine.eval_stmt(scope, stmt)); - if !retain_functions { - self.clear_functions(); + if !retain_functions { + engine.clear_functions(); + } + + result } - match result { + match eval_ast_internal(self, scope, retain_functions, ast) { Err(EvalAltResult::Return(out, pos)) => out.downcast::().map(|v| *v).map_err(|a| { EvalAltResult::ErrorMismatchOutputType( self.map_type_name((*a).type_name()).to_string(), @@ -285,45 +292,45 @@ impl<'e> Engine<'e> { ast: &AST, args: A, ) -> Result { + fn call_fn_internal( + engine: &mut Engine, + name: &str, + ast: &AST, + args: FnCallArgs, + ) -> Result { + ast.1.iter().for_each(|f| { + engine.script_functions.insert( + FnSpec { + name: f.name.clone().into(), + args: None, + }, + Arc::new(FnIntExt::Int(f.clone())), + ); + }); + + let result = engine.call_fn_raw(name, args, None, Position::none()); + + engine.clear_functions(); + + result + } + let mut arg_values = args.into_vec(); - self.call_fn_internal( + call_fn_internal( + self, name, ast, arg_values.iter_mut().map(|v| v.as_mut()).collect(), ) - } - - pub(crate) fn call_fn_internal( - &mut self, - name: &str, - ast: &AST, - args: FnCallArgs, - ) -> Result { - let pos = Default::default(); - - ast.1.iter().for_each(|f| { - self.script_functions.insert( - FnSpec { - name: f.name.clone().into(), - args: None, - }, - Arc::new(FnIntExt::Int(f.clone())), - ); - }); - - let result = self.call_fn_raw(name, args, None, pos).and_then(|b| { + .and_then(|b| { b.downcast().map(|b| *b).map_err(|a| { EvalAltResult::ErrorMismatchOutputType( self.map_type_name((*a).type_name()).into(), - pos, + Position::none(), ) }) - }); - - self.clear_functions(); - - result + }) } /// Override default action of `print` (print to stdout using `println!`) diff --git a/src/builtin.rs b/src/builtin.rs index 6cce507e..02e64581 100644 --- a/src/builtin.rs +++ b/src/builtin.rs @@ -2,8 +2,12 @@ //! _standard library_ of utility functions. use crate::any::Any; -use crate::engine::{Array, Engine}; +use crate::engine::Engine; use crate::fn_register::RegisterFn; + +#[cfg(not(feature = "no_index"))] +use crate::engine::Array; + use std::{ fmt::{Debug, Display}, i32, i64, @@ -15,14 +19,13 @@ use std::{ use std::ops::{Shl, Shr}; #[cfg(not(feature = "unchecked"))] -use crate::{parser::Position, result::EvalAltResult, RegisterResultFn}; - -#[cfg(not(feature = "unchecked"))] -use std::convert::TryFrom; - -#[cfg(not(feature = "unchecked"))] -use num_traits::{ - CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedRem, CheckedShl, CheckedShr, CheckedSub, +use { + crate::{parser::Position, result::EvalAltResult, RegisterResultFn}, + num_traits::{ + CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedRem, CheckedShl, CheckedShr, + CheckedSub, + }, + std::convert::TryFrom, }; macro_rules! reg_op { @@ -102,6 +105,7 @@ macro_rules! reg_func2y { } #[cfg(not(feature = "no_stdlib"))] +#[cfg(not(feature = "no_index"))] macro_rules! reg_func3 { ($self:expr, $x:expr, $op:expr, $v:ty, $w:ty, $r:ty, $( $y:ty ),*) => ( $( @@ -438,19 +442,23 @@ impl Engine<'_> { reg_func1!(self, "print", print, String, i8, u8, i16, u16); 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", || "".to_string()); self.register_fn("print", |_: ()| "".to_string()); reg_func1!(self, "debug", print_debug, String, i8, u8, i16, u16); 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, String, Array, ()); - // Register array iterator - self.register_iterator::(|a| { - Box::new(a.downcast_ref::().unwrap().clone().into_iter()) - }); + #[cfg(not(feature = "no_index"))] + { + reg_func1!(self, "print", print_debug, String, Array); + reg_func1!(self, "debug", print_debug, String, String, Array, ()); + + // Register array iterator + self.register_iterator::(|a| { + Box::new(a.downcast_ref::().unwrap().clone().into_iter()) + }); + } // Register range function self.register_iterator::, _>(|a| { @@ -468,6 +476,7 @@ impl Engine<'_> { /// Register the built-in library. #[cfg(not(feature = "no_stdlib"))] pub(crate) fn register_stdlib(&mut self) { + #[cfg(not(feature = "no_index"))] use crate::fn_register::RegisterDynamicFn; // Advanced math functions @@ -547,43 +556,46 @@ impl Engine<'_> { self.register_fn("to_int", |x: f64| x as i64); } - // Register array utility functions - fn push(list: &mut Array, item: T) { - list.push(Box::new(item)); - } - fn pad(list: &mut Array, len: i64, item: T) { - if len >= 0 { - while list.len() < len as usize { - push(list, item.clone()); + #[cfg(not(feature = "no_index"))] + { + // Register array utility functions + fn push(list: &mut Array, item: T) { + list.push(Box::new(item)); + } + fn pad(list: &mut Array, len: i64, item: T) { + if len >= 0 { + while list.len() < len as usize { + push(list, item.clone()); + } } } + + reg_func2x!(self, "push", push, &mut Array, (), i8, u8, i16, u16); + reg_func2x!(self, "push", push, &mut Array, (), i32, i64, u32, u64); + reg_func2x!(self, "push", push, &mut Array, (), f32, f64, bool, char); + reg_func2x!(self, "push", push, &mut Array, (), String, Array, ()); + reg_func3!(self, "pad", pad, &mut Array, i64, (), i8, u8, i16, u16); + reg_func3!(self, "pad", pad, &mut Array, i64, (), i32, u32, f32); + reg_func3!(self, "pad", pad, &mut Array, i64, (), i64, u64, f64); + reg_func3!(self, "pad", pad, &mut Array, i64, (), bool, char); + reg_func3!(self, "pad", pad, &mut Array, i64, (), String, Array, ()); + + self.register_dynamic_fn("pop", |list: &mut Array| { + list.pop().unwrap_or_else(|| ().into_dynamic()) + }); + self.register_dynamic_fn("shift", |list: &mut Array| match list.len() { + 0 => ().into_dynamic(), + _ => list.remove(0), + }); + self.register_fn("len", |list: &mut Array| list.len() as i64); + self.register_fn("clear", |list: &mut Array| list.clear()); + self.register_fn("truncate", |list: &mut Array, len: i64| { + if len >= 0 { + list.truncate(len as usize); + } + }); } - reg_func2x!(self, "push", push, &mut Array, (), i8, u8, i16, u16); - reg_func2x!(self, "push", push, &mut Array, (), i32, i64, u32, u64); - reg_func2x!(self, "push", push, &mut Array, (), f32, f64, bool, char); - reg_func2x!(self, "push", push, &mut Array, (), String, Array, ()); - reg_func3!(self, "pad", pad, &mut Array, i64, (), i8, u8, i16, u16); - reg_func3!(self, "pad", pad, &mut Array, i64, (), i32, u32, f32); - reg_func3!(self, "pad", pad, &mut Array, i64, (), i64, u64, f64); - reg_func3!(self, "pad", pad, &mut Array, i64, (), bool, char); - reg_func3!(self, "pad", pad, &mut Array, i64, (), String, Array, ()); - - self.register_dynamic_fn("pop", |list: &mut Array| { - list.pop().unwrap_or_else(|| ().into_dynamic()) - }); - self.register_dynamic_fn("shift", |list: &mut Array| match list.len() { - 0 => ().into_dynamic(), - _ => list.remove(0), - }); - self.register_fn("len", |list: &mut Array| list.len() as i64); - self.register_fn("clear", |list: &mut Array| list.clear()); - self.register_fn("truncate", |list: &mut Array, len: i64| { - if len >= 0 { - list.truncate(len as usize); - } - }); - // Register string concatenate functions fn prepend(x: T, y: String) -> String { format!("{}{}", x, y) @@ -596,16 +608,20 @@ impl Engine<'_> { self, "+", append, String, String, i8, u8, i16, u16, i32, i64, u32, u64, f32, f64, bool, char ); - self.register_fn("+", |x: String, y: Array| format!("{}{:?}", x, y)); self.register_fn("+", |x: String, _: ()| format!("{}", x)); reg_func2y!( self, "+", prepend, String, String, i8, u8, i16, u16, i32, i64, u32, u64, f32, f64, bool, char ); - self.register_fn("+", |x: Array, y: String| format!("{:?}{}", x, y)); self.register_fn("+", |_: (), y: String| format!("{}", y)); + #[cfg(not(feature = "no_index"))] + { + self.register_fn("+", |x: String, y: Array| format!("{}{:?}", x, y)); + self.register_fn("+", |x: Array, y: String| format!("{:?}{}", x, y)); + } + // Register string utility functions self.register_fn("len", |s: &mut String| s.chars().count() as i64); self.register_fn("contains", |s: &mut String, ch: char| s.contains(ch)); diff --git a/src/engine.rs b/src/engine.rs index 89dac01c..4ac2d67e 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -14,6 +14,7 @@ use std::{ }; /// An dynamic array of `Dynamic` values. +#[cfg(not(feature = "no_index"))] pub type Array = Vec; pub type FnCallArgs<'a> = Vec<&'a mut Variant>; @@ -29,6 +30,7 @@ pub(crate) const FUNC_GETTER: &'static str = "get$"; pub(crate) const FUNC_SETTER: &'static str = "set$"; #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] +#[cfg(not(feature = "no_index"))] enum IndexSourceType { Array, String, @@ -82,8 +84,9 @@ impl Engine<'_> { // User-friendly names for built-in types let type_names = [ (type_name::(), "string"), - (type_name::(), "array"), (type_name::(), "dynamic"), + #[cfg(not(feature = "no_index"))] + (type_name::(), "array"), ] .iter() .map(|(k, v)| (k.to_string(), v.to_string())) @@ -251,7 +254,7 @@ impl Engine<'_> { match dot_rhs { // xxx.fn_name(args) Expr::FunctionCall(fn_name, args, def_val, pos) => { - let mut args: Array = args + let mut args = args .iter() .map(|arg| self.eval_expr(scope, arg)) .collect::, _>>()?; @@ -271,6 +274,7 @@ impl Engine<'_> { } // xxx.idx_lhs[idx_expr] + #[cfg(not(feature = "no_index"))] Expr::Index(idx_lhs, idx_expr, idx_pos) => { let (expr, _) = match idx_lhs.as_ref() { // xxx.id[idx_expr] @@ -309,6 +313,7 @@ impl Engine<'_> { .and_then(|mut v| self.get_dot_val_helper(scope, v.as_mut(), rhs)) } // xxx.idx_lhs[idx_expr].rhs + #[cfg(not(feature = "no_index"))] Expr::Index(idx_lhs, idx_expr, idx_pos) => { let (expr, _) = match idx_lhs.as_ref() { // xxx.id[idx_expr].rhs @@ -371,6 +376,7 @@ impl Engine<'_> { } // idx_lhs[idx_expr].??? + #[cfg(not(feature = "no_index"))] Expr::Index(idx_lhs, idx_expr, idx_pos) => { let (src_type, src, idx, mut target) = self.eval_index_expr(scope, idx_lhs, idx_expr, *idx_pos)?; @@ -414,6 +420,7 @@ impl Engine<'_> { } /// Evaluate the value of an index (must evaluate to i64) + #[cfg(not(feature = "no_index"))] fn eval_index_value( &mut self, scope: &mut Scope, @@ -426,6 +433,7 @@ impl Engine<'_> { } /// Get the value at the indexed position of a base type + #[cfg(not(feature = "no_index"))] fn get_indexed_value( &self, val: Dynamic, @@ -473,6 +481,7 @@ impl Engine<'_> { } /// Evaluate an index expression + #[cfg(not(feature = "no_index"))] fn eval_index_expr<'a>( &mut self, scope: &mut Scope, @@ -505,6 +514,7 @@ impl Engine<'_> { } /// Replace a character at an index position in a mutable string + #[cfg(not(feature = "no_index"))] fn str_replace_char(s: &mut String, idx: usize, new_ch: char) { let mut chars: Vec = s.chars().collect(); let ch = *chars.get(idx).expect("string index out of bounds"); @@ -518,6 +528,7 @@ impl Engine<'_> { } /// Update the value at an index position in a variable inside the scope + #[cfg(not(feature = "no_index"))] fn update_indexed_var_in_scope( src_type: IndexSourceType, scope: &mut Scope, @@ -550,6 +561,7 @@ impl Engine<'_> { } /// Update the value at an index position + #[cfg(not(feature = "no_index"))] fn update_indexed_value( mut target: Dynamic, idx: usize, @@ -593,6 +605,7 @@ impl Engine<'_> { // xxx.lhs[idx_expr] // TODO - Allow chaining of indexing! + #[cfg(not(feature = "no_index"))] Expr::Index(lhs, idx_expr, idx_pos) => match lhs.as_ref() { // xxx.id[idx_expr] Expr::Identifier(id, pos) => { @@ -636,6 +649,7 @@ impl Engine<'_> { // xxx.lhs[idx_expr].rhs // TODO - Allow chaining of indexing! + #[cfg(not(feature = "no_index"))] Expr::Index(lhs, idx_expr, idx_pos) => match lhs.as_ref() { // xxx.id[idx_expr].rhs Expr::Identifier(id, pos) => { @@ -720,6 +734,7 @@ impl Engine<'_> { // lhs[idx_expr].??? // TODO - Allow chaining of indexing! + #[cfg(not(feature = "no_index"))] Expr::Index(lhs, idx_expr, idx_pos) => { let (src_type, src, idx, mut target) = self.eval_index_expr(scope, lhs, idx_expr, *idx_pos)?; @@ -762,6 +777,7 @@ impl Engine<'_> { } // lhs[idx_expr] + #[cfg(not(feature = "no_index"))] Expr::Index(lhs, idx_expr, idx_pos) => self .eval_index_expr(scope, lhs, idx_expr, *idx_pos) .map(|(_, _, _, x)| x), @@ -785,6 +801,7 @@ impl Engine<'_> { } // idx_lhs[idx_expr] = rhs + #[cfg(not(feature = "no_index"))] Expr::Index(idx_lhs, idx_expr, idx_pos) => { let (src_type, src, idx, _) = self.eval_index_expr(scope, idx_lhs, idx_expr, *idx_pos)?; @@ -818,6 +835,7 @@ impl Engine<'_> { Expr::Dot(lhs, rhs, _) => self.get_dot_val(scope, lhs, rhs), + #[cfg(not(feature = "no_index"))] Expr::Array(contents, _) => { let mut arr = Vec::new(); @@ -836,7 +854,7 @@ impl Engine<'_> { let mut args = args .iter() .map(|expr| self.eval_expr(scope, expr)) - .collect::>()?; + .collect::, _>>()?; self.call_fn_raw( fn_name, diff --git a/src/lib.rs b/src/lib.rs index 56df1ae2..fb23e27c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -75,9 +75,12 @@ mod scope; pub use any::{Any, AnyExt, Dynamic, Variant}; pub use call::FuncArgs; -pub use engine::{Array, Engine}; +pub use engine::Engine; pub use error::{ParseError, ParseErrorType}; pub use fn_register::{RegisterDynamicFn, RegisterFn, RegisterResultFn}; pub use parser::{Position, AST}; pub use result::EvalAltResult; pub use scope::Scope; + +#[cfg(not(feature = "no_index"))] +pub use engine::Array; diff --git a/src/optimize.rs b/src/optimize.rs index 4a0b26b1..0f2eb585 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -139,11 +139,24 @@ fn optimize_expr(expr: Expr, changed: &mut bool) -> Expr { Box::new(optimize_expr(*rhs, changed)), pos, ), - Expr::Index(lhs, rhs, pos) => Expr::Index( - Box::new(optimize_expr(*lhs, changed)), - Box::new(optimize_expr(*rhs, changed)), - pos, - ), + #[cfg(not(feature = "no_index"))] + Expr::Index(lhs, rhs, pos) => match (*lhs, *rhs) { + (Expr::Array(mut items, _), Expr::IntegerConstant(i, _)) + if i >= 0 + && (i as usize) < items.len() + && !items.iter().any(|x| x.is_constant()) => + { + // Array where everything is a constant - promote the item + *changed = true; + items.remove(i as usize) + } + (lhs, rhs) => Expr::Index( + Box::new(optimize_expr(lhs, changed)), + Box::new(optimize_expr(rhs, changed)), + pos, + ), + }, + #[cfg(not(feature = "no_index"))] Expr::Array(items, pos) => { let original_len = items.len(); diff --git a/src/parser.rs b/src/parser.rs index 10e629d0..9009305b 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -167,7 +167,9 @@ pub enum Expr { FunctionCall(String, Vec, Option, Position), Assignment(Box, Box, Position), Dot(Box, Box, Position), + #[cfg(not(feature = "no_index"))] Index(Box, Box, Position), + #[cfg(not(feature = "no_index"))] Array(Vec, Position), And(Box, Box), Or(Box, Box), @@ -186,16 +188,19 @@ impl Expr { | Expr::StringConstant(_, pos) | Expr::FunctionCall(_, _, _, pos) | Expr::Stmt(_, pos) - | Expr::Array(_, pos) | Expr::True(pos) | Expr::False(pos) | Expr::Unit(pos) => *pos, - Expr::Index(e, _, _) - | Expr::Assignment(e, _, _) - | Expr::Dot(e, _, _) - | Expr::And(e, _) - | Expr::Or(e, _) => e.position(), + Expr::Assignment(e, _, _) | Expr::Dot(e, _, _) | Expr::And(e, _) | Expr::Or(e, _) => { + e.position() + } + + #[cfg(not(feature = "no_index"))] + Expr::Index(e, _, _) => e.position(), + + #[cfg(not(feature = "no_index"))] + Expr::Array(_, pos) => *pos, } } @@ -1166,6 +1171,7 @@ fn parse_call_expr<'a>( } } +#[cfg(not(feature = "no_index"))] fn parse_index_expr<'a>( lhs: Box, input: &mut Peekable>, @@ -1260,6 +1266,7 @@ fn parse_ident_expr<'a>( input.next(); parse_call_expr(id, input, begin) } + #[cfg(not(feature = "no_index"))] Some(&(Token::LeftBracket, pos)) => { input.next(); parse_index_expr(Box::new(Expr::Identifier(id, begin)), input, pos) @@ -1269,6 +1276,7 @@ fn parse_ident_expr<'a>( } } +#[cfg(not(feature = "no_index"))] fn parse_array_expr<'a>( input: &mut Peekable>, begin: Position, @@ -1320,26 +1328,28 @@ fn parse_primary<'a>(input: &mut Peekable>) -> Result Ok(Expr::IntegerConstant(x, pos)), Some((Token::FloatConstant(x), pos)) => Ok(Expr::FloatConstant(x, pos)), Some((Token::CharConstant(c), pos)) => Ok(Expr::CharConstant(c, pos)), Some((Token::StringConst(s), pos)) => { - follow_on = true; + can_be_indexed = true; Ok(Expr::StringConstant(s, pos)) } Some((Token::Identifier(s), pos)) => { - follow_on = true; + can_be_indexed = true; parse_ident_expr(s, input, pos) } Some((Token::LeftParen, pos)) => { - follow_on = true; + can_be_indexed = true; parse_paren_expr(input, pos) } + #[cfg(not(feature = "no_index"))] Some((Token::LeftBracket, pos)) => { - follow_on = true; + can_be_indexed = true; parse_array_expr(input, pos) } Some((Token::True, pos)) => Ok(Expr::True(pos)), @@ -1354,14 +1364,13 @@ fn parse_primary<'a>(input: &mut Peekable>) -> Result Err(ParseError::new(PERR::InputPastEndOfFile, Position::eof())), }?; - if !follow_on { - return Ok(root_expr); - } - - // Tail processing all possible indexing - while let Some(&(Token::LeftBracket, pos)) = input.peek() { - input.next(); - root_expr = parse_index_expr(Box::new(root_expr), input, pos)?; + if can_be_indexed { + // Tail processing all possible indexing + #[cfg(not(feature = "no_index"))] + while let Some(&(Token::LeftBracket, pos)) = input.peek() { + input.next(); + root_expr = parse_index_expr(Box::new(root_expr), input, pos)?; + } } Ok(root_expr) @@ -1408,6 +1417,7 @@ fn parse_assignment(lhs: Expr, rhs: Expr, pos: Position) -> Result (true, *pos), + #[cfg(not(feature = "no_index"))] Expr::Index(idx_lhs, _, _) => match idx_lhs.as_ref() { Expr::Identifier(_, _) => (true, idx_lhs.position()), _ => (false, idx_lhs.position()), @@ -1415,10 +1425,13 @@ fn parse_assignment(lhs: Expr, rhs: Expr, pos: Position) -> Result match dot_lhs.as_ref() { Expr::Identifier(_, _) => valid_assignment_chain(dot_rhs), + + #[cfg(not(feature = "no_index"))] Expr::Index(idx_lhs, _, _) => match idx_lhs.as_ref() { Expr::Identifier(_, _) => valid_assignment_chain(dot_rhs), _ => (false, idx_lhs.position()), }, + _ => (false, dot_lhs.position()), }, diff --git a/src/result.rs b/src/result.rs index 224c8232..f95e4b6a 100644 --- a/src/result.rs +++ b/src/result.rs @@ -173,6 +173,12 @@ impl From for EvalAltResult { } } +impl> From for EvalAltResult { + fn from(err: T) -> Self { + Self::ErrorRuntime(err.as_ref().to_string(), Position::none()) + } +} + impl EvalAltResult { pub fn position(&self) -> Position { match self { @@ -225,9 +231,3 @@ impl EvalAltResult { } } } - -impl> From for EvalAltResult { - fn from(err: T) -> Self { - Self::ErrorRuntime(err.as_ref().to_string(), Position::none()) - } -} diff --git a/src/scope.rs b/src/scope.rs index 2e2c492b..d097f422 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -93,6 +93,7 @@ impl<'a> Scope<'a> { } /// Get a mutable reference to a variable in the Scope and downcast it to a specific type + #[cfg(not(feature = "no_index"))] pub(crate) fn get_mut_by_type(&mut self, key: &str, index: usize) -> &mut T { self.get_mut(key, index) .downcast_mut::() From cc772c6e2ad04ad21f5da9700f70a9f0413b7832 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 10 Mar 2020 18:32:01 +0800 Subject: [PATCH 03/24] Add no_float feature to disable floating-point. --- Cargo.toml | 1 + README.md | 1 + src/builtin.rs | 231 +++++++++++++++++++++++++++++------------------- src/engine.rs | 4 +- src/optimize.rs | 8 +- src/parser.rs | 86 +++++++++++------- 6 files changed, 206 insertions(+), 125 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 61609161..3bbd25ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ debug_msgs = [] unchecked = [] no_stdlib = [] no_index = [] +no_float = [] [profile.release] lto = "fat" diff --git a/README.md b/README.md index 6cf47caf..5488ec97 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,7 @@ Optional features | `no_stdlib` | Exclude the standard library of utility functions in the build, and only include the minimum necessary functionalities. | | `unchecked` | Exclude arithmetic checking in the standard library. Beware that a bad script may panic the entire system! | | `no_index` | Disable arrays and indexing features | +| `no_float` | Disable floating-point numbers and math | Related ------- diff --git a/src/builtin.rs b/src/builtin.rs index 02e64581..106d647b 100644 --- a/src/builtin.rs +++ b/src/builtin.rs @@ -11,13 +11,16 @@ use crate::engine::Array; use std::{ fmt::{Debug, Display}, i32, i64, - ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Range, Rem, Sub}, + ops::{BitAnd, BitOr, BitXor, Range}, u32, }; #[cfg(feature = "unchecked")] use std::ops::{Shl, Shr}; +#[cfg(any(feature = "unchecked", not(feature = "no_float")))] +use std::ops::{Add, Div, Mul, Neg, Rem, Sub}; + #[cfg(not(feature = "unchecked"))] use { crate::{parser::Position, result::EvalAltResult, RegisterResultFn}, @@ -188,21 +191,27 @@ impl Engine<'_> { }) } } + #[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn add_u(x: T, y: T) -> ::Output { x + y } + #[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn sub_u(x: T, y: T) -> ::Output { x - y } + #[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn mul_u(x: T, y: T) -> ::Output { x * y } + #[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn div_u(x: T, y: T) -> ::Output { x / y } + #[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn neg_u(x: T) -> ::Output { -x } + #[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn abs_u>(x: T) -> T where ::Output: Into, @@ -298,6 +307,7 @@ impl Engine<'_> { ) }) } + #[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn modulo_u(x: T, y: T) -> ::Output { x % y } @@ -321,10 +331,12 @@ impl Engine<'_> { fn pow_i64_i64(x: i64, y: i64) -> i64 { x.pow(y as u32) } + #[cfg(not(feature = "no_float"))] fn pow_f64_f64(x: f64, y: f64) -> f64 { x.powf(y) } #[cfg(not(feature = "unchecked"))] + #[cfg(not(feature = "no_float"))] fn pow_f64_i64_u(x: f64, y: i64) -> Result { if y > (i32::MAX as i64) { return Err(EvalAltResult::ErrorArithmetic( @@ -336,6 +348,7 @@ impl Engine<'_> { Ok(x.powi(y as i32)) } #[cfg(feature = "unchecked")] + #[cfg(not(feature = "no_float"))] fn pow_f64_i64(x: f64, y: i64) -> f64 { x.powi(y as i32) } @@ -356,21 +369,30 @@ impl Engine<'_> { reg_op!(self, "/", div_u, i8, u8, i16, u16, i32, i64, u32, u64); } - reg_op!(self, "+", add_u, f32, f64); - reg_op!(self, "-", sub_u, f32, f64); - reg_op!(self, "*", mul_u, f32, f64); - reg_op!(self, "/", div_u, f32, f64); + #[cfg(not(feature = "no_float"))] + { + reg_op!(self, "+", add_u, f32, f64); + reg_op!(self, "-", sub_u, f32, f64); + reg_op!(self, "*", mul_u, f32, f64); + reg_op!(self, "/", div_u, f32, f64); + } - reg_cmp!(self, "<", lt, i8, u8, i16, u16, i32, i64, u32, u64, f32, f64, String, char); - reg_cmp!(self, "<=", lte, i8, u8, i16, u16, i32, i64, u32, u64, f32, f64, String, char); - reg_cmp!(self, ">", gt, i8, u8, i16, u16, i32, i64, u32, u64, f32, f64, String, char); - reg_cmp!(self, ">=", gte, i8, u8, i16, u16, i32, i64, u32, u64, f32, f64, String, char); - reg_cmp!( - self, "==", eq, i8, u8, i16, u16, i32, i64, u32, u64, bool, f32, f64, String, char - ); - reg_cmp!( - self, "!=", ne, i8, u8, i16, u16, i32, i64, u32, u64, bool, f32, f64, String, char - ); + reg_cmp!(self, "<", lt, i8, u8, i16, u16, i32, i64, u32, u64, String, char); + reg_cmp!(self, "<=", lte, i8, u8, i16, u16, i32, i64, u32, u64, String, char); + reg_cmp!(self, ">", gt, i8, u8, i16, u16, i32, i64, u32, u64, String, char); + reg_cmp!(self, ">=", gte, i8, u8, i16, u16, i32, i64, u32, u64, String, char); + reg_cmp!(self, "==", eq, i8, u8, i16, u16, i32, i64, u32, u64, bool, String, char); + reg_cmp!(self, "!=", ne, i8, u8, i16, u16, i32, i64, u32, u64, bool, String, char); + + #[cfg(not(feature = "no_float"))] + { + reg_cmp!(self, "<", lt, f32, f64); + reg_cmp!(self, "<=", lte, f32, f64); + reg_cmp!(self, ">", gt, f32, f64); + reg_cmp!(self, ">=", gte, f32, f64); + reg_cmp!(self, "==", eq, f32, f64); + reg_cmp!(self, "!=", ne, f32, f64); + } //reg_op!(self, "||", or, bool); //reg_op!(self, "&&", and, bool); @@ -396,19 +418,25 @@ impl Engine<'_> { reg_op!(self, "%", modulo_u, i8, u8, i16, u16, i32, i64, u32, u64); } - reg_op!(self, "%", modulo_u, f32, f64); - - self.register_fn("~", pow_f64_f64); + #[cfg(not(feature = "no_float"))] + { + reg_op!(self, "%", modulo_u, f32, f64); + self.register_fn("~", pow_f64_f64); + } #[cfg(not(feature = "unchecked"))] { self.register_result_fn("~", pow_i64_i64_u); + + #[cfg(not(feature = "no_float"))] self.register_result_fn("~", pow_f64_i64_u); } #[cfg(feature = "unchecked")] { self.register_fn("~", pow_i64_i64); + + #[cfg(not(feature = "no_float"))] self.register_fn("~", pow_f64_i64); } @@ -424,8 +452,12 @@ impl Engine<'_> { reg_un!(self, "abs", abs_u, i8, i16, i32, i64); } - reg_un!(self, "-", neg_u, f32, f64); - reg_un!(self, "abs", abs_u, f32, f64); + #[cfg(not(feature = "no_float"))] + { + reg_un!(self, "-", neg_u, f32, f64); + reg_un!(self, "abs", abs_u, f32, f64); + } + reg_un!(self, "!", not, bool); self.register_fn("+", |x: String, y: String| x + &y); // String + String @@ -441,13 +473,19 @@ impl Engine<'_> { reg_func1!(self, "print", print, String, i8, u8, i16, u16); 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, bool, char, String); self.register_fn("print", || "".to_string()); self.register_fn("print", |_: ()| "".to_string()); reg_func1!(self, "debug", print_debug, String, i8, u8, i16, u16); 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, bool, char); + + #[cfg(not(feature = "no_float"))] + { + reg_func1!(self, "print", print, String, f32, f64); + reg_func1!(self, "debug", print_debug, String, f32, f64); + } #[cfg(not(feature = "no_index"))] { @@ -479,43 +517,46 @@ impl Engine<'_> { #[cfg(not(feature = "no_index"))] use crate::fn_register::RegisterDynamicFn; - // Advanced math functions - self.register_fn("sin", |x: f64| x.to_radians().sin()); - self.register_fn("cos", |x: f64| x.to_radians().cos()); - self.register_fn("tan", |x: f64| x.to_radians().tan()); - self.register_fn("sinh", |x: f64| x.to_radians().sinh()); - self.register_fn("cosh", |x: f64| x.to_radians().cosh()); - self.register_fn("tanh", |x: f64| x.to_radians().tanh()); - self.register_fn("asin", |x: f64| x.asin().to_degrees()); - self.register_fn("acos", |x: f64| x.acos().to_degrees()); - self.register_fn("atan", |x: f64| x.atan().to_degrees()); - self.register_fn("asinh", |x: f64| x.asinh().to_degrees()); - self.register_fn("acosh", |x: f64| x.acosh().to_degrees()); - self.register_fn("atanh", |x: f64| x.atanh().to_degrees()); - self.register_fn("sqrt", |x: f64| x.sqrt()); - self.register_fn("exp", |x: f64| x.exp()); - self.register_fn("ln", |x: f64| x.ln()); - self.register_fn("log", |x: f64, base: f64| x.log(base)); - self.register_fn("log10", |x: f64| x.log10()); - self.register_fn("floor", |x: f64| x.floor()); - self.register_fn("ceiling", |x: f64| x.ceil()); - self.register_fn("round", |x: f64| x.ceil()); - self.register_fn("int", |x: f64| x.trunc()); - self.register_fn("fraction", |x: f64| x.fract()); - self.register_fn("is_nan", |x: f64| x.is_nan()); - self.register_fn("is_finite", |x: f64| x.is_finite()); - self.register_fn("is_infinite", |x: f64| x.is_infinite()); + #[cfg(not(feature = "no_float"))] + { + // Advanced math functions + self.register_fn("sin", |x: f64| x.to_radians().sin()); + self.register_fn("cos", |x: f64| x.to_radians().cos()); + self.register_fn("tan", |x: f64| x.to_radians().tan()); + self.register_fn("sinh", |x: f64| x.to_radians().sinh()); + self.register_fn("cosh", |x: f64| x.to_radians().cosh()); + self.register_fn("tanh", |x: f64| x.to_radians().tanh()); + self.register_fn("asin", |x: f64| x.asin().to_degrees()); + self.register_fn("acos", |x: f64| x.acos().to_degrees()); + self.register_fn("atan", |x: f64| x.atan().to_degrees()); + self.register_fn("asinh", |x: f64| x.asinh().to_degrees()); + self.register_fn("acosh", |x: f64| x.acosh().to_degrees()); + self.register_fn("atanh", |x: f64| x.atanh().to_degrees()); + self.register_fn("sqrt", |x: f64| x.sqrt()); + self.register_fn("exp", |x: f64| x.exp()); + self.register_fn("ln", |x: f64| x.ln()); + self.register_fn("log", |x: f64, base: f64| x.log(base)); + self.register_fn("log10", |x: f64| x.log10()); + self.register_fn("floor", |x: f64| x.floor()); + self.register_fn("ceiling", |x: f64| x.ceil()); + self.register_fn("round", |x: f64| x.ceil()); + self.register_fn("int", |x: f64| x.trunc()); + self.register_fn("fraction", |x: f64| x.fract()); + self.register_fn("is_nan", |x: f64| x.is_nan()); + self.register_fn("is_finite", |x: f64| x.is_finite()); + self.register_fn("is_infinite", |x: f64| x.is_infinite()); - // Register conversion functions - self.register_fn("to_float", |x: i8| x as f64); - self.register_fn("to_float", |x: u8| x as f64); - self.register_fn("to_float", |x: i16| x as f64); - self.register_fn("to_float", |x: u16| x as f64); - self.register_fn("to_float", |x: i32| x as f64); - self.register_fn("to_float", |x: u32| x as f64); - self.register_fn("to_float", |x: i64| x as f64); - self.register_fn("to_float", |x: u64| x as f64); - self.register_fn("to_float", |x: f32| x as f64); + // Register conversion functions + self.register_fn("to_float", |x: i8| x as f64); + self.register_fn("to_float", |x: u8| x as f64); + self.register_fn("to_float", |x: i16| x as f64); + self.register_fn("to_float", |x: u16| x as f64); + self.register_fn("to_float", |x: i32| x as f64); + self.register_fn("to_float", |x: u32| x as f64); + self.register_fn("to_float", |x: i64| x as f64); + self.register_fn("to_float", |x: u64| x as f64); + self.register_fn("to_float", |x: f32| x as f64); + } self.register_fn("to_int", |x: i8| x as i64); self.register_fn("to_int", |x: u8| x as i64); @@ -526,34 +567,37 @@ impl Engine<'_> { self.register_fn("to_int", |x: u64| x as i64); self.register_fn("to_int", |ch: char| ch as i64); - #[cfg(not(feature = "unchecked"))] + #[cfg(not(feature = "no_float"))] { - self.register_result_fn("to_int", |x: f32| { - if x > (i64::MAX as f32) { - return Err(EvalAltResult::ErrorArithmetic( - format!("Integer overflow: to_int({})", x), - Position::none(), - )); - } + #[cfg(not(feature = "unchecked"))] + { + self.register_result_fn("to_int", |x: f32| { + if x > (i64::MAX as f32) { + return Err(EvalAltResult::ErrorArithmetic( + format!("Integer overflow: to_int({})", x), + Position::none(), + )); + } - Ok(x.trunc() as i64) - }); - self.register_result_fn("to_int", |x: f64| { - if x > (i64::MAX as f64) { - return Err(EvalAltResult::ErrorArithmetic( - format!("Integer overflow: to_int({})", x), - Position::none(), - )); - } + Ok(x.trunc() as i64) + }); + self.register_result_fn("to_int", |x: f64| { + if x > (i64::MAX as f64) { + return Err(EvalAltResult::ErrorArithmetic( + format!("Integer overflow: to_int({})", x), + Position::none(), + )); + } - Ok(x.trunc() as i64) - }); - } + Ok(x.trunc() as i64) + }); + } - #[cfg(feature = "unchecked")] - { - self.register_fn("to_int", |x: f32| x as i64); - self.register_fn("to_int", |x: f64| x as i64); + #[cfg(feature = "unchecked")] + { + self.register_fn("to_int", |x: f32| x as i64); + self.register_fn("to_int", |x: f64| x as i64); + } } #[cfg(not(feature = "no_index"))] @@ -572,14 +616,19 @@ impl Engine<'_> { reg_func2x!(self, "push", push, &mut Array, (), i8, u8, i16, u16); reg_func2x!(self, "push", push, &mut Array, (), i32, i64, u32, u64); - reg_func2x!(self, "push", push, &mut Array, (), f32, f64, bool, char); + reg_func2x!(self, "push", push, &mut Array, (), bool, char); reg_func2x!(self, "push", push, &mut Array, (), String, Array, ()); reg_func3!(self, "pad", pad, &mut Array, i64, (), i8, u8, i16, u16); - reg_func3!(self, "pad", pad, &mut Array, i64, (), i32, u32, f32); - reg_func3!(self, "pad", pad, &mut Array, i64, (), i64, u64, f64); + reg_func3!(self, "pad", pad, &mut Array, i64, (), i32, u32, i64, u64); reg_func3!(self, "pad", pad, &mut Array, i64, (), bool, char); reg_func3!(self, "pad", pad, &mut Array, i64, (), String, Array, ()); + #[cfg(not(feature = "no_float"))] + { + reg_func2x!(self, "push", push, &mut Array, (), f32, f64); + reg_func3!(self, "pad", pad, &mut Array, i64, (), f32, f64); + } + self.register_dynamic_fn("pop", |list: &mut Array| { list.pop().unwrap_or_else(|| ().into_dynamic()) }); @@ -605,17 +654,21 @@ impl Engine<'_> { } reg_func2x!( - self, "+", append, String, String, i8, u8, i16, u16, i32, i64, u32, u64, f32, f64, - bool, char + self, "+", append, String, String, i8, u8, i16, u16, i32, i64, u32, u64, bool, char ); self.register_fn("+", |x: String, _: ()| format!("{}", x)); reg_func2y!( - self, "+", prepend, String, String, i8, u8, i16, u16, i32, i64, u32, u64, f32, f64, - bool, char + self, "+", prepend, String, String, i8, u8, i16, u16, i32, i64, u32, u64, bool, char ); self.register_fn("+", |_: (), y: String| format!("{}", y)); + #[cfg(not(feature = "no_float"))] + { + reg_func2x!(self, "+", append, String, String, f32, f64); + reg_func2y!(self, "+", prepend, String, String, f32, f64); + } + #[cfg(not(feature = "no_index"))] { self.register_fn("+", |x: String, y: Array| format!("{}{:?}", x, y)); diff --git a/src/engine.rs b/src/engine.rs index 4ac2d67e..d13d45d3 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -768,8 +768,10 @@ impl Engine<'_> { /// Evaluate an expression fn eval_expr(&mut self, scope: &mut Scope, expr: &Expr) -> Result { match expr { - Expr::IntegerConstant(i, _) => Ok(i.into_dynamic()), + #[cfg(not(feature = "no_float"))] Expr::FloatConstant(f, _) => Ok(f.into_dynamic()), + + Expr::IntegerConstant(i, _) => Ok(i.into_dynamic()), Expr::StringConstant(s, _) => Ok(s.into_dynamic()), Expr::CharConstant(c, _) => Ok(c.into_dynamic()), Expr::Identifier(id, pos) => { diff --git a/src/optimize.rs b/src/optimize.rs index 0f2eb585..29f226f6 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -112,7 +112,6 @@ fn optimize_stmt(stmt: Stmt, changed: &mut bool) -> Stmt { fn optimize_expr(expr: Expr, changed: &mut bool) -> Expr { match expr { Expr::IntegerConstant(_, _) - | Expr::FloatConstant(_, _) | Expr::Identifier(_, _) | Expr::CharConstant(_, _) | Expr::StringConstant(_, _) @@ -120,6 +119,9 @@ fn optimize_expr(expr: Expr, changed: &mut bool) -> Expr { | Expr::False(_) | Expr::Unit(_) => expr, + #[cfg(not(feature = "no_float"))] + Expr::FloatConstant(_, _) => expr, + Expr::Stmt(stmt, pos) => match optimize_stmt(*stmt, changed) { Stmt::Noop(_) => { *changed = true; @@ -144,9 +146,9 @@ fn optimize_expr(expr: Expr, changed: &mut bool) -> Expr { (Expr::Array(mut items, _), Expr::IntegerConstant(i, _)) if i >= 0 && (i as usize) < items.len() - && !items.iter().any(|x| x.is_constant()) => + && !items.iter().any(|x| x.is_constant() || x.is_identifier()) => { - // Array where everything is a constant - promote the item + // Array where everything is a constant or identifier - promote the item *changed = true; items.remove(i as usize) } diff --git a/src/parser.rs b/src/parser.rs index 9009305b..9943a7f7 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -159,6 +159,7 @@ impl Stmt { #[derive(Debug, Clone)] pub enum Expr { IntegerConstant(i64, Position), + #[cfg(not(feature = "no_float"))] FloatConstant(f64, Position), Identifier(String, Position), CharConstant(char, Position), @@ -182,7 +183,6 @@ impl Expr { pub fn position(&self) -> Position { match self { Expr::IntegerConstant(_, pos) - | Expr::FloatConstant(_, pos) | Expr::Identifier(_, pos) | Expr::CharConstant(_, pos) | Expr::StringConstant(_, pos) @@ -196,6 +196,9 @@ impl Expr { e.position() } + #[cfg(not(feature = "no_float"))] + Expr::FloatConstant(_, pos) => *pos, + #[cfg(not(feature = "no_index"))] Expr::Index(e, _, _) => e.position(), @@ -207,14 +210,22 @@ impl Expr { pub fn is_constant(&self) -> bool { match self { Expr::IntegerConstant(_, _) - | Expr::FloatConstant(_, _) - | Expr::Identifier(_, _) | Expr::CharConstant(_, _) | Expr::StringConstant(_, _) | Expr::True(_) | Expr::False(_) | Expr::Unit(_) => true, + #[cfg(not(feature = "no_float"))] + Expr::FloatConstant(_, _) => true, + + _ => false, + } + } + + pub fn is_identifier(&self) -> bool { + match self { + Expr::Identifier(_, _) => true, _ => false, } } @@ -223,6 +234,7 @@ impl Expr { #[derive(Debug, PartialEq, Clone)] pub enum Token { IntegerConstant(i64), + #[cfg(not(feature = "no_float"))] FloatConstant(f64), Identifier(String), CharConstant(char), @@ -293,6 +305,7 @@ impl Token { match *self { IntegerConstant(ref i) => i.to_string().into(), + #[cfg(not(feature = "no_float"))] FloatConstant(ref f) => f.to_string().into(), Identifier(ref s) => s.into(), CharConstant(ref c) => c.to_string().into(), @@ -622,6 +635,7 @@ impl<'a> TokenIterator<'a> { self.char_stream.next(); self.advance(); } + #[cfg(not(feature = "no_float"))] '.' => { result.push(next_char); self.char_stream.next(); @@ -707,6 +721,17 @@ impl<'a> TokenIterator<'a> { } else { let out: String = result.iter().filter(|&&c| c != '_').collect(); + #[cfg(feature = "no_float")] + return Some(( + i64::from_str(&out) + .map(Token::IntegerConstant) + .unwrap_or_else(|_| { + Token::LexError(LERR::MalformedNumber(result.iter().collect())) + }), + pos, + )); + + #[cfg(not(feature = "no_float"))] return Some(( i64::from_str(&out) .map(Token::IntegerConstant) @@ -1190,6 +1215,7 @@ fn parse_index_expr<'a>( *pos, )) } + #[cfg(not(feature = "no_float"))] Expr::FloatConstant(_, pos) => { return Err(ParseError::new( PERR::MalformedIndexExpr("Array access expects integer index, not a float".into()), @@ -1332,8 +1358,10 @@ fn parse_primary<'a>(input: &mut Peekable>) -> Result Ok(Expr::IntegerConstant(x, pos)), + #[cfg(not(feature = "no_float"))] Some((Token::FloatConstant(x), pos)) => Ok(Expr::FloatConstant(x, pos)), + + Some((Token::IntegerConstant(x), pos)) => Ok(Expr::IntegerConstant(x, pos)), Some((Token::CharConstant(c), pos)) => Ok(Expr::CharConstant(c, pos)), Some((Token::StringConst(s), pos)) => { can_be_indexed = true; @@ -1383,14 +1411,31 @@ fn parse_unary<'a>(input: &mut Peekable>) -> Result Ok(i + #[cfg(not(feature = "no_float"))] + Ok(Expr::IntegerConstant(i, _)) => Ok(i .checked_neg() .map(|x| Expr::IntegerConstant(x, pos)) .unwrap_or_else(|| Expr::FloatConstant(-(i as f64), pos))), + + // Negative integer + #[cfg(feature = "no_float")] + Ok(Expr::IntegerConstant(i, _)) => i + .checked_neg() + .map(|x| Expr::IntegerConstant(x, pos)) + .ok_or_else(|| { + ParseError::new( + PERR::BadInput(LERR::MalformedNumber(format!("-{}", i)).to_string()), + pos, + ) + }), + // Negative float + #[cfg(not(feature = "no_float"))] Ok(Expr::FloatConstant(f, pos)) => Ok(Expr::FloatConstant(-f, pos)), + // Call negative function Ok(expr) => Ok(Expr::FunctionCall("-".into(), vec![expr], None, pos)), + err @ Err(_) => err, } } @@ -1418,10 +1463,9 @@ fn parse_assignment(lhs: Expr, rhs: Expr, pos: Position) -> Result (true, *pos), #[cfg(not(feature = "no_index"))] - Expr::Index(idx_lhs, _, _) => match idx_lhs.as_ref() { - Expr::Identifier(_, _) => (true, idx_lhs.position()), - _ => (false, idx_lhs.position()), - }, + Expr::Index(idx_lhs, _, _) if idx_lhs.is_identifier() => (true, idx_lhs.position()), + #[cfg(not(feature = "no_index"))] + Expr::Index(idx_lhs, _, _) => (false, idx_lhs.position()), Expr::Dot(dot_lhs, dot_rhs, _) => match dot_lhs.as_ref() { Expr::Identifier(_, _) => valid_assignment_chain(dot_rhs), @@ -1460,29 +1504,6 @@ fn parse_op_assignment( Expr::FunctionCall(function.into(), vec![lhs_copy, rhs], None, pos), pos, ) - - /* - const LHS_VALUE: &'static str = "@LHS_VALUE@"; - - let lhs_pos = lhs.position(); - - Ok(Expr::Block( - Box::new(Stmt::Block(vec![ - Stmt::Let(LHS_VALUE.to_string(), Some(Box::new(lhs)), lhs_pos), - Stmt::Expr(Box::new(parse_assignment( - lhs, - Expr::FunctionCall( - function.into(), - vec![Expr::Identifier(LHS_VALUE.to_string(), lhs_pos), rhs], - None, - pos, - ), - pos, - )?)), - ])), - pos, - )) - */ } fn parse_binary_op<'a>( @@ -1532,6 +1553,7 @@ fn parse_binary_op<'a>( Token::Equals => parse_assignment(current_lhs, rhs, pos)?, Token::PlusAssign => parse_op_assignment("+", current_lhs, rhs, pos)?, Token::MinusAssign => parse_op_assignment("-", current_lhs, rhs, pos)?, + Token::Period => Expr::Dot(Box::new(current_lhs), Box::new(rhs), pos), // Comparison operators default to false when passed invalid operands From e22aaca5c1648adcc5e1245392810587f1e7ad1f Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 10 Mar 2020 19:48:47 +0800 Subject: [PATCH 04/24] Make sure all tests run with all features. --- .gitignore | 3 ++- README.md | 20 ++++++++++---------- src/api.rs | 3 +++ src/builtin.rs | 4 ++-- src/optimize.rs | 2 +- tests/arrays.rs | 1 + tests/chars.rs | 14 +++++++++----- tests/engine.rs | 1 + tests/float.rs | 1 + tests/for.rs | 1 + tests/mismatched_op.rs | 9 +++------ tests/power_of.rs | 34 +++++++++++++++++++++------------- tests/string.rs | 10 ++++++++++ tests/types.rs | 6 ++++++ 14 files changed, 71 insertions(+), 38 deletions(-) diff --git a/.gitignore b/.gitignore index 1ec7ed7e..067f30fc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ target/ Cargo.lock -.vscode/ \ No newline at end of file +.vscode/ +.cargo/ \ No newline at end of file diff --git a/README.md b/README.md index 5488ec97..6f4d7ded 100644 --- a/README.md +++ b/README.md @@ -43,8 +43,8 @@ Optional features | `debug_msgs` | Print debug messages to stdout (using `println!`) related to function registrations and function calls. | | `no_stdlib` | Exclude the standard library of utility functions in the build, and only include the minimum necessary functionalities. | | `unchecked` | Exclude arithmetic checking in the standard library. Beware that a bad script may panic the entire system! | -| `no_index` | Disable arrays and indexing features | -| `no_float` | Disable floating-point numbers and math | +| `no_index` | Disable arrays and indexing features if you don't need them. | +| `no_float` | Disable floating-point numbers and math if you don't need them. | Related ------- @@ -190,14 +190,14 @@ Values and types The following primitive types are supported natively: -| Category | Types | -| ------------------------------ | -------------------------------------- | -| Integer | `i32`, `u32`, `i64` _(default)_, `u64` | -| Floating-point | `f32`, `f64` _(default)_ | -| Character | `char` | -| Boolean | `bool` | -| Array | `rhai::Array` | -| Dynamic (i.e. can be anything) | `rhai::Dynamic` | +| Category | Types | +| --------------------------------------------------------------- | -------------------------------------- | +| Integer | `i32`, `u32`, `i64` _(default)_, `u64` | +| Floating-point (disabled with [`no_float`](#optional-features)) | `f32`, `f64` _(default)_ | +| Character | `char` | +| Boolean | `bool` | +| Array (disabled with [`no_index`](#optional-features)) | `rhai::Array` | +| Dynamic (i.e. can be anything) | `rhai::Dynamic` | Value conversions ----------------- diff --git a/src/api.rs b/src/api.rs index ac9914d1..9d309756 100644 --- a/src/api.rs +++ b/src/api.rs @@ -274,6 +274,8 @@ impl<'e> Engine<'e> { /// /// ```rust /// # fn main() -> Result<(), rhai::EvalAltResult> { + /// # #[cfg(not(feature = "no_stdlib"))] + /// # { /// use rhai::Engine; /// /// let mut engine = Engine::new(); @@ -283,6 +285,7 @@ impl<'e> Engine<'e> { /// let result: i64 = engine.call_fn("add", &ast, (String::from("abc"), 123_i64))?; /// /// assert_eq!(result, 126); + /// # } /// # Ok(()) /// # } /// ``` diff --git a/src/builtin.rs b/src/builtin.rs index 106d647b..16a8acb1 100644 --- a/src/builtin.rs +++ b/src/builtin.rs @@ -479,7 +479,7 @@ impl Engine<'_> { reg_func1!(self, "debug", print_debug, String, i8, u8, i16, u16); reg_func1!(self, "debug", print_debug, String, i32, i64, u32, u64); - reg_func1!(self, "debug", print_debug, String, bool, char); + reg_func1!(self, "debug", print_debug, String, bool, char, String, ()); #[cfg(not(feature = "no_float"))] { @@ -490,7 +490,7 @@ impl Engine<'_> { #[cfg(not(feature = "no_index"))] { reg_func1!(self, "print", print_debug, String, Array); - reg_func1!(self, "debug", print_debug, String, String, Array, ()); + reg_func1!(self, "debug", print_debug, String, Array); // Register array iterator self.register_iterator::(|a| { diff --git a/src/optimize.rs b/src/optimize.rs index 29f226f6..085b09f9 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -61,7 +61,7 @@ fn optimize_stmt(stmt: Stmt, changed: &mut bool) -> Stmt { // Remove all raw expression statements that evaluate to constants // except for the very last statement result.retain(|stmt| match stmt { - Stmt::Expr(expr) if expr.is_constant() => false, + Stmt::Expr(expr) if expr.is_constant() || expr.is_identifier() => false, _ => true, }); diff --git a/tests/arrays.rs b/tests/arrays.rs index cfd74e6e..2cf9309f 100644 --- a/tests/arrays.rs +++ b/tests/arrays.rs @@ -1,3 +1,4 @@ +#![cfg(not(feature = "no_index"))] use rhai::{Engine, EvalAltResult, RegisterFn}; #[test] diff --git a/tests/chars.rs b/tests/chars.rs index 4a5c2d98..cf207e43 100644 --- a/tests/chars.rs +++ b/tests/chars.rs @@ -6,11 +6,15 @@ fn test_chars() -> Result<(), EvalAltResult> { assert_eq!(engine.eval::("'y'")?, 'y'); assert_eq!(engine.eval::("'\\u2764'")?, '❤'); - assert_eq!(engine.eval::(r#"let x="hello"; x[2]"#)?, 'l'); - assert_eq!( - engine.eval::(r#"let x="hello"; x[2]='$'; x"#)?, - "he$lo".to_string() - ); + + #[cfg(not(feature = "no_index"))] + { + assert_eq!(engine.eval::(r#"let x="hello"; x[2]"#)?, 'l'); + assert_eq!( + engine.eval::(r#"let x="hello"; x[2]='$'; x"#)?, + "he$lo".to_string() + ); + } assert!(engine.eval::("'\\uhello'").is_err()); assert!(engine.eval::("''").is_err()); diff --git a/tests/engine.rs b/tests/engine.rs index b049d107..17b83288 100644 --- a/tests/engine.rs +++ b/tests/engine.rs @@ -1,3 +1,4 @@ +#![cfg(not(feature = "no_stdlib"))] use rhai::{Engine, EvalAltResult}; #[test] diff --git a/tests/float.rs b/tests/float.rs index 8e291e9e..af33ee34 100644 --- a/tests/float.rs +++ b/tests/float.rs @@ -1,3 +1,4 @@ +#![cfg(not(feature = "no_float"))] use rhai::{Engine, EvalAltResult, RegisterFn}; #[test] diff --git a/tests/for.rs b/tests/for.rs index 63390b31..f67b95f2 100644 --- a/tests/for.rs +++ b/tests/for.rs @@ -1,3 +1,4 @@ +#![cfg(not(feature = "no_index"))] use rhai::{Engine, EvalAltResult}; #[test] diff --git a/tests/mismatched_op.rs b/tests/mismatched_op.rs index 9964189b..235062d5 100644 --- a/tests/mismatched_op.rs +++ b/tests/mismatched_op.rs @@ -1,6 +1,7 @@ use rhai::{Engine, EvalAltResult, RegisterFn}; #[test] +#[cfg(not(feature = "no_stdlib"))] fn test_mismatched_op() { let mut engine = Engine::new(); @@ -26,17 +27,13 @@ fn test_mismatched_op_custom_type() { } let mut engine = Engine::new(); - engine.register_type::(); + engine.register_type_with_name::("TestStruct"); engine.register_fn("new_ts", TestStruct::new); let r = engine.eval::("60 + new_ts()"); match r { - Err(EvalAltResult::ErrorFunctionNotFound(err, _)) - if err == "+ (i64, mismatched_op::test_mismatched_op_custom_type::TestStruct)" => - { - () - } + Err(EvalAltResult::ErrorFunctionNotFound(err, _)) if err == "+ (i64, TestStruct)" => (), _ => panic!(), } } diff --git a/tests/power_of.rs b/tests/power_of.rs index ac41a611..78abcb5d 100644 --- a/tests/power_of.rs +++ b/tests/power_of.rs @@ -6,11 +6,15 @@ fn test_power_of() -> Result<(), EvalAltResult> { assert_eq!(engine.eval::("2 ~ 3")?, 8); assert_eq!(engine.eval::("(-2 ~ 3)")?, -8); - assert_eq!(engine.eval::("2.2 ~ 3.3")?, 13.489468760533386_f64); - assert_eq!(engine.eval::("2.0~-2.0")?, 0.25_f64); - assert_eq!(engine.eval::("(-2.0~-2.0)")?, 0.25_f64); - assert_eq!(engine.eval::("(-2.0~-2)")?, 0.25_f64); - assert_eq!(engine.eval::("4~3")?, 64); + + #[cfg(not(feature = "no_float"))] + { + assert_eq!(engine.eval::("2.2 ~ 3.3")?, 13.489468760533386_f64); + assert_eq!(engine.eval::("2.0~-2.0")?, 0.25_f64); + assert_eq!(engine.eval::("(-2.0~-2.0)")?, 0.25_f64); + assert_eq!(engine.eval::("(-2.0~-2)")?, 0.25_f64); + assert_eq!(engine.eval::("4~3")?, 64); + } Ok(()) } @@ -21,14 +25,18 @@ fn test_power_of_equals() -> Result<(), EvalAltResult> { assert_eq!(engine.eval::("let x = 2; x ~= 3; x")?, 8); assert_eq!(engine.eval::("let x = -2; x ~= 3; x")?, -8); - assert_eq!( - engine.eval::("let x = 2.2; x ~= 3.3; x")?, - 13.489468760533386_f64 - ); - assert_eq!(engine.eval::("let x = 2.0; x ~= -2.0; x")?, 0.25_f64); - assert_eq!(engine.eval::("let x = -2.0; x ~= -2.0; x")?, 0.25_f64); - assert_eq!(engine.eval::("let x = -2.0; x ~= -2; x")?, 0.25_f64); - assert_eq!(engine.eval::("let x =4; x ~= 3; x")?, 64); + + #[cfg(not(feature = "no_float"))] + { + assert_eq!( + engine.eval::("let x = 2.2; x ~= 3.3; x")?, + 13.489468760533386_f64 + ); + assert_eq!(engine.eval::("let x = 2.0; x ~= -2.0; x")?, 0.25_f64); + assert_eq!(engine.eval::("let x = -2.0; x ~= -2.0; x")?, 0.25_f64); + assert_eq!(engine.eval::("let x = -2.0; x ~= -2; x")?, 0.25_f64); + assert_eq!(engine.eval::("let x =4; x ~= 3; x")?, 64); + } Ok(()) } diff --git a/tests/string.rs b/tests/string.rs index 2032154b..5d63b88f 100644 --- a/tests/string.rs +++ b/tests/string.rs @@ -12,10 +12,20 @@ fn test_string() -> Result<(), EvalAltResult> { engine.eval::(r#""Test string: \x58""#)?, "Test string: X".to_string() ); + assert_eq!( engine.eval::(r#""foo" + "bar""#)?, "foobar".to_string() ); + + #[cfg(not(feature = "no_stdlib"))] + assert_eq!( + engine.eval::(r#""foo" + 123"#)?, + "foo123".to_string() + ); + + #[cfg(not(feature = "no_float"))] + #[cfg(not(feature = "no_stdlib"))] assert_eq!( engine.eval::(r#""foo" + 123.4556"#)?, "foo123.4556".to_string() diff --git a/tests/types.rs b/tests/types.rs index 1cf303ad..ed3ff2ea 100644 --- a/tests/types.rs +++ b/tests/types.rs @@ -5,11 +5,17 @@ fn test_type_of() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); assert_eq!(engine.eval::("type_of(60 + 5)")?, "i64"); + + #[cfg(not(feature = "no_float"))] assert_eq!(engine.eval::("type_of(1.0 + 2.0)")?, "f64"); + + #[cfg(not(feature = "no_index"))] + #[cfg(not(feature = "no_float"))] assert_eq!( engine.eval::(r#"type_of([1.0, 2, "hello"])"#)?, "array" ); + assert_eq!(engine.eval::(r#"type_of("hello")"#)?, "string"); assert_eq!(engine.eval::("let x = 123; x.type_of()")?, "i64"); From 708c285a0a482e0d90069089efe399dd410eb835 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 10 Mar 2020 23:06:20 +0800 Subject: [PATCH 05/24] Add `only_i32` and `only_i64` features. --- Cargo.toml | 2 + README.md | 47 +++-- src/builtin.rs | 406 ++++++++++++++++++++++++------------ src/engine.rs | 12 +- src/lib.rs | 2 +- src/parser.rs | 29 ++- src/result.rs | 6 +- tests/arrays.rs | 16 +- tests/binary_ops.rs | 14 +- tests/bit_shift.rs | 6 +- tests/comments.rs | 6 +- tests/compound_equality.rs | 18 +- tests/decrement.rs | 4 +- tests/engine.rs | 4 +- tests/for.rs | 4 +- tests/get_set.rs | 18 +- tests/if_block.rs | 12 +- tests/increment.rs | 4 +- tests/internal_fn.rs | 8 +- tests/math.rs | 82 +++++--- tests/method_call.rs | 4 +- tests/mismatched_op.rs | 13 +- tests/number_literals.rs | 16 +- tests/ops.rs | 8 +- tests/power_of.rs | 44 ++-- tests/throw.rs | 6 +- tests/types.rs | 9 + tests/unary_after_binary.rs | 14 +- tests/unary_minus.rs | 8 +- tests/var_scope.rs | 18 +- tests/while_loop.rs | 4 +- 31 files changed, 532 insertions(+), 312 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3bbd25ea..8fa20102 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,8 @@ unchecked = [] no_stdlib = [] no_index = [] no_float = [] +only_i32 = [] +only_i64 = [] [profile.release] lto = "fat" diff --git a/README.md b/README.md index 6f4d7ded..8d7724ea 100644 --- a/README.md +++ b/README.md @@ -38,13 +38,18 @@ Beware that in order to use pre-releases (alpha and beta) you need to specify th Optional features ----------------- -| Feature | Description | -| ------------ | ----------------------------------------------------------------------------------------------------------------------- | -| `debug_msgs` | Print debug messages to stdout (using `println!`) related to function registrations and function calls. | -| `no_stdlib` | Exclude the standard library of utility functions in the build, and only include the minimum necessary functionalities. | -| `unchecked` | Exclude arithmetic checking in the standard library. Beware that a bad script may panic the entire system! | -| `no_index` | Disable arrays and indexing features if you don't need them. | -| `no_float` | Disable floating-point numbers and math if you don't need them. | +| Feature | Description | +| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `debug_msgs` | Print debug messages to stdout related to function registrations and calls. | +| `no_stdlib` | Exclude the standard library of utility functions in the build, and only include the minimum necessary functionalities. Standard types are not affected. | +| `unchecked` | Exclude arithmetic checking (such as overflows and division by zero). Beware that a bad script may panic the entire system! | +| `no_index` | Disable arrays and indexing features if you don't need them. | +| `no_float` | Disable floating-point numbers and math if you don't need them. | +| `only_i32` | Set the system integer type to `i32` and disable all other integer types. | +| `only_i64` | Set the system integer type to `i64` and disable all other integer types. | + +By default, Rhai includes all the standard functionalities in a small, tight package. Most features are here for you to opt-**out** of certain functionalities that you do not need. +Excluding unneeded functionalities can result in smaller, faster builds as well as less bugs due to a more restricted language. Related ------- @@ -190,20 +195,28 @@ Values and types The following primitive types are supported natively: -| Category | Types | -| --------------------------------------------------------------- | -------------------------------------- | -| Integer | `i32`, `u32`, `i64` _(default)_, `u64` | -| Floating-point (disabled with [`no_float`](#optional-features)) | `f32`, `f64` _(default)_ | -| Character | `char` | -| Boolean | `bool` | -| Array (disabled with [`no_index`](#optional-features)) | `rhai::Array` | -| Dynamic (i.e. can be anything) | `rhai::Dynamic` | +| Category | Types | +| ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | +| **Integer** | `u8`, `i8`, `u16`, `i16`,
`u32`, `i32` (default for [`only_i32`](#optional-features)),
`u64`, `i64` _(default)_ | +| **Floating-point** (disabled with [`no_float`](#optional-features)) | `f32`, `f64` _(default)_ | +| **Character** | `char` | +| **Boolean** | `bool` | +| **Array** (disabled with [`no_index`](#optional-features)) | `rhai::Array` | +| **Dynamic** (i.e. can be anything) | `rhai::Dynamic` | +| **System** (current configuration) | `rhai::INT` (`i32` or `i64`),
`rhai::FLOAT` (`f32` or `f64`) | + +All types are treated strictly separate by Rhai, meaning that `i32` and `i64` and `u32` are completely different; you cannot even add them together. + +The default integer type is `i64`. If you do not need any other integer type, you can enable the [`only_i64`](#optional-features) feature. + +If you only need 32-bit integers, you can enable the [`only_i32`](#optional-features) feature and remove support for all integer types other than `i32` including `i64`. +This is useful on 32-bit systems where using 64-bit integers incurs a performance penalty. + +If you do not need floating-point, enable the [`no_float`](#optional-features) feature to remove support. Value conversions ----------------- -All types are treated strictly separate by Rhai, meaning that `i32` and `i64` and `u32` are completely different; you cannot even add them together. - There is a `to_float` function to convert a supported number to an `f64`, and a `to_int` function to convert a supported number to `i64` and that's about it. For other conversions you can register your own conversion functions. There is also a `type_of` function to detect the type of a value. diff --git a/src/builtin.rs b/src/builtin.rs index 16a8acb1..0e8ab757 100644 --- a/src/builtin.rs +++ b/src/builtin.rs @@ -4,26 +4,38 @@ use crate::any::Any; use crate::engine::Engine; use crate::fn_register::RegisterFn; +use crate::parser::INT; + +#[cfg(not(feature = "unchecked"))] +use crate::{parser::Position, result::EvalAltResult, RegisterResultFn}; #[cfg(not(feature = "no_index"))] use crate::engine::Array; +#[cfg(not(feature = "no_float"))] +use crate::FLOAT; + use std::{ fmt::{Debug, Display}, - i32, i64, ops::{BitAnd, BitOr, BitXor, Range}, - u32, }; #[cfg(feature = "unchecked")] use std::ops::{Shl, Shr}; +#[cfg(not(feature = "unchecked"))] +#[cfg(not(feature = "no_float"))] +use std::{i32, i64}; + +#[cfg(not(feature = "unchecked"))] +#[cfg(not(feature = "only_i32"))] +use std::u32; + #[cfg(any(feature = "unchecked", not(feature = "no_float")))] use std::ops::{Add, Div, Mul, Neg, Rem, Sub}; #[cfg(not(feature = "unchecked"))] use { - crate::{parser::Position, result::EvalAltResult, RegisterResultFn}, num_traits::{ CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedRem, CheckedShl, CheckedShr, CheckedSub, @@ -259,7 +271,7 @@ impl Engine<'_> { x ^ y } #[cfg(not(feature = "unchecked"))] - fn shl(x: T, y: i64) -> Result { + fn shl(x: T, y: INT) -> Result { if y < 0 { return Err(EvalAltResult::ErrorArithmetic( format!("Left-shift by a negative number: {} << {}", x, y), @@ -275,7 +287,7 @@ impl Engine<'_> { }) } #[cfg(not(feature = "unchecked"))] - fn shr(x: T, y: i64) -> Result { + fn shr(x: T, y: INT) -> Result { if y < 0 { return Err(EvalAltResult::ErrorArithmetic( format!("Right-shift by a negative number: {} >> {}", x, y), @@ -312,33 +324,58 @@ impl Engine<'_> { x % y } #[cfg(not(feature = "unchecked"))] - fn pow_i64_i64_u(x: i64, y: i64) -> Result { - if y > (u32::MAX as i64) { - return Err(EvalAltResult::ErrorArithmetic( - format!("Power overflow: {} ~ {}", x, y), - Position::none(), - )); + fn pow_i_i_u(x: INT, y: INT) -> Result { + #[cfg(not(feature = "only_i32"))] + { + if y > (u32::MAX as INT) { + Err(EvalAltResult::ErrorArithmetic( + format!("Power overflow: {} ~ {}", x, y), + Position::none(), + )) + } else if y < 0 { + Err(EvalAltResult::ErrorArithmetic( + format!("Power underflow: {} ~ {}", x, y), + Position::none(), + )) + } else { + x.checked_pow(y as u32).ok_or_else(|| { + EvalAltResult::ErrorArithmetic( + format!("Power overflow: {} ~ {}", x, y), + Position::none(), + ) + }) + } } - x.checked_pow(y as u32).ok_or_else(|| { - EvalAltResult::ErrorArithmetic( - format!("Power overflow: {} ~ {}", x, y), - Position::none(), - ) - }) + #[cfg(feature = "only_i32")] + { + if y < 0 { + Err(EvalAltResult::ErrorArithmetic( + format!("Power underflow: {} ~ {}", x, y), + Position::none(), + )) + } else { + x.checked_pow(y as u32).ok_or_else(|| { + EvalAltResult::ErrorArithmetic( + format!("Power overflow: {} ~ {}", x, y), + Position::none(), + ) + }) + } + } } #[cfg(feature = "unchecked")] - fn pow_i64_i64(x: i64, y: i64) -> i64 { + fn pow_i_i(x: INT, y: INT) -> INT { x.pow(y as u32) } #[cfg(not(feature = "no_float"))] - fn pow_f64_f64(x: f64, y: f64) -> f64 { + fn pow_f_f(x: FLOAT, y: FLOAT) -> FLOAT { x.powf(y) } #[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "no_float"))] - fn pow_f64_i64_u(x: f64, y: i64) -> Result { - if y > (i32::MAX as i64) { + fn pow_f_i_u(x: FLOAT, y: INT) -> Result { + if y > (i32::MAX as INT) { return Err(EvalAltResult::ErrorArithmetic( format!("Power overflow: {} ~ {}", x, y), Position::none(), @@ -349,24 +386,42 @@ impl Engine<'_> { } #[cfg(feature = "unchecked")] #[cfg(not(feature = "no_float"))] - fn pow_f64_i64(x: f64, y: i64) -> f64 { + fn pow_f_i(x: FLOAT, y: INT) -> FLOAT { x.powi(y as i32) } #[cfg(not(feature = "unchecked"))] { - reg_op_result!(self, "+", add, i8, u8, i16, u16, i32, i64, u32, u64); - reg_op_result!(self, "-", sub, i8, u8, i16, u16, i32, i64, u32, u64); - reg_op_result!(self, "*", mul, i8, u8, i16, u16, i32, i64, u32, u64); - reg_op_result!(self, "/", div, i8, u8, i16, u16, i32, i64, u32, u64); + reg_op_result!(self, "+", add, INT); + reg_op_result!(self, "-", sub, INT); + reg_op_result!(self, "*", mul, INT); + reg_op_result!(self, "/", div, INT); + + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + reg_op_result!(self, "+", add, i8, u8, i16, u16, i32, i64, u32, u64); + reg_op_result!(self, "-", sub, i8, u8, i16, u16, i32, i64, u32, u64); + reg_op_result!(self, "*", mul, i8, u8, i16, u16, i32, i64, u32, u64); + reg_op_result!(self, "/", div, i8, u8, i16, u16, i32, i64, u32, u64); + } } #[cfg(feature = "unchecked")] { - reg_op!(self, "+", add_u, i8, u8, i16, u16, i32, i64, u32, u64); - reg_op!(self, "-", sub_u, i8, u8, i16, u16, i32, i64, u32, u64); - reg_op!(self, "*", mul_u, i8, u8, i16, u16, i32, i64, u32, u64); - reg_op!(self, "/", div_u, i8, u8, i16, u16, i32, i64, u32, u64); + reg_op!(self, "+", add_u, INT); + reg_op!(self, "-", sub_u, INT); + reg_op!(self, "*", mul_u, INT); + reg_op!(self, "/", div_u, INT); + + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + reg_op!(self, "+", add_u, i8, u8, i16, u16, i32, i64, u32, u64); + reg_op!(self, "-", sub_u, i8, u8, i16, u16, i32, i64, u32, u64); + reg_op!(self, "*", mul_u, i8, u8, i16, u16, i32, i64, u32, u64); + reg_op!(self, "/", div_u, i8, u8, i16, u16, i32, i64, u32, u64); + } } #[cfg(not(feature = "no_float"))] @@ -377,12 +432,23 @@ impl Engine<'_> { reg_op!(self, "/", div_u, f32, f64); } - reg_cmp!(self, "<", lt, i8, u8, i16, u16, i32, i64, u32, u64, String, char); - reg_cmp!(self, "<=", lte, i8, u8, i16, u16, i32, i64, u32, u64, String, char); - reg_cmp!(self, ">", gt, i8, u8, i16, u16, i32, i64, u32, u64, String, char); - reg_cmp!(self, ">=", gte, i8, u8, i16, u16, i32, i64, u32, u64, String, char); - reg_cmp!(self, "==", eq, i8, u8, i16, u16, i32, i64, u32, u64, bool, String, char); - reg_cmp!(self, "!=", ne, i8, u8, i16, u16, i32, i64, u32, u64, bool, String, char); + reg_cmp!(self, "<", lt, INT, String, char); + reg_cmp!(self, "<=", lte, INT, String, char); + reg_cmp!(self, ">", gt, INT, String, char); + reg_cmp!(self, ">=", gte, INT, String, char); + reg_cmp!(self, "==", eq, INT, String, char, bool); + reg_cmp!(self, "!=", ne, INT, String, char, bool); + + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + reg_cmp!(self, "<", lt, i8, u8, i16, u16, i32, i64, u32, u64); + reg_cmp!(self, "<=", lte, i8, u8, i16, u16, i32, i64, u32, u64); + reg_cmp!(self, ">", gt, i8, u8, i16, u16, i32, i64, u32, u64); + reg_cmp!(self, ">=", gte, i8, u8, i16, u16, i32, i64, u32, u64); + reg_cmp!(self, "==", eq, i8, u8, i16, u16, i32, i64, u32, u64); + reg_cmp!(self, "!=", ne, i8, u8, i16, u16, i32, i64, u32, u64); + } #[cfg(not(feature = "no_float"))] { @@ -396,60 +462,97 @@ impl Engine<'_> { //reg_op!(self, "||", or, bool); //reg_op!(self, "&&", and, bool); - reg_op!(self, "|", binary_or, i8, u8, i16, u16, i32, i64, u32, u64); reg_op!(self, "|", or, bool); - reg_op!(self, "&", binary_and, i8, u8, i16, u16, i32, i64, u32, u64); reg_op!(self, "&", and, bool); - reg_op!(self, "^", binary_xor, i8, u8, i16, u16, i32, i64, u32, u64); + + reg_op!(self, "|", binary_or, INT); + reg_op!(self, "&", binary_and, INT); + reg_op!(self, "^", binary_xor, INT); + + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + reg_op!(self, "|", binary_or, i8, u8, i16, u16, i32, i64, u32, u64); + reg_op!(self, "&", binary_and, i8, u8, i16, u16, i32, i64, u32, u64); + reg_op!(self, "^", binary_xor, i8, u8, i16, u16, i32, i64, u32, u64); + } #[cfg(not(feature = "unchecked"))] { - reg_op_result1!(self, "<<", shl, i64, i8, u8, i16, u16, i32, i64, u32, u64); - reg_op_result1!(self, ">>", shr, i64, i8, u8, i16, u16); - reg_op_result1!(self, ">>", shr, i64, i32, i64, u32, u64); - reg_op_result!(self, "%", modulo, i8, u8, i16, u16, i32, i64, u32, u64); + reg_op_result1!(self, "<<", shl, INT, INT); + reg_op_result1!(self, ">>", shr, INT, INT); + reg_op_result!(self, "%", modulo, INT); + + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + reg_op_result1!(self, "<<", shl, i64, i8, u8, i16, u16, i32, i64, u32, u64); + reg_op_result1!(self, ">>", shr, i64, i8, u8, i16, u16, i32, i64, u32, u64); + reg_op_result!(self, "%", modulo, i8, u8, i16, u16, i32, i64, u32, u64); + } } #[cfg(feature = "unchecked")] { - reg_op!(self, "<<", shl_u, i64, i8, u8, i16, u16, i32, i64, u32, u64); - reg_op!(self, ">>", shr_u, i64, i8, u8, i16, u16); - reg_op!(self, ">>", shr_u, i64, i32, i64, u32, u64); - reg_op!(self, "%", modulo_u, i8, u8, i16, u16, i32, i64, u32, u64); + reg_op!(self, "<<", shl_u, INT, INT); + reg_op!(self, ">>", shr_u, INT, INT); + reg_op!(self, "%", modulo_u, INT); + + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + reg_op!(self, "<<", shl_u, i64, i8, u8, i16, u16, i32, i64, u32, u64); + reg_op!(self, ">>", shr_u, i64, i8, u8, i16, u16, i32, i64, u32, u64); + reg_op!(self, "%", modulo_u, i8, u8, i16, u16, i32, i64, u32, u64); + } } #[cfg(not(feature = "no_float"))] { reg_op!(self, "%", modulo_u, f32, f64); - self.register_fn("~", pow_f64_f64); + self.register_fn("~", pow_f_f); } #[cfg(not(feature = "unchecked"))] { - self.register_result_fn("~", pow_i64_i64_u); + self.register_result_fn("~", pow_i_i_u); #[cfg(not(feature = "no_float"))] - self.register_result_fn("~", pow_f64_i64_u); + self.register_result_fn("~", pow_f_i_u); } #[cfg(feature = "unchecked")] { - self.register_fn("~", pow_i64_i64); + self.register_fn("~", pow_i_i); #[cfg(not(feature = "no_float"))] - self.register_fn("~", pow_f64_i64); + self.register_fn("~", pow_f_i); } #[cfg(not(feature = "unchecked"))] { - reg_un_result!(self, "-", neg, i8, i16, i32, i64); - reg_un_result!(self, "abs", abs, i8, i16, i32, i64); + reg_un_result!(self, "-", neg, INT); + reg_un_result!(self, "abs", abs, INT); + + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + reg_un_result!(self, "-", neg, i8, i16, i32, i64); + reg_un_result!(self, "abs", abs, i8, i16, i32, i64); + } } #[cfg(feature = "unchecked")] { - reg_un!(self, "-", neg_u, i8, i16, i32, i64); - reg_un!(self, "abs", abs_u, i8, i16, i32, i64); + reg_un!(self, "-", neg_u, INT); + reg_un!(self, "abs", abs_u, INT); + + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + reg_un!(self, "-", neg_u, i8, i16, i32, i64); + reg_un!(self, "abs", abs_u, i8, i16, i32, i64); + } } #[cfg(not(feature = "no_float"))] @@ -464,33 +567,37 @@ impl Engine<'_> { self.register_fn("==", |_: (), _: ()| true); // () == () // Register print and debug - fn print_debug(x: T) -> String { + fn debug(x: T) -> String { format!("{:?}", x) } fn print(x: T) -> String { format!("{}", x) } - reg_func1!(self, "print", print, String, i8, u8, i16, u16); - reg_func1!(self, "print", print, String, i32, i64, u32, u64); - reg_func1!(self, "print", print, String, bool, char, String); + reg_func1!(self, "print", print, String, INT, bool, char, String); self.register_fn("print", || "".to_string()); self.register_fn("print", |_: ()| "".to_string()); + reg_func1!(self, "debug", debug, String, INT, bool, char, String, ()); - reg_func1!(self, "debug", print_debug, String, i8, u8, i16, u16); - reg_func1!(self, "debug", print_debug, String, i32, i64, u32, u64); - reg_func1!(self, "debug", print_debug, String, bool, char, String, ()); + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + reg_func1!(self, "print", print, String, i8, u8, i16, u16); + reg_func1!(self, "print", print, String, i32, i64, u32, u64); + reg_func1!(self, "debug", debug, String, i8, u8, i16, u16); + reg_func1!(self, "debug", debug, String, i32, i64, u32, u64); + } #[cfg(not(feature = "no_float"))] { reg_func1!(self, "print", print, String, f32, f64); - reg_func1!(self, "debug", print_debug, String, f32, f64); + reg_func1!(self, "debug", debug, String, f32, f64); } #[cfg(not(feature = "no_index"))] { - reg_func1!(self, "print", print_debug, String, Array); - reg_func1!(self, "debug", print_debug, String, Array); + reg_func1!(self, "print", debug, String, Array); + reg_func1!(self, "debug", debug, String, Array); // Register array iterator self.register_iterator::(|a| { @@ -499,16 +606,16 @@ impl Engine<'_> { } // Register range function - self.register_iterator::, _>(|a| { + self.register_iterator::, _>(|a| { Box::new( - a.downcast_ref::>() + a.downcast_ref::>() .unwrap() .clone() .map(|n| n.into_dynamic()), ) }); - self.register_fn("range", |i1: i64, i2: i64| (i1..i2)); + self.register_fn("range", |i1: INT, i2: INT| (i1..i2)); } /// Register the built-in library. @@ -520,52 +627,69 @@ impl Engine<'_> { #[cfg(not(feature = "no_float"))] { // Advanced math functions - self.register_fn("sin", |x: f64| x.to_radians().sin()); - self.register_fn("cos", |x: f64| x.to_radians().cos()); - self.register_fn("tan", |x: f64| x.to_radians().tan()); - self.register_fn("sinh", |x: f64| x.to_radians().sinh()); - self.register_fn("cosh", |x: f64| x.to_radians().cosh()); - self.register_fn("tanh", |x: f64| x.to_radians().tanh()); - self.register_fn("asin", |x: f64| x.asin().to_degrees()); - self.register_fn("acos", |x: f64| x.acos().to_degrees()); - self.register_fn("atan", |x: f64| x.atan().to_degrees()); - self.register_fn("asinh", |x: f64| x.asinh().to_degrees()); - self.register_fn("acosh", |x: f64| x.acosh().to_degrees()); - self.register_fn("atanh", |x: f64| x.atanh().to_degrees()); - self.register_fn("sqrt", |x: f64| x.sqrt()); - self.register_fn("exp", |x: f64| x.exp()); - self.register_fn("ln", |x: f64| x.ln()); - self.register_fn("log", |x: f64, base: f64| x.log(base)); - self.register_fn("log10", |x: f64| x.log10()); - self.register_fn("floor", |x: f64| x.floor()); - self.register_fn("ceiling", |x: f64| x.ceil()); - self.register_fn("round", |x: f64| x.ceil()); - self.register_fn("int", |x: f64| x.trunc()); - self.register_fn("fraction", |x: f64| x.fract()); - self.register_fn("is_nan", |x: f64| x.is_nan()); - self.register_fn("is_finite", |x: f64| x.is_finite()); - self.register_fn("is_infinite", |x: f64| x.is_infinite()); + self.register_fn("sin", |x: FLOAT| x.to_radians().sin()); + self.register_fn("cos", |x: FLOAT| x.to_radians().cos()); + self.register_fn("tan", |x: FLOAT| x.to_radians().tan()); + self.register_fn("sinh", |x: FLOAT| x.to_radians().sinh()); + self.register_fn("cosh", |x: FLOAT| x.to_radians().cosh()); + self.register_fn("tanh", |x: FLOAT| x.to_radians().tanh()); + self.register_fn("asin", |x: FLOAT| x.asin().to_degrees()); + self.register_fn("acos", |x: FLOAT| x.acos().to_degrees()); + self.register_fn("atan", |x: FLOAT| x.atan().to_degrees()); + self.register_fn("asinh", |x: FLOAT| x.asinh().to_degrees()); + self.register_fn("acosh", |x: FLOAT| x.acosh().to_degrees()); + self.register_fn("atanh", |x: FLOAT| x.atanh().to_degrees()); + self.register_fn("sqrt", |x: FLOAT| x.sqrt()); + self.register_fn("exp", |x: FLOAT| x.exp()); + self.register_fn("ln", |x: FLOAT| x.ln()); + self.register_fn("log", |x: FLOAT, base: FLOAT| x.log(base)); + self.register_fn("log10", |x: FLOAT| x.log10()); + self.register_fn("floor", |x: FLOAT| x.floor()); + self.register_fn("ceiling", |x: FLOAT| x.ceil()); + self.register_fn("round", |x: FLOAT| x.ceil()); + self.register_fn("int", |x: FLOAT| x.trunc()); + self.register_fn("fraction", |x: FLOAT| x.fract()); + self.register_fn("is_nan", |x: FLOAT| x.is_nan()); + self.register_fn("is_finite", |x: FLOAT| x.is_finite()); + self.register_fn("is_infinite", |x: FLOAT| x.is_infinite()); // Register conversion functions - self.register_fn("to_float", |x: i8| x as f64); - self.register_fn("to_float", |x: u8| x as f64); - self.register_fn("to_float", |x: i16| x as f64); - self.register_fn("to_float", |x: u16| x as f64); - self.register_fn("to_float", |x: i32| x as f64); - self.register_fn("to_float", |x: u32| x as f64); - self.register_fn("to_float", |x: i64| x as f64); - self.register_fn("to_float", |x: u64| x as f64); - self.register_fn("to_float", |x: f32| x as f64); + self.register_fn("to_float", |x: INT| x as FLOAT); + self.register_fn("to_float", |x: f32| x as FLOAT); + + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + self.register_fn("to_float", |x: i8| x as FLOAT); + self.register_fn("to_float", |x: u8| x as FLOAT); + self.register_fn("to_float", |x: i16| x as FLOAT); + self.register_fn("to_float", |x: u16| x as FLOAT); + self.register_fn("to_float", |x: i32| x as FLOAT); + self.register_fn("to_float", |x: u32| x as FLOAT); + self.register_fn("to_float", |x: i64| x as FLOAT); + self.register_fn("to_float", |x: u64| x as FLOAT); + } } - self.register_fn("to_int", |x: i8| x as i64); - self.register_fn("to_int", |x: u8| x as i64); - self.register_fn("to_int", |x: i16| x as i64); - self.register_fn("to_int", |x: u16| x as i64); - self.register_fn("to_int", |x: i32| x as i64); - self.register_fn("to_int", |x: u32| x as i64); - self.register_fn("to_int", |x: u64| x as i64); - self.register_fn("to_int", |ch: char| ch as i64); + self.register_fn("to_int", |ch: char| ch as INT); + + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + self.register_fn("to_int", |x: i8| x as INT); + self.register_fn("to_int", |x: u8| x as INT); + self.register_fn("to_int", |x: i16| x as INT); + self.register_fn("to_int", |x: u16| x as INT); + } + + #[cfg(not(feature = "only_i32"))] + { + self.register_fn("to_int", |x: i32| x as INT); + self.register_fn("to_int", |x: u64| x as INT); + + #[cfg(feature = "only_i64")] + self.register_fn("to_int", |x: u32| x as INT); + } #[cfg(not(feature = "no_float"))] { @@ -579,24 +703,24 @@ impl Engine<'_> { )); } - Ok(x.trunc() as i64) + Ok(x.trunc() as INT) }); - self.register_result_fn("to_int", |x: f64| { - if x > (i64::MAX as f64) { + self.register_result_fn("to_int", |x: FLOAT| { + if x > (i64::MAX as FLOAT) { return Err(EvalAltResult::ErrorArithmetic( format!("Integer overflow: to_int({})", x), Position::none(), )); } - Ok(x.trunc() as i64) + Ok(x.trunc() as INT) }); } #[cfg(feature = "unchecked")] { - self.register_fn("to_int", |x: f32| x as i64); - self.register_fn("to_int", |x: f64| x as i64); + self.register_fn("to_int", |x: f32| x as INT); + self.register_fn("to_int", |x: f64| x as INT); } } @@ -606,7 +730,7 @@ impl Engine<'_> { fn push(list: &mut Array, item: T) { list.push(Box::new(item)); } - fn pad(list: &mut Array, len: i64, item: T) { + fn pad(list: &mut Array, len: INT, item: T) { if len >= 0 { while list.len() < len as usize { push(list, item.clone()); @@ -614,19 +738,24 @@ impl Engine<'_> { } } - reg_func2x!(self, "push", push, &mut Array, (), i8, u8, i16, u16); - reg_func2x!(self, "push", push, &mut Array, (), i32, i64, u32, u64); - reg_func2x!(self, "push", push, &mut Array, (), bool, char); + reg_func2x!(self, "push", push, &mut Array, (), INT, bool, char); reg_func2x!(self, "push", push, &mut Array, (), String, Array, ()); - reg_func3!(self, "pad", pad, &mut Array, i64, (), i8, u8, i16, u16); - reg_func3!(self, "pad", pad, &mut Array, i64, (), i32, u32, i64, u64); - reg_func3!(self, "pad", pad, &mut Array, i64, (), bool, char); - reg_func3!(self, "pad", pad, &mut Array, i64, (), String, Array, ()); + reg_func3!(self, "pad", pad, &mut Array, INT, (), INT, bool, char); + reg_func3!(self, "pad", pad, &mut Array, INT, (), String, Array, ()); + + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + reg_func2x!(self, "push", push, &mut Array, (), i8, u8, i16, u16); + reg_func2x!(self, "push", push, &mut Array, (), i32, i64, u32, u64); + reg_func3!(self, "pad", pad, &mut Array, INT, (), i8, u8, i16, u16); + reg_func3!(self, "pad", pad, &mut Array, INT, (), i32, u32, i64, u64); + } #[cfg(not(feature = "no_float"))] { reg_func2x!(self, "push", push, &mut Array, (), f32, f64); - reg_func3!(self, "pad", pad, &mut Array, i64, (), f32, f64); + reg_func3!(self, "pad", pad, &mut Array, INT, (), f32, f64); } self.register_dynamic_fn("pop", |list: &mut Array| { @@ -636,9 +765,9 @@ impl Engine<'_> { 0 => ().into_dynamic(), _ => list.remove(0), }); - self.register_fn("len", |list: &mut Array| list.len() as i64); + self.register_fn("len", |list: &mut Array| list.len() as INT); self.register_fn("clear", |list: &mut Array| list.clear()); - self.register_fn("truncate", |list: &mut Array, len: i64| { + self.register_fn("truncate", |list: &mut Array, len: INT| { if len >= 0 { list.truncate(len as usize); } @@ -653,16 +782,19 @@ impl Engine<'_> { format!("{}{}", x, y) } - reg_func2x!( - self, "+", append, String, String, i8, u8, i16, u16, i32, i64, u32, u64, bool, char - ); + reg_func2x!(self, "+", append, String, String, INT, bool, char); self.register_fn("+", |x: String, _: ()| format!("{}", x)); - reg_func2y!( - self, "+", prepend, String, String, i8, u8, i16, u16, i32, i64, u32, u64, bool, char - ); + reg_func2y!(self, "+", prepend, String, String, INT, bool, char); self.register_fn("+", |_: (), y: String| format!("{}", y)); + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + reg_func2x!(self, "+", append, String, String, i8, u8, i16, u16, i32, i64, u32, u64); + reg_func2y!(self, "+", prepend, String, String, i8, u8, i16, u16, i32, i64, u32, u64); + } + #[cfg(not(feature = "no_float"))] { reg_func2x!(self, "+", append, String, String, f32, f64); @@ -676,13 +808,13 @@ impl Engine<'_> { } // Register string utility functions - self.register_fn("len", |s: &mut String| s.chars().count() as i64); + self.register_fn("len", |s: &mut String| s.chars().count() as INT); self.register_fn("contains", |s: &mut String, ch: char| s.contains(ch)); self.register_fn("contains", |s: &mut String, find: String| s.contains(&find)); self.register_fn("clear", |s: &mut String| s.clear()); self.register_fn("append", |s: &mut String, ch: char| s.push(ch)); self.register_fn("append", |s: &mut String, add: String| s.push_str(&add)); - self.register_fn("truncate", |s: &mut String, len: i64| { + self.register_fn("truncate", |s: &mut String, len: INT| { if len >= 0 { let chars: Vec<_> = s.chars().take(len as usize).collect(); s.clear(); @@ -691,7 +823,7 @@ impl Engine<'_> { s.clear(); } }); - self.register_fn("pad", |s: &mut String, len: i64, ch: char| { + self.register_fn("pad", |s: &mut String, len: INT, ch: char| { for _ in 0..s.chars().count() - len as usize { s.push(ch); } diff --git a/src/engine.rs b/src/engine.rs index d13d45d3..ebbf4ec7 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -4,6 +4,10 @@ use crate::any::{Any, AnyExt, Dynamic, Variant}; use crate::parser::{Expr, FnDef, Position, Stmt}; use crate::result::EvalAltResult; use crate::scope::Scope; + +#[cfg(not(feature = "no_index"))] +use crate::INT; + use std::{ any::{type_name, TypeId}, borrow::Cow, @@ -419,15 +423,15 @@ impl Engine<'_> { .and_then(move |(idx, _, val)| map(val).map(|v| (idx, v))) } - /// Evaluate the value of an index (must evaluate to i64) + /// Evaluate the value of an index (must evaluate to INT) #[cfg(not(feature = "no_index"))] fn eval_index_value( &mut self, scope: &mut Scope, idx_expr: &Expr, - ) -> Result { + ) -> Result { self.eval_expr(scope, idx_expr)? - .downcast::() + .downcast::() .map(|v| *v) .map_err(|_| EvalAltResult::ErrorIndexExpr(idx_expr.position())) } @@ -437,7 +441,7 @@ impl Engine<'_> { fn get_indexed_value( &self, val: Dynamic, - idx: i64, + idx: INT, val_pos: Position, idx_pos: Position, ) -> Result<(Dynamic, IndexSourceType), EvalAltResult> { diff --git a/src/lib.rs b/src/lib.rs index fb23e27c..cc043e7e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -78,7 +78,7 @@ pub use call::FuncArgs; pub use engine::Engine; pub use error::{ParseError, ParseErrorType}; pub use fn_register::{RegisterDynamicFn, RegisterFn, RegisterResultFn}; -pub use parser::{Position, AST}; +pub use parser::{Position, AST, FLOAT, INT}; pub use result::EvalAltResult; pub use scope::Scope; diff --git a/src/parser.rs b/src/parser.rs index 9943a7f7..fcbe7e20 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -5,6 +5,17 @@ use crate::error::{LexError, ParseError, ParseErrorType}; use crate::optimize::optimize; use std::{borrow::Cow, char, fmt, iter::Peekable, str::Chars, str::FromStr, usize}; +/// The system integer type +#[cfg(not(feature = "only_i32"))] +pub type INT = i64; + +/// The system integer type +#[cfg(feature = "only_i32")] +pub type INT = i32; + +/// The system floating-point type +pub type FLOAT = f64; + type LERR = LexError; type PERR = ParseErrorType; @@ -158,9 +169,9 @@ impl Stmt { #[derive(Debug, Clone)] pub enum Expr { - IntegerConstant(i64, Position), + IntegerConstant(INT, Position), #[cfg(not(feature = "no_float"))] - FloatConstant(f64, Position), + FloatConstant(FLOAT, Position), Identifier(String, Position), CharConstant(char, Position), StringConstant(String, Position), @@ -233,9 +244,9 @@ impl Expr { #[derive(Debug, PartialEq, Clone)] pub enum Token { - IntegerConstant(i64), + IntegerConstant(INT), #[cfg(not(feature = "no_float"))] - FloatConstant(f64), + FloatConstant(FLOAT), Identifier(String), CharConstant(char), StringConst(String), @@ -711,7 +722,7 @@ impl<'a> TokenIterator<'a> { let out: String = result.iter().skip(2).filter(|&&c| c != '_').collect(); return Some(( - i64::from_str_radix(&out, radix) + INT::from_str_radix(&out, radix) .map(Token::IntegerConstant) .unwrap_or_else(|_| { Token::LexError(LERR::MalformedNumber(result.iter().collect())) @@ -723,7 +734,7 @@ impl<'a> TokenIterator<'a> { #[cfg(feature = "no_float")] return Some(( - i64::from_str(&out) + INT::from_str(&out) .map(Token::IntegerConstant) .unwrap_or_else(|_| { Token::LexError(LERR::MalformedNumber(result.iter().collect())) @@ -733,9 +744,9 @@ impl<'a> TokenIterator<'a> { #[cfg(not(feature = "no_float"))] return Some(( - i64::from_str(&out) + INT::from_str(&out) .map(Token::IntegerConstant) - .or_else(|_| f64::from_str(&out).map(Token::FloatConstant)) + .or_else(|_| FLOAT::from_str(&out).map(Token::FloatConstant)) .unwrap_or_else(|_| { Token::LexError(LERR::MalformedNumber(result.iter().collect())) }), @@ -1415,7 +1426,7 @@ fn parse_unary<'a>(input: &mut Peekable>) -> Result Ok(i .checked_neg() .map(|x| Expr::IntegerConstant(x, pos)) - .unwrap_or_else(|| Expr::FloatConstant(-(i as f64), pos))), + .unwrap_or_else(|| Expr::FloatConstant(-(i as FLOAT), pos))), // Negative integer #[cfg(feature = "no_float")] diff --git a/src/result.rs b/src/result.rs index f95e4b6a..8034329c 100644 --- a/src/result.rs +++ b/src/result.rs @@ -2,7 +2,7 @@ use crate::any::Dynamic; use crate::error::ParseError; -use crate::parser::Position; +use crate::parser::{Position, INT}; use std::{error::Error, fmt}; /// Evaluation result. @@ -24,10 +24,10 @@ pub enum EvalAltResult { ErrorCharMismatch(Position), /// Array access out-of-bounds. /// Wrapped values are the current number of elements in the array and the index number. - ErrorArrayBounds(usize, i64, Position), + ErrorArrayBounds(usize, INT, Position), /// String indexing out-of-bounds. /// Wrapped values are the current number of characters in the string and the index number. - ErrorStringBounds(usize, i64, Position), + ErrorStringBounds(usize, INT, Position), /// Trying to index into a type that is not an array and not a string. ErrorIndexingType(String, Position), /// Trying to index into an array or string with an index that is not `i64`. diff --git a/tests/arrays.rs b/tests/arrays.rs index 2cf9309f..4bee090e 100644 --- a/tests/arrays.rs +++ b/tests/arrays.rs @@ -1,12 +1,12 @@ #![cfg(not(feature = "no_index"))] -use rhai::{Engine, EvalAltResult, RegisterFn}; +use rhai::{Engine, EvalAltResult, RegisterFn, INT}; #[test] fn test_arrays() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - assert_eq!(engine.eval::("let x = [1, 2, 3]; x[1]")?, 2); - assert_eq!(engine.eval::("let y = [1, 2, 3]; y[1] = 5; y[1]")?, 5); + assert_eq!(engine.eval::("let x = [1, 2, 3]; x[1]")?, 2); + assert_eq!(engine.eval::("let y = [1, 2, 3]; y[1] = 5; y[1]")?, 5); Ok(()) } @@ -15,7 +15,7 @@ fn test_arrays() -> Result<(), EvalAltResult> { fn test_array_with_structs() -> Result<(), EvalAltResult> { #[derive(Clone)] struct TestStruct { - x: i64, + x: INT, } impl TestStruct { @@ -23,11 +23,11 @@ fn test_array_with_structs() -> Result<(), EvalAltResult> { self.x += 1000; } - fn get_x(&mut self) -> i64 { + fn get_x(&mut self) -> INT { self.x } - fn set_x(&mut self, new_x: i64) { + fn set_x(&mut self, new_x: INT) { self.x = new_x; } @@ -44,10 +44,10 @@ fn test_array_with_structs() -> Result<(), EvalAltResult> { engine.register_fn("update", TestStruct::update); engine.register_fn("new_ts", TestStruct::new); - assert_eq!(engine.eval::("let a = [new_ts()]; a[0].x")?, 1); + assert_eq!(engine.eval::("let a = [new_ts()]; a[0].x")?, 1); assert_eq!( - engine.eval::( + engine.eval::( "let a = [new_ts()]; \ a[0].x = 100; \ a[0].update(); \ diff --git a/tests/binary_ops.rs b/tests/binary_ops.rs index edf560aa..3e59e8fd 100644 --- a/tests/binary_ops.rs +++ b/tests/binary_ops.rs @@ -1,15 +1,15 @@ -use rhai::{Engine, EvalAltResult}; +use rhai::{Engine, EvalAltResult, INT}; #[test] fn test_binary_ops() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - assert_eq!(engine.eval::("10 % 4")?, 2); - assert_eq!(engine.eval::("10 << 4")?, 160); - assert_eq!(engine.eval::("10 >> 4")?, 0); - assert_eq!(engine.eval::("10 & 4")?, 0); - assert_eq!(engine.eval::("10 | 4")?, 14); - assert_eq!(engine.eval::("10 ^ 4")?, 14); + assert_eq!(engine.eval::("10 % 4")?, 2); + assert_eq!(engine.eval::("10 << 4")?, 160); + assert_eq!(engine.eval::("10 >> 4")?, 0); + assert_eq!(engine.eval::("10 & 4")?, 0); + assert_eq!(engine.eval::("10 | 4")?, 14); + assert_eq!(engine.eval::("10 ^ 4")?, 14); assert_eq!(engine.eval::("42 == 42")?, true); assert_eq!(engine.eval::("42 > 42")?, false); diff --git a/tests/bit_shift.rs b/tests/bit_shift.rs index 7b0a4ab5..7dd3aa76 100644 --- a/tests/bit_shift.rs +++ b/tests/bit_shift.rs @@ -1,15 +1,15 @@ -use rhai::{Engine, EvalAltResult}; +use rhai::{Engine, EvalAltResult, INT}; #[test] fn test_left_shift() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - assert_eq!(engine.eval::("4 << 2")?, 16); + assert_eq!(engine.eval::("4 << 2")?, 16); Ok(()) } #[test] fn test_right_shift() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - assert_eq!(engine.eval::("9 >> 1")?, 4); + assert_eq!(engine.eval::("9 >> 1")?, 4); Ok(()) } diff --git a/tests/comments.rs b/tests/comments.rs index 0b871944..8c071f07 100644 --- a/tests/comments.rs +++ b/tests/comments.rs @@ -1,14 +1,14 @@ -use rhai::Engine; +use rhai::{Engine, INT}; #[test] fn test_comments() { let mut engine = Engine::new(); assert!(engine - .eval::("let x = 5; x // I am a single line comment, yay!") + .eval::("let x = 5; x // I am a single line comment, yay!") .is_ok()); assert!(engine - .eval::("let /* I am a multiline comment, yay! */ x = 5; x") + .eval::("let /* I am a multiline comment, yay! */ x = 5; x") .is_ok()); } diff --git a/tests/compound_equality.rs b/tests/compound_equality.rs index 65b3b3e4..569cfd94 100644 --- a/tests/compound_equality.rs +++ b/tests/compound_equality.rs @@ -1,10 +1,10 @@ -use rhai::{Engine, EvalAltResult}; +use rhai::{Engine, EvalAltResult, INT}; #[test] fn test_or_equals() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - assert_eq!(engine.eval::("let x = 16; x |= 74; x")?, 90); + assert_eq!(engine.eval::("let x = 16; x |= 74; x")?, 90); assert_eq!(engine.eval::("let x = true; x |= false; x")?, true); assert_eq!(engine.eval::("let x = false; x |= true; x")?, true); @@ -15,7 +15,7 @@ fn test_or_equals() -> Result<(), EvalAltResult> { fn test_and_equals() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - assert_eq!(engine.eval::("let x = 16; x &= 31; x")?, 16); + assert_eq!(engine.eval::("let x = 16; x &= 31; x")?, 16); assert_eq!(engine.eval::("let x = true; x &= false; x")?, false); assert_eq!(engine.eval::("let x = false; x &= true; x")?, false); assert_eq!(engine.eval::("let x = true; x &= true; x")?, true); @@ -26,41 +26,41 @@ fn test_and_equals() -> Result<(), EvalAltResult> { #[test] fn test_xor_equals() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - assert_eq!(engine.eval::("let x = 90; x ^= 12; x")?, 86); + assert_eq!(engine.eval::("let x = 90; x ^= 12; x")?, 86); Ok(()) } #[test] fn test_multiply_equals() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - assert_eq!(engine.eval::("let x = 2; x *= 3; x")?, 6); + assert_eq!(engine.eval::("let x = 2; x *= 3; x")?, 6); Ok(()) } #[test] fn test_divide_equals() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - assert_eq!(engine.eval::("let x = 6; x /= 2; x")?, 3); + assert_eq!(engine.eval::("let x = 6; x /= 2; x")?, 3); Ok(()) } #[test] fn test_left_shift_equals() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - assert_eq!(engine.eval::("let x = 9; x >>=1; x")?, 4); + assert_eq!(engine.eval::("let x = 9; x >>=1; x")?, 4); Ok(()) } #[test] fn test_right_shift_equals() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - assert_eq!(engine.eval::("let x = 4; x<<= 2; x")?, 16); + assert_eq!(engine.eval::("let x = 4; x<<= 2; x")?, 16); Ok(()) } #[test] fn test_modulo_equals() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - assert_eq!(engine.eval::("let x = 10; x %= 4; x")?, 2); + assert_eq!(engine.eval::("let x = 10; x %= 4; x")?, 2); Ok(()) } diff --git a/tests/decrement.rs b/tests/decrement.rs index 75d85076..3547adc4 100644 --- a/tests/decrement.rs +++ b/tests/decrement.rs @@ -1,10 +1,10 @@ -use rhai::{Engine, EvalAltResult}; +use rhai::{Engine, EvalAltResult, INT}; #[test] fn test_decrement() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - assert_eq!(engine.eval::("let x = 10; x -= 7; x")?, 3); + assert_eq!(engine.eval::("let x = 10; x -= 7; x")?, 3); let r = engine.eval::("let s = \"test\"; s -= \"ing\"; s"); diff --git a/tests/engine.rs b/tests/engine.rs index 17b83288..a01ae916 100644 --- a/tests/engine.rs +++ b/tests/engine.rs @@ -1,5 +1,5 @@ #![cfg(not(feature = "no_stdlib"))] -use rhai::{Engine, EvalAltResult}; +use rhai::{Engine, EvalAltResult, INT}; #[test] fn test_engine_call_fn() -> Result<(), EvalAltResult> { @@ -13,7 +13,7 @@ fn test_engine_call_fn() -> Result<(), EvalAltResult> { ", )?; - let result: i64 = engine.call_fn("hello", &ast, (String::from("abc"), 123_i64))?; + let result: INT = engine.call_fn("hello", &ast, (String::from("abc"), 123 as INT))?; assert_eq!(result, 126); diff --git a/tests/for.rs b/tests/for.rs index f67b95f2..be476328 100644 --- a/tests/for.rs +++ b/tests/for.rs @@ -1,5 +1,5 @@ #![cfg(not(feature = "no_index"))] -use rhai::{Engine, EvalAltResult}; +use rhai::{Engine, EvalAltResult, INT}; #[test] fn test_for() -> Result<(), EvalAltResult> { @@ -21,7 +21,7 @@ fn test_for() -> Result<(), EvalAltResult> { sum1 + sum2 "; - assert_eq!(engine.eval::(script)?, 30); + assert_eq!(engine.eval::(script)?, 30); Ok(()) } diff --git a/tests/get_set.rs b/tests/get_set.rs index 0a9fdd16..71886d2d 100644 --- a/tests/get_set.rs +++ b/tests/get_set.rs @@ -1,18 +1,18 @@ -use rhai::{Engine, EvalAltResult, RegisterFn}; +use rhai::{Engine, EvalAltResult, RegisterFn, INT}; #[test] fn test_get_set() -> Result<(), EvalAltResult> { #[derive(Clone)] struct TestStruct { - x: i64, + x: INT, } impl TestStruct { - fn get_x(&mut self) -> i64 { + fn get_x(&mut self) -> INT { self.x } - fn set_x(&mut self, new_x: i64) { + fn set_x(&mut self, new_x: INT) { self.x = new_x; } @@ -28,7 +28,7 @@ fn test_get_set() -> Result<(), EvalAltResult> { engine.register_get_set("x", TestStruct::get_x, TestStruct::set_x); engine.register_fn("new_ts", TestStruct::new); - assert_eq!(engine.eval::("let a = new_ts(); a.x = 500; a.x")?, 500); + assert_eq!(engine.eval::("let a = new_ts(); a.x = 500; a.x")?, 500); Ok(()) } @@ -37,15 +37,15 @@ fn test_get_set() -> Result<(), EvalAltResult> { fn test_big_get_set() -> Result<(), EvalAltResult> { #[derive(Clone)] struct TestChild { - x: i64, + x: INT, } impl TestChild { - fn get_x(&mut self) -> i64 { + fn get_x(&mut self) -> INT { self.x } - fn set_x(&mut self, new_x: i64) { + fn set_x(&mut self, new_x: INT) { self.x = new_x; } @@ -86,7 +86,7 @@ fn test_big_get_set() -> Result<(), EvalAltResult> { engine.register_fn("new_tp", TestParent::new); assert_eq!( - engine.eval::("let a = new_tp(); a.child.x = 500; a.child.x")?, + engine.eval::("let a = new_tp(); a.child.x = 500; a.child.x")?, 500 ); diff --git a/tests/if_block.rs b/tests/if_block.rs index 286aa7fd..e8ddb25b 100644 --- a/tests/if_block.rs +++ b/tests/if_block.rs @@ -1,18 +1,18 @@ -use rhai::{Engine, EvalAltResult}; +use rhai::{Engine, EvalAltResult, INT}; #[test] fn test_if() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - assert_eq!(engine.eval::("if true { 55 }")?, 55); - assert_eq!(engine.eval::("if false { 55 } else { 44 }")?, 44); - assert_eq!(engine.eval::("if true { 55 } else { 44 }")?, 55); + assert_eq!(engine.eval::("if true { 55 }")?, 55); + assert_eq!(engine.eval::("if false { 55 } else { 44 }")?, 44); + assert_eq!(engine.eval::("if true { 55 } else { 44 }")?, 55); assert_eq!( - engine.eval::("if false { 55 } else if true { 33 } else { 44 }")?, + engine.eval::("if false { 55 } else if true { 33 } else { 44 }")?, 33 ); assert_eq!( - engine.eval::( + engine.eval::( r" if false { 55 } else if false { 33 } diff --git a/tests/increment.rs b/tests/increment.rs index 62b1b36c..980dda56 100644 --- a/tests/increment.rs +++ b/tests/increment.rs @@ -1,10 +1,10 @@ -use rhai::{Engine, EvalAltResult}; +use rhai::{Engine, EvalAltResult, INT}; #[test] fn test_increment() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - assert_eq!(engine.eval::("let x = 1; x += 2; x")?, 3); + assert_eq!(engine.eval::("let x = 1; x += 2; x")?, 3); assert_eq!( engine.eval::("let s = \"test\"; s += \"ing\"; s")?, "testing".to_string() diff --git a/tests/internal_fn.rs b/tests/internal_fn.rs index 95f584a5..09bb357e 100644 --- a/tests/internal_fn.rs +++ b/tests/internal_fn.rs @@ -1,11 +1,11 @@ -use rhai::{Engine, EvalAltResult}; +use rhai::{Engine, EvalAltResult, INT}; #[test] fn test_internal_fn() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - assert_eq!(engine.eval::("fn addme(a, b) { a+b } addme(3, 4)")?, 7); - assert_eq!(engine.eval::("fn bob() { return 4; 5 } bob()")?, 4); + assert_eq!(engine.eval::("fn addme(a, b) { a+b } addme(3, 4)")?, 7); + assert_eq!(engine.eval::("fn bob() { return 4; 5 } bob()")?, 4); Ok(()) } @@ -15,7 +15,7 @@ fn test_big_internal_fn() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); assert_eq!( - engine.eval::( + engine.eval::( r" fn mathme(a, b, c, d, e, f) { a - b * c + d * e - f diff --git a/tests/math.rs b/tests/math.rs index 9b92f1fb..4dd7fecc 100644 --- a/tests/math.rs +++ b/tests/math.rs @@ -1,41 +1,73 @@ -use rhai::{Engine, EvalAltResult}; +use rhai::{Engine, EvalAltResult, INT}; #[test] fn test_math() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - assert_eq!(engine.eval::("1 + 2")?, 3); - assert_eq!(engine.eval::("1 - 2")?, -1); - assert_eq!(engine.eval::("2 * 3")?, 6); - assert_eq!(engine.eval::("1 / 2")?, 0); - assert_eq!(engine.eval::("3 % 2")?, 1); + assert_eq!(engine.eval::("1 + 2")?, 3); + assert_eq!(engine.eval::("1 - 2")?, -1); + assert_eq!(engine.eval::("2 * 3")?, 6); + assert_eq!(engine.eval::("1 / 2")?, 0); + assert_eq!(engine.eval::("3 % 2")?, 1); + + #[cfg(not(feature = "only_i32"))] assert_eq!( - engine.eval::("(-9223372036854775807).abs()")?, + engine.eval::("(-9223372036854775807).abs()")?, 9223372036854775807 ); + #[cfg(feature = "only_i32")] + assert_eq!(engine.eval::("(-2147483647).abs()")?, 2147483647); + // Overflow/underflow/division-by-zero errors #[cfg(not(feature = "unchecked"))] { - match engine.eval::("9223372036854775807 + 1") { - Err(EvalAltResult::ErrorArithmetic(_, _)) => (), - r => panic!("should return overflow error: {:?}", r), + #[cfg(not(feature = "only_i32"))] + { + match engine.eval::("9223372036854775807 + 1") { + Err(EvalAltResult::ErrorArithmetic(_, _)) => (), + r => panic!("should return overflow error: {:?}", r), + } + match engine.eval::("-9223372036854775808 - 1") { + Err(EvalAltResult::ErrorArithmetic(_, _)) => (), + r => panic!("should return underflow error: {:?}", r), + } + match engine.eval::("9223372036854775807 * 9223372036854775807") { + Err(EvalAltResult::ErrorArithmetic(_, _)) => (), + r => panic!("should return overflow error: {:?}", r), + } + match engine.eval::("9223372036854775807 / 0") { + Err(EvalAltResult::ErrorArithmetic(_, _)) => (), + r => panic!("should return division by zero error: {:?}", r), + } + match engine.eval::("9223372036854775807 % 0") { + Err(EvalAltResult::ErrorArithmetic(_, _)) => (), + r => panic!("should return division by zero error: {:?}", r), + } } - match engine.eval::("-9223372036854775808 - 1") { - Err(EvalAltResult::ErrorArithmetic(_, _)) => (), - r => panic!("should return underflow error: {:?}", r), - } - match engine.eval::("9223372036854775807 * 9223372036854775807") { - Err(EvalAltResult::ErrorArithmetic(_, _)) => (), - r => panic!("should return overflow error: {:?}", r), - } - match engine.eval::("9223372036854775807 / 0") { - Err(EvalAltResult::ErrorArithmetic(_, _)) => (), - r => panic!("should return division by zero error: {:?}", r), - } - match engine.eval::("9223372036854775807 % 0") { - Err(EvalAltResult::ErrorArithmetic(_, _)) => (), - r => panic!("should return division by zero error: {:?}", r), + + #[cfg(feature = "only_i32")] + { + match engine.eval::("2147483647 + 1") { + Err(EvalAltResult::ErrorArithmetic(_, _)) => (), + r => panic!("should return overflow error: {:?}", r), + } + match engine.eval::("-2147483648 - 1") { + Err(EvalAltResult::ErrorArithmetic(_, _)) => (), + r => panic!("should return underflow error: {:?}", r), + } + match engine.eval::("2147483647 * 2147483647") { + Err(EvalAltResult::ErrorArithmetic(_, _)) => (), + r => panic!("should return overflow error: {:?}", r), + } + match engine.eval::("2147483647 / 0") { + Err(EvalAltResult::ErrorArithmetic(_, _)) => (), + r => panic!("should return division by zero error: {:?}", r), + } + match engine.eval::("2147483647 % 0") { + Err(EvalAltResult::ErrorArithmetic(_, _)) => (), + r => panic!("should return division by zero error: {:?}", r), + } } } diff --git a/tests/method_call.rs b/tests/method_call.rs index f9f09652..e1825094 100644 --- a/tests/method_call.rs +++ b/tests/method_call.rs @@ -1,10 +1,10 @@ -use rhai::{Engine, EvalAltResult, RegisterFn}; +use rhai::{Engine, EvalAltResult, RegisterFn, INT}; #[test] fn test_method_call() -> Result<(), EvalAltResult> { #[derive(Clone)] struct TestStruct { - x: i64, + x: INT, } impl TestStruct { diff --git a/tests/mismatched_op.rs b/tests/mismatched_op.rs index 235062d5..1abb3cc1 100644 --- a/tests/mismatched_op.rs +++ b/tests/mismatched_op.rs @@ -1,11 +1,11 @@ -use rhai::{Engine, EvalAltResult, RegisterFn}; +use rhai::{Engine, EvalAltResult, RegisterFn, INT}; #[test] #[cfg(not(feature = "no_stdlib"))] fn test_mismatched_op() { let mut engine = Engine::new(); - let r = engine.eval::("60 + \"hello\""); + let r = engine.eval::("60 + \"hello\""); match r { Err(EvalAltResult::ErrorMismatchOutputType(err, _)) if err == "string" => (), @@ -17,7 +17,7 @@ fn test_mismatched_op() { fn test_mismatched_op_custom_type() { #[derive(Clone)] struct TestStruct { - x: i64, + x: INT, } impl TestStruct { @@ -30,10 +30,15 @@ fn test_mismatched_op_custom_type() { engine.register_type_with_name::("TestStruct"); engine.register_fn("new_ts", TestStruct::new); - let r = engine.eval::("60 + new_ts()"); + let r = engine.eval::("60 + new_ts()"); match r { + #[cfg(feature = "only_i32")] + Err(EvalAltResult::ErrorFunctionNotFound(err, _)) if err == "+ (i32, TestStruct)" => (), + + #[cfg(not(feature = "only_i32"))] Err(EvalAltResult::ErrorFunctionNotFound(err, _)) if err == "+ (i64, TestStruct)" => (), + _ => panic!(), } } diff --git a/tests/number_literals.rs b/tests/number_literals.rs index 16e12719..f6466967 100644 --- a/tests/number_literals.rs +++ b/tests/number_literals.rs @@ -1,10 +1,10 @@ -use rhai::{Engine, EvalAltResult}; +use rhai::{Engine, EvalAltResult, INT}; #[test] fn test_number_literal() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - assert_eq!(engine.eval::("65")?, 65); + assert_eq!(engine.eval::("65")?, 65); Ok(()) } @@ -13,8 +13,8 @@ fn test_number_literal() -> Result<(), EvalAltResult> { fn test_hex_literal() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - assert_eq!(engine.eval::("let x = 0xf; x")?, 15); - assert_eq!(engine.eval::("let x = 0xff; x")?, 255); + assert_eq!(engine.eval::("let x = 0xf; x")?, 15); + assert_eq!(engine.eval::("let x = 0xff; x")?, 255); Ok(()) } @@ -23,8 +23,8 @@ fn test_hex_literal() -> Result<(), EvalAltResult> { fn test_octal_literal() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - assert_eq!(engine.eval::("let x = 0o77; x")?, 63); - assert_eq!(engine.eval::("let x = 0o1234; x")?, 668); + assert_eq!(engine.eval::("let x = 0o77; x")?, 63); + assert_eq!(engine.eval::("let x = 0o1234; x")?, 668); Ok(()) } @@ -33,9 +33,9 @@ fn test_octal_literal() -> Result<(), EvalAltResult> { fn test_binary_literal() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - assert_eq!(engine.eval::("let x = 0b1111; x")?, 15); + assert_eq!(engine.eval::("let x = 0b1111; x")?, 15); assert_eq!( - engine.eval::("let x = 0b0011_1100_1010_0101; x")?, + engine.eval::("let x = 0b0011_1100_1010_0101; x")?, 15525 ); diff --git a/tests/ops.rs b/tests/ops.rs index d1464dfb..aaddea8a 100644 --- a/tests/ops.rs +++ b/tests/ops.rs @@ -1,11 +1,11 @@ -use rhai::{Engine, EvalAltResult}; +use rhai::{Engine, EvalAltResult, INT}; #[test] fn test_ops() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - assert_eq!(engine.eval::("60 + 5")?, 65); - assert_eq!(engine.eval::("(1 + 2) * (6 - 4) / 2")?, 3); + assert_eq!(engine.eval::("60 + 5")?, 65); + assert_eq!(engine.eval::("(1 + 2) * (6 - 4) / 2")?, 3); Ok(()) } @@ -15,7 +15,7 @@ fn test_op_prec() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); assert_eq!( - engine.eval::("let x = 0; if x == 10 || true { x = 1} x")?, + engine.eval::("let x = 0; if x == 10 || true { x = 1} x")?, 1 ); diff --git a/tests/power_of.rs b/tests/power_of.rs index 78abcb5d..8e1ed522 100644 --- a/tests/power_of.rs +++ b/tests/power_of.rs @@ -1,19 +1,22 @@ -use rhai::{Engine, EvalAltResult}; +use rhai::{Engine, EvalAltResult, FLOAT, INT}; #[test] fn test_power_of() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - assert_eq!(engine.eval::("2 ~ 3")?, 8); - assert_eq!(engine.eval::("(-2 ~ 3)")?, -8); + assert_eq!(engine.eval::("2 ~ 3")?, 8); + assert_eq!(engine.eval::("(-2 ~ 3)")?, -8); #[cfg(not(feature = "no_float"))] { - assert_eq!(engine.eval::("2.2 ~ 3.3")?, 13.489468760533386_f64); - assert_eq!(engine.eval::("2.0~-2.0")?, 0.25_f64); - assert_eq!(engine.eval::("(-2.0~-2.0)")?, 0.25_f64); - assert_eq!(engine.eval::("(-2.0~-2)")?, 0.25_f64); - assert_eq!(engine.eval::("4~3")?, 64); + assert_eq!( + engine.eval::("2.2 ~ 3.3")?, + 13.489468760533386 as FLOAT + ); + assert_eq!(engine.eval::("2.0~-2.0")?, 0.25 as FLOAT); + assert_eq!(engine.eval::("(-2.0~-2.0)")?, 0.25 as FLOAT); + assert_eq!(engine.eval::("(-2.0~-2)")?, 0.25 as FLOAT); + assert_eq!(engine.eval::("4~3")?, 64); } Ok(()) @@ -23,19 +26,28 @@ fn test_power_of() -> Result<(), EvalAltResult> { fn test_power_of_equals() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - assert_eq!(engine.eval::("let x = 2; x ~= 3; x")?, 8); - assert_eq!(engine.eval::("let x = -2; x ~= 3; x")?, -8); + assert_eq!(engine.eval::("let x = 2; x ~= 3; x")?, 8); + assert_eq!(engine.eval::("let x = -2; x ~= 3; x")?, -8); #[cfg(not(feature = "no_float"))] { assert_eq!( - engine.eval::("let x = 2.2; x ~= 3.3; x")?, - 13.489468760533386_f64 + engine.eval::("let x = 2.2; x ~= 3.3; x")?, + 13.489468760533386 as FLOAT ); - assert_eq!(engine.eval::("let x = 2.0; x ~= -2.0; x")?, 0.25_f64); - assert_eq!(engine.eval::("let x = -2.0; x ~= -2.0; x")?, 0.25_f64); - assert_eq!(engine.eval::("let x = -2.0; x ~= -2; x")?, 0.25_f64); - assert_eq!(engine.eval::("let x =4; x ~= 3; x")?, 64); + assert_eq!( + engine.eval::("let x = 2.0; x ~= -2.0; x")?, + 0.25 as FLOAT + ); + assert_eq!( + engine.eval::("let x = -2.0; x ~= -2.0; x")?, + 0.25 as FLOAT + ); + assert_eq!( + engine.eval::("let x = -2.0; x ~= -2; x")?, + 0.25 as FLOAT + ); + assert_eq!(engine.eval::("let x =4; x ~= 3; x")?, 64); } Ok(()) diff --git a/tests/throw.rs b/tests/throw.rs index ca14cb3f..5edecedc 100644 --- a/tests/throw.rs +++ b/tests/throw.rs @@ -1,16 +1,16 @@ -use rhai::{Engine, EvalAltResult}; +use rhai::{Engine, EvalAltResult, INT}; #[test] fn test_throw() { let mut engine = Engine::new(); - match engine.eval::(r#"if true { throw "hello" }"#) { + match engine.eval::(r#"if true { throw "hello" }"#) { Ok(_) => panic!("not an error"), Err(EvalAltResult::ErrorRuntime(s, _)) if s == "hello" => (), Err(err) => panic!("wrong error: {}", err), } - match engine.eval::(r#"throw;"#) { + match engine.eval::(r#"throw;"#) { Ok(_) => panic!("not an error"), Err(EvalAltResult::ErrorRuntime(s, _)) if s == "" => (), Err(err) => panic!("wrong error: {}", err), diff --git a/tests/types.rs b/tests/types.rs index ed3ff2ea..fd9cda32 100644 --- a/tests/types.rs +++ b/tests/types.rs @@ -4,8 +4,12 @@ use rhai::{Engine, EvalAltResult}; fn test_type_of() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); + #[cfg(not(feature = "only_i32"))] assert_eq!(engine.eval::("type_of(60 + 5)")?, "i64"); + #[cfg(feature = "only_i32")] + assert_eq!(engine.eval::("type_of(60 + 5)")?, "i32"); + #[cfg(not(feature = "no_float"))] assert_eq!(engine.eval::("type_of(1.0 + 2.0)")?, "f64"); @@ -17,7 +21,12 @@ fn test_type_of() -> Result<(), EvalAltResult> { ); assert_eq!(engine.eval::(r#"type_of("hello")"#)?, "string"); + + #[cfg(not(feature = "only_i32"))] assert_eq!(engine.eval::("let x = 123; x.type_of()")?, "i64"); + #[cfg(feature = "only_i32")] + assert_eq!(engine.eval::("let x = 123; x.type_of()")?, "i32"); + Ok(()) } diff --git a/tests/unary_after_binary.rs b/tests/unary_after_binary.rs index f44111d4..16d4730a 100644 --- a/tests/unary_after_binary.rs +++ b/tests/unary_after_binary.rs @@ -1,4 +1,4 @@ -use rhai::{Engine, EvalAltResult}; +use rhai::{Engine, EvalAltResult, INT}; #[test] // TODO also add test case for unary after compound @@ -6,12 +6,12 @@ use rhai::{Engine, EvalAltResult}; fn test_unary_after_binary() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - assert_eq!(engine.eval::("10 % +4")?, 2); - assert_eq!(engine.eval::("10 << +4")?, 160); - assert_eq!(engine.eval::("10 >> +4")?, 0); - assert_eq!(engine.eval::("10 & +4")?, 0); - assert_eq!(engine.eval::("10 | +4")?, 14); - assert_eq!(engine.eval::("10 ^ +4")?, 14); + assert_eq!(engine.eval::("10 % +4")?, 2); + assert_eq!(engine.eval::("10 << +4")?, 160); + assert_eq!(engine.eval::("10 >> +4")?, 0); + assert_eq!(engine.eval::("10 & +4")?, 0); + assert_eq!(engine.eval::("10 | +4")?, 14); + assert_eq!(engine.eval::("10 ^ +4")?, 14); Ok(()) } diff --git a/tests/unary_minus.rs b/tests/unary_minus.rs index 37718326..8f5f6013 100644 --- a/tests/unary_minus.rs +++ b/tests/unary_minus.rs @@ -1,12 +1,12 @@ -use rhai::{Engine, EvalAltResult}; +use rhai::{Engine, EvalAltResult, INT}; #[test] fn test_unary_minus() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - assert_eq!(engine.eval::("let x = -5; x")?, -5); - assert_eq!(engine.eval::("fn neg(x) { -x } neg(5)")?, -5); - assert_eq!(engine.eval::("5 - -+++--+-5")?, 0); + assert_eq!(engine.eval::("let x = -5; x")?, -5); + assert_eq!(engine.eval::("fn neg(x) { -x } neg(5)")?, -5); + assert_eq!(engine.eval::("5 - -+++--+-5")?, 0); Ok(()) } diff --git a/tests/var_scope.rs b/tests/var_scope.rs index f8371bb3..0ad6c618 100644 --- a/tests/var_scope.rs +++ b/tests/var_scope.rs @@ -1,4 +1,4 @@ -use rhai::{Engine, EvalAltResult, Scope}; +use rhai::{Engine, EvalAltResult, Scope, INT}; #[test] fn test_var_scope() -> Result<(), EvalAltResult> { @@ -6,14 +6,14 @@ fn test_var_scope() -> Result<(), EvalAltResult> { let mut scope = Scope::new(); engine.eval_with_scope::<()>(&mut scope, false, "let x = 4 + 5")?; - assert_eq!(engine.eval_with_scope::(&mut scope, false, "x")?, 9); + assert_eq!(engine.eval_with_scope::(&mut scope, false, "x")?, 9); engine.eval_with_scope::<()>(&mut scope, false, "x = x + 1; x = x + 2;")?; - assert_eq!(engine.eval_with_scope::(&mut scope, false, "x")?, 12); + assert_eq!(engine.eval_with_scope::(&mut scope, false, "x")?, 12); assert_eq!( engine.eval_with_scope::<()>(&mut scope, false, "{let x = 3}")?, () ); - assert_eq!(engine.eval_with_scope::(&mut scope, false, "x")?, 12); + assert_eq!(engine.eval_with_scope::(&mut scope, false, "x")?, 12); Ok(()) } @@ -26,10 +26,10 @@ fn test_scope_eval() -> Result<(), EvalAltResult> { let mut scope = Scope::new(); // Then push some initialized variables into the state - // NOTE: Remember the default numbers used by Rhai are i64 and f64. + // NOTE: Remember the default numbers used by Rhai are INT and f64. // Better stick to them or it gets hard to work with other variables in the script. - scope.push("y", 42_i64); - scope.push("z", 999_i64); + scope.push("y", 42 as INT); + scope.push("z", 999 as INT); // First invocation engine @@ -37,12 +37,12 @@ fn test_scope_eval() -> Result<(), EvalAltResult> { .expect("y and z not found?"); // Second invocation using the same state - let result = engine.eval_with_scope::(&mut scope, false, "x")?; + let result = engine.eval_with_scope::(&mut scope, false, "x")?; println!("result: {}", result); // should print 966 // Variable y is changed in the script - assert_eq!(scope.get_value::("y").unwrap(), 1); + assert_eq!(scope.get_value::("y").unwrap(), 1); Ok(()) } diff --git a/tests/while_loop.rs b/tests/while_loop.rs index 8dc1ae85..fa76ea17 100644 --- a/tests/while_loop.rs +++ b/tests/while_loop.rs @@ -1,11 +1,11 @@ -use rhai::{Engine, EvalAltResult}; +use rhai::{Engine, EvalAltResult, INT}; #[test] fn test_while() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); assert_eq!( - engine.eval::( + engine.eval::( "let x = 0; while x < 10 { x = x + 1; if x > 5 { \ break } } x", )?, From 880bce1114d9cefcb2b9aad49c9d9d5e0d9ef753 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 11 Mar 2020 11:03:18 +0800 Subject: [PATCH 06/24] General cleanup. --- Cargo.toml | 1 + src/builtin.rs | 68 +++++++++++++------------------------- src/engine.rs | 7 ++-- src/fn_register.rs | 82 +++++++++++++++++++++++----------------------- src/optimize.rs | 8 ++++- src/parser.rs | 26 +++++++-------- src/result.rs | 43 ++++++++++++++++-------- src/scope.rs | 1 + tests/math.rs | 4 +++ 9 files changed, 124 insertions(+), 116 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8fa20102..600705e7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ include = [ num-traits = "*" [features] +#default = ["no_index", "no_float", "only_i32", "no_stdlib", "unchecked"] default = [] debug_msgs = [] unchecked = [] diff --git a/src/builtin.rs b/src/builtin.rs index 0e8ab757..04a290ce 100644 --- a/src/builtin.rs +++ b/src/builtin.rs @@ -2,45 +2,23 @@ //! _standard library_ of utility functions. use crate::any::Any; -use crate::engine::Engine; -use crate::fn_register::RegisterFn; -use crate::parser::INT; - -#[cfg(not(feature = "unchecked"))] -use crate::{parser::Position, result::EvalAltResult, RegisterResultFn}; - #[cfg(not(feature = "no_index"))] use crate::engine::Array; - -#[cfg(not(feature = "no_float"))] +use crate::engine::Engine; +use crate::fn_register::{RegisterFn, RegisterResultFn}; +use crate::parser::{Position, INT}; +use crate::result::EvalAltResult; use crate::FLOAT; +use num_traits::{ + identities::Zero, CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedRem, CheckedShl, + CheckedShr, CheckedSub, +}; + use std::{ fmt::{Debug, Display}, - ops::{BitAnd, BitOr, BitXor, Range}, -}; - -#[cfg(feature = "unchecked")] -use std::ops::{Shl, Shr}; - -#[cfg(not(feature = "unchecked"))] -#[cfg(not(feature = "no_float"))] -use std::{i32, i64}; - -#[cfg(not(feature = "unchecked"))] -#[cfg(not(feature = "only_i32"))] -use std::u32; - -#[cfg(any(feature = "unchecked", not(feature = "no_float")))] -use std::ops::{Add, Div, Mul, Neg, Rem, Sub}; - -#[cfg(not(feature = "unchecked"))] -use { - num_traits::{ - CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedRem, CheckedShl, CheckedShr, - CheckedSub, - }, - std::convert::TryFrom, + ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Range, Rem, Shl, Shr, Sub}, + {i32, i64, u32}, }; macro_rules! reg_op { @@ -162,12 +140,9 @@ impl Engine<'_> { #[cfg(not(feature = "unchecked"))] fn div(x: T, y: T) -> Result where - T: Display + CheckedDiv + PartialEq + TryFrom, + T: Display + CheckedDiv + PartialEq + Zero, { - if y == >::try_from(0) - .map_err(|_| ()) - .expect("zero should always succeed") - { + if y == T::zero() { return Err(EvalAltResult::ErrorArithmetic( format!("Division by zero: {} / {}", x, y), Position::none(), @@ -191,8 +166,10 @@ impl Engine<'_> { }) } #[cfg(not(feature = "unchecked"))] - fn abs>(x: T) -> Result { - if x >= 0.into() { + fn abs(x: T) -> Result { + // FIX - We don't use Signed::abs() here because, contrary to documentation, it panics + // when the number is ::MIN instead of returning ::MIN itself. + if x >= ::zero() { Ok(x) } else { x.checked_neg().ok_or_else(|| { @@ -224,14 +201,15 @@ impl Engine<'_> { -x } #[cfg(any(feature = "unchecked", not(feature = "no_float")))] - fn abs_u>(x: T) -> T + fn abs_u(x: T) -> ::Output where - ::Output: Into, + T: Neg + PartialOrd + Default + Into<::Output>, { - if x < 0.into() { - (-x).into() + // Numbers should default to zero + if x < Default::default() { + -x } else { - x + x.into() } } fn lt(x: T, y: T) -> bool { diff --git a/src/engine.rs b/src/engine.rs index ebbf4ec7..cba45674 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -4,8 +4,6 @@ use crate::any::{Any, AnyExt, Dynamic, Variant}; use crate::parser::{Expr, FnDef, Position, Stmt}; use crate::result::EvalAltResult; use crate::scope::Scope; - -#[cfg(not(feature = "no_index"))] use crate::INT; use std::{ @@ -788,6 +786,9 @@ impl Engine<'_> { .eval_index_expr(scope, lhs, idx_expr, *idx_pos) .map(|(_, _, _, x)| x), + #[cfg(feature = "no_index")] + Expr::Index(_, _, _) => panic!("encountered an index expression during no_index!"), + // Statement block Expr::Stmt(stmt, _) => self.eval_stmt(scope, stmt), @@ -855,6 +856,8 @@ impl Engine<'_> { Ok(Box::new(arr)) } + #[cfg(feature = "no_index")] + Expr::Array(_, _) => panic!("encountered an array during no_index!"), Expr::FunctionCall(fn_name, args, def_val, pos) => { let mut args = args diff --git a/src/fn_register.rs b/src/fn_register.rs index 9af9ab82..84dbf150 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -120,20 +120,20 @@ macro_rules! def_register { const NUM_ARGS: usize = count_args!($($par)*); if args.len() != NUM_ARGS { - Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, args.len(), pos)) - } else { - #[allow(unused_variables, unused_mut)] - let mut drain = args.drain(..); - $( - // Downcast every element, return in case of a type mismatch - let $par = drain.next().unwrap().downcast_mut::<$par>().unwrap(); - )* - - // Call the user-supplied function using ($clone) to - // potentially clone the value, otherwise pass the reference. - let r = f($(($clone)($par)),*); - Ok(Box::new(r) as Dynamic) + return Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, args.len(), pos)); } + + #[allow(unused_variables, unused_mut)] + let mut drain = args.drain(..); + $( + // Downcast every element, return in case of a type mismatch + let $par = drain.next().unwrap().downcast_mut::<$par>().unwrap(); + )* + + // Call the user-supplied function using ($clone) to + // potentially clone the value, otherwise pass the reference. + let r = f($(($clone)($par)),*); + Ok(Box::new(r) as Dynamic) }; self.register_fn_raw(name, Some(vec![$(TypeId::of::<$par>()),*]), Box::new(fun)); } @@ -152,19 +152,19 @@ macro_rules! def_register { const NUM_ARGS: usize = count_args!($($par)*); if args.len() != NUM_ARGS { - Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, args.len(), pos)) - } else { - #[allow(unused_variables, unused_mut)] - let mut drain = args.drain(..); - $( - // Downcast every element, return in case of a type mismatch - let $par = drain.next().unwrap().downcast_mut::<$par>().unwrap(); - )* - - // Call the user-supplied function using ($clone) to - // potentially clone the value, otherwise pass the reference. - Ok(f($(($clone)($par)),*)) + return Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, args.len(), pos)); } + + #[allow(unused_variables, unused_mut)] + let mut drain = args.drain(..); + $( + // Downcast every element, return in case of a type mismatch + let $par = drain.next().unwrap().downcast_mut::<$par>().unwrap(); + )* + + // Call the user-supplied function using ($clone) to + // potentially clone the value, otherwise pass the reference. + Ok(f($(($clone)($par)),*)) }; self.register_fn_raw(name, Some(vec![$(TypeId::of::<$par>()),*]), Box::new(fun)); } @@ -184,23 +184,23 @@ macro_rules! def_register { const NUM_ARGS: usize = count_args!($($par)*); if args.len() != NUM_ARGS { - Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, args.len(), pos)) - } else { - #[allow(unused_variables, unused_mut)] - let mut drain = args.drain(..); - $( - // Downcast every element, return in case of a type mismatch - let $par = drain.next().unwrap().downcast_mut::<$par>().unwrap(); - )* + return Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, args.len(), pos)); + } - // Call the user-supplied function using ($clone) to - // potentially clone the value, otherwise pass the reference. - match f($(($clone)($par)),*) { - Ok(r) => Ok(Box::new(r) as Dynamic), - Err(mut err) => { - err.set_position(pos); - Err(err) - } + #[allow(unused_variables, unused_mut)] + let mut drain = args.drain(..); + $( + // Downcast every element, return in case of a type mismatch + let $par = drain.next().unwrap().downcast_mut::<$par>().unwrap(); + )* + + // Call the user-supplied function using ($clone) to + // potentially clone the value, otherwise pass the reference. + match f($(($clone)($par)),*) { + Ok(r) => Ok(Box::new(r) as Dynamic), + Err(mut err) => { + err.set_position(pos); + Err(err) } } }; diff --git a/src/optimize.rs b/src/optimize.rs index 085b09f9..cb92fb11 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -141,6 +141,7 @@ fn optimize_expr(expr: Expr, changed: &mut bool) -> Expr { Box::new(optimize_expr(*rhs, changed)), pos, ), + #[cfg(not(feature = "no_index"))] Expr::Index(lhs, rhs, pos) => match (*lhs, *rhs) { (Expr::Array(mut items, _), Expr::IntegerConstant(i, _)) @@ -158,6 +159,9 @@ fn optimize_expr(expr: Expr, changed: &mut bool) -> Expr { pos, ), }, + #[cfg(feature = "no_index")] + Expr::Index(_, _, _) => panic!("encountered an index expression during no_index!"), + #[cfg(not(feature = "no_index"))] Expr::Array(items, pos) => { let original_len = items.len(); @@ -172,6 +176,9 @@ fn optimize_expr(expr: Expr, changed: &mut bool) -> Expr { Expr::Array(items, pos) } + #[cfg(feature = "no_index")] + Expr::Array(_, _) => panic!("encountered an array during no_index!"), + Expr::And(lhs, rhs) => match (*lhs, *rhs) { (Expr::True(_), rhs) => { *changed = true; @@ -208,7 +215,6 @@ fn optimize_expr(expr: Expr, changed: &mut bool) -> Expr { Box::new(optimize_expr(rhs, changed)), ), }, - Expr::FunctionCall(id, args, def_value, pos) => { let original_len = args.len(); diff --git a/src/parser.rs b/src/parser.rs index fcbe7e20..cdb8bac9 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -3,13 +3,18 @@ use crate::any::Dynamic; use crate::error::{LexError, ParseError, ParseErrorType}; use crate::optimize::optimize; + use std::{borrow::Cow, char, fmt, iter::Peekable, str::Chars, str::FromStr, usize}; -/// The system integer type +/// The system integer type. +/// +/// If the `only_i32` feature is enabled, this will be `i32` instead. #[cfg(not(feature = "only_i32"))] pub type INT = i64; /// The system integer type +/// +/// If the `only_i32` feature is not enabled, this will be `i64` instead. #[cfg(feature = "only_i32")] pub type INT = i32; @@ -179,9 +184,7 @@ pub enum Expr { FunctionCall(String, Vec, Option, Position), Assignment(Box, Box, Position), Dot(Box, Box, Position), - #[cfg(not(feature = "no_index"))] Index(Box, Box, Position), - #[cfg(not(feature = "no_index"))] Array(Vec, Position), And(Box, Box), Or(Box, Box), @@ -197,24 +200,21 @@ impl Expr { | Expr::Identifier(_, pos) | Expr::CharConstant(_, pos) | Expr::StringConstant(_, pos) - | Expr::FunctionCall(_, _, _, pos) | Expr::Stmt(_, pos) + | Expr::FunctionCall(_, _, _, pos) + | Expr::Array(_, pos) | Expr::True(pos) | Expr::False(pos) | Expr::Unit(pos) => *pos, - Expr::Assignment(e, _, _) | Expr::Dot(e, _, _) | Expr::And(e, _) | Expr::Or(e, _) => { - e.position() - } + Expr::Assignment(e, _, _) + | Expr::Dot(e, _, _) + | Expr::Index(e, _, _) + | Expr::And(e, _) + | Expr::Or(e, _) => e.position(), #[cfg(not(feature = "no_float"))] Expr::FloatConstant(_, pos) => *pos, - - #[cfg(not(feature = "no_index"))] - Expr::Index(e, _, _) => e.position(), - - #[cfg(not(feature = "no_index"))] - Expr::Array(_, pos) => *pos, } } diff --git a/src/result.rs b/src/result.rs index 8034329c..e0bb3734 100644 --- a/src/result.rs +++ b/src/result.rs @@ -3,6 +3,7 @@ use crate::any::Dynamic; use crate::error::ParseError; use crate::parser::{Position, INT}; + use std::{error::Error, fmt}; /// Evaluation result. @@ -75,12 +76,12 @@ impl Error for EvalAltResult { Self::ErrorArrayBounds(_, index, _) if *index < 0 => { "Array access expects non-negative index" } - Self::ErrorArrayBounds(max, _, _) if *max == 0 => "Access of empty array", + Self::ErrorArrayBounds(0, _, _) => "Access of empty array", Self::ErrorArrayBounds(_, _, _) => "Array index out of bounds", Self::ErrorStringBounds(_, index, _) if *index < 0 => { "Indexing a string expects a non-negative index" } - Self::ErrorStringBounds(max, _, _) if *max == 0 => "Indexing of empty string", + Self::ErrorStringBounds(0, _, _) => "Indexing of empty string", Self::ErrorStringBounds(_, _, _) => "String index out of bounds", Self::ErrorIfGuard(_) => "If guard expects boolean expression", Self::ErrorFor(_) => "For loop expects array or range", @@ -128,6 +129,16 @@ impl fmt::Display for EvalAltResult { write!(f, "{} '{}': {}", desc, filename, err) } Self::ErrorParsing(p) => write!(f, "Syntax error: {}", p), + Self::ErrorFunctionArgsMismatch(fun, 0, n, pos) => write!( + f, + "Function '{}' expects no argument but {} found ({})", + fun, n, pos + ), + Self::ErrorFunctionArgsMismatch(fun, 1, n, pos) => write!( + f, + "Function '{}' expects one argument but {} found ({})", + fun, n, pos + ), Self::ErrorFunctionArgsMismatch(fun, need, n, pos) => write!( f, "Function '{}' expects {} argument(s) but {} found ({})", @@ -142,26 +153,30 @@ impl fmt::Display for EvalAltResult { Self::ErrorArrayBounds(_, index, pos) if *index < 0 => { write!(f, "{}: {} < 0 ({})", desc, index, pos) } - Self::ErrorArrayBounds(max, _, pos) if *max == 0 => write!(f, "{} ({})", desc, pos), + Self::ErrorArrayBounds(0, _, pos) => write!(f, "{} ({})", desc, pos), + Self::ErrorArrayBounds(1, index, pos) => write!( + f, + "Array index {} is out of bounds: only one element in the array ({})", + index, pos + ), Self::ErrorArrayBounds(max, index, pos) => write!( f, - "Array index {} is out of bounds: only {} element{} in the array ({})", - index, - max, - if *max > 1 { "s" } else { "" }, - pos + "Array index {} is out of bounds: only {} elements in the array ({})", + index, max, pos ), Self::ErrorStringBounds(_, index, pos) if *index < 0 => { write!(f, "{}: {} < 0 ({})", desc, index, pos) } - Self::ErrorStringBounds(max, _, pos) if *max == 0 => write!(f, "{} ({})", desc, pos), + Self::ErrorStringBounds(0, _, pos) => write!(f, "{} ({})", desc, pos), + Self::ErrorStringBounds(1, index, pos) => write!( + f, + "String index {} is out of bounds: only one character in the string ({})", + index, pos + ), Self::ErrorStringBounds(max, index, pos) => write!( f, - "String index {} is out of bounds: only {} character{} in the string ({})", - index, - max, - if *max > 1 { "s" } else { "" }, - pos + "String index {} is out of bounds: only {} characters in the string ({})", + index, max, pos ), } } diff --git a/src/scope.rs b/src/scope.rs index d097f422..ca3ec4d9 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -1,6 +1,7 @@ //! Module that defines the `Scope` type representing a function call-stack scope. use crate::any::{Any, Dynamic}; + use std::borrow::Cow; /// A type containing information about current scope. diff --git a/tests/math.rs b/tests/math.rs index 4dd7fecc..e9565b39 100644 --- a/tests/math.rs +++ b/tests/math.rs @@ -24,6 +24,10 @@ fn test_math() -> Result<(), EvalAltResult> { { #[cfg(not(feature = "only_i32"))] { + match engine.eval::("(-9223372036854775808).abs()") { + Err(EvalAltResult::ErrorArithmetic(_, _)) => (), + r => panic!("should return overflow error: {:?}", r), + } match engine.eval::("9223372036854775807 + 1") { Err(EvalAltResult::ErrorArithmetic(_, _)) => (), r => panic!("should return overflow error: {:?}", r), From 047f064cd1081605e3003b9428f468f2dc0c302d Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 11 Mar 2020 11:39:15 +0800 Subject: [PATCH 07/24] Add `dump_ast` function for debugging. --- src/engine.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/engine.rs b/src/engine.rs index cba45674..c1130e14 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -27,6 +27,7 @@ type IteratorFn = dyn Fn(&Dynamic) -> Box>; pub(crate) const KEYWORD_PRINT: &'static str = "print"; pub(crate) const KEYWORD_DEBUG: &'static str = "debug"; +pub(crate) const KEYWORD_DUMP_AST: &'static str = "dump_ast"; pub(crate) const KEYWORD_TYPE_OF: &'static str = "type_of"; pub(crate) const FUNC_GETTER: &'static str = "get$"; pub(crate) const FUNC_SETTER: &'static str = "set$"; @@ -859,6 +860,31 @@ impl Engine<'_> { #[cfg(feature = "no_index")] Expr::Array(_, _) => panic!("encountered an array during no_index!"), + // Dump AST + Expr::FunctionCall(fn_name, args, _, pos) if fn_name == KEYWORD_DUMP_AST => { + let pos = if args.len() == 0 { + *pos + } else { + args[0].position() + }; + + // Change the argument to a debug dump of the expressions + let result = args + .into_iter() + .map(|expr| format!("{:#?}", expr)) + .collect::>() + .join("\n"); + + // Redirect call to `print` + self.call_fn_raw( + KEYWORD_PRINT, + vec![result.into_dynamic().as_mut()], + None, + pos, + ) + } + + // Normal function call Expr::FunctionCall(fn_name, args, def_val, pos) => { let mut args = args .iter() From 7c4d22d98af1856bd7d35dc322ca9adde897cc2c Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 11 Mar 2020 13:28:12 +0800 Subject: [PATCH 08/24] Add `no_function` feature to disable script-defined functions. --- Cargo.toml | 18 ++++---- README.md | 99 +++++++++++++++++++++++++------------------- src/api.rs | 60 ++++++++++++++++++--------- src/engine.rs | 2 + src/parser.rs | 20 +++++++-- tests/bool_op.rs | 20 ++++----- tests/engine.rs | 1 + tests/internal_fn.rs | 2 + tests/not.rs | 1 + tests/unary_minus.rs | 3 ++ 10 files changed, 140 insertions(+), 86 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 600705e7..a5177aa6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,16 +18,18 @@ include = [ num-traits = "*" [features] -#default = ["no_index", "no_float", "only_i32", "no_stdlib", "unchecked"] +#default = ["no_function", "no_index", "no_float", "only_i32", "no_stdlib", "unchecked"] default = [] -debug_msgs = [] -unchecked = [] -no_stdlib = [] -no_index = [] -no_float = [] -only_i32 = [] -only_i64 = [] +debug_msgs = [] # print debug messages on function registrations and calls +unchecked = [] # unchecked arithmetic +no_stdlib = [] # no standard library of utility functions +no_index = [] # no arrays and indexing +no_float = [] # no floating-point +no_function = [] # no script-defined functions +only_i32 = [] # set INT=i32 (useful for 32-bit systems) +only_i64 = [] # set INT=i64 (default) and disable support for all other integer types [profile.release] lto = "fat" codegen-units = 1 +#opt-level = "z" # optimize for size diff --git a/README.md b/README.md index 8d7724ea..c96d0a32 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Rhai's current feature set: * Low compile-time overhead (~0.6 sec debug/~3 sec release for script runner app) * Easy-to-use language similar to JS+Rust * Support for overloaded functions -* Very few additional dependencies (right now only `num-traits` to do checked arithmetic operations) +* Very few additional dependencies (right now only [`num-traits`] to do checked arithmetic operations) **Note:** Currently, the version is 0.10.2, so the language and API's may change before they stabilize. @@ -38,15 +38,16 @@ Beware that in order to use pre-releases (alpha and beta) you need to specify th Optional features ----------------- -| Feature | Description | -| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `debug_msgs` | Print debug messages to stdout related to function registrations and calls. | -| `no_stdlib` | Exclude the standard library of utility functions in the build, and only include the minimum necessary functionalities. Standard types are not affected. | -| `unchecked` | Exclude arithmetic checking (such as overflows and division by zero). Beware that a bad script may panic the entire system! | -| `no_index` | Disable arrays and indexing features if you don't need them. | -| `no_float` | Disable floating-point numbers and math if you don't need them. | -| `only_i32` | Set the system integer type to `i32` and disable all other integer types. | -| `only_i64` | Set the system integer type to `i64` and disable all other integer types. | +| Feature | Description | +| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `debug_msgs` | Print debug messages to stdout related to function registrations and calls. | +| `no_stdlib` | Exclude the standard library of utility functions in the build, and only include the minimum necessary functionalities. Standard types are not affected. | +| `unchecked` | Exclude arithmetic checking (such as overflows and division by zero). Beware that a bad script may panic the entire system! | +| `no_function` | Disable script-defined functions if you don't need them. | +| `no_index` | Disable arrays and indexing features if you don't need them. | +| `no_float` | Disable floating-point numbers and math if you don't need them. | +| `only_i32` | Set the system integer type to `i32` and disable all other integer types. | +| `only_i64` | Set the system integer type to `i64` and disable all other integer types. | By default, Rhai includes all the standard functionalities in a small, tight package. Most features are here for you to opt-**out** of certain functionalities that you do not need. Excluding unneeded functionalities can result in smaller, faster builds as well as less bugs due to a more restricted language. @@ -56,8 +57,8 @@ Related Other cool projects to check out: -* [ChaiScript](http://chaiscript.com/) - A strong inspiration for Rhai. An embedded scripting language for C++ that I helped created many moons ago, now being lead by my cousin. -* You can also check out the list of [scripting languages for Rust](https://github.com/rust-unofficial/awesome-rust#scripting) on [awesome-rust](https://github.com/rust-unofficial/awesome-rust) +* [ChaiScript] - A strong inspiration for Rhai. An embedded scripting language for C++ that I helped created many moons ago, now being lead by my cousin. +* You can also check out the list of [scripting languages for Rust] on [awesome-rust]. Examples -------- @@ -195,24 +196,24 @@ Values and types The following primitive types are supported natively: -| Category | Types | -| ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | -| **Integer** | `u8`, `i8`, `u16`, `i16`,
`u32`, `i32` (default for [`only_i32`](#optional-features)),
`u64`, `i64` _(default)_ | -| **Floating-point** (disabled with [`no_float`](#optional-features)) | `f32`, `f64` _(default)_ | -| **Character** | `char` | -| **Boolean** | `bool` | -| **Array** (disabled with [`no_index`](#optional-features)) | `rhai::Array` | -| **Dynamic** (i.e. can be anything) | `rhai::Dynamic` | -| **System** (current configuration) | `rhai::INT` (`i32` or `i64`),
`rhai::FLOAT` (`f32` or `f64`) | +| Category | Types | +| ----------------------------------------------- | ---------------------------------------------------------------------------------------------------- | +| **Integer** | `u8`, `i8`, `u16`, `i16`,
`u32`, `i32` (default for [`only_i32`]),
`u64`, `i64` _(default)_ | +| **Floating-point** (disabled with [`no_float`]) | `f32`, `f64` _(default)_ | +| **Character** | `char` | +| **Boolean** | `bool` | +| **Array** (disabled with [`no_index`]) | `rhai::Array` | +| **Dynamic** (i.e. can be anything) | `rhai::Dynamic` | +| **System** (current configuration) | `rhai::INT` (`i32` or `i64`),
`rhai::FLOAT` (`f32` or `f64`) | All types are treated strictly separate by Rhai, meaning that `i32` and `i64` and `u32` are completely different; you cannot even add them together. -The default integer type is `i64`. If you do not need any other integer type, you can enable the [`only_i64`](#optional-features) feature. +The default integer type is `i64`. If you do not need any other integer type, you can enable the [`only_i64`] feature. -If you only need 32-bit integers, you can enable the [`only_i32`](#optional-features) feature and remove support for all integer types other than `i32` including `i64`. +If you only need 32-bit integers, you can enable the [`only_i32`] feature and remove support for all integer types other than `i32` including `i64`. This is useful on 32-bit systems where using 64-bit integers incurs a performance penalty. -If you do not need floating-point, enable the [`no_float`](#optional-features) feature to remove support. +If you do not need floating-point, enable the [`no_float`] feature to remove support. Value conversions ----------------- @@ -303,17 +304,17 @@ use std::fmt::Display; use rhai::{Engine, RegisterFn}; -fn showit(x: &mut T) -> () { - println!("{}", x) +fn show_it(x: &mut T) -> () { + println!("put up a good show: {}!", x) } fn main() { let mut engine = Engine::new(); - engine.register_fn("print", showit as fn(x: &mut i64)->()); - engine.register_fn("print", showit as fn(x: &mut bool)->()); - engine.register_fn("print", showit as fn(x: &mut String)->()); + engine.register_fn("print", show_it as fn(x: &mut i64)->()); + engine.register_fn("print", show_it as fn(x: &mut bool)->()); + engine.register_fn("print", show_it as fn(x: &mut String)->()); } ``` @@ -615,24 +616,23 @@ Unary operators ```rust let number = -5; number = -5 - +5; -let booly = !true; +let boolean = !true; ``` Numeric functions ----------------- -The following standard functions (defined in the standard library but excluded if [`no_stdlib`](#optional-features)) operate on `i8`, `i16`, `i32`, `i64`, `f32` and `f64` only: +The following standard functions (defined in the standard library but excluded if [`no_stdlib`]) operate on `i8`, `i16`, `i32`, `i64`, `f32` and `f64` only: -| Function | Description | -| ---------- | ----------------------------------- | -| `abs` | absolute value | -| `to_int` | converts an `f32` or `f64` to `i64` | -| `to_float` | converts an integer type to `f64` | +| Function | Description | +| ---------- | --------------------------------- | +| `abs` | absolute value | +| `to_float` | converts an integer type to `f64` | Floating-point functions ------------------------ -The following standard functions (defined in the standard library but excluded if [`no_stdlib`](#optional-features)) operate on `f64` only: +The following standard functions (defined in the standard library but excluded if [`no_stdlib`]) operate on `f64` only: | Category | Functions | | ---------------- | ------------------------------------------------------------ | @@ -642,7 +642,8 @@ The following standard functions (defined in the standard library but excluded i | Exponential | `exp` (base _e_) | | Logarithmic | `ln` (base _e_), `log10` (base 10), `log` (any base) | | Rounding | `floor`, `ceiling`, `round`, `int`, `fraction` | -| Tests | `is_nan`, `is_finite`, `is_infinite` | +| Conversion | `to_int` | +| Testing | `is_nan`, `is_finite`, `is_infinite` | Strings and Chars ----------------- @@ -684,7 +685,7 @@ record[4] = '\x58'; // 0x58 = 'X' record == "Bob X. Davis: age 42 ❤\n"; ``` -The following standard functions (defined in the standard library but excluded if [`no_stdlib`](#optional-features)) operate on strings: +The following standard functions (defined in the standard library but excluded if [`no_stdlib`]) operate on strings: | Function | Description | | ---------- | ------------------------------------------------------------------------ | @@ -731,7 +732,7 @@ Arrays You can create arrays of values, and then access them with numeric indices. -The following functions (defined in the standard library but excluded if [`no_stdlib`](#optional-features)) operate on arrays: +The following functions (defined in the standard library but excluded if [`no_stdlib`]) operate on arrays: | Function | Description | | ---------- | ------------------------------------------------------------------------------------- | @@ -803,7 +804,7 @@ engine.register_fn("push", The type of a Rhai array is `rhai::Array`. `type_of()` returns `"array"`. -Arrays are disabled via the [`no_index`](#optional-features) feature. +Arrays are disabled via the [`no_index`] feature. Comparison operators -------------------- @@ -954,7 +955,7 @@ println!(result); // prints "Runtime error: 42 is too large! (line 5, position Functions --------- -Rhai supports defining functions in script: +Rhai supports defining functions in script (unless disabled with [`no_function`]): ```rust fn add(x, y) { @@ -1048,3 +1049,17 @@ for entry in log { println!("{}", entry); } ``` + +[ChaiScript]: http://chaiscript.com/ +[scripting languages for Rust]: https://github.com/rust-unofficial/awesome-rust#scripting +[awesome-rust]: https://github.com/rust-unofficial/awesome-rust + +[`num-traits`]: https://crates.io/crates/num-traits/ +[`debug_msgs`]: #optional-features +[`unchecked`]: #optional-features +[`no_stdlib`]: #optional-features +[`no_index`]: #optional-features +[`no_float`]: #optional-features +[`no_function`]: #optional-features +[`only_i32`]: #optional-features +[`only_i64`]: #optional-features diff --git a/src/api.rs b/src/api.rs index 9d309756..f5c7e0ce 100644 --- a/src/api.rs +++ b/src/api.rs @@ -168,17 +168,25 @@ impl<'e> Engine<'e> { retain_functions: bool, ast: &AST, ) -> Result { - let AST(statements, functions) = ast; + #[cfg(feature = "no_function")] + let AST(statements) = ast; - functions.iter().for_each(|f| { - engine.script_functions.insert( - FnSpec { - name: f.name.clone().into(), - args: None, - }, - Arc::new(FnIntExt::Int(f.clone())), - ); - }); + #[cfg(not(feature = "no_function"))] + let statements = { + let AST(statements, functions) = ast; + + functions.iter().for_each(|f| { + engine.script_functions.insert( + FnSpec { + name: f.name.clone().into(), + args: None, + }, + Arc::new(FnIntExt::Int(f.clone())), + ); + }); + + statements + }; let result = statements .iter() @@ -244,16 +252,26 @@ impl<'e> Engine<'e> { parse(&mut tokens.peekable(), self.optimize) .map_err(|err| EvalAltResult::ErrorParsing(err)) - .and_then(|AST(ref statements, ref functions)| { - for f in functions { - self.script_functions.insert( - FnSpec { - name: f.name.clone().into(), - args: None, - }, - Arc::new(FnIntExt::Int(f.clone())), - ); - } + .and_then(|ast| { + #[cfg(feature = "no_function")] + let AST(statements) = ast; + + #[cfg(not(feature = "no_function"))] + let statements = { + let AST(ref statements, ref functions) = ast; + + functions.iter().for_each(|f| { + self.script_functions.insert( + FnSpec { + name: f.name.clone().into(), + args: None, + }, + Arc::new(FnIntExt::Int(f.clone())), + ); + }); + + statements + }; let val = statements .iter() @@ -275,6 +293,7 @@ impl<'e> Engine<'e> { /// ```rust /// # fn main() -> Result<(), rhai::EvalAltResult> { /// # #[cfg(not(feature = "no_stdlib"))] + /// # #[cfg(not(feature = "no_function"))] /// # { /// use rhai::Engine; /// @@ -289,6 +308,7 @@ impl<'e> Engine<'e> { /// # Ok(()) /// # } /// ``` + #[cfg(not(feature = "no_function"))] pub fn call_fn( &mut self, name: &str, diff --git a/src/engine.rs b/src/engine.rs index c1130e14..62acd255 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -4,6 +4,8 @@ use crate::any::{Any, AnyExt, Dynamic, Variant}; use crate::parser::{Expr, FnDef, Position, Stmt}; use crate::result::EvalAltResult; use crate::scope::Scope; + +#[cfg(not(feature = "no_index"))] use crate::INT; use std::{ diff --git a/src/parser.rs b/src/parser.rs index cdb8bac9..5aec9d46 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -139,7 +139,10 @@ impl fmt::Debug for Position { } /// Compiled AST (abstract syntax tree) of a Rhai script. -pub struct AST(pub(crate) Vec, pub(crate) Vec>); +pub struct AST( + pub(crate) Vec, + #[cfg(not(feature = "no_function"))] pub(crate) Vec>, +); #[derive(Debug, Clone)] pub struct FnDef<'a> { @@ -1820,6 +1823,7 @@ fn parse_stmt<'a>(input: &mut Peekable>) -> Result(input: &mut Peekable>) -> Result, ParseError> { let pos = match input.next() { Some((_, tok_pos)) => tok_pos, @@ -1892,11 +1896,14 @@ fn parse_top_level<'a>( input: &mut Peekable>, optimize_ast: bool, ) -> Result { - let mut statements = Vec::new(); - let mut functions = Vec::new(); + let mut statements = Vec::::new(); + + #[cfg(not(feature = "no_function"))] + let mut functions = Vec::::new(); while input.peek().is_some() { match input.peek() { + #[cfg(not(feature = "no_function"))] Some(&(Token::Fn, _)) => functions.push(parse_fn(input)?), _ => statements.push(parse_stmt(input)?), } @@ -1910,6 +1917,7 @@ fn parse_top_level<'a>( return Ok(if optimize_ast { AST( optimize(statements), + #[cfg(not(feature = "no_function"))] functions .into_iter() .map(|mut fn_def| { @@ -1920,7 +1928,11 @@ fn parse_top_level<'a>( .collect(), ) } else { - AST(statements, functions) + AST( + statements, + #[cfg(not(feature = "no_function"))] + functions, + ) }); } diff --git a/tests/bool_op.rs b/tests/bool_op.rs index 7c1654bb..a7960476 100644 --- a/tests/bool_op.rs +++ b/tests/bool_op.rs @@ -39,10 +39,9 @@ fn test_bool_op_short_circuit() -> Result<(), EvalAltResult> { assert_eq!( engine.eval::( r" - fn this() { true } - fn that() { 9/0 } + let this = true; - this() || that(); + this || { throw; }; " )?, true @@ -51,10 +50,9 @@ fn test_bool_op_short_circuit() -> Result<(), EvalAltResult> { assert_eq!( engine.eval::( r" - fn this() { false } - fn that() { 9/0 } + let this = false; - this() && that(); + this && { throw; }; " )?, false @@ -72,10 +70,9 @@ fn test_bool_op_no_short_circuit1() { engine .eval::( r" - fn this() { false } - fn that() { 9/0 } + let this = true; - this() | that(); + this | { throw; } " ) .unwrap(), @@ -92,10 +89,9 @@ fn test_bool_op_no_short_circuit2() { engine .eval::( r" - fn this() { false } - fn that() { 9/0 } + let this = false; - this() & that(); + this & { throw; } " ) .unwrap(), diff --git a/tests/engine.rs b/tests/engine.rs index a01ae916..d795b35b 100644 --- a/tests/engine.rs +++ b/tests/engine.rs @@ -1,4 +1,5 @@ #![cfg(not(feature = "no_stdlib"))] +#![cfg(not(feature = "no_function"))] use rhai::{Engine, EvalAltResult, INT}; #[test] diff --git a/tests/internal_fn.rs b/tests/internal_fn.rs index 09bb357e..8e38f0d9 100644 --- a/tests/internal_fn.rs +++ b/tests/internal_fn.rs @@ -1,3 +1,5 @@ +#![cfg(not(feature = "no_function"))] + use rhai::{Engine, EvalAltResult, INT}; #[test] diff --git a/tests/not.rs b/tests/not.rs index 8bade80a..52913136 100644 --- a/tests/not.rs +++ b/tests/not.rs @@ -9,6 +9,7 @@ fn test_not() -> Result<(), EvalAltResult> { false ); + #[cfg(not(feature = "no_function"))] assert_eq!(engine.eval::("fn not(x) { !x } not(false)")?, true); // TODO - do we allow stacking unary operators directly? e.g '!!!!!!!true' diff --git a/tests/unary_minus.rs b/tests/unary_minus.rs index 8f5f6013..80170498 100644 --- a/tests/unary_minus.rs +++ b/tests/unary_minus.rs @@ -5,7 +5,10 @@ fn test_unary_minus() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); assert_eq!(engine.eval::("let x = -5; x")?, -5); + + #[cfg(not(feature = "no_function"))] assert_eq!(engine.eval::("fn neg(x) { -x } neg(5)")?, -5); + assert_eq!(engine.eval::("5 - -+++--+-5")?, 0); Ok(()) From defa29b28f49088eb34224fe6851c448ad0548f2 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 11 Mar 2020 14:29:21 +0800 Subject: [PATCH 09/24] Do not track .cargo. --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 1ec7ed7e..90e6863e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ target/ Cargo.lock -.vscode/ \ No newline at end of file +.vscode/ +.cargo/ From 952932f64cee5c62bc88c8adab06d8d03e13bac6 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 11 Mar 2020 23:43:04 +0800 Subject: [PATCH 10/24] Reduce cloning. --- src/api.rs | 31 ++----- src/engine.rs | 236 ++++++++++++++++++++++++-------------------------- src/parser.rs | 131 +++++++++++++++++----------- 3 files changed, 200 insertions(+), 198 deletions(-) diff --git a/src/api.rs b/src/api.rs index f5c7e0ce..c1c42f83 100644 --- a/src/api.rs +++ b/src/api.rs @@ -2,7 +2,7 @@ use crate::any::{Any, AnyExt, Dynamic}; use crate::call::FuncArgs; -use crate::engine::{Engine, FnAny, FnCallArgs, FnIntExt, FnSpec}; +use crate::engine::{Engine, FnAny, FnCallArgs, FnSpec}; use crate::error::ParseError; use crate::fn_register::RegisterFn; use crate::parser::{lex, parse, Position, AST}; @@ -12,7 +12,6 @@ use std::{ any::{type_name, TypeId}, fs::File, io::prelude::*, - sync::Arc, }; impl<'e> Engine<'e> { @@ -41,7 +40,7 @@ impl<'e> Engine<'e> { args, }; - self.ext_functions.insert(spec, Arc::new(FnIntExt::Ext(f))); + self.ext_functions.insert(spec, f); } /// Register a custom type for use with the `Engine`. @@ -63,7 +62,7 @@ impl<'e> Engine<'e> { where F: Fn(&Dynamic) -> Box> + 'static, { - self.type_iterators.insert(TypeId::of::(), Arc::new(f)); + self.type_iterators.insert(TypeId::of::(), Box::new(f)); } /// Register a getter function for a member of a registered type with the `Engine`. @@ -176,13 +175,7 @@ impl<'e> Engine<'e> { let AST(statements, functions) = ast; functions.iter().for_each(|f| { - engine.script_functions.insert( - FnSpec { - name: f.name.clone().into(), - args: None, - }, - Arc::new(FnIntExt::Int(f.clone())), - ); + engine.script_functions.push(f.clone()); }); statements @@ -261,13 +254,7 @@ impl<'e> Engine<'e> { let AST(ref statements, ref functions) = ast; functions.iter().for_each(|f| { - self.script_functions.insert( - FnSpec { - name: f.name.clone().into(), - args: None, - }, - Arc::new(FnIntExt::Int(f.clone())), - ); + self.script_functions.push(f.clone()); }); statements @@ -322,13 +309,7 @@ impl<'e> Engine<'e> { args: FnCallArgs, ) -> Result { ast.1.iter().for_each(|f| { - engine.script_functions.insert( - FnSpec { - name: f.name.clone().into(), - args: None, - }, - Arc::new(FnIntExt::Int(f.clone())), - ); + engine.script_functions.push(f.clone()); }); let result = engine.call_fn_raw(name, args, None, Position::none()); diff --git a/src/engine.rs b/src/engine.rs index 62acd255..7cf8b114 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1,7 +1,7 @@ //! Main module defining the script evaluation `Engine`. use crate::any::{Any, AnyExt, Dynamic, Variant}; -use crate::parser::{Expr, FnDef, Position, Stmt}; +use crate::parser::{Expr, FnDef, Position, ReturnType, Stmt}; use crate::result::EvalAltResult; use crate::scope::Scope; @@ -11,7 +11,6 @@ use crate::INT; use std::{ any::{type_name, TypeId}, borrow::Cow, - cmp::{PartialEq, PartialOrd}, collections::HashMap, iter::once, sync::Arc, @@ -34,7 +33,7 @@ pub(crate) const KEYWORD_TYPE_OF: &'static str = "type_of"; pub(crate) const FUNC_GETTER: &'static str = "get$"; pub(crate) const FUNC_SETTER: &'static str = "set$"; -#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] +#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)] #[cfg(not(feature = "no_index"))] enum IndexSourceType { Array, @@ -42,7 +41,7 @@ enum IndexSourceType { Expression, } -#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] +#[derive(Debug, Eq, PartialEq, Hash)] pub struct FnSpec<'a> { pub name: Cow<'a, str>, pub args: Option>, @@ -66,11 +65,11 @@ pub struct Engine<'e> { /// Optimize the AST after compilation pub(crate) optimize: bool, /// A hashmap containing all compiled functions known to the engine - pub(crate) ext_functions: HashMap, Arc>>, + pub(crate) ext_functions: HashMap, Box>, /// A hashmap containing all script-defined functions - pub(crate) script_functions: HashMap, Arc>>, + pub(crate) script_functions: Vec>, /// A hashmap containing all iterators known to the engine - pub(crate) type_iterators: HashMap>, + pub(crate) type_iterators: HashMap>, pub(crate) type_names: HashMap, // Closures for implementing the print/debug commands @@ -78,11 +77,6 @@ pub struct Engine<'e> { pub(crate) on_debug: Box, } -pub enum FnIntExt<'a> { - Ext(Box), - Int(FnDef<'a>), -} - impl Engine<'_> { /// Create a new `Engine` pub fn new() -> Self { @@ -101,7 +95,7 @@ impl Engine<'_> { let mut engine = Engine { optimize: true, ext_functions: HashMap::new(), - script_functions: HashMap::new(), + script_functions: Vec::new(), type_iterators: HashMap::new(), type_names, on_print: Box::new(default_print), // default print/debug implementations @@ -140,113 +134,112 @@ impl Engine<'_> { .join(", ") ); - let mut spec = FnSpec { + // First search in script-defined functions (can override built-in) + if let Some(func) = self + .script_functions + .iter() + .rev() + .find(|fn_def| fn_def.name == fn_name) + .map(|fn_def| fn_def.clone()) + { + // First check number of parameters + if func.params.len() != args.len() { + return Err(EvalAltResult::ErrorFunctionArgsMismatch( + fn_name.into(), + func.params.len(), + args.len(), + pos, + )); + } + + let mut scope = Scope::new(); + + scope.extend( + // Put arguments into scope as variables + func.params + .iter() + .zip(args.iter().map(|x| (*x).into_dynamic())), + ); + + // Evaluate + return match self.eval_stmt(&mut scope, &func.body) { + // Convert return statement to return value + Err(EvalAltResult::Return(x, _)) => Ok(x), + other => other, + }; + } + + let spec = FnSpec { name: fn_name.into(), - args: None, + args: Some(args.iter().map(|a| Any::type_id(&**a)).collect()), }; - // First search in script-defined functions (can override built-in), - // then built-in's and external functions - let fn_def = self - .script_functions - .get(&spec) - .or_else(|| { - spec.args = Some(args.iter().map(|a| Any::type_id(&**a)).collect()); - self.ext_functions.get(&spec) - }) - .map(|f| f.clone()); + // Then search built-in's and external functions + if let Some(func) = self.ext_functions.get(&spec) { + // Run external function + let result = func(args, pos)?; - if let Some(f) = fn_def { - match *f { - // Run external function - FnIntExt::Ext(ref func) => { - let result = func(args, pos)?; + // See if the function match print/debug (which requires special processing) + let callback = match spec.name.as_ref() { + KEYWORD_PRINT => self.on_print.as_mut(), + KEYWORD_DEBUG => self.on_debug.as_mut(), + _ => return Ok(result), + }; - // See if the function match print/debug (which requires special processing) - let callback = match spec.name.as_ref() { - KEYWORD_PRINT => self.on_print.as_mut(), - KEYWORD_DEBUG => self.on_debug.as_mut(), - _ => return Ok(result), - }; + let val = &result + .downcast::() + .map(|s| *s) + .unwrap_or("error: not a string".into()); - let val = &result - .downcast::() - .map(|s| *s) - .unwrap_or("error: not a string".into()); + return Ok(callback(val).into_dynamic()); + } - Ok(callback(val).into_dynamic()) - } - - // Run script-defined function - FnIntExt::Int(ref func) => { - // First check number of parameters - if func.params.len() != args.len() { - return Err(EvalAltResult::ErrorFunctionArgsMismatch( - spec.name.into(), - func.params.len(), - args.len(), - pos, - )); - } - - let mut scope = Scope::new(); - - scope.extend( - // Put arguments into scope as variables - func.params - .iter() - .cloned() - .zip(args.iter().map(|x| (*x).into_dynamic())), - ); - - // Evaluate - match self.eval_stmt(&mut scope, &func.body) { - // Convert return statement to return value - Err(EvalAltResult::Return(x, _)) => Ok(x), - other => other, - } - } - } - } else if spec.name == KEYWORD_TYPE_OF && args.len() == 1 { + if spec.name == KEYWORD_TYPE_OF && args.len() == 1 { // Handle `type_of` function - Ok(self + return Ok(self .map_type_name(args[0].type_name()) .to_string() - .into_dynamic()) - } else if spec.name.starts_with(FUNC_GETTER) { + .into_dynamic()); + } + + if spec.name.starts_with(FUNC_GETTER) { // Getter function not found - Err(EvalAltResult::ErrorDotExpr( + return Err(EvalAltResult::ErrorDotExpr( format!( "- property '{}' unknown or write-only", &spec.name[FUNC_GETTER.len()..] ), pos, - )) - } else if spec.name.starts_with(FUNC_SETTER) { + )); + } + + if spec.name.starts_with(FUNC_SETTER) { // Setter function not found - Err(EvalAltResult::ErrorDotExpr( + return Err(EvalAltResult::ErrorDotExpr( format!( "- property '{}' unknown or read-only", &spec.name[FUNC_SETTER.len()..] ), pos, - )) - } else if let Some(val) = def_val { - // Return default value - Ok(val.clone()) - } else { - // Raise error - let types_list = args - .iter() - .map(|x| (*x).type_name()) - .map(|name| self.map_type_name(name)) - .collect::>(); - - Err(EvalAltResult::ErrorFunctionNotFound( - format!("{} ({})", spec.name, types_list.join(", ")), - pos, - )) + )); } + + if let Some(val) = def_val { + // Return default value + return Ok(val.clone()); + } + + // Raise error + let types_list = args + .iter() + .map(|x| (*x).type_name()) + .map(|name| self.map_type_name(name)) + .collect::>(); + + Err(EvalAltResult::ErrorFunctionNotFound( + format!("{} ({})", spec.name, types_list.join(", ")), + pos, + )) } /// Chain-evaluate a dot setter @@ -281,7 +274,7 @@ impl Engine<'_> { // xxx.idx_lhs[idx_expr] #[cfg(not(feature = "no_index"))] Expr::Index(idx_lhs, idx_expr, idx_pos) => { - let (expr, _) = match idx_lhs.as_ref() { + let (val, _) = match idx_lhs.as_ref() { // xxx.id[idx_expr] Expr::Identifier(id, pos) => { let get_fn_name = format!("{}{}", FUNC_GETTER, id); @@ -304,7 +297,7 @@ impl Engine<'_> { }; let idx = self.eval_index_value(scope, idx_expr)?; - self.get_indexed_value(expr, idx, idx_expr.position(), *idx_pos) + self.get_indexed_value(&val, idx, idx_expr.position(), *idx_pos) .map(|(v, _)| v) } @@ -320,7 +313,7 @@ impl Engine<'_> { // xxx.idx_lhs[idx_expr].rhs #[cfg(not(feature = "no_index"))] Expr::Index(idx_lhs, idx_expr, idx_pos) => { - let (expr, _) = match idx_lhs.as_ref() { + let (val, _) = match idx_lhs.as_ref() { // xxx.id[idx_expr].rhs Expr::Identifier(id, pos) => { let get_fn_name = format!("{}{}", FUNC_GETTER, id); @@ -343,7 +336,7 @@ impl Engine<'_> { }; let idx = self.eval_index_value(scope, idx_expr)?; - self.get_indexed_value(expr, idx, idx_expr.position(), *idx_pos) + self.get_indexed_value(&val, idx, idx_expr.position(), *idx_pos) .and_then(|(mut v, _)| self.get_dot_val_helper(scope, v.as_mut(), rhs)) } // Syntax error @@ -441,14 +434,14 @@ impl Engine<'_> { #[cfg(not(feature = "no_index"))] fn get_indexed_value( &self, - val: Dynamic, + val: &Dynamic, idx: INT, val_pos: Position, idx_pos: Position, ) -> Result<(Dynamic, IndexSourceType), EvalAltResult> { if val.is::() { // val_array[idx] - let arr = val.downcast::().expect("array expected"); + let arr = val.downcast_ref::().expect("array expected"); if idx >= 0 { arr.get(idx as usize) @@ -460,7 +453,7 @@ impl Engine<'_> { } } else if val.is::() { // val_string[idx] - let s = val.downcast::().expect("string expected"); + let s = val.downcast_ref::().expect("string expected"); if idx >= 0 { s.chars() @@ -501,7 +494,7 @@ impl Engine<'_> { Expr::Identifier(id, _) => Self::search_scope( scope, &id, - |val| self.get_indexed_value(val, idx, idx_expr.position(), idx_pos), + |val| self.get_indexed_value(&val, idx, idx_expr.position(), idx_pos), lhs.position(), ) .map(|(src_idx, (val, src_type))| { @@ -512,7 +505,7 @@ impl Engine<'_> { expr => { let val = self.eval_expr(scope, expr)?; - self.get_indexed_value(val, idx, idx_expr.position(), idx_pos) + self.get_indexed_value(&val, idx, idx_expr.position(), idx_pos) .map(|(v, _)| (IndexSourceType::Expression, None, idx as usize, v)) } } @@ -663,12 +656,8 @@ impl Engine<'_> { self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos) .and_then(|v| { let idx = self.eval_index_value(scope, idx_expr)?; - let (mut target, _) = self.get_indexed_value( - v.clone(), // TODO - Avoid cloning this - idx, - idx_expr.position(), - *idx_pos, - )?; + let (mut target, _) = + self.get_indexed_value(&v, idx, idx_expr.position(), *idx_pos)?; self.set_dot_val_helper( scope, @@ -1042,22 +1031,22 @@ impl Engine<'_> { Stmt::Break(_) => Err(EvalAltResult::LoopBreak), // Empty return - Stmt::ReturnWithVal(None, true, pos) => { + Stmt::ReturnWithVal(None, ReturnType::Return, pos) => { Err(EvalAltResult::Return(().into_dynamic(), *pos)) } // Return value - Stmt::ReturnWithVal(Some(a), true, pos) => { + Stmt::ReturnWithVal(Some(a), ReturnType::Return, pos) => { Err(EvalAltResult::Return(self.eval_expr(scope, a)?, *pos)) } // Empty throw - Stmt::ReturnWithVal(None, false, pos) => { + Stmt::ReturnWithVal(None, ReturnType::Exception, pos) => { Err(EvalAltResult::ErrorRuntime("".into(), *pos)) } // Throw value - Stmt::ReturnWithVal(Some(a), false, pos) => { + Stmt::ReturnWithVal(Some(a), ReturnType::Exception, pos) => { let val = self.eval_expr(scope, a)?; Err(EvalAltResult::ErrorRuntime( val.downcast::() @@ -1068,13 +1057,14 @@ impl Engine<'_> { } // Let statement - Stmt::Let(name, init, _) => { - if let Some(v) = init { - let val = self.eval_expr(scope, v)?; - scope.push_dynamic(name.clone(), val); - } else { - scope.push(name.clone(), ()); - } + Stmt::Let(name, Some(expr), _) => { + let val = self.eval_expr(scope, expr)?; + scope.push_dynamic(name.clone(), val); + Ok(().into_dynamic()) + } + + Stmt::Let(name, None, _) => { + scope.push(name.clone(), ()); Ok(().into_dynamic()) } } diff --git a/src/parser.rs b/src/parser.rs index 5aec9d46..45ecc42b 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -4,7 +4,7 @@ use crate::any::Dynamic; use crate::error::{LexError, ParseError, ParseErrorType}; use crate::optimize::optimize; -use std::{borrow::Cow, char, fmt, iter::Peekable, str::Chars, str::FromStr, usize}; +use std::{borrow::Cow, char, fmt, iter::Peekable, str::Chars, str::FromStr, sync::Arc, usize}; /// The system integer type. /// @@ -139,19 +139,26 @@ impl fmt::Debug for Position { } /// Compiled AST (abstract syntax tree) of a Rhai script. +#[derive(Debug, Clone)] pub struct AST( pub(crate) Vec, - #[cfg(not(feature = "no_function"))] pub(crate) Vec>, + #[cfg(not(feature = "no_function"))] pub(crate) Vec>, ); -#[derive(Debug, Clone)] -pub struct FnDef<'a> { - pub name: Cow<'a, str>, - pub params: Vec>, +#[derive(Debug)] // Do not derive Clone because it is expensive +pub struct FnDef { + pub name: String, + pub params: Vec, pub body: Stmt, pub pos: Position, } +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] +pub enum ReturnType { + Return, + Exception, +} + #[derive(Debug, Clone)] pub enum Stmt { Noop(Position), @@ -163,7 +170,7 @@ pub enum Stmt { Block(Vec, Position), Expr(Box), Break(Position), - ReturnWithVal(Option>, bool, Position), + ReturnWithVal(Option>, ReturnType, Position), } impl Stmt { @@ -173,6 +180,25 @@ impl Stmt { _ => true, } } + + pub fn is_var(&self) -> bool { + match self { + Stmt::Let(_, _, _) => true, + _ => false, + } + } + + pub fn position(&self) -> Position { + match self { + Stmt::Noop(pos) + | Stmt::Let(_, _, pos) + | Stmt::Block(_, pos) + | Stmt::Break(pos) + | Stmt::ReturnWithVal(_, _, pos) => *pos, + Stmt::IfElse(expr, _, _) | Stmt::Expr(expr) => expr.position(), + Stmt::While(_, stmt) | Stmt::Loop(stmt) | Stmt::For(_, _, stmt) => stmt.position(), + } + } } #[derive(Debug, Clone)] @@ -221,9 +247,13 @@ impl Expr { } } - pub fn is_constant(&self) -> bool { + /// Is this expression pure? + /// + /// A pure expression has no side effects. + pub fn is_pure(&self) -> bool { match self { - Expr::IntegerConstant(_, _) + Expr::Identifier(_, _) + | Expr::IntegerConstant(_, _) | Expr::CharConstant(_, _) | Expr::StringConstant(_, _) | Expr::True(_) @@ -233,6 +263,8 @@ impl Expr { #[cfg(not(feature = "no_float"))] Expr::FloatConstant(_, _) => true, + Expr::Array(expressions, _) => expressions.iter().all(Expr::is_pure), + _ => false, } } @@ -1425,17 +1457,16 @@ fn parse_unary<'a>(input: &mut Peekable>) -> Result Ok(i - .checked_neg() - .map(|x| Expr::IntegerConstant(x, pos)) - .unwrap_or_else(|| Expr::FloatConstant(-(i as FLOAT), pos))), - - // Negative integer - #[cfg(feature = "no_float")] Ok(Expr::IntegerConstant(i, _)) => i .checked_neg() .map(|x| Expr::IntegerConstant(x, pos)) + .or_else(|| { + #[cfg(not(feature = "no_float"))] + return Some(Expr::FloatConstant(-(i as FLOAT), pos)); + + #[cfg(feature = "no_float")] + return None; + }) .ok_or_else(|| { ParseError::new( PERR::BadInput(LERR::MalformedNumber(format!("-{}", i)).to_string()), @@ -1485,10 +1516,11 @@ fn parse_assignment(lhs: Expr, rhs: Expr, pos: Position) -> Result valid_assignment_chain(dot_rhs), #[cfg(not(feature = "no_index"))] - Expr::Index(idx_lhs, _, _) => match idx_lhs.as_ref() { - Expr::Identifier(_, _) => valid_assignment_chain(dot_rhs), - _ => (false, idx_lhs.position()), - }, + Expr::Index(idx_lhs, _, _) if idx_lhs.is_identifier() => { + valid_assignment_chain(dot_rhs) + } + #[cfg(not(feature = "no_index"))] + Expr::Index(idx_lhs, _, _) => (false, idx_lhs.position()), _ => (false, dot_lhs.position()), }, @@ -1497,7 +1529,7 @@ fn parse_assignment(lhs: Expr, rhs: Expr, pos: Position) -> Result Ok(Expr::Assignment(Box::new(lhs), Box::new(rhs), pos)), @@ -1797,23 +1829,23 @@ fn parse_stmt<'a>(input: &mut Peekable>) -> Result { - let is_return = match token { - Token::Return => true, - Token::Throw => false, - _ => panic!(), + let return_type = match token { + Token::Return => ReturnType::Return, + Token::Throw => ReturnType::Exception, + _ => panic!("unexpected token!"), }; input.next(); match input.peek() { // return; or throw; - Some(&(Token::SemiColon, pos)) => Ok(Stmt::ReturnWithVal(None, is_return, pos)), + Some(&(Token::SemiColon, pos)) => Ok(Stmt::ReturnWithVal(None, return_type, pos)), // Just a return/throw without anything at the end of script - None => Ok(Stmt::ReturnWithVal(None, is_return, Position::eof())), + None => Ok(Stmt::ReturnWithVal(None, return_type, Position::eof())), // return or throw with expression Some(&(_, pos)) => { let ret = parse_expr(input)?; - Ok(Stmt::ReturnWithVal(Some(Box::new(ret)), is_return, pos)) + Ok(Stmt::ReturnWithVal(Some(Box::new(ret)), return_type, pos)) } } } @@ -1824,7 +1856,7 @@ fn parse_stmt<'a>(input: &mut Peekable>) -> Result(input: &mut Peekable>) -> Result, ParseError> { +fn parse_fn<'a>(input: &mut Peekable>) -> Result { let pos = match input.next() { Some((_, tok_pos)) => tok_pos, _ => return Err(ParseError::new(PERR::InputPastEndOfFile, Position::eof())), @@ -1885,7 +1917,7 @@ fn parse_fn<'a>(input: &mut Peekable>) -> Result( } } - return Ok(if optimize_ast { - AST( - optimize(statements), - #[cfg(not(feature = "no_function"))] - functions - .into_iter() - .map(|mut fn_def| { + return Ok(AST( + if optimize_ast { + optimize(statements) + } else { + statements + }, + #[cfg(not(feature = "no_function"))] + functions + .into_iter() + .map(|mut fn_def| { + if optimize_ast { + let pos = fn_def.body.position(); let mut body = optimize(vec![fn_def.body]); - fn_def.body = body.pop().unwrap(); - fn_def - }) - .collect(), - ) - } else { - AST( - statements, - #[cfg(not(feature = "no_function"))] - functions, - ) - }); + fn_def.body = body.pop().unwrap_or_else(|| Stmt::Noop(pos)); + } + Arc::new(fn_def) + }) + .collect(), + )); } pub fn parse<'a>( From a02c0cfaa039ab19896aa66fd419a5d875099863 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 11 Mar 2020 23:43:10 +0800 Subject: [PATCH 11/24] More optimizations. --- src/optimize.rs | 152 ++++++++++++++++++++++++++++++------------------ 1 file changed, 95 insertions(+), 57 deletions(-) diff --git a/src/optimize.rs b/src/optimize.rs index cb92fb11..046dfdb4 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -1,27 +1,28 @@ +use crate::engine::KEYWORD_DUMP_AST; use crate::parser::{Expr, Stmt}; -fn optimize_stmt(stmt: Stmt, changed: &mut bool) -> Stmt { +fn optimize_stmt(stmt: Stmt, changed: &mut bool, preserve_result: bool) -> Stmt { match stmt { Stmt::IfElse(expr, stmt1, None) => match *expr { Expr::False(pos) => { *changed = true; Stmt::Noop(pos) } - Expr::True(_) => optimize_stmt(*stmt1, changed), + Expr::True(_) => optimize_stmt(*stmt1, changed, true), expr => Stmt::IfElse( Box::new(optimize_expr(expr, changed)), - Box::new(optimize_stmt(*stmt1, changed)), + Box::new(optimize_stmt(*stmt1, changed, true)), None, ), }, Stmt::IfElse(expr, stmt1, Some(stmt2)) => match *expr { - Expr::False(_) => optimize_stmt(*stmt2, changed), - Expr::True(_) => optimize_stmt(*stmt1, changed), + Expr::False(_) => optimize_stmt(*stmt2, changed, true), + Expr::True(_) => optimize_stmt(*stmt1, changed, true), expr => Stmt::IfElse( Box::new(optimize_expr(expr, changed)), - Box::new(optimize_stmt(*stmt1, changed)), - Some(Box::new(optimize_stmt(*stmt2, changed))), + Box::new(optimize_stmt(*stmt1, changed, true)), + Some(Box::new(optimize_stmt(*stmt2, changed, true))), ), }, @@ -30,18 +31,18 @@ fn optimize_stmt(stmt: Stmt, changed: &mut bool) -> Stmt { *changed = true; Stmt::Noop(pos) } - Expr::True(_) => Stmt::Loop(Box::new(optimize_stmt(*stmt, changed))), + Expr::True(_) => Stmt::Loop(Box::new(optimize_stmt(*stmt, changed, false))), expr => Stmt::While( Box::new(optimize_expr(expr, changed)), - Box::new(optimize_stmt(*stmt, changed)), + Box::new(optimize_stmt(*stmt, changed, false)), ), }, - Stmt::Loop(stmt) => Stmt::Loop(Box::new(optimize_stmt(*stmt, changed))), + Stmt::Loop(stmt) => Stmt::Loop(Box::new(optimize_stmt(*stmt, changed, false))), Stmt::For(id, expr, stmt) => Stmt::For( id, Box::new(optimize_expr(*expr, changed)), - Box::new(optimize_stmt(*stmt, changed)), + Box::new(optimize_stmt(*stmt, changed, false)), ), Stmt::Let(id, Some(expr), pos) => { Stmt::Let(id, Some(Box::new(optimize_expr(*expr, changed))), pos) @@ -53,42 +54,67 @@ fn optimize_stmt(stmt: Stmt, changed: &mut bool) -> Stmt { let mut result: Vec<_> = statements .into_iter() // For each statement - .map(|s| optimize_stmt(s, changed)) // Optimize the statement - .filter(Stmt::is_op) // Remove no-op's + .rev() // Scan in reverse + .map(|s| optimize_stmt(s, changed, preserve_result)) // Optimize the statement + .enumerate() + .filter(|(i, s)| s.is_op() || (preserve_result && *i == 0)) // Remove no-op's but leave the last one if we need the result + .map(|(_, s)| s) + .rev() .collect(); - if let Some(last_stmt) = result.pop() { - // Remove all raw expression statements that evaluate to constants - // except for the very last statement - result.retain(|stmt| match stmt { - Stmt::Expr(expr) if expr.is_constant() || expr.is_identifier() => false, - _ => true, - }); + // Remove all raw expression statements that are pure except for the very last statement + let last_stmt = if preserve_result { result.pop() } else { None }; - result.push(last_stmt); + result.retain(|stmt| match stmt { + Stmt::Expr(expr) if expr.is_pure() => false, + _ => true, + }); + + if let Some(stmt) = last_stmt { + result.push(stmt); + } + + // Remove all let statements at the end of a block - the new variables will go away anyway. + // But be careful only remove ones that have no initial values or have values that are pure expressions, + // otherwise there may be side effects. + let mut removed = false; + + while let Some(expr) = result.pop() { + match expr { + Stmt::Let(_, None, _) => removed = true, + Stmt::Let(_, Some(val_expr), _) if val_expr.is_pure() => removed = true, + + _ => { + result.push(expr); + break; + } + } + } + + if preserve_result { + if removed { + result.push(Stmt::Noop(pos)) + } + + result = result + .into_iter() + .rev() + .enumerate() + .map(|(i, s)| optimize_stmt(s, changed, i == 0)) // Optimize all other statements again + .rev() + .collect(); } *changed = *changed || original_len != result.len(); match result[..] { + // No statements in block - change to No-op [] => { - // No statements in block - change to No-op *changed = true; Stmt::Noop(pos) } - [Stmt::Let(_, None, _)] => { - // Only one empty variable declaration - change to No-op - *changed = true; - Stmt::Noop(pos) - } - [Stmt::Let(_, Some(_), _)] => { - // Only one let statement, but cannot promote - // (otherwise the variable gets declared in the scope above) - // and still need to run just in case there are side effects - Stmt::Block(result, pos) - } + // Only one statement - promote [_] => { - // Only one statement - promote *changed = true; result.remove(0) } @@ -103,26 +129,14 @@ fn optimize_stmt(stmt: Stmt, changed: &mut bool) -> Stmt { is_return, pos, ), - stmt @ Stmt::ReturnWithVal(None, _, _) => stmt, - stmt @ Stmt::Noop(_) | stmt @ Stmt::Break(_) => stmt, + stmt => stmt, } } fn optimize_expr(expr: Expr, changed: &mut bool) -> Expr { match expr { - Expr::IntegerConstant(_, _) - | Expr::Identifier(_, _) - | Expr::CharConstant(_, _) - | Expr::StringConstant(_, _) - | Expr::True(_) - | Expr::False(_) - | Expr::Unit(_) => expr, - - #[cfg(not(feature = "no_float"))] - Expr::FloatConstant(_, _) => expr, - - Expr::Stmt(stmt, pos) => match optimize_stmt(*stmt, changed) { + Expr::Stmt(stmt, pos) => match optimize_stmt(*stmt, changed, true) { Stmt::Noop(_) => { *changed = true; Expr::Unit(pos) @@ -145,11 +159,10 @@ fn optimize_expr(expr: Expr, changed: &mut bool) -> Expr { #[cfg(not(feature = "no_index"))] Expr::Index(lhs, rhs, pos) => match (*lhs, *rhs) { (Expr::Array(mut items, _), Expr::IntegerConstant(i, _)) - if i >= 0 - && (i as usize) < items.len() - && !items.iter().any(|x| x.is_constant() || x.is_identifier()) => + if i >= 0 && (i as usize) < items.len() && items.iter().all(|x| x.is_pure()) => { - // Array where everything is a constant or identifier - promote the item + // Array where everything is a pure - promote the indexed item. + // All other items can be thrown away. *changed = true; items.remove(i as usize) } @@ -215,6 +228,10 @@ fn optimize_expr(expr: Expr, changed: &mut bool) -> Expr { Box::new(optimize_expr(rhs, changed)), ), }, + + // Expr::FunctionCall(id, args, def_value, pos) if id == KEYWORD_DUMP_AST => { + // Expr::FunctionCall(id, args, def_value, pos) + // } Expr::FunctionCall(id, args, def_value, pos) => { let original_len = args.len(); @@ -227,17 +244,29 @@ fn optimize_expr(expr: Expr, changed: &mut bool) -> Expr { Expr::FunctionCall(id, args, def_value, pos) } + + expr => expr, } } -pub(crate) fn optimize(mut statements: Vec) -> Vec { +pub(crate) fn optimize(statements: Vec) -> Vec { + let mut result = statements; + loop { let mut changed = false; - statements = statements + result = result .into_iter() - .map(|stmt| optimize_stmt(stmt, &mut changed)) - .filter(Stmt::is_op) + .rev() // Scan in reverse + .enumerate() + .map(|(i, stmt)| { + // Keep all variable declarations at this level + let keep = stmt.is_var(); + + // Always keep the last return value + optimize_stmt(stmt, &mut changed, keep || i == 0) + }) + .rev() .collect(); if !changed { @@ -245,5 +274,14 @@ pub(crate) fn optimize(mut statements: Vec) -> Vec { } } - statements + // Eliminate No-op's but always keep the last statement + let last_stmt = result.pop(); + + result.retain(Stmt::is_op); // Remove all No-op's + + if let Some(stmt) = last_stmt { + result.push(stmt); // Add back the last statement + } + + result } From 0d1a24029266d84263855e9f3480193cce1eb32b Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 12 Mar 2020 12:32:26 +0800 Subject: [PATCH 12/24] Expand range function to cover all integer types. --- src/builtin.rs | 362 +++++++++++++++++++++++++++---------------------- 1 file changed, 197 insertions(+), 165 deletions(-) diff --git a/src/builtin.rs b/src/builtin.rs index 04a290ce..da139f60 100644 --- a/src/builtin.rs +++ b/src/builtin.rs @@ -47,66 +47,6 @@ macro_rules! reg_op_result1 { ) } -macro_rules! reg_un { - ($self:expr, $x:expr, $op:expr, $( $y:ty ),*) => ( - $( - $self.register_fn($x, $op as fn(x: $y)->$y); - )* - ) -} - -#[cfg(not(feature = "unchecked"))] -macro_rules! reg_un_result { - ($self:expr, $x:expr, $op:expr, $( $y:ty ),*) => ( - $( - $self.register_result_fn($x, $op as fn(x: $y)->Result<$y,EvalAltResult>); - )* - ) -} -macro_rules! reg_cmp { - ($self:expr, $x:expr, $op:expr, $( $y:ty ),*) => ( - $( - $self.register_fn($x, $op as fn(x: $y, y: $y)->bool); - )* - ) -} - -macro_rules! reg_func1 { - ($self:expr, $x:expr, $op:expr, $r:ty, $( $y:ty ),*) => ( - $( - $self.register_fn($x, $op as fn(x: $y)->$r); - )* - ) -} - -#[cfg(not(feature = "no_stdlib"))] -macro_rules! reg_func2x { - ($self:expr, $x:expr, $op:expr, $v:ty, $r:ty, $( $y:ty ),*) => ( - $( - $self.register_fn($x, $op as fn(x: $v, y: $y)->$r); - )* - ) -} - -#[cfg(not(feature = "no_stdlib"))] -macro_rules! reg_func2y { - ($self:expr, $x:expr, $op:expr, $v:ty, $r:ty, $( $y:ty ),*) => ( - $( - $self.register_fn($x, $op as fn(y: $y, x: $v)->$r); - )* - ) -} - -#[cfg(not(feature = "no_stdlib"))] -#[cfg(not(feature = "no_index"))] -macro_rules! reg_func3 { - ($self:expr, $x:expr, $op:expr, $v:ty, $w:ty, $r:ty, $( $y:ty ),*) => ( - $( - $self.register_fn($x, $op as fn(x: $v, y: $w, z: $y)->$r); - )* - ) -} - impl Engine<'_> { /// Register the core built-in library. pub(crate) fn register_core_lib(&mut self) { @@ -410,32 +350,42 @@ impl Engine<'_> { reg_op!(self, "/", div_u, f32, f64); } - reg_cmp!(self, "<", lt, INT, String, char); - reg_cmp!(self, "<=", lte, INT, String, char); - reg_cmp!(self, ">", gt, INT, String, char); - reg_cmp!(self, ">=", gte, INT, String, char); - reg_cmp!(self, "==", eq, INT, String, char, bool); - reg_cmp!(self, "!=", ne, INT, String, char, bool); - - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] { - reg_cmp!(self, "<", lt, i8, u8, i16, u16, i32, i64, u32, u64); - reg_cmp!(self, "<=", lte, i8, u8, i16, u16, i32, i64, u32, u64); - reg_cmp!(self, ">", gt, i8, u8, i16, u16, i32, i64, u32, u64); - reg_cmp!(self, ">=", gte, i8, u8, i16, u16, i32, i64, u32, u64); - reg_cmp!(self, "==", eq, i8, u8, i16, u16, i32, i64, u32, u64); - reg_cmp!(self, "!=", ne, i8, u8, i16, u16, i32, i64, u32, u64); - } + macro_rules! reg_cmp { + ($self:expr, $x:expr, $op:expr, $( $y:ty ),*) => ( + $( + $self.register_fn($x, $op as fn(x: $y, y: $y)->bool); + )* + ) + } - #[cfg(not(feature = "no_float"))] - { - reg_cmp!(self, "<", lt, f32, f64); - reg_cmp!(self, "<=", lte, f32, f64); - reg_cmp!(self, ">", gt, f32, f64); - reg_cmp!(self, ">=", gte, f32, f64); - reg_cmp!(self, "==", eq, f32, f64); - reg_cmp!(self, "!=", ne, f32, f64); + reg_cmp!(self, "<", lt, INT, String, char); + reg_cmp!(self, "<=", lte, INT, String, char); + reg_cmp!(self, ">", gt, INT, String, char); + reg_cmp!(self, ">=", gte, INT, String, char); + reg_cmp!(self, "==", eq, INT, String, char, bool); + reg_cmp!(self, "!=", ne, INT, String, char, bool); + + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + reg_cmp!(self, "<", lt, i8, u8, i16, u16, i32, i64, u32, u64); + reg_cmp!(self, "<=", lte, i8, u8, i16, u16, i32, i64, u32, u64); + reg_cmp!(self, ">", gt, i8, u8, i16, u16, i32, i64, u32, u64); + reg_cmp!(self, ">=", gte, i8, u8, i16, u16, i32, i64, u32, u64); + reg_cmp!(self, "==", eq, i8, u8, i16, u16, i32, i64, u32, u64); + reg_cmp!(self, "!=", ne, i8, u8, i16, u16, i32, i64, u32, u64); + } + + #[cfg(not(feature = "no_float"))] + { + reg_cmp!(self, "<", lt, f32, f64); + reg_cmp!(self, "<=", lte, f32, f64); + reg_cmp!(self, ">", gt, f32, f64); + reg_cmp!(self, ">=", gte, f32, f64); + reg_cmp!(self, "==", eq, f32, f64); + reg_cmp!(self, "!=", ne, f32, f64); + } } //reg_op!(self, "||", or, bool); @@ -507,39 +457,58 @@ impl Engine<'_> { self.register_fn("~", pow_f_i); } - #[cfg(not(feature = "unchecked"))] { - reg_un_result!(self, "-", neg, INT); - reg_un_result!(self, "abs", abs, INT); - - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { - reg_un_result!(self, "-", neg, i8, i16, i32, i64); - reg_un_result!(self, "abs", abs, i8, i16, i32, i64); + macro_rules! reg_un { + ($self:expr, $x:expr, $op:expr, $( $y:ty ),*) => ( + $( + $self.register_fn($x, $op as fn(x: $y)->$y); + )* + ) } - } - #[cfg(feature = "unchecked")] - { - reg_un!(self, "-", neg_u, INT); - reg_un!(self, "abs", abs_u, INT); - - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { - reg_un!(self, "-", neg_u, i8, i16, i32, i64); - reg_un!(self, "abs", abs_u, i8, i16, i32, i64); + #[cfg(not(feature = "unchecked"))] + macro_rules! reg_un_result { + ($self:expr, $x:expr, $op:expr, $( $y:ty ),*) => ( + $( + $self.register_result_fn($x, $op as fn(x: $y)->Result<$y,EvalAltResult>); + )* + ) } - } - #[cfg(not(feature = "no_float"))] - { - reg_un!(self, "-", neg_u, f32, f64); - reg_un!(self, "abs", abs_u, f32, f64); - } + #[cfg(not(feature = "unchecked"))] + { + reg_un_result!(self, "-", neg, INT); + reg_un_result!(self, "abs", abs, INT); - reg_un!(self, "!", not, bool); + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + reg_un_result!(self, "-", neg, i8, i16, i32, i64); + reg_un_result!(self, "abs", abs, i8, i16, i32, i64); + } + } + + #[cfg(feature = "unchecked")] + { + reg_un!(self, "-", neg_u, INT); + reg_un!(self, "abs", abs_u, INT); + + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + reg_un!(self, "-", neg_u, i8, i16, i32, i64); + reg_un!(self, "abs", abs_u, i8, i16, i32, i64); + } + } + + #[cfg(not(feature = "no_float"))] + { + reg_un!(self, "-", neg_u, f32, f64); + reg_un!(self, "abs", abs_u, f32, f64); + } + + reg_un!(self, "!", not, bool); + } self.register_fn("+", |x: String, y: String| x + &y); // String + String self.register_fn("==", |_: (), _: ()| true); // () == () @@ -552,51 +521,106 @@ impl Engine<'_> { format!("{}", x) } - reg_func1!(self, "print", print, String, INT, bool, char, String); - self.register_fn("print", || "".to_string()); - self.register_fn("print", |_: ()| "".to_string()); - reg_func1!(self, "debug", debug, String, INT, bool, char, String, ()); + { + macro_rules! reg_fn1 { + ($self:expr, $x:expr, $op:expr, $r:ty, $( $y:ty ),*) => ( + $( + $self.register_fn($x, $op as fn(x: $y)->$r); + )* + ) + } + + reg_fn1!(self, "print", print, String, INT, bool, char, String); + self.register_fn("print", || "".to_string()); + self.register_fn("print", |_: ()| "".to_string()); + reg_fn1!(self, "debug", debug, String, INT, bool, char, String, ()); + + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + reg_fn1!(self, "print", print, String, i8, u8, i16, u16); + reg_fn1!(self, "print", print, String, i32, i64, u32, u64); + reg_fn1!(self, "debug", debug, String, i8, u8, i16, u16); + reg_fn1!(self, "debug", debug, String, i32, i64, u32, u64); + } + + #[cfg(not(feature = "no_float"))] + { + reg_fn1!(self, "print", print, String, f32, f64); + reg_fn1!(self, "debug", debug, String, f32, f64); + } + + #[cfg(not(feature = "no_index"))] + { + reg_fn1!(self, "print", debug, String, Array); + reg_fn1!(self, "debug", debug, String, Array); + + // Register array iterator + self.register_iterator::(|a| { + Box::new(a.downcast_ref::().unwrap().clone().into_iter()) + }); + } + } + + // Register range function + fn reg_iterator(engine: &mut Engine) + where + Range: Iterator, + { + engine.register_iterator::, _>(|a| { + Box::new( + a.downcast_ref::>() + .unwrap() + .clone() + .map(|n| n.into_dynamic()), + ) + }); + } + + fn range(from: T, to: T) -> Range { + (from..to) + } + + reg_iterator::(self); + self.register_fn("range", |i1: INT, i2: INT| (i1..i2)); #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] { - reg_func1!(self, "print", print, String, i8, u8, i16, u16); - reg_func1!(self, "print", print, String, i32, i64, u32, u64); - reg_func1!(self, "debug", debug, String, i8, u8, i16, u16); - reg_func1!(self, "debug", debug, String, i32, i64, u32, u64); + macro_rules! reg_range { + ($self:expr, $x:expr, $op:expr, $( $y:ty ),*) => ( + $( + reg_iterator::<$y>(self); + $self.register_fn($x, $op as fn(x: $y, y: $y)->Range<$y>); + )* + ) + } + + reg_range!(self, "range", range, i8, u8, i16, u16, i32, i64, u32, u64); } - - #[cfg(not(feature = "no_float"))] - { - reg_func1!(self, "print", print, String, f32, f64); - reg_func1!(self, "debug", debug, String, f32, f64); - } - - #[cfg(not(feature = "no_index"))] - { - reg_func1!(self, "print", debug, String, Array); - reg_func1!(self, "debug", debug, String, Array); - - // Register array iterator - self.register_iterator::(|a| { - Box::new(a.downcast_ref::().unwrap().clone().into_iter()) - }); - } - - // Register range function - self.register_iterator::, _>(|a| { - Box::new( - a.downcast_ref::>() - .unwrap() - .clone() - .map(|n| n.into_dynamic()), - ) - }); - - self.register_fn("range", |i1: INT, i2: INT| (i1..i2)); } +} - /// Register the built-in library. +#[cfg(not(feature = "no_stdlib"))] +macro_rules! reg_fn2x { + ($self:expr, $x:expr, $op:expr, $v:ty, $r:ty, $( $y:ty ),*) => ( + $( + $self.register_fn($x, $op as fn(x: $v, y: $y)->$r); + )* + ) +} + +#[cfg(not(feature = "no_stdlib"))] +macro_rules! reg_fn2y { + ($self:expr, $x:expr, $op:expr, $v:ty, $r:ty, $( $y:ty ),*) => ( + $( + $self.register_fn($x, $op as fn(y: $y, x: $v)->$r); + )* + ) +} + +/// Register the built-in library. +impl Engine<'_> { #[cfg(not(feature = "no_stdlib"))] pub(crate) fn register_stdlib(&mut self) { #[cfg(not(feature = "no_index"))] @@ -704,6 +728,14 @@ impl Engine<'_> { #[cfg(not(feature = "no_index"))] { + macro_rules! reg_fn3 { + ($self:expr, $x:expr, $op:expr, $v:ty, $w:ty, $r:ty, $( $y:ty ),*) => ( + $( + $self.register_fn($x, $op as fn(x: $v, y: $w, z: $y)->$r); + )* + ) + } + // Register array utility functions fn push(list: &mut Array, item: T) { list.push(Box::new(item)); @@ -716,24 +748,24 @@ impl Engine<'_> { } } - reg_func2x!(self, "push", push, &mut Array, (), INT, bool, char); - reg_func2x!(self, "push", push, &mut Array, (), String, Array, ()); - reg_func3!(self, "pad", pad, &mut Array, INT, (), INT, bool, char); - reg_func3!(self, "pad", pad, &mut Array, INT, (), String, Array, ()); + reg_fn2x!(self, "push", push, &mut Array, (), INT, bool, char); + reg_fn2x!(self, "push", push, &mut Array, (), String, Array, ()); + reg_fn3!(self, "pad", pad, &mut Array, INT, (), INT, bool, char); + reg_fn3!(self, "pad", pad, &mut Array, INT, (), String, Array, ()); #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] { - reg_func2x!(self, "push", push, &mut Array, (), i8, u8, i16, u16); - reg_func2x!(self, "push", push, &mut Array, (), i32, i64, u32, u64); - reg_func3!(self, "pad", pad, &mut Array, INT, (), i8, u8, i16, u16); - reg_func3!(self, "pad", pad, &mut Array, INT, (), i32, u32, i64, u64); + reg_fn2x!(self, "push", push, &mut Array, (), i8, u8, i16, u16); + reg_fn2x!(self, "push", push, &mut Array, (), i32, i64, u32, u64); + reg_fn3!(self, "pad", pad, &mut Array, INT, (), i8, u8, i16, u16); + reg_fn3!(self, "pad", pad, &mut Array, INT, (), i32, u32, i64, u64); } #[cfg(not(feature = "no_float"))] { - reg_func2x!(self, "push", push, &mut Array, (), f32, f64); - reg_func3!(self, "pad", pad, &mut Array, INT, (), f32, f64); + reg_fn2x!(self, "push", push, &mut Array, (), f32, f64); + reg_fn3!(self, "pad", pad, &mut Array, INT, (), f32, f64); } self.register_dynamic_fn("pop", |list: &mut Array| { @@ -760,23 +792,23 @@ impl Engine<'_> { format!("{}{}", x, y) } - reg_func2x!(self, "+", append, String, String, INT, bool, char); + reg_fn2x!(self, "+", append, String, String, INT, bool, char); self.register_fn("+", |x: String, _: ()| format!("{}", x)); - reg_func2y!(self, "+", prepend, String, String, INT, bool, char); + reg_fn2y!(self, "+", prepend, String, String, INT, bool, char); self.register_fn("+", |_: (), y: String| format!("{}", y)); #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] { - reg_func2x!(self, "+", append, String, String, i8, u8, i16, u16, i32, i64, u32, u64); - reg_func2y!(self, "+", prepend, String, String, i8, u8, i16, u16, i32, i64, u32, u64); + reg_fn2x!(self, "+", append, String, String, i8, u8, i16, u16, i32, i64, u32, u64); + reg_fn2y!(self, "+", prepend, String, String, i8, u8, i16, u16, i32, i64, u32, u64); } #[cfg(not(feature = "no_float"))] { - reg_func2x!(self, "+", append, String, String, f32, f64); - reg_func2y!(self, "+", prepend, String, String, f32, f64); + reg_fn2x!(self, "+", append, String, String, f32, f64); + reg_fn2y!(self, "+", prepend, String, String, f32, f64); } #[cfg(not(feature = "no_index"))] From 4662b9bd0ccbe182197c4f817157dc72406c1036 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 12 Mar 2020 12:35:30 +0800 Subject: [PATCH 13/24] Fine tune some tests. --- tests/bool_op.rs | 28 ++++++++++------------------ tests/var_scope.rs | 10 +++++++++- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/tests/bool_op.rs b/tests/bool_op.rs index a7960476..a39001a2 100644 --- a/tests/bool_op.rs +++ b/tests/bool_op.rs @@ -62,39 +62,31 @@ fn test_bool_op_short_circuit() -> Result<(), EvalAltResult> { } #[test] -#[should_panic] fn test_bool_op_no_short_circuit1() { let mut engine = Engine::new(); - assert_eq!( - engine - .eval::( - r" + assert!(engine + .eval::( + r" let this = true; this | { throw; } " - ) - .unwrap(), - false - ); + ) + .is_err()); } #[test] -#[should_panic] fn test_bool_op_no_short_circuit2() { let mut engine = Engine::new(); - assert_eq!( - engine - .eval::( - r" + assert!(engine + .eval::( + r" let this = false; this & { throw; } " - ) - .unwrap(), - false - ); + ) + .is_err()); } diff --git a/tests/var_scope.rs b/tests/var_scope.rs index 0ad6c618..518e3b76 100644 --- a/tests/var_scope.rs +++ b/tests/var_scope.rs @@ -42,7 +42,15 @@ fn test_scope_eval() -> Result<(), EvalAltResult> { println!("result: {}", result); // should print 966 // Variable y is changed in the script - assert_eq!(scope.get_value::("y").unwrap(), 1); + assert_eq!( + scope + .get_value::("y") + .ok_or(EvalAltResult::ErrorRuntime( + "variable y not found".into(), + Default::default() + ))?, + 1 + ); Ok(()) } From 66edd28fb3c339e18e6d59b7e99e63194c1c6d95 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 12 Mar 2020 12:39:33 +0800 Subject: [PATCH 14/24] Do not optimize AST for `dump_ast`. --- src/optimize.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/optimize.rs b/src/optimize.rs index 046dfdb4..20975d29 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -229,9 +229,9 @@ fn optimize_expr(expr: Expr, changed: &mut bool) -> Expr { ), }, - // Expr::FunctionCall(id, args, def_value, pos) if id == KEYWORD_DUMP_AST => { - // Expr::FunctionCall(id, args, def_value, pos) - // } + Expr::FunctionCall(id, args, def_value, pos) if id == KEYWORD_DUMP_AST => { + Expr::FunctionCall(id, args, def_value, pos) + } Expr::FunctionCall(id, args, def_value, pos) => { let original_len = args.len(); From 1765d302b9bb2eac5fd5590e1c91d76ba3c0b5c4 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 12 Mar 2020 12:40:28 +0800 Subject: [PATCH 15/24] Minor refactor. --- src/parser.rs | 44 ++++++++++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/src/parser.rs b/src/parser.rs index 45ecc42b..038680cb 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -4,7 +4,10 @@ use crate::any::Dynamic; use crate::error::{LexError, ParseError, ParseErrorType}; use crate::optimize::optimize; -use std::{borrow::Cow, char, fmt, iter::Peekable, str::Chars, str::FromStr, sync::Arc, usize}; +use std::{ + borrow::Cow, char, cmp::Ordering, fmt, iter::Peekable, str::Chars, str::FromStr, sync::Arc, + usize, +}; /// The system integer type. /// @@ -153,6 +156,15 @@ pub struct FnDef { pub pos: Position, } +impl FnDef { + pub fn compare(&self, name: &str, params_len: usize) -> Ordering { + match self.name.as_str().cmp(name) { + Ordering::Equal => self.params.len().cmp(¶ms_len), + order => order, + } + } +} + #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] pub enum ReturnType { Return, @@ -252,8 +264,14 @@ impl Expr { /// A pure expression has no side effects. pub fn is_pure(&self) -> bool { match self { - Expr::Identifier(_, _) - | Expr::IntegerConstant(_, _) + Expr::Array(expressions, _) => expressions.iter().all(Expr::is_pure), + expr => expr.is_constant() || expr.is_identifier(), + } + } + + pub fn is_constant(&self) -> bool { + match self { + Expr::IntegerConstant(_, _) | Expr::CharConstant(_, _) | Expr::StringConstant(_, _) | Expr::True(_) @@ -263,12 +281,9 @@ impl Expr { #[cfg(not(feature = "no_float"))] Expr::FloatConstant(_, _) => true, - Expr::Array(expressions, _) => expressions.iter().all(Expr::is_pure), - _ => false, } } - pub fn is_identifier(&self) -> bool { match self { Expr::Identifier(_, _) => true, @@ -532,14 +547,9 @@ impl<'a> TokenIterator<'a> { loop { let next_char = self.char_stream.next(); - - if next_char.is_none() { - return Err((LERR::UnterminatedString, Position::eof())); - } - self.advance(); - match next_char.unwrap() { + match next_char.ok_or((LERR::UnterminatedString, Position::eof()))? { '\\' if escape.is_empty() => { escape.push('\\'); } @@ -1767,13 +1777,11 @@ fn parse_var<'a>(input: &mut Peekable>) -> Result(input: &mut Peekable>) -> Result { - match input.peek() { - Some(&(Token::LeftBrace, _)) => (), - Some(&(_, pos)) => return Err(ParseError::new(PERR::MissingLeftBrace, pos)), + let pos = match input.next() { + Some((Token::LeftBrace, pos)) => pos, + Some((_, pos)) => return Err(ParseError::new(PERR::MissingLeftBrace, pos)), None => return Err(ParseError::new(PERR::MissingLeftBrace, Position::eof())), - } - - let pos = input.next().unwrap().1; + }; let mut statements = Vec::new(); From e24d3a7ade38ff40b263ff70e08f43279daa5edb Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 12 Mar 2020 13:02:13 +0800 Subject: [PATCH 16/24] Allow overloading of script functions. --- README.md | 29 ++++++++++++++++++++++------- src/api.rs | 36 +++++++++++++++++++++++++++--------- src/engine.rs | 33 +++++++++------------------------ src/parser.rs | 10 +++++++++- tests/internal_fn.rs | 22 ++++++++++++++++++++++ 5 files changed, 89 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index c96d0a32..0a91272f 100644 --- a/README.md +++ b/README.md @@ -362,10 +362,10 @@ Any similarly-named function defined in a script overrides any built-in function ```rust // Override the built-in function 'to_int' fn to_int(num) { - print("Ha! Gotcha!" + num); + print("Ha! Gotcha! " + num); } -print(to_int(123)); // what will happen? +print(to_int(123)); // what happens? ``` Custom types and methods @@ -794,7 +794,7 @@ print(y.len()); // prints 0 ``` `push` and `pad` are only defined for standard built-in types. If you want to use them with -your own custom type, you need to define a specific override: +your own custom type, you need to register a type-specific version: ```rust engine.register_fn("push", @@ -975,9 +975,8 @@ fn add(x, y) { print(add(2, 3)); ``` -Remember that functions defined in script always take `Dynamic` arguments (i.e. the arguments can be of any type). - -However, all arguments are passed by _value_, so all functions are _pure_ (i.e. they never modify their arguments). +Functions defined in script always take `Dynamic` arguments (i.e. the arguments can be of any type). +It is important to remember that all arguments are passed by _value_, so all functions are _pure_ (i.e. they never modify their arguments). Any update to an argument will **not** be reflected back to the caller. This can introduce subtle bugs, if you are not careful. ```rust @@ -990,7 +989,7 @@ x.change(); x == 500; // 'x' is NOT changed! ``` -Furthermore, functions can only be defined at the top level, never inside a block or another function. +Functions can only be defined at the top level, never inside a block or another function. ```rust // Top level is OK @@ -1008,6 +1007,22 @@ fn do_addition(x) { } ``` +Functions can be _overloaded_ based on the number of parameters (but not parameter types, since all parameters are `Dynamic`). +New definitions of the same name and number of parameters overwrite previous definitions. + +```rust +fn abc(x,y,z) { print("Three!!! " + x + "," + y + "," + z) } +fn abc(x) { print("One! " + x) } +fn abc(x,y) { print("Two! " + x + "," + y) } +fn abc() { print("None.") } +fn abc(x) { print("HA! NEW ONE! " + x) } // overwrites previous definition + +abc(1,2,3); // prints "Three!!! 1,2,3" +abc(42); // prints "HA! NEW ONE! 42" +abc(1,2); // prints "Two!! 1,2" +abc(); // prints "None." +``` + Members and methods ------------------- diff --git a/src/api.rs b/src/api.rs index c1c42f83..31416fbb 100644 --- a/src/api.rs +++ b/src/api.rs @@ -174,9 +174,15 @@ impl<'e> Engine<'e> { let statements = { let AST(statements, functions) = ast; - functions.iter().for_each(|f| { - engine.script_functions.push(f.clone()); - }); + for f in functions { + match engine + .script_functions + .binary_search_by(|fn_def| fn_def.compare(&f.name, f.params.len())) + { + Ok(n) => engine.script_functions[n] = f.clone(), + Err(n) => engine.script_functions.insert(n, f.clone()), + } + } statements }; @@ -253,9 +259,15 @@ impl<'e> Engine<'e> { let statements = { let AST(ref statements, ref functions) = ast; - functions.iter().for_each(|f| { - self.script_functions.push(f.clone()); - }); + for f in functions { + match self + .script_functions + .binary_search_by(|fn_def| fn_def.compare(&f.name, f.params.len())) + { + Ok(n) => self.script_functions[n] = f.clone(), + Err(n) => self.script_functions.insert(n, f.clone()), + } + } statements }; @@ -308,9 +320,15 @@ impl<'e> Engine<'e> { ast: &AST, args: FnCallArgs, ) -> Result { - ast.1.iter().for_each(|f| { - engine.script_functions.push(f.clone()); - }); + for f in &ast.1 { + match engine + .script_functions + .binary_search_by(|fn_def| fn_def.compare(&f.name, f.params.len())) + { + Ok(n) => engine.script_functions[n] = f.clone(), + Err(n) => engine.script_functions.insert(n, f.clone()), + } + } let result = engine.call_fn_raw(name, args, None, Position::none()); diff --git a/src/engine.rs b/src/engine.rs index 7cf8b114..8bfc4eb9 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -41,7 +41,7 @@ enum IndexSourceType { Expression, } -#[derive(Debug, Eq, PartialEq, Hash)] +#[derive(Debug, Eq, PartialEq, Hash, Clone)] pub struct FnSpec<'a> { pub name: Cow<'a, str>, pub args: Option>, @@ -82,10 +82,10 @@ impl Engine<'_> { pub fn new() -> Self { // User-friendly names for built-in types let type_names = [ - (type_name::(), "string"), - (type_name::(), "dynamic"), #[cfg(not(feature = "no_index"))] (type_name::(), "array"), + (type_name::(), "string"), + (type_name::(), "dynamic"), ] .iter() .map(|(k, v)| (k.to_string(), v.to_string())) @@ -135,22 +135,11 @@ impl Engine<'_> { ); // First search in script-defined functions (can override built-in) - if let Some(func) = self + if let Ok(n) = self .script_functions - .iter() - .rev() - .find(|fn_def| fn_def.name == fn_name) - .map(|fn_def| fn_def.clone()) + .binary_search_by(|f| f.compare(fn_name, args.len())) { - // First check number of parameters - if func.params.len() != args.len() { - return Err(EvalAltResult::ErrorFunctionArgsMismatch( - fn_name.into(), - func.params.len(), - args.len(), - pos, - )); - } + let func = self.script_functions[n].clone(); let mut scope = Scope::new(); @@ -838,13 +827,9 @@ impl Engine<'_> { Expr::Array(contents, _) => { let mut arr = Vec::new(); - contents - .iter() - .try_for_each::<_, Result<_, EvalAltResult>>(|item| { - let arg = self.eval_expr(scope, item)?; - arr.push(arg); - Ok(()) - })?; + for item in contents { + arr.push(self.eval_expr(scope, item)?); + } Ok(Box::new(arr)) } diff --git a/src/parser.rs b/src/parser.rs index 038680cb..613d205a 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1944,7 +1944,15 @@ fn parse_top_level<'a>( while input.peek().is_some() { match input.peek() { #[cfg(not(feature = "no_function"))] - Some(&(Token::Fn, _)) => functions.push(parse_fn(input)?), + Some(&(Token::Fn, _)) => { + let f = parse_fn(input)?; + + // Ensure list is sorted + match functions.binary_search_by(|fn_def| fn_def.compare(&f.name, f.params.len())) { + Ok(n) => functions[n] = f, // Override previous definition + Err(n) => functions.insert(n, f), // New function definition + } + } _ => statements.push(parse_stmt(input)?), } diff --git a/tests/internal_fn.rs b/tests/internal_fn.rs index 8e38f0d9..4a6720d5 100644 --- a/tests/internal_fn.rs +++ b/tests/internal_fn.rs @@ -30,3 +30,25 @@ fn test_big_internal_fn() -> Result<(), EvalAltResult> { Ok(()) } + +#[test] +fn test_internal_fn_overloading() -> Result<(), EvalAltResult> { + let mut engine = Engine::new(); + + assert_eq!( + engine.eval::( + r#" + fn abc(x,y,z) { 2*x + 3*y + 4*z + 888 } + fn abc(x) { x + 42 } + fn abc(x,y) { x + 2*y + 88 } + fn abc() { 42 } + fn abc(x) { x - 42 } // should override previous definition + + abc() + abc(1) + abc(1,2) + abc(1,2,3) + "# + )?, + 1002 + ); + + Ok(()) +} From 560da5fdc6baf0f47c61cce21001bb57f1776162 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 12 Mar 2020 14:54:14 +0800 Subject: [PATCH 17/24] Allow call_fn with only one parameter; consume can retain functions, eval cannot. --- README.md | 31 +++++--- examples/reuse_scope.rs | 4 +- examples/rhai_runner.rs | 2 +- src/api.rs | 168 ++++++++++++++++++---------------------- src/call.rs | 37 +++++++-- src/engine.rs | 31 ++++---- src/optimize.rs | 12 +-- src/parser.rs | 12 +-- src/scope.rs | 4 +- tests/engine.rs | 12 ++- tests/var_scope.rs | 19 ++--- 11 files changed, 176 insertions(+), 156 deletions(-) diff --git a/README.md b/README.md index 0a91272f..ffb34bf3 100644 --- a/README.md +++ b/README.md @@ -167,28 +167,35 @@ let ast = engine.compile_file("hello_world.rhai")?; ``` Rhai also allows you to work _backwards_ from the other direction - i.e. calling a Rhai-scripted function from Rust. -You do this via `call_fn`, which takes a compiled AST (output from `compile`) and the -function call arguments: +You do this via `call_fn`: ```rust use rhai::Engine; let mut engine = Engine::new(); -// Define a function in a script and compile to AST -let ast = engine.compile( +// Define a function in a script and load it into the Engine. +engine.consume( r" - fn hello(x, y) { // a function with two arguments: String and i64 - x.len() + y // returning i64 - } - ")?; + fn hello(x, y) { // a function with two parameters: String and i64 + x.len() + y // returning i64 + } -// Evaluate the function in the AST, passing arguments into the script as a tuple. + fn hello(x) { // script-functions can be overloaded: this one takes only one parameter + x * 2 // returning i64 + } + ", true)?; // pass true to 'retain_functions' otherwise these functions will be cleared + // at the end of consume() + +// Evaluate the function in the AST, passing arguments into the script as a tuple if there are more than one. // Beware, arguments must be of the correct types because Rhai does not have built-in type conversions. // If you pass in arguments of the wrong type, the Engine will not find the function. let result: i64 = engine.call_fn("hello", &ast, ( String::from("abc"), 123_i64 ) )?; // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ put arguments in a tuple + +let result: i64 = engine.call_fn("hello", 123_i64)? +// ^^^^^^^ this one calls the 'hello' function with one parameter (so need for tuple) ``` Values and types @@ -318,7 +325,7 @@ fn main() } ``` -You can also see in this example how you can register multiple functions (or in this case multiple instances of the same function) to the same name in script. This gives you a way to overload functions the correct one, based on the types of the arguments, from your script. +You can also see in this example how you can register multiple functions (or in this case multiple instances of the same function) to the same name in script. This gives you a way to overload functions the correct one, based on the types of the parameters, from your script. Fallible functions ------------------ @@ -975,8 +982,8 @@ fn add(x, y) { print(add(2, 3)); ``` -Functions defined in script always take `Dynamic` arguments (i.e. the arguments can be of any type). -It is important to remember that all arguments are passed by _value_, so all functions are _pure_ (i.e. they never modify their arguments). +Functions defined in script always take `Dynamic` parameters (i.e. the parameter can be of any type). +It is important to remember that all parameters are passed by _value_, so all functions are _pure_ (i.e. they never modify their parameters). Any update to an argument will **not** be reflected back to the caller. This can introduce subtle bugs, if you are not careful. ```rust diff --git a/examples/reuse_scope.rs b/examples/reuse_scope.rs index c83d0a72..a6509ffe 100644 --- a/examples/reuse_scope.rs +++ b/examples/reuse_scope.rs @@ -4,9 +4,9 @@ fn main() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); let mut scope = Scope::new(); - engine.eval_with_scope::<()>(&mut scope, false, "let x = 4 + 5")?; + engine.eval_with_scope::<()>(&mut scope, "let x = 4 + 5")?; - let result = engine.eval_with_scope::(&mut scope, false, "x")?; + let result = engine.eval_with_scope::(&mut scope, "x")?; println!("result: {}", result); diff --git a/examples/rhai_runner.rs b/examples/rhai_runner.rs index d535f078..2955c76e 100644 --- a/examples/rhai_runner.rs +++ b/examples/rhai_runner.rs @@ -67,7 +67,7 @@ fn main() { _ => (), } - if let Err(err) = engine.consume(&contents) { + if let Err(err) = engine.consume(&contents, false) { eprintln!("{}", padding("=", filename.len())); eprintln!("{}", filename); eprintln!("{}", padding("=", filename.len())); diff --git a/src/api.rs b/src/api.rs index 31416fbb..f199643d 100644 --- a/src/api.rs +++ b/src/api.rs @@ -2,16 +2,17 @@ use crate::any::{Any, AnyExt, Dynamic}; use crate::call::FuncArgs; -use crate::engine::{Engine, FnAny, FnCallArgs, FnSpec}; +use crate::engine::{Engine, FnAny, FnSpec}; use crate::error::ParseError; use crate::fn_register::RegisterFn; -use crate::parser::{lex, parse, Position, AST}; +use crate::parser::{lex, parse, FnDef, Position, AST}; use crate::result::EvalAltResult; use crate::scope::Scope; use std::{ any::{type_name, TypeId}, fs::File, io::prelude::*, + sync::Arc, }; impl<'e> Engine<'e> { @@ -99,8 +100,8 @@ impl<'e> Engine<'e> { /// Compile a string into an AST. pub fn compile(&self, input: &str) -> Result { - let tokens = lex(input); - parse(&mut tokens.peekable(), self.optimize) + let tokens_stream = lex(input); + parse(&mut tokens_stream.peekable(), self.optimize) } fn read_file(filename: &str) -> Result { @@ -128,62 +129,45 @@ impl<'e> Engine<'e> { /// Evaluate a string. pub fn eval(&mut self, input: &str) -> Result { let mut scope = Scope::new(); - self.eval_with_scope(&mut scope, false, input) + self.eval_with_scope(&mut scope, input) } /// Evaluate a string with own scope. - /// - /// Note - if `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ - /// and not cleared from run to run. pub fn eval_with_scope( &mut self, scope: &mut Scope, - retain_functions: bool, input: &str, ) -> Result { let ast = self.compile(input).map_err(EvalAltResult::ErrorParsing)?; - self.eval_ast_with_scope(scope, retain_functions, &ast) + 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, false, ast) + self.eval_ast_with_scope(&mut scope, ast) } /// Evaluate an AST with own scope. - /// - /// Note - if `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ - /// and not cleared from run to run. pub fn eval_ast_with_scope( &mut self, scope: &mut Scope, - retain_functions: bool, ast: &AST, ) -> Result { fn eval_ast_internal( engine: &mut Engine, scope: &mut Scope, - retain_functions: bool, ast: &AST, ) -> Result { + engine.clear_functions(); + #[cfg(feature = "no_function")] let AST(statements) = ast; #[cfg(not(feature = "no_function"))] let statements = { let AST(statements, functions) = ast; - - for f in functions { - match engine - .script_functions - .binary_search_by(|fn_def| fn_def.compare(&f.name, f.params.len())) - { - Ok(n) => engine.script_functions[n] = f.clone(), - Err(n) => engine.script_functions.insert(n, f.clone()), - } - } - + engine.load_script_functions(functions); statements }; @@ -191,14 +175,12 @@ impl<'e> Engine<'e> { .iter() .try_fold(().into_dynamic(), |_, stmt| engine.eval_stmt(scope, stmt)); - if !retain_functions { - engine.clear_functions(); - } + engine.clear_functions(); result } - match eval_ast_internal(self, scope, retain_functions, ast) { + match eval_ast_internal(self, scope, ast) { Err(EvalAltResult::Return(out, pos)) => out.downcast::().map(|v| *v).map_err(|a| { EvalAltResult::ErrorMismatchOutputType( self.map_type_name((*a).type_name()).to_string(), @@ -219,7 +201,14 @@ impl<'e> Engine<'e> { /// Evaluate a file, but throw away the result and only return error (if 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> { + /// + /// Note - if `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ + /// and not cleared from run to run. + pub fn consume_file( + &mut self, + filename: &str, + retain_functions: bool, + ) -> Result<(), EvalAltResult> { let mut f = File::open(filename) .map_err(|err| EvalAltResult::ErrorReadingScriptFile(filename.into(), err))?; @@ -227,13 +216,16 @@ impl<'e> Engine<'e> { f.read_to_string(&mut contents) .map_err(|err| EvalAltResult::ErrorReadingScriptFile(filename.into(), err)) - .and_then(|_| self.consume(&contents)) + .and_then(|_| self.consume(&contents, retain_functions)) } /// Evaluate a string, but throw away the result and only return error (if 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(), false, input) + /// + /// Note - if `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ + /// and not cleared from run to run. + pub fn consume(&mut self, input: &str, retain_functions: bool) -> Result<(), EvalAltResult> { + self.consume_with_scope(&mut Scope::new(), retain_functions, input) } /// Evaluate a string, but throw away the result and only return error (if any). @@ -247,45 +239,54 @@ impl<'e> Engine<'e> { retain_functions: bool, input: &str, ) -> Result<(), EvalAltResult> { - let tokens = lex(input); + let tokens_stream = lex(input); - parse(&mut tokens.peekable(), self.optimize) - .map_err(|err| EvalAltResult::ErrorParsing(err)) - .and_then(|ast| { - #[cfg(feature = "no_function")] - let AST(statements) = ast; + let ast = parse(&mut tokens_stream.peekable(), self.optimize) + .map_err(EvalAltResult::ErrorParsing)?; - #[cfg(not(feature = "no_function"))] - let statements = { - let AST(ref statements, ref functions) = ast; + if !retain_functions { + self.clear_functions(); + } - for f in functions { - match self - .script_functions - .binary_search_by(|fn_def| fn_def.compare(&f.name, f.params.len())) - { - Ok(n) => self.script_functions[n] = f.clone(), - Err(n) => self.script_functions.insert(n, f.clone()), - } - } + #[cfg(feature = "no_function")] + let AST(statements) = ast; - statements - }; + #[cfg(not(feature = "no_function"))] + let statements = { + let AST(ref statements, ref functions) = ast; + self.load_script_functions(functions); + statements + }; - let val = statements - .iter() - .try_fold(().into_dynamic(), |_, o| self.eval_stmt(scope, o)) - .map(|_| ()); + let result = statements + .iter() + .try_fold(().into_dynamic(), |_, o| self.eval_stmt(scope, o)) + .map(|_| ()); - if !retain_functions { - self.clear_functions(); - } + if !retain_functions { + self.clear_functions(); + } - val - }) + result } - /// Call a script function defined in a compiled AST. + /// Load a list of functions into the Engine. + pub(crate) fn load_script_functions<'a>( + &mut self, + functions: impl IntoIterator>, + ) { + for f in functions.into_iter() { + match self + .script_functions + .binary_search_by(|fn_def| fn_def.compare(&f.name, f.params.len())) + { + Ok(n) => self.script_functions[n] = f.clone(), + Err(n) => self.script_functions.insert(n, f.clone()), + } + } + } + + /// Call a script function retained inside the Engine. /// /// # Example /// @@ -298,9 +299,9 @@ impl<'e> Engine<'e> { /// /// let mut engine = Engine::new(); /// - /// let ast = engine.compile("fn add(x, y) { x.len() + y }")?; + /// engine.consume("fn add(x, y) { x.len() + y }", true)?; /// - /// let result: i64 = engine.call_fn("add", &ast, (String::from("abc"), 123_i64))?; + /// let result: i64 = engine.call_fn("add", (String::from("abc"), 123_i64))?; /// /// assert_eq!(result, 126); /// # } @@ -311,41 +312,22 @@ impl<'e> Engine<'e> { pub fn call_fn( &mut self, name: &str, - ast: &AST, args: A, ) -> Result { + // Split out non-generic portion to avoid exploding code size fn call_fn_internal( engine: &mut Engine, name: &str, - ast: &AST, - args: FnCallArgs, + mut values: Vec, ) -> Result { - for f in &ast.1 { - match engine - .script_functions - .binary_search_by(|fn_def| fn_def.compare(&f.name, f.params.len())) - { - Ok(n) => engine.script_functions[n] = f.clone(), - Err(n) => engine.script_functions.insert(n, f.clone()), - } - } + let values: Vec<_> = values.iter_mut().map(Dynamic::as_mut).collect(); - let result = engine.call_fn_raw(name, args, None, Position::none()); - - engine.clear_functions(); + let result = engine.call_fn_raw(name, values, None, Position::none()); result } - let mut arg_values = args.into_vec(); - - call_fn_internal( - self, - name, - ast, - arg_values.iter_mut().map(|v| v.as_mut()).collect(), - ) - .and_then(|b| { + call_fn_internal(self, name, args.into_vec()).and_then(|b| { b.downcast().map(|b| *b).map_err(|a| { EvalAltResult::ErrorMismatchOutputType( self.map_type_name((*a).type_name()).into(), @@ -369,7 +351,7 @@ impl<'e> Engine<'e> { /// /// // Override action of 'print' function /// engine.on_print(|s| result.push_str(s)); - /// engine.consume("print(40 + 2);")?; + /// engine.consume("print(40 + 2);", false)?; /// } /// assert_eq!(result, "42"); /// # Ok(()) @@ -393,7 +375,7 @@ impl<'e> Engine<'e> { /// /// // Override action of 'debug' function /// engine.on_debug(|s| result.push_str(s)); - /// engine.consume(r#"debug("hello");"#)?; + /// engine.consume(r#"debug("hello");"#, false)?; /// } /// assert_eq!(result, "\"hello\""); /// # Ok(()) diff --git a/src/call.rs b/src/call.rs index f9faf563..9a2252c0 100644 --- a/src/call.rs +++ b/src/call.rs @@ -1,6 +1,7 @@ //! Helper module which defines `FnArgs` to make function calling easier. use crate::any::{Any, Dynamic}; +use crate::engine::Array; /// Trait that represent arguments to a function call. pub trait FuncArgs { @@ -8,12 +9,36 @@ pub trait FuncArgs { fn into_vec(self) -> Vec; } -impl FuncArgs for &mut Vec { - fn into_vec(self) -> Vec { - self.iter_mut().map(|x| x.into_dynamic()).collect() - } +macro_rules! impl_std_args { + ($($p:ty),*) => { + $( + impl FuncArgs for $p { + fn into_vec(self) -> Vec { + vec![self.into_dynamic()] + } + } + )* + }; } +impl_std_args!(String, char, bool); + +#[cfg(not(feature = "no_index"))] +impl_std_args!(Array); + +#[cfg(not(feature = "only_i32"))] +#[cfg(not(feature = "only_i64"))] +impl_std_args!(u8, i8, u16, i16, u32, i32, u64, i64); + +#[cfg(feature = "only_i32")] +impl_std_args!(i32); + +#[cfg(feature = "only_i64")] +impl_std_args!(i64); + +#[cfg(not(feature = "no_float"))] +impl_std_args!(f32, f64); + macro_rules! impl_args { ($($p:ident),*) => { impl<$($p: Any + Clone),*> FuncArgs for ($($p,)*) @@ -33,7 +58,9 @@ macro_rules! impl_args { }; (@pop) => { }; - (@pop $head:ident $(, $tail:ident)*) => { + (@pop $head:ident) => { + }; + (@pop $head:ident $(, $tail:ident)+) => { impl_args!($($tail),*); }; } diff --git a/src/engine.rs b/src/engine.rs index 8bfc4eb9..f3fa3ad4 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -139,19 +139,20 @@ impl Engine<'_> { .script_functions .binary_search_by(|f| f.compare(fn_name, args.len())) { - let func = self.script_functions[n].clone(); - let mut scope = Scope::new(); + let fn_def = self.script_functions[n].clone(); + scope.extend( // Put arguments into scope as variables - func.params + fn_def + .params .iter() .zip(args.iter().map(|x| (*x).into_dynamic())), ); // Evaluate - return match self.eval_stmt(&mut scope, &func.body) { + return match self.eval_stmt(&mut scope, &fn_def.body) { // Convert return statement to return value Err(EvalAltResult::Return(x, _)) => Ok(x), other => other, @@ -240,14 +241,14 @@ impl Engine<'_> { ) -> Result { match dot_rhs { // xxx.fn_name(args) - Expr::FunctionCall(fn_name, args, def_val, pos) => { - let mut args = args + Expr::FunctionCall(fn_name, arg_expr_list, def_val, pos) => { + let mut values = arg_expr_list .iter() - .map(|arg| self.eval_expr(scope, arg)) + .map(|arg_expr| self.eval_expr(scope, arg_expr)) .collect::, _>>()?; let args = once(this_ptr) - .chain(args.iter_mut().map(|b| b.as_mut())) + .chain(values.iter_mut().map(|b| b.as_mut())) .collect(); self.call_fn_raw(fn_name, args, def_val.as_ref(), *pos) @@ -837,15 +838,15 @@ impl Engine<'_> { Expr::Array(_, _) => panic!("encountered an array during no_index!"), // Dump AST - Expr::FunctionCall(fn_name, args, _, pos) if fn_name == KEYWORD_DUMP_AST => { - let pos = if args.len() == 0 { + Expr::FunctionCall(fn_name, args_expr_list, _, pos) if fn_name == KEYWORD_DUMP_AST => { + let pos = if args_expr_list.len() == 0 { *pos } else { - args[0].position() + args_expr_list[0].position() }; // Change the argument to a debug dump of the expressions - let result = args + let result = args_expr_list .into_iter() .map(|expr| format!("{:#?}", expr)) .collect::>() @@ -861,15 +862,15 @@ impl Engine<'_> { } // Normal function call - Expr::FunctionCall(fn_name, args, def_val, pos) => { - let mut args = args + Expr::FunctionCall(fn_name, args_expr_list, def_val, pos) => { + let mut values = args_expr_list .iter() .map(|expr| self.eval_expr(scope, expr)) .collect::, _>>()?; self.call_fn_raw( fn_name, - args.iter_mut().map(|b| b.as_mut()).collect(), + values.iter_mut().map(|b| b.as_mut()).collect(), def_val.as_ref(), *pos, ) diff --git a/src/optimize.rs b/src/optimize.rs index 20975d29..ab8d863b 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -50,7 +50,7 @@ fn optimize_stmt(stmt: Stmt, changed: &mut bool, preserve_result: bool) -> Stmt Stmt::Let(_, None, _) => stmt, Stmt::Block(statements, pos) => { - let original_len = statements.len(); + let orig_len = statements.len(); let mut result: Vec<_> = statements .into_iter() // For each statement @@ -105,7 +105,7 @@ fn optimize_stmt(stmt: Stmt, changed: &mut bool, preserve_result: bool) -> Stmt .collect(); } - *changed = *changed || original_len != result.len(); + *changed = *changed || orig_len != result.len(); match result[..] { // No statements in block - change to No-op @@ -177,14 +177,14 @@ fn optimize_expr(expr: Expr, changed: &mut bool) -> Expr { #[cfg(not(feature = "no_index"))] Expr::Array(items, pos) => { - let original_len = items.len(); + let orig_len = items.len(); let items: Vec<_> = items .into_iter() .map(|expr| optimize_expr(expr, changed)) .collect(); - *changed = *changed || original_len != items.len(); + *changed = *changed || orig_len != items.len(); Expr::Array(items, pos) } @@ -233,14 +233,14 @@ fn optimize_expr(expr: Expr, changed: &mut bool) -> Expr { Expr::FunctionCall(id, args, def_value, pos) } Expr::FunctionCall(id, args, def_value, pos) => { - let original_len = args.len(); + let orig_len = args.len(); let args: Vec<_> = args .into_iter() .map(|a| optimize_expr(a, changed)) .collect(); - *changed = *changed || original_len != args.len(); + *changed = *changed || orig_len != args.len(); Expr::FunctionCall(id, args, def_value, pos) } diff --git a/src/parser.rs b/src/parser.rs index 613d205a..db2da373 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1212,26 +1212,26 @@ fn parse_call_expr<'a>( input: &mut Peekable>, begin: Position, ) -> Result { - let mut args = Vec::new(); + let mut args_expr_list = Vec::new(); if let Some(&(Token::RightParen, _)) = input.peek() { input.next(); - return Ok(Expr::FunctionCall(id, args, None, begin)); + return Ok(Expr::FunctionCall(id, args_expr_list, None, begin)); } loop { - args.push(parse_expr(input)?); + args_expr_list.push(parse_expr(input)?); match input.peek() { Some(&(Token::RightParen, _)) => { input.next(); - return Ok(Expr::FunctionCall(id, args, None, begin)); + return Ok(Expr::FunctionCall(id, args_expr_list, None, begin)); } Some(&(Token::Comma, _)) => (), Some(&(_, pos)) => { return Err(ParseError::new( PERR::MissingRightParen(format!( - "closing the arguments list to function call of '{}'", + "closing the parameters list to function call of '{}'", id )), pos, @@ -1240,7 +1240,7 @@ fn parse_call_expr<'a>( None => { return Err(ParseError::new( PERR::MissingRightParen(format!( - "closing the arguments list to function call of '{}'", + "closing the parameters list to function call of '{}'", id )), Position::eof(), diff --git a/src/scope.rs b/src/scope.rs index ca3ec4d9..cf726d39 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -16,9 +16,9 @@ use std::borrow::Cow; /// let mut engine = Engine::new(); /// let mut my_scope = Scope::new(); /// -/// engine.eval_with_scope::<()>(&mut my_scope, false, "let x = 5;")?; +/// engine.eval_with_scope::<()>(&mut my_scope, "let x = 5;")?; /// -/// assert_eq!(engine.eval_with_scope::(&mut my_scope, false, "x + 1")?, 6); +/// assert_eq!(engine.eval_with_scope::(&mut my_scope, "x + 1")?, 6); /// # Ok(()) /// # } /// ``` diff --git a/tests/engine.rs b/tests/engine.rs index d795b35b..cd9f7fb0 100644 --- a/tests/engine.rs +++ b/tests/engine.rs @@ -6,17 +6,23 @@ use rhai::{Engine, EvalAltResult, INT}; fn test_engine_call_fn() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - let ast = engine.compile( + engine.consume( r" fn hello(x, y) { x.len() + y + } + fn hello(x) { + x * 2 } ", + true, )?; - let result: INT = engine.call_fn("hello", &ast, (String::from("abc"), 123 as INT))?; + let r: i64 = engine.call_fn("hello", (String::from("abc"), 123 as INT))?; + assert_eq!(r, 126); - assert_eq!(result, 126); + let r: i64 = engine.call_fn("hello", 123 as INT)?; + assert_eq!(r, 246); Ok(()) } diff --git a/tests/var_scope.rs b/tests/var_scope.rs index 518e3b76..bdb4674e 100644 --- a/tests/var_scope.rs +++ b/tests/var_scope.rs @@ -5,15 +5,12 @@ fn test_var_scope() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); let mut scope = Scope::new(); - engine.eval_with_scope::<()>(&mut scope, false, "let x = 4 + 5")?; - assert_eq!(engine.eval_with_scope::(&mut scope, false, "x")?, 9); - engine.eval_with_scope::<()>(&mut scope, false, "x = x + 1; x = x + 2;")?; - assert_eq!(engine.eval_with_scope::(&mut scope, false, "x")?, 12); - assert_eq!( - engine.eval_with_scope::<()>(&mut scope, false, "{let x = 3}")?, - () - ); - assert_eq!(engine.eval_with_scope::(&mut scope, false, "x")?, 12); + engine.eval_with_scope::<()>(&mut scope, "let x = 4 + 5")?; + assert_eq!(engine.eval_with_scope::(&mut scope, "x")?, 9); + engine.eval_with_scope::<()>(&mut scope, "x = x + 1; x = x + 2;")?; + assert_eq!(engine.eval_with_scope::(&mut scope, "x")?, 12); + assert_eq!(engine.eval_with_scope::<()>(&mut scope, "{let x = 3}")?, ()); + assert_eq!(engine.eval_with_scope::(&mut scope, "x")?, 12); Ok(()) } @@ -33,11 +30,11 @@ fn test_scope_eval() -> Result<(), EvalAltResult> { // First invocation engine - .eval_with_scope::<()>(&mut scope, false, " let x = 4 + 5 - y + z; y = 1;") + .eval_with_scope::<()>(&mut scope, " let x = 4 + 5 - y + z; y = 1;") .expect("y and z not found?"); // Second invocation using the same state - let result = engine.eval_with_scope::(&mut scope, false, "x")?; + let result = engine.eval_with_scope::(&mut scope, "x")?; println!("result: {}", result); // should print 966 From 55dcd2f0f40db3fc54f13dc38687238bf8d4c7a3 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 12 Mar 2020 15:30:42 +0800 Subject: [PATCH 18/24] Fix call_fn calls with only one argument. --- src/api.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/api.rs b/src/api.rs index f199643d..cf03125f 100644 --- a/src/api.rs +++ b/src/api.rs @@ -244,6 +244,20 @@ impl<'e> Engine<'e> { let ast = parse(&mut tokens_stream.peekable(), self.optimize) .map_err(EvalAltResult::ErrorParsing)?; + self.consume_ast_with_scope(scope, retain_functions, &ast) + } + + /// Evaluate an AST, but throw away the result and only return error (if any). + /// Useful for when you don't need the result, but still need to keep track of possible errors. + /// + /// Note - if `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ + /// and not cleared from run to run. + pub fn consume_ast_with_scope( + &mut self, + scope: &mut Scope, + retain_functions: bool, + ast: &AST, + ) -> Result<(), EvalAltResult> { if !retain_functions { self.clear_functions(); } From 7e9a4fd9657f9c044c8b9047f4ca2154cc706dba Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 12 Mar 2020 15:31:01 +0800 Subject: [PATCH 19/24] Improve repl with command to print AST. --- examples/repl.rs | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/examples/repl.rs b/examples/repl.rs index a4221614..381bc265 100644 --- a/examples/repl.rs +++ b/examples/repl.rs @@ -1,4 +1,4 @@ -use rhai::{Engine, EvalAltResult, Scope}; +use rhai::{Engine, EvalAltResult, Scope, AST}; use std::{ io::{stdin, stdout, Write}, iter, @@ -46,6 +46,7 @@ fn main() { let mut scope = Scope::new(); let mut input = String::new(); + let mut ast: Option = None; loop { print!("rhai> "); @@ -57,7 +58,28 @@ fn main() { println!("input error: {}", err); } - if let Err(err) = engine.consume_with_scope(&mut scope, true, &input) { + // Implement standard commands + match input.as_str().trim() { + "exit" | "quit" => break, // quit + "ast" => { + // print the last AST + match &ast { + Some(ast) => println!("{:#?}", ast), + None => println!("()"), + } + continue; + } + _ => (), + } + + if let Err(err) = engine + .compile(&input) + .map_err(EvalAltResult::ErrorParsing) + .and_then(|r| { + ast = Some(r); + engine.consume_ast_with_scope(&mut scope, true, ast.as_ref().unwrap()) + }) + { println!(""); print_error(&input, err); println!(""); From da440a5dffba6de974114861943e0c13837072d1 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 12 Mar 2020 21:19:34 +0800 Subject: [PATCH 20/24] Add optimization section to README. --- README.md | 133 ++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 113 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index ffb34bf3..c74ce893 100644 --- a/README.md +++ b/README.md @@ -181,21 +181,22 @@ engine.consume( x.len() + y // returning i64 } - fn hello(x) { // script-functions can be overloaded: this one takes only one parameter + fn hello(x) { // functions can be overloaded: this one takes only one parameter x * 2 // returning i64 } - ", true)?; // pass true to 'retain_functions' otherwise these functions will be cleared - // at the end of consume() + ", true)?; // pass true to 'retain_functions' otherwise these functions + // will be cleared at the end of consume() -// Evaluate the function in the AST, passing arguments into the script as a tuple if there are more than one. -// Beware, arguments must be of the correct types because Rhai does not have built-in type conversions. -// If you pass in arguments of the wrong type, the Engine will not find the function. +// Evaluate the function in the AST, passing arguments into the script as a tuple +// if there are more than one. Beware, arguments must be of the correct types because +// Rhai does not have built-in type conversions. If you pass in arguments of the wrong type, +// the Engine will not find the function. let result: i64 = engine.call_fn("hello", &ast, ( String::from("abc"), 123_i64 ) )?; // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ put arguments in a tuple let result: i64 = engine.call_fn("hello", 123_i64)? -// ^^^^^^^ this one calls the 'hello' function with one parameter (so need for tuple) +// ^^^^^^^ calls 'hello' with one parameter (no need for tuple) ``` Values and types @@ -255,8 +256,8 @@ Rhai's scripting engine is very lightweight. It gets its ability from the funct ```rust use rhai::{Engine, EvalAltResult}; -use rhai::RegisterFn; // include the `RegisterFn` trait to use `register_fn` -use rhai::{Dynamic, RegisterDynamicFn}; // include the `RegisterDynamicFn` trait to use `register_dynamic_fn` +use rhai::RegisterFn; // use `RegisterFn` trait for `register_fn` +use rhai::{Dynamic, RegisterDynamicFn}; // use `RegisterDynamicFn` trait for `register_dynamic_fn` // Normal function fn add(x: i64, y: i64) -> i64 { @@ -336,13 +337,13 @@ Your function must return `Result<_, EvalAltResult>`. `EvalAltResult` implements ```rust use rhai::{Engine, EvalAltResult, Position}; -use rhai::RegisterResultFn; // include the `RegisterResultFn` trait to use `register_result_fn` +use rhai::RegisterResultFn; // use `RegisterResultFn` trait for `register_result_fn` // Function that may fail fn safe_divide(x: i64, y: i64) -> Result { if y == 0 { // Return an error if y is zero - Err("Division by zero detected!".into()) // short-cut to create EvalAltResult + Err("Division by zero detected!".into()) // short-cut to create EvalAltResult } else { Ok(x / y) } @@ -540,21 +541,19 @@ fn main() -> Result<(), EvalAltResult> let mut scope = Scope::new(); // Then push some initialized variables into the state - // NOTE: Remember the default numbers used by Rhai are i64 and f64. - // Better stick to them or it gets hard to work with other variables in the script. + // NOTE: Remember the system number types in Rhai are i64 (i32 if 'only_i32') ond f64. + // Better stick to them or it gets hard working with the script. scope.push("y".into(), 42_i64); scope.push("z".into(), 999_i64); // First invocation - // (the second boolean argument indicates that we don't need to retain function definitions - // because we didn't declare any!) - engine.eval_with_scope::<()>(&mut scope, false, r" + engine.eval_with_scope::<()>(&mut scope, r" let x = 4 + 5 - y + z; y = 1; ")?; // Second invocation using the same state - let result = engine.eval_with_scope::(&mut scope, false, "x")?; + let result = engine.eval_with_scope::(&mut scope, "x")?; println!("result: {}", result); // should print 966 @@ -668,7 +667,8 @@ let age = 42; let record = full_name + ": age " + age; record == "Bob C. Davis: age 42"; -// Strings can be indexed to get a character (disabled with the 'no_index' feature) +// Strings can be indexed to get a character +// (disabled with the 'no_index' feature) let c = record[4]; c == 'C'; @@ -1052,7 +1052,8 @@ debug("world!"); // prints "world!" to stdout using debug formatting ### Overriding `print` and `debug` with callback functions ```rust -// Any function or closure that takes an &str argument can be used to override print and debug +// Any function or closure that takes an &str argument can be used to override +// print and debug engine.on_print(|x| println!("hello: {}", x)); engine.on_debug(|x| println!("DEBUG: {}", x)); @@ -1063,7 +1064,7 @@ let mut log: Vec = Vec::new(); engine.on_print(|s| log.push(format!("entry: {}", s))); engine.on_debug(|s| log.push(format!("DEBUG: {}", s))); -// Evalulate script +// Evaluate script engine.eval::<()>(script)?; // 'log' captures all the 'print' and 'debug' output @@ -1072,6 +1073,98 @@ for entry in log { } ``` +Optimizations +============= + +Rhai includes an _optimizer_ that tries to optimize a script after parsing. This can reduce resource utilization and increase execution speed. + +For example, in the following: + +```rust +{ + let x = 999; // NOT eliminated - Rhai doesn't check yet whether a variable is used later on + 123; // eliminated - no effect + "hello"; // eliminated - no effect + [1, 2, x, x*2, 5]; // eliminated - no effect + foo(42); // NOT eliminated - the function 'foo' may have side effects + 666 // NOT eliminated - this is the return value of the block, + // and the block is the last one + // so this is the return value of the whole script +} +``` + +Rhai attempts to eliminate _dead code_ (i.e. code that does nothing, for example an expression by itself as a statement, which is allowed in Rhai). +The above script optimizes to: + +```rust +{ + let x = 999; + foo(42); + 666 +} +``` + +Constant propagation is used to remove dead code: + +```rust +if true || some_work() { print("done!"); } // since '||' short-circuits, 'some_work' is never called +if true { print("done!"); } // <-- the line above is equivalent to this +print("done!"); // <-- the line above is further simplified to this + // because the condition is always true +``` + +These are quite effective for template-based machine-generated scripts where certain constant values are spliced into the script text in order to turn on/off certain sections. + +Beware, however, that most operators are actually function calls, and those functions can be overridden, so they are not optimized away: + +```rust +if 1 == 1 { ... } // '1==1' is NOT optimized away because you can define + // your own '==' function to override the built-in default! +``` + +### Here be dragons! + +Some optimizations can be quite aggressive and can alter subtle semantics of the script. For example: + +```rust +if true { // <-- condition always true + 123.456; // <-- eliminated + hello; // <-- eliminated, EVEN THOUGH the variable doesn't exist! + foo(42) // <-- promoted up-level +} + +// The above optimizes to: + +foo(42) +``` + +Nevertheless, if you would be evaluating the original script, it would have been an error - the variable `hello` doesn't exist, so the script would have been terminated at that point with an error return. + +In fact, any errors inside a statement that has been eliminated will silently _go away_: + +```rust +print("start!"); +if my_decision { /* do nothing... */ } // <-- eliminated due to no effect +print("end!"); + +// The above optimizes to: + +print("start!"); +print("end!"); +``` + +In the script above, if `my_decision` holds anything other than a boolean value, the script should have been terminated due to a type error. +However, after optimization, the entire `if` statement is removed, thus the script silently runs to completion without errors. + +It is usually a bad idea to depend on a script failing or such kind of subtleties, but if it turns out to be necessary (why? I would never guess), +there is a setting in `Engine` to turn off optimizations. + +```rust +let engine = rhai::Engine::new(); +engine.set_optimization(false); // turn off the optimizer +``` + + [ChaiScript]: http://chaiscript.com/ [scripting languages for Rust]: https://github.com/rust-unofficial/awesome-rust#scripting [awesome-rust]: https://github.com/rust-unofficial/awesome-rust From 91317c0d3e927ece77978f00c4f34f6bd204ae66 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 12 Mar 2020 23:46:40 +0800 Subject: [PATCH 21/24] New sample script - primes.rhai. --- README.md | 40 ++++++++++++++++++++++------------------ scripts/primes.rhai | 28 ++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 18 deletions(-) create mode 100644 scripts/primes.rhai diff --git a/README.md b/README.md index c74ce893..6ce0ad15 100644 --- a/README.md +++ b/README.md @@ -89,23 +89,27 @@ Example Scripts There are also a number of examples scripts that showcase Rhai's features, all in the `scripts` folder: -| Script | Description | -| --------------------- | ------------------------------------------------------------- | -| `array.rhai` | arrays in Rhai | -| `assignment.rhai` | variable declarations | -| `comments.rhai` | just comments | -| `for1.rhai` | for loops | -| `function_decl1.rhai` | a function without parameters | -| `function_decl2.rhai` | a function with two parameters | -| `function_decl3.rhai` | a function with many parameters | -| `if1.rhai` | if example | -| `loop.rhai` | endless loop in Rhai, this example emulates a do..while cycle | -| `op1.rhai` | just a simple addition | -| `op2.rhai` | simple addition and multiplication | -| `op3.rhai` | change evaluation order with parenthesis | -| `speed_test.rhai` | a simple program to measure the speed of Rhai's interpreter | -| `string.rhai` | string operations | -| `while.rhai` | while loop | +| Language feature scripts | Description | +| ------------------------ | ------------------------------------------------------------- | +| `array.rhai` | arrays in Rhai | +| `assignment.rhai` | variable declarations | +| `comments.rhai` | just comments | +| `for1.rhai` | for loops | +| `function_decl1.rhai` | a function without parameters | +| `function_decl2.rhai` | a function with two parameters | +| `function_decl3.rhai` | a function with many parameters | +| `if1.rhai` | if example | +| `loop.rhai` | endless loop in Rhai, this example emulates a do..while cycle | +| `op1.rhai` | just a simple addition | +| `op2.rhai` | simple addition and multiplication | +| `op3.rhai` | change evaluation order with parenthesis | +| `string.rhai` | string operations | +| `while.rhai` | while loop | + +| Example scripts | Description | +| ----------------- | ----------------------------------------------------------------- | +| `speed_test.rhai` | a simple program to measure the speed of Rhai's interpreter | +| `primes.rhai` | use Sieve of Eratosthenes to find all primes smaller than a limit | To run the scripts, either make a tiny program or use of the `rhai_runner` example: @@ -916,7 +920,7 @@ for x in array { if x == 42 { break; } } -// The 'range' function allows iterating from first..last-1 +// The 'range' function allows iterating from first..last for x in range(0, 50) { print(x); if x == 42 { break; } diff --git a/scripts/primes.rhai b/scripts/primes.rhai new file mode 100644 index 00000000..fae57bbb --- /dev/null +++ b/scripts/primes.rhai @@ -0,0 +1,28 @@ +// This is a script to calculate prime numbers. + +let MAX_NUMBER_TO_CHECK = 10000; // 1229 primes + +let prime_mask = []; +prime_mask.pad(MAX_NUMBER_TO_CHECK, true); + +prime_mask[0] = false; +prime_mask[1] = false; + +let total_primes_found = 0; + +for p in range(2, MAX_NUMBER_TO_CHECK) { + if prime_mask[p] { + print(p); + + total_primes_found += 1; + let i = 2 * p; + + while i < MAX_NUMBER_TO_CHECK { + prime_mask[i] = false; + i += p; + } + } +} + +print("Total " + total_primes_found + " primes."); + From 9bd66c7db361011209007fd3db8dda6cfa52ac92 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 12 Mar 2020 23:46:52 +0800 Subject: [PATCH 22/24] Minor improvements to the optimizer. --- src/optimize.rs | 33 ++++++++++++++++++++++++++++++--- src/parser.rs | 8 ++++++++ 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/src/optimize.rs b/src/optimize.rs index ab8d863b..bf7eab8f 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -3,6 +3,26 @@ use crate::parser::{Expr, Stmt}; fn optimize_stmt(stmt: Stmt, changed: &mut bool, preserve_result: bool) -> Stmt { match stmt { + Stmt::IfElse(expr, stmt1, None) if stmt1.is_noop() => { + *changed = true; + + let pos = expr.position(); + let expr = optimize_expr(*expr, changed); + + match expr { + Expr::False(_) | Expr::True(_) => Stmt::Noop(stmt1.position()), + expr => { + let stmt = Stmt::Expr(Box::new(expr)); + + if preserve_result { + Stmt::Block(vec![stmt, *stmt1], pos) + } else { + stmt + } + } + } + } + Stmt::IfElse(expr, stmt1, None) => match *expr { Expr::False(pos) => { *changed = true; @@ -22,7 +42,10 @@ fn optimize_stmt(stmt: Stmt, changed: &mut bool, preserve_result: bool) -> Stmt expr => Stmt::IfElse( Box::new(optimize_expr(expr, changed)), Box::new(optimize_stmt(*stmt1, changed, true)), - Some(Box::new(optimize_stmt(*stmt2, changed, true))), + match optimize_stmt(*stmt2, changed, true) { + stmt if stmt.is_noop() => None, + stmt => Some(Box::new(stmt)), + }, ), }, @@ -274,10 +297,14 @@ pub(crate) fn optimize(statements: Vec) -> Vec { } } - // Eliminate No-op's but always keep the last statement + // Eliminate code that is pure but always keep the last statement let last_stmt = result.pop(); - result.retain(Stmt::is_op); // Remove all No-op's + // Remove all pure statements at top level + result.retain(|stmt| match stmt { + Stmt::Expr(expr) if expr.is_pure() => false, + _ => true, + }); if let Some(stmt) = last_stmt { result.push(stmt); // Add back the last statement diff --git a/src/parser.rs b/src/parser.rs index db2da373..66eec95f 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -186,6 +186,13 @@ pub enum Stmt { } impl Stmt { + pub fn is_noop(&self) -> bool { + match self { + Stmt::Noop(_) => true, + _ => false, + } + } + pub fn is_op(&self) -> bool { match self { Stmt::Noop(_) => false, @@ -265,6 +272,7 @@ impl Expr { pub fn is_pure(&self) -> bool { match self { Expr::Array(expressions, _) => expressions.iter().all(Expr::is_pure), + Expr::And(x, y) | Expr::Or(x, y) | Expr::Index(x, y, _) => x.is_pure() && y.is_pure(), expr => expr.is_constant() || expr.is_identifier(), } } From 2a181e5aab956fcb76e0a6eb9f6f21cc0eaa81ee Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 13 Mar 2020 16:54:00 +0800 Subject: [PATCH 23/24] FIX - fix minus/negative number conflict. --- src/parser.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/parser.rs b/src/parser.rs index 66eec95f..a02baf0f 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -891,7 +891,8 @@ impl<'a> TokenIterator<'a> { } '-' => match self.char_stream.peek() { // Negative number? - Some('0'..='9') => negated = true, + Some('0'..='9') if self.last.is_next_unary() => negated = true, + Some('0'..='9') => return Some((Token::Minus, pos)), Some('=') => { self.char_stream.next(); self.advance(); From f91d068c0f2372ab3d965c6691f0b78d98609653 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 13 Mar 2020 18:31:17 +0800 Subject: [PATCH 24/24] Update api.rs --- src/api.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/api.rs b/src/api.rs index 919e6460..c12d86bf 100644 --- a/src/api.rs +++ b/src/api.rs @@ -112,7 +112,7 @@ impl<'e> Engine<'e> { let mut contents = String::new(); f.read_to_string(&mut contents) - .map_err(|err| EvalAltResult::ErrorReadingScriptFile(filename.into(), err)) + .map_err(|err| EvalAltResult::ErrorReadingScriptFile(path.clone(), err)) .map(|_| contents) } @@ -210,7 +210,7 @@ impl<'e> Engine<'e> { path: PathBuf, retain_functions: bool, ) -> Result<(), EvalAltResult> { - Self::read_file(path).and_then(|_| self.consume(&contents, retain_functions)) + Self::read_file(path).and_then(|contents| self.consume(&contents, retain_functions)) } /// Evaluate a string, but throw away the result and only return error (if any).