diff --git a/Cargo.toml b/Cargo.toml index 24a3d7ff..c1ea251a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rhai" -version = "0.16.0" +version = "0.15.1" edition = "2018" authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung"] description = "Embedded scripting for Rust" diff --git a/README.md b/README.md index 4fb86ec8..99ce9018 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ Features to do checked arithmetic operations); for [`no-std`](#optional-features) builds, a number of additional dependencies are pulled in to provide for functionalities that used to be in `std`. -**Note:** Currently, the version is `0.16.0`, so the language and API's may change before they stabilize. +**Note:** Currently, the version is `0.15.1`, so the language and API's may change before they stabilize. What Rhai doesn't do -------------------- @@ -71,7 +71,7 @@ Install the Rhai crate on [`crates.io`](https::/crates.io/crates/rhai/) by addin ```toml [dependencies] -rhai = "0.16.0" +rhai = "0.15.1" ``` Use the latest released crate version on [`crates.io`](https::/crates.io/crates/rhai/): @@ -179,13 +179,14 @@ A number of examples can be found in the `examples` folder: | Example | Description | | ------------------------------------------------------------------ | --------------------------------------------------------------------------- | -| [`arrays_and_structs`](examples/arrays_and_structs.rs) | demonstrates registering a new type to Rhai and the usage of [arrays] on it | -| [`custom_types_and_methods`](examples/custom_types_and_methods.rs) | shows how to register a type and methods for it | +| [`arrays_and_structs`](examples/arrays_and_structs.rs) | shows how to register a custom Rust type and using [arrays] on it | +| [`custom_types_and_methods`](examples/custom_types_and_methods.rs) | shows how to register a custom Rust type and methods for it | | [`hello`](examples/hello.rs) | simple example that evaluates an expression and prints the result | | [`no_std`](examples/no_std.rs) | example to test out `no-std` builds | | [`reuse_scope`](examples/reuse_scope.rs) | evaluates two pieces of code in separate runs, but using a common [`Scope`] | | [`rhai_runner`](examples/rhai_runner.rs) | runs each filename passed to it as a Rhai script | -| [`simple_fn`](examples/simple_fn.rs) | shows how to register a Rust function to a Rhai [`Engine`] | +| [`simple_fn`](examples/simple_fn.rs) | shows how to register a simple function | +| [`strings`](examples/strings.rs) | shows different ways to register functions taking string arguments | | [`repl`](examples/repl.rs) | a simple REPL, interactively evaluate statements from stdin | Examples can be run with the following command: @@ -314,7 +315,7 @@ Functions declared with `private` are hidden and cannot be called from Rust (see ```rust // Define functions in a script. let ast = engine.compile(true, - r" + r#" // a function with two parameters: String and i64 fn hello(x, y) { x.len + y @@ -334,7 +335,7 @@ let ast = engine.compile(true, private hidden() { throw "you shouldn't see me!"; } - ")?; + "#)?; // A custom scope can also contain any variables/constants available to the functions let mut scope = Scope::new(); @@ -521,7 +522,7 @@ The following primitive types are supported natively: | **Floating-point number** (disabled with [`no_float`]) | `f32`, `f64` _(default)_ | `"f32"` or `"f64"` | `"123.4567"` etc. | | **Boolean value** | `bool` | `"bool"` | `"true"` or `"false"` | | **Unicode character** | `char` | `"char"` | `"A"`, `"x"` etc. | -| **Immutable Unicode string** | `rhai::ImmutableString` (implemented as `Rc` or `Arc`, _not_ `&str`) | `"string"` | `"hello"` etc. | +| **Immutable Unicode string** | `rhai::ImmutableString` (implemented as `Rc` or `Arc`) | `"string"` | `"hello"` etc. | | **Array** (disabled with [`no_index`]) | `rhai::Array` | `"array"` | `"[ ?, ?, ? ]"` | | **Object map** (disabled with [`no_object`]) | `rhai::Map` | `"map"` | `#{ "a": 1, "b": 2 }` | | **Timestamp** (implemented in the [`BasicTimePackage`](#packages)) | `std::time::Instant` | `"timestamp"` | _not supported_ | @@ -1038,13 +1039,13 @@ struct TestStruct { field: String } -// Remember Rhai uses 'ImmutableString' instead of 'String' impl TestStruct { - fn get_field(&mut self) -> ImmutableString { - // Make an 'ImmutableString' from a 'String' - self.field.into(0) + // Returning a 'String' is OK - Rhai converts it into 'ImmutableString' + fn get_field(&mut self) -> String { + self.field.clone() } + // Remember Rhai uses 'ImmutableString' or '&str' instead of 'String' fn set_field(&mut self, new_val: ImmutableString) { // Get a 'String' from an 'ImmutableString' self.field = (*new_val).clone(); diff --git a/RELEASES.md b/RELEASES.md index 3622167a..011c5c80 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,9 +1,12 @@ Rhai Release Notes ================== -Version 0.16.0 +Version 0.15.1 ============== +This is a minor release which enables updating indexers (via registered indexer setters) and supports functions +with `&str` parameters (maps transparently to `ImmutableString`). + Breaking changes ---------------- @@ -14,13 +17,15 @@ Breaking changes New features ------------ -* Indexers are now split into getters ans setters (which now support updates). The API is split into `Engine::register_indexer_get` and `Engine::register_indexer_set` with `Engine::register_indexer_get_set` being a shorthand. Similarly, `Module::set_indexer_get_fn` and `Module::set_indexer_set_fn` are added. +* Indexers are now split into getters and setters (which now support updates). The API is split into `Engine::register_indexer_get` and `Engine::register_indexer_set` with `Engine::register_indexer_get_set` being a shorthand. Similarly, `Module::set_indexer_get_fn` and `Module::set_indexer_set_fn` are added. * `Engine:register_fn` and `Engine:register_result_fn` accepts functions that take parameters of type `&str` (immutable string slice), which maps directly to `ImmutableString`. This is to avoid needing wrappers for functions taking string parameters. Version 0.15.0 ============== +This version uses immutable strings (`ImmutableString` type) and built-in operator functions (e.g. `+`, `>`, `+=`) to improve speed, plus some bug fixes. + Regression fix -------------- diff --git a/examples/strings.rs b/examples/strings.rs new file mode 100644 index 00000000..e9d53016 --- /dev/null +++ b/examples/strings.rs @@ -0,0 +1,77 @@ +///! This example registers a variety of functions that operate on strings. +///! Remember to use `ImmutableString` or `&str` instead of `String` as parameters. +use rhai::{Engine, EvalAltResult, ImmutableString, RegisterFn, Scope, INT}; +use std::io::{stdin, stdout, Write}; + +/// Trim whitespace from a string. The original string argument is changed. +/// +/// This version uses `&mut ImmutableString` +fn trim_string(s: &mut ImmutableString) { + *s = s.trim().into(); +} + +/// Notice this is different from the built-in Rhai 'len' function for strings +/// which counts the actual number of Unicode _characters_ in a string. +/// This version simply counts the number of _bytes_ in the UTF-8 representation. +/// +/// This version uses `&str`. +fn count_string_bytes(s: &str) -> INT { + s.len() as INT +} + +/// This version uses `ImmutableString` and `&str`. +fn find_substring(s: ImmutableString, sub: &str) -> INT { + s.as_str().find(sub).map(|x| x as INT).unwrap_or(-1) +} + +fn main() -> Result<(), Box> { + // Create a `raw` Engine with no built-in string functions. + let mut engine = Engine::new_raw(); + + // Register string functions + engine.register_fn("trim", trim_string); + engine.register_fn("len", count_string_bytes); + engine.register_fn("index_of", find_substring); + + // Register string functions using closures + engine.register_fn("display", |label: &str, x: INT| { + println!("{}: {}", label, x) + }); + engine.register_fn("display", |label: ImmutableString, x: &str| { + println!(r#"{}: "{}""#, label, x) // Quote the input string + }); + + let mut scope = Scope::new(); + let mut input = String::new(); + + loop { + scope.clear(); + + println!("Type something. Press Ctrl-C to exit."); + print!("strings> "); + stdout().flush().expect("couldn't flush stdout"); + + input.clear(); + + if let Err(err) = stdin().read_line(&mut input) { + panic!("input error: {}", err); + } + + scope.push("x", input.clone()); + + println!("Line: {}", input.replace('\r', "\\r").replace('\n', "\\n")); + + engine.consume_with_scope( + &mut scope, + r#" + display("Length", x.len()); + x.trim(); + display("Trimmed", x); + display("Trimmed Length", x.len()); + display("Index of \"!!!\"", x.index_of("!!!")); + "#, + )?; + + println!(); + } +} diff --git a/src/any.rs b/src/any.rs index 0b6d81d2..a50d6820 100644 --- a/src/any.rs +++ b/src/any.rs @@ -1,5 +1,6 @@ //! Helper module which defines the `Any` trait to to allow dynamic value handling. +use crate::fn_native::SendSync; use crate::module::Module; use crate::parser::{ImmutableString, INT}; use crate::r#unsafe::{unsafe_cast_box, unsafe_try_cast}; @@ -54,31 +55,6 @@ pub trait Variant: Any { fn _closed(&self) -> _Private; } -#[cfg(not(feature = "sync"))] -impl Variant for T { - fn as_any(&self) -> &dyn Any { - self as &dyn Any - } - fn as_mut_any(&mut self) -> &mut dyn Any { - self as &mut dyn Any - } - fn as_box_any(self: Box) -> Box { - self as Box - } - fn type_name(&self) -> &'static str { - type_name::() - } - fn into_dynamic(self) -> Dynamic { - Dynamic::from(self) - } - fn clone_into_dynamic(&self) -> Dynamic { - Dynamic::from(self.clone()) - } - fn _closed(&self) -> _Private { - _Private - } -} - /// Trait to represent any type. /// /// `From<_>` is implemented for `i64` (`i32` if `only_i32`), `f64` (if not `no_float`), @@ -108,8 +84,7 @@ pub trait Variant: Any + Send + Sync { fn _closed(&self) -> _Private; } -#[cfg(feature = "sync")] -impl Variant for T { +impl Variant for T { fn as_any(&self) -> &dyn Any { self as &dyn Any } @@ -227,17 +202,17 @@ impl fmt::Display for Dynamic { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match &self.0 { Union::Unit(_) => write!(f, ""), - Union::Bool(value) => write!(f, "{}", value), - Union::Str(value) => write!(f, "{}", value), - Union::Char(value) => write!(f, "{}", value), - Union::Int(value) => write!(f, "{}", value), + Union::Bool(value) => fmt::Display::fmt(value, f), + Union::Str(value) => fmt::Display::fmt(value, f), + Union::Char(value) => fmt::Display::fmt(value, f), + Union::Int(value) => fmt::Display::fmt(value, f), #[cfg(not(feature = "no_float"))] - Union::Float(value) => write!(f, "{}", value), + Union::Float(value) => fmt::Display::fmt(value, f), #[cfg(not(feature = "no_index"))] - Union::Array(value) => write!(f, "{:?}", value), + Union::Array(value) => fmt::Debug::fmt(value, f), #[cfg(not(feature = "no_object"))] Union::Map(value) => write!(f, "#{:?}", value), - Union::Module(value) => write!(f, "{:?}", value), + Union::Module(value) => fmt::Debug::fmt(value, f), #[cfg(not(feature = "no_std"))] Union::Variant(value) if value.is::() => write!(f, ""), @@ -249,18 +224,18 @@ impl fmt::Display for Dynamic { impl fmt::Debug for Dynamic { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match &self.0 { - Union::Unit(value) => write!(f, "{:?}", value), - Union::Bool(value) => write!(f, "{:?}", value), - Union::Str(value) => write!(f, "{:?}", value), - Union::Char(value) => write!(f, "{:?}", value), - Union::Int(value) => write!(f, "{:?}", value), + Union::Unit(value) => fmt::Debug::fmt(value, f), + Union::Bool(value) => fmt::Debug::fmt(value, f), + Union::Str(value) => fmt::Debug::fmt(value, f), + Union::Char(value) => fmt::Debug::fmt(value, f), + Union::Int(value) => fmt::Debug::fmt(value, f), #[cfg(not(feature = "no_float"))] - Union::Float(value) => write!(f, "{:?}", value), + Union::Float(value) => fmt::Debug::fmt(value, f), #[cfg(not(feature = "no_index"))] - Union::Array(value) => write!(f, "{:?}", value), + Union::Array(value) => fmt::Debug::fmt(value, f), #[cfg(not(feature = "no_object"))] Union::Map(value) => write!(f, "#{:?}", value), - Union::Module(value) => write!(f, "{:?}", value), + Union::Module(value) => fmt::Debug::fmt(value, f), #[cfg(not(feature = "no_std"))] Union::Variant(value) if value.is::() => write!(f, ""), diff --git a/src/engine.rs b/src/engine.rs index b62265e5..43d32b42 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -200,8 +200,8 @@ pub fn get_script_function_by_signature<'a>( public_only: bool, ) -> Option<&'a ScriptFnDef> { // Qualifiers (none) + function name + number of arguments. - let hash_fn_def = calc_fn_hash(empty(), name, params, empty()); - let func = module.get_fn(hash_fn_def)?; + let hash_script = calc_fn_hash(empty(), name, params, empty()); + let func = module.get_fn(hash_script)?; if !func.is_script() { return None; } @@ -228,7 +228,7 @@ pub fn get_script_function_by_signature<'a>( /// # } /// ``` /// -/// Currently, `Engine` is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`. +/// Currently, `Engine` is neither `Send` nor `Sync`. Use the `sync` feature to make it `Send + Sync`. pub struct Engine { /// A module containing all functions directly loaded into the Engine. pub(crate) global_module: Module, @@ -524,7 +524,7 @@ impl Engine { state: &mut State, lib: &Module, fn_name: &str, - hashes: (u64, u64), + (hash_fn, hash_script): (u64, u64), args: &mut FnCallArgs, is_ref: bool, def_val: Option<&Dynamic>, @@ -532,7 +532,7 @@ impl Engine { ) -> Result<(Dynamic, bool), Box> { self.inc_operations(state)?; - let native_only = hashes.1 == 0; + let native_only = hash_script == 0; // Check for stack overflow #[cfg(not(feature = "no_function"))] @@ -587,14 +587,14 @@ impl Engine { // Then search packages // NOTE: We skip script functions for global_module and packages, and native functions for lib let func = if !native_only { - lib.get_fn(hashes.1) //.or_else(|| lib.get_fn(hashes.0)) + lib.get_fn(hash_script) //.or_else(|| lib.get_fn(hash_fn)) } else { None } - //.or_else(|| self.global_module.get_fn(hashes.1)) - .or_else(|| self.global_module.get_fn(hashes.0)) - //.or_else(|| self.packages.get_fn(hashes.1)) - .or_else(|| self.packages.get_fn(hashes.0)); + //.or_else(|| self.global_module.get_fn(hash_script)) + .or_else(|| self.global_module.get_fn(hash_fn)) + //.or_else(|| self.packages.get_fn(hash_script)) + .or_else(|| self.packages.get_fn(hash_fn)); if let Some(func) = func { // Calling pure function in method-call? @@ -784,18 +784,18 @@ impl Engine { } // Has a system function an override? - fn has_override(&self, lib: &Module, hashes: (u64, u64)) -> bool { + fn has_override(&self, lib: &Module, (hash_fn, hash_script): (u64, u64)) -> bool { // NOTE: We skip script functions for global_module and packages, and native functions for lib // First check script-defined functions - lib.contains_fn(hashes.1) - //|| lib.contains_fn(hashes.0) + lib.contains_fn(hash_script) + //|| lib.contains_fn(hash_fn) // Then check registered functions - //|| self.global_module.contains_fn(hashes.1) - || self.global_module.contains_fn(hashes.0) + //|| self.global_module.contains_fn(hash_script) + || self.global_module.contains_fn(hash_fn) // Then check packages - //|| self.packages.contains_fn(hashes.1) - || self.packages.contains_fn(hashes.0) + //|| self.packages.contains_fn(hash_script) + || self.packages.contains_fn(hash_fn) } /// Perform an actual function call, taking care of special functions @@ -812,7 +812,7 @@ impl Engine { lib: &Module, fn_name: &str, native_only: bool, - hash_fn_def: u64, + hash_script: u64, args: &mut FnCallArgs, is_ref: bool, def_val: Option<&Dynamic>, @@ -821,7 +821,7 @@ impl Engine { // Qualifiers (none) + function name + number of arguments + argument `TypeId`'s. let arg_types = args.iter().map(|a| a.type_id()); let hash_fn = calc_fn_hash(empty(), fn_name, args.len(), arg_types); - let hashes = (hash_fn, if native_only { 0 } else { hash_fn_def }); + let hashes = (hash_fn, if native_only { 0 } else { hash_script }); match fn_name { // type_of @@ -1412,7 +1412,7 @@ impl Engine { Expr::Property(_) => unreachable!(), // Statement block - Expr::Stmt(stmt) => self.eval_stmt(scope, state, lib, &stmt.0, level), + Expr::Stmt(x) => self.eval_stmt(scope, state, lib, &x.0, level), // var op= rhs Expr::Assignment(x) if matches!(x.0, Expr::Variable(_)) => { @@ -1625,7 +1625,7 @@ impl Engine { // Module-qualified function call Expr::FnCall(x) if x.1.is_some() => { - let ((name, _, pos), modules, hash_fn_def, args_expr, def_val) = x.as_ref(); + let ((name, _, pos), modules, hash_script, args_expr, def_val) = x.as_ref(); let modules = modules.as_ref().unwrap(); let mut arg_values = args_expr @@ -1650,13 +1650,13 @@ impl Engine { }; // First search in script-defined functions (can override built-in) - let func = match module.get_qualified_fn(name, *hash_fn_def) { + let func = match module.get_qualified_fn(name, *hash_script) { Err(err) if matches!(*err, EvalAltResult::ErrorFunctionNotFound(_, _)) => { // Then search in Rust functions self.inc_operations(state) .map_err(|err| EvalAltResult::new_position(err, *pos))?; - // Rust functions are indexed in two steps: + // Qualified Rust functions are indexed in two steps: // 1) Calculate a hash in a similar manner to script-defined functions, // i.e. qualifiers + function name + number of arguments. // 2) Calculate a second hash with no qualifiers, empty function name, @@ -1664,9 +1664,9 @@ impl Engine { let hash_fn_args = calc_fn_hash(empty(), "", 0, args.iter().map(|a| a.type_id())); // 3) The final hash is the XOR of the two hashes. - let hash_fn_native = *hash_fn_def ^ hash_fn_args; + let hash_qualified_fn = *hash_script ^ hash_fn_args; - module.get_qualified_fn(name, hash_fn_native) + module.get_qualified_fn(name, hash_qualified_fn) } r => r, }; diff --git a/src/fn_native.rs b/src/fn_native.rs index acc4eb94..3eb5beaa 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -89,9 +89,9 @@ impl fmt::Debug for CallableFunction { match self { Self::Pure(_) => write!(f, "NativePureFunction"), Self::Method(_) => write!(f, "NativeMethod"), - Self::Iterator(_) => write!(f, "IteratorFunction"), + Self::Iterator(_) => write!(f, "NativeIterator"), Self::Plugin(_) => write!(f, "PluginFunction"), - Self::Script(fn_def) => write!(f, "{:?}", fn_def), + Self::Script(fn_def) => fmt::Debug::fmt(fn_def, f), } } } diff --git a/src/fn_register.rs b/src/fn_register.rs index e68e18d4..e40b4f9a 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -4,7 +4,7 @@ use crate::any::{Dynamic, Variant}; use crate::engine::Engine; -use crate::fn_native::{CallableFunction, FnAny, FnCallArgs}; +use crate::fn_native::{CallableFunction, FnAny, FnCallArgs, SendSync}; use crate::parser::FnAccess; use crate::plugin::Plugin; use crate::result::EvalAltResult; @@ -264,13 +264,7 @@ macro_rules! def_register { // ^ dereferencing function impl< $($par: Variant + Clone,)* - - #[cfg(feature = "sync")] - FN: Fn($($param),*) -> RET + Send + Sync + 'static, - - #[cfg(not(feature = "sync"))] - FN: Fn($($param),*) -> RET + 'static, - + FN: Fn($($param),*) -> RET + SendSync + 'static, RET: Variant + Clone > RegisterFn for Engine { @@ -284,11 +278,7 @@ macro_rules! def_register { impl< $($par: Variant + Clone,)* - - #[cfg(feature = "sync")] - FN: Fn($($param),*) -> Result> + Send + Sync + 'static, - #[cfg(not(feature = "sync"))] - FN: Fn($($param),*) -> Result> + 'static, + FN: Fn($($param),*) -> Result> + SendSync + 'static, > RegisterResultFn for Engine { fn register_result_fn(&mut self, name: &str, f: FN) { diff --git a/src/module.rs b/src/module.rs index 2a722e3b..91fa3388 100644 --- a/src/module.rs +++ b/src/module.rs @@ -65,7 +65,7 @@ impl fmt::Debug for Module { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, - "", + "", self.variables, self.functions.len(), ) @@ -187,9 +187,9 @@ impl Module { /// If there is an existing function of the same name and number of arguments, it is replaced. pub(crate) fn set_script_fn(&mut self, fn_def: ScriptFnDef) { // None + function name + number of arguments. - let hash_fn_def = calc_fn_hash(empty(), &fn_def.name, fn_def.params.len(), empty()); + let hash_script = calc_fn_hash(empty(), &fn_def.name, fn_def.params.len(), empty()); self.functions.insert( - hash_fn_def, + hash_script, ( fn_def.name.to_string(), fn_def.access, @@ -321,8 +321,7 @@ impl Module { pub fn set_fn_0( &mut self, name: impl Into, - #[cfg(not(feature = "sync"))] func: impl Fn() -> FuncReturn + 'static, - #[cfg(feature = "sync")] func: impl Fn() -> FuncReturn + Send + Sync + 'static, + func: impl Fn() -> FuncReturn + SendSync + 'static, ) -> u64 { let f = move |_: &mut FnCallArgs| func().map(Dynamic::from); let args = []; @@ -350,8 +349,7 @@ impl Module { pub fn set_fn_1( &mut self, name: impl Into, - #[cfg(not(feature = "sync"))] func: impl Fn(A) -> FuncReturn + 'static, - #[cfg(feature = "sync")] func: impl Fn(A) -> FuncReturn + Send + Sync + 'static, + func: impl Fn(A) -> FuncReturn + SendSync + 'static, ) -> u64 { let f = move |args: &mut FnCallArgs| func(mem::take(args[0]).cast::()).map(Dynamic::from); @@ -380,8 +378,7 @@ impl Module { pub fn set_fn_1_mut( &mut self, name: impl Into, - #[cfg(not(feature = "sync"))] func: impl Fn(&mut A) -> FuncReturn + 'static, - #[cfg(feature = "sync")] func: impl Fn(&mut A) -> FuncReturn + Send + Sync + 'static, + func: impl Fn(&mut A) -> FuncReturn + SendSync + 'static, ) -> u64 { let f = move |args: &mut FnCallArgs| { func(args[0].downcast_mut::().unwrap()).map(Dynamic::from) @@ -412,8 +409,7 @@ impl Module { pub fn set_getter_fn( &mut self, name: impl Into, - #[cfg(not(feature = "sync"))] func: impl Fn(&mut A) -> FuncReturn + 'static, - #[cfg(feature = "sync")] func: impl Fn(&mut A) -> FuncReturn + Send + Sync + 'static, + func: impl Fn(&mut A) -> FuncReturn + SendSync + 'static, ) -> u64 { self.set_fn_1_mut(make_getter(&name.into()), func) } @@ -436,8 +432,7 @@ impl Module { pub fn set_fn_2( &mut self, name: impl Into, - #[cfg(not(feature = "sync"))] func: impl Fn(A, B) -> FuncReturn + 'static, - #[cfg(feature = "sync")] func: impl Fn(A, B) -> FuncReturn + Send + Sync + 'static, + func: impl Fn(A, B) -> FuncReturn + SendSync + 'static, ) -> u64 { let f = move |args: &mut FnCallArgs| { let a = mem::take(args[0]).cast::(); @@ -473,8 +468,7 @@ impl Module { pub fn set_fn_2_mut( &mut self, name: impl Into, - #[cfg(not(feature = "sync"))] func: impl Fn(&mut A, B) -> FuncReturn + 'static, - #[cfg(feature = "sync")] func: impl Fn(&mut A, B) -> FuncReturn + Send + Sync + 'static, + func: impl Fn(&mut A, B) -> FuncReturn + SendSync + 'static, ) -> u64 { let f = move |args: &mut FnCallArgs| { let b = mem::take(args[1]).cast::(); @@ -512,8 +506,7 @@ impl Module { pub fn set_setter_fn( &mut self, name: impl Into, - #[cfg(not(feature = "sync"))] func: impl Fn(&mut A, B) -> FuncReturn<()> + 'static, - #[cfg(feature = "sync")] func: impl Fn(&mut A, B) -> FuncReturn<()> + Send + Sync + 'static, + func: impl Fn(&mut A, B) -> FuncReturn<()> + SendSync + 'static, ) -> u64 { self.set_fn_2_mut(make_setter(&name.into()), func) } @@ -538,8 +531,7 @@ impl Module { #[cfg(not(feature = "no_index"))] pub fn set_indexer_get_fn( &mut self, - #[cfg(not(feature = "sync"))] func: impl Fn(&mut A, B) -> FuncReturn + 'static, - #[cfg(feature = "sync")] func: impl Fn(&mut A, B) -> FuncReturn + Send + Sync + 'static, + func: impl Fn(&mut A, B) -> FuncReturn + SendSync + 'static, ) -> u64 { self.set_fn_2_mut(FUNC_INDEXER_GET, func) } @@ -567,8 +559,7 @@ impl Module { >( &mut self, name: impl Into, - #[cfg(not(feature = "sync"))] func: impl Fn(A, B, C) -> FuncReturn + 'static, - #[cfg(feature = "sync")] func: impl Fn(A, B, C) -> FuncReturn + Send + Sync + 'static, + func: impl Fn(A, B, C) -> FuncReturn + SendSync + 'static, ) -> u64 { let f = move |args: &mut FnCallArgs| { let a = mem::take(args[0]).cast::(); @@ -610,8 +601,7 @@ impl Module { >( &mut self, name: impl Into, - #[cfg(not(feature = "sync"))] func: impl Fn(&mut A, B, C) -> FuncReturn + 'static, - #[cfg(feature = "sync")] func: impl Fn(&mut A, B, C) -> FuncReturn + Send + Sync + 'static, + func: impl Fn(&mut A, B, C) -> FuncReturn + SendSync + 'static, ) -> u64 { let f = move |args: &mut FnCallArgs| { let b = mem::take(args[1]).cast::(); @@ -648,8 +638,7 @@ impl Module { /// ``` pub fn set_indexer_set_fn( &mut self, - #[cfg(not(feature = "sync"))] func: impl Fn(&mut A, B, A) -> FuncReturn<()> + 'static, - #[cfg(feature = "sync")] func: impl Fn(&mut A, B, A) -> FuncReturn<()> + Send + Sync + 'static, + func: impl Fn(&mut A, B, A) -> FuncReturn<()> + SendSync + 'static, ) -> u64 { let f = move |args: &mut FnCallArgs| { let b = mem::take(args[1]).cast::(); @@ -691,8 +680,7 @@ impl Module { >( &mut self, name: impl Into, - #[cfg(not(feature = "sync"))] func: impl Fn(A, B, C, D) -> FuncReturn + 'static, - #[cfg(feature = "sync")] func: impl Fn(A, B, C, D) -> FuncReturn + Send + Sync + 'static, + func: impl Fn(A, B, C, D) -> FuncReturn + SendSync + 'static, ) -> u64 { let f = move |args: &mut FnCallArgs| { let a = mem::take(args[0]).cast::(); @@ -741,8 +729,7 @@ impl Module { >( &mut self, name: impl Into, - #[cfg(not(feature = "sync"))] func: impl Fn(&mut A, B, C, D) -> FuncReturn + 'static, - #[cfg(feature = "sync")] func: impl Fn(&mut A, B, C, D) -> FuncReturn + Send + Sync + 'static, + func: impl Fn(&mut A, B, C, D) -> FuncReturn + SendSync + 'static, ) -> u64 { let f = move |args: &mut FnCallArgs| { let b = mem::take(args[1]).cast::(); @@ -791,9 +778,9 @@ impl Module { pub(crate) fn get_qualified_fn( &mut self, name: &str, - hash_fn_native: u64, + hash_qualified_fn: u64, ) -> Result<&CallableFunction, Box> { - self.all_functions.get(&hash_fn_native).ok_or_else(|| { + self.all_functions.get(&hash_qualified_fn).ok_or_else(|| { Box::new(EvalAltResult::ErrorFunctionNotFound( name.to_string(), Position::none(), @@ -920,26 +907,26 @@ impl Module { if func.is_script() { let fn_def = func.get_shared_fn_def(); // Qualifiers + function name + number of arguments. - let hash_fn_def = calc_fn_hash( + let hash_qualified_script = calc_fn_hash( qualifiers.iter().map(|&v| v), &fn_def.name, fn_def.params.len(), empty(), ); - functions.push((hash_fn_def, fn_def.into())); + functions.push((hash_qualified_script, fn_def.into())); } else { - // Rust functions are indexed in two steps: + // Qualified Rust functions are indexed in two steps: // 1) Calculate a hash in a similar manner to script-defined functions, // i.e. qualifiers + function name + number of arguments. - let hash_fn_def = + let hash_qualified_script = calc_fn_hash(qualifiers.iter().map(|&v| v), name, params.len(), empty()); // 2) Calculate a second hash with no qualifiers, empty function name, // zero number of arguments, and the actual list of argument `TypeId`'.s let hash_fn_args = calc_fn_hash(empty(), "", 0, params.iter().cloned()); // 3) The final hash is the XOR of the two hashes. - let hash_fn_native = hash_fn_def ^ hash_fn_args; + let hash_qualified_fn = hash_qualified_script ^ hash_fn_args; - functions.push((hash_fn_native, func.clone())); + functions.push((hash_qualified_fn, func.clone())); } } } diff --git a/src/parser.rs b/src/parser.rs index ab8de95c..0cd50b57 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -7,7 +7,7 @@ use crate::error::{LexError, ParseError, ParseErrorType}; use crate::module::{Module, ModuleRef}; use crate::optimize::{optimize_into_ast, OptimizationLevel}; use crate::scope::{EntryType as ScopeEntryType, Scope}; -use crate::token::{Position, Token, TokenIterator}; +use crate::token::{Position, Token, TokenStream}; use crate::utils::{StaticVec, StraightHasherBuilder}; use crate::stdlib::{ @@ -195,10 +195,11 @@ pub enum ReturnType { Exception, } -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Eq, PartialEq, Hash, Default)] struct ParseState { /// Encapsulates a local stack with variable names to simulate an actual runtime scope. stack: Vec<(String, ScopeEntryType)>, + /// Maximum levels of expression nesting. max_expr_depth: usize, } @@ -256,6 +257,41 @@ impl DerefMut for ParseState { } } +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +/// A type that encapsulates all the settings for a particular parsing function. +struct ParseSettings { + /// Current position. + pos: Position, + /// Is the construct being parsed located at global level? + is_global: bool, + /// Is the current position inside a loop? + is_breakable: bool, + /// Is if-expression allowed? + allow_if_expr: bool, + /// Is statement-expression allowed? + allow_stmt_expr: bool, + /// Current expression nesting level. + level: usize, +} + +impl ParseSettings { + /// Create a new `ParseSettings` with one higher expression level. + pub fn level_up(&self) -> Self { + Self { + level: self.level + 1, + ..*self + } + } + /// Make sure that the current level of expression nesting is within the maximum limit. + pub fn ensure_level_within_max_limit(&self, limit: usize) -> Result<(), ParseError> { + if self.level > limit { + Err(PERR::ExprTooDeep.into_err(self.pos)) + } else { + Ok(()) + } + } +} + /// A statement. /// /// Each variant is at most one pointer in size (for speed), @@ -672,7 +708,7 @@ impl Expr { } /// Consume a particular token, checking that it is the expected one. -fn eat_token(input: &mut Peekable, token: Token) -> Position { +fn eat_token(input: &mut TokenStream, token: Token) -> Position { let (t, pos) = input.next().unwrap(); if t != token { @@ -687,7 +723,7 @@ fn eat_token(input: &mut Peekable, token: Token) -> Position { } /// Match a particular token, consuming it if matched. -fn match_token(input: &mut Peekable, token: Token) -> Result { +fn match_token(input: &mut TokenStream, token: Token) -> Result { let (t, _) = input.peek().unwrap(); if *t == token { eat_token(input, token); @@ -698,54 +734,46 @@ fn match_token(input: &mut Peekable, token: Token) -> Result( - input: &mut Peekable>, +fn parse_paren_expr( + input: &mut TokenStream, state: &mut ParseState, - pos: Position, - level: usize, - if_expr: bool, - stmt_expr: bool, + settings: ParseSettings, ) -> Result { - if level > state.max_expr_depth { - return Err(PERR::ExprTooDeep.into_err(pos)); - } + settings.ensure_level_within_max_limit(state.max_expr_depth)?; if match_token(input, Token::RightParen)? { - return Ok(Expr::Unit(pos)); + return Ok(Expr::Unit(settings.pos)); } - let expr = parse_expr(input, state, level + 1, if_expr, stmt_expr)?; + let expr = parse_expr(input, state, settings.level_up())?; match input.next().unwrap() { // ( xxx ) (Token::RightParen, _) => Ok(expr), // ( - (Token::LexError(err), pos) => return Err(PERR::BadInput(err.to_string()).into_err(pos)), + (Token::LexError(err), pos) => { + return Err(PERR::BadInput(err.to_string()).into_err(settings.pos)) + } // ( xxx ??? (_, pos) => Err(PERR::MissingToken( Token::RightParen.into(), "for a matching ( in this expression".into(), ) - .into_err(pos)), + .into_err(settings.pos)), } } /// Parse a function call. -fn parse_call_expr<'a>( - input: &mut Peekable>, +fn parse_call_expr( + input: &mut TokenStream, state: &mut ParseState, id: String, mut modules: Option>, - begin: Position, - level: usize, - if_expr: bool, - stmt_expr: bool, + mut settings: ParseSettings, ) -> Result { - let (token, pos) = input.peek().unwrap(); - - if level > state.max_expr_depth { - return Err(PERR::ExprTooDeep.into_err(*pos)); - } + let (token, token_pos) = input.peek().unwrap(); + settings.pos = *token_pos; + settings.ensure_level_within_max_limit(state.max_expr_depth)?; let mut args = StaticVec::new(); @@ -756,15 +784,15 @@ fn parse_call_expr<'a>( Token::RightParen.into(), format!("to close the arguments list of this function call '{}'", id), ) - .into_err(*pos)) + .into_err(settings.pos)) } // id - Token::LexError(err) => return Err(PERR::BadInput(err.to_string()).into_err(*pos)), + Token::LexError(err) => return Err(PERR::BadInput(err.to_string()).into_err(settings.pos)), // id() Token::RightParen => { eat_token(input, Token::RightParen); - let hash_fn_def = if let Some(modules) = modules.as_mut() { + let hash_script = if let Some(modules) = modules.as_mut() { modules.set_index(state.find_module(&modules.get(0).0)); // Rust functions are indexed in two steps: @@ -781,9 +809,9 @@ fn parse_call_expr<'a>( }; return Ok(Expr::FnCall(Box::new(( - (id.into(), false, begin), + (id.into(), false, settings.pos), modules, - hash_fn_def, + hash_script, args, None, )))); @@ -792,15 +820,17 @@ fn parse_call_expr<'a>( _ => (), } + let settings = settings.level_up(); + loop { - args.push(parse_expr(input, state, level + 1, if_expr, stmt_expr)?); + args.push(parse_expr(input, state, settings)?); match input.peek().unwrap() { // id(...args) (Token::RightParen, _) => { eat_token(input, Token::RightParen); - let hash_fn_def = if let Some(modules) = modules.as_mut() { + let hash_script = if let Some(modules) = modules.as_mut() { modules.set_index(state.find_module(&modules.get(0).0)); // Rust functions are indexed in two steps: @@ -817,9 +847,9 @@ fn parse_call_expr<'a>( }; return Ok(Expr::FnCall(Box::new(( - (id.into(), false, begin), + (id.into(), false, settings.pos), modules, - hash_fn_def, + hash_script, args, None, )))); @@ -854,20 +884,15 @@ fn parse_call_expr<'a>( /// Parse an indexing chain. /// Indexing binds to the right, so this call parses all possible levels of indexing following in the input. -fn parse_index_chain<'a>( - input: &mut Peekable>, +fn parse_index_chain( + input: &mut TokenStream, state: &mut ParseState, lhs: Expr, - pos: Position, - level: usize, - if_expr: bool, - stmt_expr: bool, + settings: ParseSettings, ) -> Result { - if level > state.max_expr_depth { - return Err(PERR::ExprTooDeep.into_err(pos)); - } + settings.ensure_level_within_max_limit(state.max_expr_depth)?; - let idx_expr = parse_expr(input, state, level + 1, if_expr, stmt_expr)?; + let idx_expr = parse_expr(input, state, settings.level_up())?; // Check type of indexing - must be integer or string match &idx_expr { @@ -1008,17 +1033,9 @@ fn parse_index_chain<'a>( (Token::LeftBracket, _) => { let idx_pos = eat_token(input, Token::LeftBracket); // Recursively parse the indexing chain, right-binding each - let idx_expr = parse_index_chain( - input, - state, - idx_expr, - idx_pos, - level + 1, - if_expr, - stmt_expr, - )?; + let idx_expr = parse_index_chain(input, state, idx_expr, settings.level_up())?; // Indexing binds to right - Ok(Expr::Index(Box::new((lhs, idx_expr, pos)))) + Ok(Expr::Index(Box::new((lhs, idx_expr, settings.pos)))) } // Otherwise terminate the indexing chain _ => { @@ -1027,9 +1044,9 @@ fn parse_index_chain<'a>( // inside brackets to be mis-parsed as another level of indexing, or a // dot expression/function call to be mis-parsed as following the indexing chain. Expr::Index(_) | Expr::Dot(_) | Expr::FnCall(_) => Ok(Expr::Index( - Box::new((lhs, Expr::Expr(Box::new(idx_expr)), pos)), + Box::new((lhs, Expr::Expr(Box::new(idx_expr)), settings.pos)), )), - _ => Ok(Expr::Index(Box::new((lhs, idx_expr, pos)))), + _ => Ok(Expr::Index(Box::new((lhs, idx_expr, settings.pos)))), } } } @@ -1044,23 +1061,18 @@ fn parse_index_chain<'a>( } /// Parse an array literal. -fn parse_array_literal<'a>( - input: &mut Peekable>, +fn parse_array_literal( + input: &mut TokenStream, state: &mut ParseState, - pos: Position, - level: usize, - if_expr: bool, - stmt_expr: bool, + settings: ParseSettings, ) -> Result { - if level > state.max_expr_depth { - return Err(PERR::ExprTooDeep.into_err(pos)); - } + settings.ensure_level_within_max_limit(state.max_expr_depth)?; let mut arr = StaticVec::new(); if !match_token(input, Token::RightBracket)? { while !input.peek().unwrap().0.is_eof() { - let expr = parse_expr(input, state, level + 1, if_expr, stmt_expr)?; + let expr = parse_expr(input, state, settings.level_up())?; arr.push(expr); match input.peek().unwrap() { @@ -1090,21 +1102,16 @@ fn parse_array_literal<'a>( } } - Ok(Expr::Array(Box::new((arr, pos)))) + Ok(Expr::Array(Box::new((arr, settings.pos)))) } /// Parse a map literal. -fn parse_map_literal<'a>( - input: &mut Peekable>, +fn parse_map_literal( + input: &mut TokenStream, state: &mut ParseState, - pos: Position, - level: usize, - if_expr: bool, - stmt_expr: bool, + settings: ParseSettings, ) -> Result { - if level > state.max_expr_depth { - return Err(PERR::ExprTooDeep.into_err(pos)); - } + settings.ensure_level_within_max_limit(state.max_expr_depth)?; let mut map = StaticVec::new(); @@ -1150,7 +1157,7 @@ fn parse_map_literal<'a>( } }; - let expr = parse_expr(input, state, level + 1, if_expr, stmt_expr)?; + let expr = parse_expr(input, state, settings.level_up())?; map.push(((name, pos), expr)); @@ -1193,56 +1200,51 @@ fn parse_map_literal<'a>( }) .map_err(|(key, pos)| PERR::DuplicatedProperty(key.to_string()).into_err(pos))?; - Ok(Expr::Map(Box::new((map, pos)))) + Ok(Expr::Map(Box::new((map, settings.pos)))) } /// Parse a primary expression. -fn parse_primary<'a>( - input: &mut Peekable>, +fn parse_primary( + input: &mut TokenStream, state: &mut ParseState, - level: usize, - if_expr: bool, - stmt_expr: bool, + mut settings: ParseSettings, ) -> Result { - let (token, pos) = input.peek().unwrap(); - let pos = *pos; - - if level > state.max_expr_depth { - return Err(PERR::ExprTooDeep.into_err(pos)); - } + let (token, pos1) = input.peek().unwrap(); + settings.pos = *pos1; + settings.ensure_level_within_max_limit(state.max_expr_depth)?; let (token, _) = match token { // { - block statement as expression - Token::LeftBrace if stmt_expr => { - return parse_block(input, state, false, level + 1, if_expr, stmt_expr) - .map(|block| Expr::Stmt(Box::new((block, pos)))); + Token::LeftBrace if settings.allow_stmt_expr => { + return parse_block(input, state, settings.level_up()) + .map(|block| Expr::Stmt(Box::new((block, settings.pos)))) } - Token::EOF => return Err(PERR::UnexpectedEOF.into_err(pos)), + Token::EOF => return Err(PERR::UnexpectedEOF.into_err(settings.pos)), _ => input.next().unwrap(), }; let mut root_expr = match token { - Token::IntegerConstant(x) => Expr::IntegerConstant(Box::new((x, pos))), + Token::IntegerConstant(x) => Expr::IntegerConstant(Box::new((x, settings.pos))), #[cfg(not(feature = "no_float"))] - Token::FloatConstant(x) => Expr::FloatConstant(Box::new((x, pos))), - Token::CharConstant(c) => Expr::CharConstant(Box::new((c, pos))), - Token::StringConst(s) => Expr::StringConstant(Box::new((s.into(), pos))), + Token::FloatConstant(x) => Expr::FloatConstant(Box::new((x, settings.pos))), + Token::CharConstant(c) => Expr::CharConstant(Box::new((c, settings.pos))), + Token::StringConst(s) => Expr::StringConstant(Box::new((s.into(), settings.pos))), Token::Identifier(s) => { let index = state.find(&s); - Expr::Variable(Box::new(((s, pos), None, 0, index))) + Expr::Variable(Box::new(((s, settings.pos), None, 0, index))) } - Token::LeftParen => parse_paren_expr(input, state, pos, level + 1, if_expr, stmt_expr)?, + Token::LeftParen => parse_paren_expr(input, state, settings.level_up())?, #[cfg(not(feature = "no_index"))] - Token::LeftBracket => { - parse_array_literal(input, state, pos, level + 1, if_expr, stmt_expr)? - } + Token::LeftBracket => parse_array_literal(input, state, settings.level_up())?, #[cfg(not(feature = "no_object"))] - Token::MapStart => parse_map_literal(input, state, pos, level + 1, if_expr, stmt_expr)?, - Token::True => Expr::True(pos), - Token::False => Expr::False(pos), - Token::LexError(err) => return Err(PERR::BadInput(err.to_string()).into_err(pos)), + Token::MapStart => parse_map_literal(input, state, settings.level_up())?, + Token::True => Expr::True(settings.pos), + Token::False => Expr::False(settings.pos), + Token::LexError(err) => return Err(PERR::BadInput(err.to_string()).into_err(settings.pos)), token => { - return Err(PERR::BadInput(format!("Unexpected '{}'", token.syntax())).into_err(pos)) + return Err( + PERR::BadInput(format!("Unexpected '{}'", token.syntax())).into_err(settings.pos) + ) } }; @@ -1260,16 +1262,7 @@ fn parse_primary<'a>( // Function call (Expr::Variable(x), Token::LeftParen) => { let ((name, pos), modules, _, _) = *x; - parse_call_expr( - input, - state, - name, - modules, - pos, - level + 1, - if_expr, - stmt_expr, - )? + parse_call_expr(input, state, name, modules, settings.level_up())? } (Expr::Property(_), _) => unreachable!(), // module access @@ -1291,7 +1284,8 @@ fn parse_primary<'a>( // Indexing #[cfg(not(feature = "no_index"))] (expr, Token::LeftBracket) => { - parse_index_chain(input, state, expr, token_pos, level + 1, if_expr, stmt_expr)? + settings.pos = token_pos; + parse_index_chain(input, state, expr, settings.level_up())? } // Unknown postfix operator (expr, token) => panic!( @@ -1319,31 +1313,26 @@ fn parse_primary<'a>( } /// Parse a potential unary operator. -fn parse_unary<'a>( - input: &mut Peekable>, +fn parse_unary( + input: &mut TokenStream, state: &mut ParseState, - level: usize, - if_expr: bool, - stmt_expr: bool, + mut settings: ParseSettings, ) -> Result { - let (token, pos) = input.peek().unwrap(); - let pos = *pos; - - if level > state.max_expr_depth { - return Err(PERR::ExprTooDeep.into_err(pos)); - } + let (token, token_pos) = input.peek().unwrap(); + settings.pos = *token_pos; + settings.ensure_level_within_max_limit(state.max_expr_depth)?; match token { // If statement is allowed to act as expressions - Token::If if if_expr => Ok(Expr::Stmt(Box::new(( - parse_if(input, state, false, level + 1, if_expr, stmt_expr)?, - pos, + Token::If if settings.allow_if_expr => Ok(Expr::Stmt(Box::new(( + parse_if(input, state, settings.level_up())?, + settings.pos, )))), // -expr Token::UnaryMinus => { let pos = eat_token(input, Token::UnaryMinus); - match parse_unary(input, state, level + 1, if_expr, stmt_expr)? { + match parse_unary(input, state, settings.level_up())? { // Negative integer Expr::IntegerConstant(x) => { let (num, pos) = *x; @@ -1392,13 +1381,13 @@ fn parse_unary<'a>( // +expr Token::UnaryPlus => { eat_token(input, Token::UnaryPlus); - parse_unary(input, state, level + 1, if_expr, stmt_expr) + parse_unary(input, state, settings.level_up()) } // !expr Token::Bang => { let pos = eat_token(input, Token::Bang); let mut args = StaticVec::new(); - let expr = parse_primary(input, state, level + 1, if_expr, stmt_expr)?; + let expr = parse_primary(input, state, settings.level_up())?; args.push(expr); let op = "!"; @@ -1413,9 +1402,9 @@ fn parse_unary<'a>( )))) } // - Token::EOF => Err(PERR::UnexpectedEOF.into_err(pos)), + Token::EOF => Err(PERR::UnexpectedEOF.into_err(settings.pos)), // All other tokens - _ => parse_primary(input, state, level + 1, if_expr, stmt_expr), + _ => parse_primary(input, state, settings.level_up()), } } @@ -1470,20 +1459,15 @@ fn make_assignment_stmt<'a>( } /// Parse an operator-assignment expression. -fn parse_op_assignment_stmt<'a>( - input: &mut Peekable>, +fn parse_op_assignment_stmt( + input: &mut TokenStream, state: &mut ParseState, lhs: Expr, - level: usize, - if_expr: bool, - stmt_expr: bool, + mut settings: ParseSettings, ) -> Result { - let (token, pos) = input.peek().unwrap(); - let pos = *pos; - - if level > state.max_expr_depth { - return Err(PERR::ExprTooDeep.into_err(pos)); - } + let (token, token_pos) = input.peek().unwrap(); + settings.pos = *token_pos; + settings.ensure_level_within_max_limit(state.max_expr_depth)?; let op = match token { Token::Equals => "".into(), @@ -1504,7 +1488,7 @@ fn parse_op_assignment_stmt<'a>( }; let (_, pos) = input.next().unwrap(); - let rhs = parse_expr(input, state, level + 1, if_expr, stmt_expr)?; + let rhs = parse_expr(input, state, settings.level_up())?; make_assignment_stmt(op, state, lhs, rhs, pos) } @@ -1713,18 +1697,15 @@ fn make_in_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result( - input: &mut Peekable>, +fn parse_binary_op( + input: &mut TokenStream, state: &mut ParseState, parent_precedence: u8, lhs: Expr, - mut level: usize, - if_expr: bool, - stmt_expr: bool, + mut settings: ParseSettings, ) -> Result { - if level > state.max_expr_depth { - return Err(PERR::ExprTooDeep.into_err(lhs.position())); - } + settings.pos = lhs.position(); + settings.ensure_level_within_max_limit(state.max_expr_depth)?; let mut root = lhs; @@ -1741,24 +1722,22 @@ fn parse_binary_op<'a>( let (op_token, pos) = input.next().unwrap(); - let rhs = parse_unary(input, state, level, if_expr, stmt_expr)?; + let rhs = parse_unary(input, state, settings)?; let next_precedence = input.peek().unwrap().0.precedence(); // Bind to right if the next operator has higher precedence // If same precedence, then check if the operator binds right let rhs = if (precedence == next_precedence && bind_right) || precedence < next_precedence { - parse_binary_op(input, state, precedence, rhs, level, if_expr, stmt_expr)? + parse_binary_op(input, state, precedence, rhs, settings)? } else { // Otherwise bind to left (even if next operator has the same precedence) rhs }; - level += 1; - - if level > state.max_expr_depth { - return Err(PERR::ExprTooDeep.into_err(pos)); - } + settings = settings.level_up(); + settings.pos = pos; + settings.ensure_level_within_max_limit(state.max_expr_depth)?; let cmp_def = Some(false.into()); let op = op_token.syntax(); @@ -1832,28 +1811,20 @@ fn parse_binary_op<'a>( } /// Parse an expression. -fn parse_expr<'a>( - input: &mut Peekable>, +fn parse_expr( + input: &mut TokenStream, state: &mut ParseState, - level: usize, - if_expr: bool, - stmt_expr: bool, + mut settings: ParseSettings, ) -> Result { - let (_, pos) = input.peek().unwrap(); + settings.pos = input.peek().unwrap().1; + settings.ensure_level_within_max_limit(state.max_expr_depth)?; - if level > state.max_expr_depth { - return Err(PERR::ExprTooDeep.into_err(*pos)); - } - - let lhs = parse_unary(input, state, level + 1, if_expr, stmt_expr)?; - parse_binary_op(input, state, 1, lhs, level + 1, if_expr, stmt_expr) + let lhs = parse_unary(input, state, settings.level_up())?; + parse_binary_op(input, state, 1, lhs, settings.level_up()) } /// Make sure that the expression is not a statement expression (i.e. wrapped in `{}`). -fn ensure_not_statement_expr<'a>( - input: &mut Peekable>, - type_name: &str, -) -> Result<(), ParseError> { +fn ensure_not_statement_expr(input: &mut TokenStream, type_name: &str) -> Result<(), ParseError> { match input.peek().unwrap() { // Disallow statement expressions (Token::LeftBrace, pos) | (Token::EOF, pos) => { @@ -1865,7 +1836,7 @@ fn ensure_not_statement_expr<'a>( } /// Make sure that the expression is not a mis-typed assignment (i.e. `a = b` instead of `a == b`). -fn ensure_not_assignment<'a>(input: &mut Peekable>) -> Result<(), ParseError> { +fn ensure_not_assignment(input: &mut TokenStream) -> Result<(), ParseError> { match input.peek().unwrap() { (Token::Equals, pos) => { return Err(PERR::BadInput("Possibly a typo of '=='?".to_string()).into_err(*pos)) @@ -1892,35 +1863,29 @@ fn ensure_not_assignment<'a>(input: &mut Peekable>) -> Result< } /// Parse an if statement. -fn parse_if<'a>( - input: &mut Peekable>, +fn parse_if( + input: &mut TokenStream, state: &mut ParseState, - breakable: bool, - level: usize, - if_expr: bool, - stmt_expr: bool, + mut settings: ParseSettings, ) -> Result { // if ... - let pos = eat_token(input, Token::If); - - if level > state.max_expr_depth { - return Err(PERR::ExprTooDeep.into_err(pos)); - } + settings.pos = eat_token(input, Token::If); + settings.ensure_level_within_max_limit(state.max_expr_depth)?; // if guard { if_body } ensure_not_statement_expr(input, "a boolean")?; - let guard = parse_expr(input, state, level + 1, if_expr, stmt_expr)?; + let guard = parse_expr(input, state, settings.level_up())?; ensure_not_assignment(input)?; - let if_body = parse_block(input, state, breakable, level + 1, if_expr, stmt_expr)?; + let if_body = parse_block(input, state, settings.level_up())?; // if guard { if_body } else ... let else_body = if match_token(input, Token::Else).unwrap_or(false) { Some(if let (Token::If, _) = input.peek().unwrap() { // if guard { if_body } else if ... - parse_if(input, state, breakable, level + 1, if_expr, stmt_expr)? + parse_if(input, state, settings.level_up())? } else { // if guard { if_body } else { else-body } - parse_block(input, state, breakable, level + 1, if_expr, stmt_expr)? + parse_block(input, state, settings.level_up())? }) } else { None @@ -1930,64 +1895,52 @@ fn parse_if<'a>( } /// Parse a while loop. -fn parse_while<'a>( - input: &mut Peekable>, +fn parse_while( + input: &mut TokenStream, state: &mut ParseState, - level: usize, - if_expr: bool, - stmt_expr: bool, + mut settings: ParseSettings, ) -> Result { // while ... - let pos = eat_token(input, Token::While); - - if level > state.max_expr_depth { - return Err(PERR::ExprTooDeep.into_err(pos)); - } + settings.pos = eat_token(input, Token::While); + settings.ensure_level_within_max_limit(state.max_expr_depth)?; // while guard { body } ensure_not_statement_expr(input, "a boolean")?; - let guard = parse_expr(input, state, level + 1, if_expr, stmt_expr)?; + let guard = parse_expr(input, state, settings.level_up())?; ensure_not_assignment(input)?; - let body = parse_block(input, state, true, level + 1, if_expr, stmt_expr)?; + + settings.is_breakable = true; + let body = parse_block(input, state, settings.level_up())?; Ok(Stmt::While(Box::new((guard, body)))) } /// Parse a loop statement. -fn parse_loop<'a>( - input: &mut Peekable>, +fn parse_loop( + input: &mut TokenStream, state: &mut ParseState, - level: usize, - if_expr: bool, - stmt_expr: bool, + mut settings: ParseSettings, ) -> Result { // loop ... - let pos = eat_token(input, Token::Loop); - - if level > state.max_expr_depth { - return Err(PERR::ExprTooDeep.into_err(pos)); - } + settings.pos = eat_token(input, Token::Loop); + settings.ensure_level_within_max_limit(state.max_expr_depth)?; // loop { body } - let body = parse_block(input, state, true, level + 1, if_expr, stmt_expr)?; + settings.is_breakable = true; + let body = parse_block(input, state, settings.level_up())?; Ok(Stmt::Loop(Box::new(body))) } /// Parse a for loop. -fn parse_for<'a>( - input: &mut Peekable>, +fn parse_for( + input: &mut TokenStream, state: &mut ParseState, - level: usize, - if_expr: bool, - stmt_expr: bool, + mut settings: ParseSettings, ) -> Result { // for ... - let pos = eat_token(input, Token::For); - - if level > state.max_expr_depth { - return Err(PERR::ExprTooDeep.into_err(pos)); - } + settings.pos = eat_token(input, Token::For); + settings.ensure_level_within_max_limit(state.max_expr_depth)?; // for name ... let name = match input.next().unwrap() { @@ -2015,12 +1968,13 @@ fn parse_for<'a>( // for name in expr { body } ensure_not_statement_expr(input, "a boolean")?; - let expr = parse_expr(input, state, level + 1, if_expr, stmt_expr)?; + let expr = parse_expr(input, state, settings.level_up())?; let prev_len = state.len(); state.push((name.clone(), ScopeEntryType::Normal)); - let body = parse_block(input, state, true, level + 1, if_expr, stmt_expr)?; + settings.is_breakable = true; + let body = parse_block(input, state, settings.level_up())?; state.truncate(prev_len); @@ -2028,20 +1982,15 @@ fn parse_for<'a>( } /// Parse a variable definition statement. -fn parse_let<'a>( - input: &mut Peekable>, +fn parse_let( + input: &mut TokenStream, state: &mut ParseState, var_type: ScopeEntryType, - level: usize, - if_expr: bool, - stmt_expr: bool, + mut settings: ParseSettings, ) -> Result { // let/const... (specified in `var_type`) - let (_, pos) = input.next().unwrap(); - - if level > state.max_expr_depth { - return Err(PERR::ExprTooDeep.into_err(pos)); - } + settings.pos = input.next().unwrap().1; + settings.ensure_level_within_max_limit(state.max_expr_depth)?; // let name ... let (name, pos) = match input.next().unwrap() { @@ -2053,7 +2002,7 @@ fn parse_let<'a>( // let name = ... if match_token(input, Token::Equals)? { // let name = expr - let init_value = parse_expr(input, state, level + 1, if_expr, stmt_expr)?; + let init_value = parse_expr(input, state, settings.level_up())?; match var_type { // let name = expr @@ -2091,22 +2040,17 @@ fn parse_let<'a>( } /// Parse an import statement. -fn parse_import<'a>( - input: &mut Peekable>, +fn parse_import( + input: &mut TokenStream, state: &mut ParseState, - level: usize, - if_expr: bool, - stmt_expr: bool, + mut settings: ParseSettings, ) -> Result { // import ... - let pos = eat_token(input, Token::Import); - - if level > state.max_expr_depth { - return Err(PERR::ExprTooDeep.into_err(pos)); - } + settings.pos = eat_token(input, Token::Import); + settings.ensure_level_within_max_limit(state.max_expr_depth)?; // import expr ... - let expr = parse_expr(input, state, level + 1, if_expr, stmt_expr)?; + let expr = parse_expr(input, state, settings.level_up())?; // import expr as ... match input.next().unwrap() { @@ -2127,21 +2071,18 @@ fn parse_import<'a>( }; state.push((name.clone(), ScopeEntryType::Module)); - Ok(Stmt::Import(Box::new((expr, (name, pos))))) + Ok(Stmt::Import(Box::new((expr, (name, settings.pos))))) } /// Parse an export statement. #[cfg(not(feature = "no_module"))] -fn parse_export<'a>( - input: &mut Peekable>, +fn parse_export( + input: &mut TokenStream, state: &mut ParseState, - level: usize, + mut settings: ParseSettings, ) -> Result { - let pos = eat_token(input, Token::Export); - - if level > state.max_expr_depth { - return Err(PERR::ExprTooDeep.into_err(pos)); - } + settings.pos = eat_token(input, Token::Export); + settings.ensure_level_within_max_limit(state.max_expr_depth)?; let mut exports = StaticVec::new(); @@ -2197,16 +2138,13 @@ fn parse_export<'a>( } /// Parse a statement block. -fn parse_block<'a>( - input: &mut Peekable>, +fn parse_block( + input: &mut TokenStream, state: &mut ParseState, - breakable: bool, - level: usize, - if_expr: bool, - stmt_expr: bool, + mut settings: ParseSettings, ) -> Result { // Must start with { - let pos = match input.next().unwrap() { + settings.pos = match input.next().unwrap() { (Token::LeftBrace, pos) => pos, (Token::LexError(err), pos) => return Err(PERR::BadInput(err.to_string()).into_err(pos)), (_, pos) => { @@ -2218,24 +2156,15 @@ fn parse_block<'a>( } }; - if level > state.max_expr_depth { - return Err(PERR::ExprTooDeep.into_err(pos)); - } + settings.ensure_level_within_max_limit(state.max_expr_depth)?; let mut statements = StaticVec::new(); let prev_len = state.len(); while !match_token(input, Token::RightBrace)? { // Parse statements inside the block - let stmt = parse_stmt( - input, - state, - breakable, - false, - level + 1, - if_expr, - stmt_expr, - )?; + settings.is_global = false; + let stmt = parse_stmt(input, state, settings.level_up())?; // See if it needs a terminating semicolon let need_semicolon = !stmt.is_self_terminated(); @@ -2274,79 +2203,66 @@ fn parse_block<'a>( state.truncate(prev_len); - Ok(Stmt::Block(Box::new((statements, pos)))) + Ok(Stmt::Block(Box::new((statements, settings.pos)))) } /// Parse an expression as a statement. -fn parse_expr_stmt<'a>( - input: &mut Peekable>, +fn parse_expr_stmt( + input: &mut TokenStream, state: &mut ParseState, - level: usize, - if_expr: bool, - stmt_expr: bool, + mut settings: ParseSettings, ) -> Result { - let (_, pos) = input.peek().unwrap(); + settings.pos = input.peek().unwrap().1; + settings.ensure_level_within_max_limit(state.max_expr_depth)?; - if level > state.max_expr_depth { - return Err(PERR::ExprTooDeep.into_err(*pos)); - } - - let expr = parse_expr(input, state, level + 1, if_expr, stmt_expr)?; - let expr = parse_op_assignment_stmt(input, state, expr, level + 1, if_expr, stmt_expr)?; + let expr = parse_expr(input, state, settings.level_up())?; + let expr = parse_op_assignment_stmt(input, state, expr, settings.level_up())?; Ok(Stmt::Expr(Box::new(expr))) } /// Parse a single statement. -fn parse_stmt<'a>( - input: &mut Peekable>, +fn parse_stmt( + input: &mut TokenStream, state: &mut ParseState, - breakable: bool, - is_global: bool, - level: usize, - if_expr: bool, - stmt_expr: bool, + mut settings: ParseSettings, ) -> Result { use ScopeEntryType::{Constant, Normal}; - let (token, pos) = match input.peek().unwrap() { + let (token, token_pos) = match input.peek().unwrap() { (Token::EOF, pos) => return Ok(Stmt::Noop(*pos)), x => x, }; - - if level > state.max_expr_depth { - return Err(PERR::ExprTooDeep.into_err(*pos)); - } + settings.pos = *token_pos; + settings.ensure_level_within_max_limit(state.max_expr_depth)?; match token { // Semicolon - empty statement - Token::SemiColon => Ok(Stmt::Noop(*pos)), + Token::SemiColon => Ok(Stmt::Noop(settings.pos)), - Token::LeftBrace => parse_block(input, state, breakable, level + 1, if_expr, stmt_expr), + Token::LeftBrace => parse_block(input, state, settings.level_up()), // fn ... #[cfg(not(feature = "no_function"))] - Token::Fn if !is_global => Err(PERR::WrongFnDefinition.into_err(*pos)), + Token::Fn if !settings.is_global => Err(PERR::WrongFnDefinition.into_err(settings.pos)), #[cfg(not(feature = "no_function"))] Token::Fn => unreachable!(), - Token::If => parse_if(input, state, breakable, level + 1, if_expr, stmt_expr), - Token::While => parse_while(input, state, level + 1, if_expr, stmt_expr), - Token::Loop => parse_loop(input, state, level + 1, if_expr, stmt_expr), - Token::For => parse_for(input, state, level + 1, if_expr, stmt_expr), + Token::If => parse_if(input, state, settings.level_up()), + Token::While => parse_while(input, state, settings.level_up()), + Token::Loop => parse_loop(input, state, settings.level_up()), + Token::For => parse_for(input, state, settings.level_up()), - Token::Continue if breakable => { + Token::Continue if settings.is_breakable => { let pos = eat_token(input, Token::Continue); Ok(Stmt::Continue(pos)) } - Token::Break if breakable => { + Token::Break if settings.is_breakable => { let pos = eat_token(input, Token::Break); Ok(Stmt::Break(pos)) } - Token::Continue | Token::Break => Err(PERR::LoopBreak.into_err(*pos)), + Token::Continue | Token::Break => Err(PERR::LoopBreak.into_err(settings.pos)), Token::Return | Token::Throw => { - let pos = *pos; - let return_type = match input.next().unwrap() { (Token::Return, _) => ReturnType::Return, (Token::Throw, _) => ReturnType::Exception, @@ -2357,12 +2273,13 @@ fn parse_stmt<'a>( // `return`/`throw` at (Token::EOF, pos) => Ok(Stmt::ReturnWithVal(Box::new(((return_type, *pos), None)))), // `return;` or `throw;` - (Token::SemiColon, _) => { - Ok(Stmt::ReturnWithVal(Box::new(((return_type, pos), None)))) - } + (Token::SemiColon, _) => Ok(Stmt::ReturnWithVal(Box::new(( + (return_type, settings.pos), + None, + )))), // `return` or `throw` with expression (_, _) => { - let expr = parse_expr(input, state, level + 1, if_expr, stmt_expr)?; + let expr = parse_expr(input, state, settings.level_up())?; let pos = expr.position(); Ok(Stmt::ReturnWithVal(Box::new(( @@ -2373,35 +2290,30 @@ fn parse_stmt<'a>( } } - Token::Let => parse_let(input, state, Normal, level + 1, if_expr, stmt_expr), - Token::Const => parse_let(input, state, Constant, level + 1, if_expr, stmt_expr), - Token::Import => parse_import(input, state, level + 1, if_expr, stmt_expr), + Token::Let => parse_let(input, state, Normal, settings.level_up()), + Token::Const => parse_let(input, state, Constant, settings.level_up()), + Token::Import => parse_import(input, state, settings.level_up()), #[cfg(not(feature = "no_module"))] - Token::Export if !is_global => Err(PERR::WrongExport.into_err(*pos)), + Token::Export if !settings.is_global => Err(PERR::WrongExport.into_err(settings.pos)), #[cfg(not(feature = "no_module"))] - Token::Export => parse_export(input, state, level + 1), + Token::Export => parse_export(input, state, settings.level_up()), - _ => parse_expr_stmt(input, state, level + 1, if_expr, stmt_expr), + _ => parse_expr_stmt(input, state, settings.level_up()), } } /// Parse a function definition. #[cfg(not(feature = "no_function"))] -fn parse_fn<'a>( - input: &mut Peekable>, +fn parse_fn( + input: &mut TokenStream, state: &mut ParseState, access: FnAccess, - level: usize, - if_expr: bool, - stmt_expr: bool, + mut settings: ParseSettings, ) -> Result { - let pos = eat_token(input, Token::Fn); - - if level > state.max_expr_depth { - return Err(PERR::ExprTooDeep.into_err(pos)); - } + settings.pos = eat_token(input, Token::Fn); + settings.ensure_level_within_max_limit(state.max_expr_depth)?; let name = match input.next().unwrap() { (Token::Identifier(s), _) => s, @@ -2466,7 +2378,10 @@ fn parse_fn<'a>( // Parse function body let body = match input.peek().unwrap() { - (Token::LeftBrace, _) => parse_block(input, state, false, level + 1, if_expr, stmt_expr)?, + (Token::LeftBrace, _) => { + settings.is_breakable = false; + parse_block(input, state, settings.level_up())? + } (_, pos) => return Err(PERR::FnMissingBody(name).into_err(*pos)), }; @@ -2477,13 +2392,13 @@ fn parse_fn<'a>( access, params, body, - pos, + pos: settings.pos, }) } /// Parse the global level statements. -fn parse_global_level<'a>( - input: &mut Peekable>, +fn parse_global_level( + input: &mut TokenStream, max_expr_depth: usize, max_function_expr_depth: usize, ) -> Result<(Vec, Vec), ParseError> { @@ -2503,9 +2418,17 @@ fn parse_global_level<'a>( match input.peek().unwrap() { #[cfg(not(feature = "no_function"))] - (Token::Fn, _) => { + (Token::Fn, pos) => { let mut state = ParseState::new(max_function_expr_depth); - let func = parse_fn(input, &mut state, access, 0, true, true)?; + let settings = ParseSettings { + allow_if_expr: true, + allow_stmt_expr: true, + is_global: false, + is_breakable: false, + level: 0, + pos: *pos, + }; + let func = parse_fn(input, &mut state, access, settings)?; // Qualifiers (none) + function name + number of arguments. let hash = calc_fn_hash(empty(), &func.name, func.params.len(), empty()); @@ -2523,8 +2446,17 @@ fn parse_global_level<'a>( _ => (), } } + // Actual statement - let stmt = parse_stmt(input, &mut state, false, true, 0, true, true)?; + let settings = ParseSettings { + allow_if_expr: true, + allow_stmt_expr: true, + is_global: true, + is_breakable: false, + level: 0, + pos: Position::none(), + }; + let stmt = parse_stmt(input, &mut state, settings)?; let need_semicolon = !stmt.is_self_terminated(); @@ -2561,14 +2493,22 @@ fn parse_global_level<'a>( } impl Engine { - pub(crate) fn parse_global_expr<'a>( + pub(crate) fn parse_global_expr( &self, - input: &mut Peekable>, + input: &mut TokenStream, scope: &Scope, optimization_level: OptimizationLevel, ) -> Result { let mut state = ParseState::new(self.max_expr_depth); - let expr = parse_expr(input, &mut state, 0, false, false)?; + let settings = ParseSettings { + allow_if_expr: false, + allow_stmt_expr: false, + is_global: true, + is_breakable: false, + level: 0, + pos: Position::none(), + }; + let expr = parse_expr(input, &mut state, settings)?; match input.peek().unwrap() { (Token::EOF, _) => (), @@ -2589,9 +2529,9 @@ impl Engine { } /// Run the parser on an input stream, returning an AST. - pub(crate) fn parse<'a>( + pub(crate) fn parse( &self, - input: &mut Peekable>, + input: &mut TokenStream, scope: &Scope, optimization_level: OptimizationLevel, ) -> Result { diff --git a/src/token.rs b/src/token.rs index 2967fc02..d6799a6f 100644 --- a/src/token.rs +++ b/src/token.rs @@ -19,6 +19,8 @@ use crate::stdlib::{ type LERR = LexError; +pub type TokenStream<'a> = Peekable>; + /// A location (line number + character position) in the input script. /// /// In order to keep footprint small, both line number and character position have 16-bit unsigned resolution, diff --git a/src/utils.rs b/src/utils.rs index 4a8cd3c8..bedf73b2 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -542,9 +542,7 @@ impl StaticVec { impl fmt::Debug for StaticVec { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "[ ")?; - self.iter().try_for_each(|v| write!(f, "{:?}, ", v))?; - write!(f, "]") + fmt::Debug::fmt(&self.iter().collect::>(), f) } } diff --git a/tests/decrement.rs b/tests/decrement.rs index ab1f9376..1b74dfe9 100644 --- a/tests/decrement.rs +++ b/tests/decrement.rs @@ -8,7 +8,7 @@ fn test_decrement() -> Result<(), Box> { assert!(matches!( *engine.eval::(r#"let s = "test"; s -= "ing"; s"#).expect_err("expects error"), - EvalAltResult::ErrorFunctionNotFound(err, _) if err == "- (string, string)" + EvalAltResult::ErrorFunctionNotFound(err, _) if err == "- (&str | ImmutableString, &str | ImmutableString)" )); Ok(()) diff --git a/tests/string.rs b/tests/string.rs index c93278de..6eed9e6a 100644 --- a/tests/string.rs +++ b/tests/string.rs @@ -168,7 +168,7 @@ fn test_string_fn() -> Result<(), Box> { assert!(matches!( *engine.eval::(r#"foo3("hello")"#).expect_err("should error"), - EvalAltResult::ErrorFunctionNotFound(ref x, _) if x == "foo3 (&str | ImmutableString)" + EvalAltResult::ErrorFunctionNotFound(err, _) if err == "foo3 (&str | ImmutableString)" )); Ok(())