From 37135e25511caea3fed5071f07916e94c4c519f9 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 1 Jun 2020 14:28:39 +0800 Subject: [PATCH 1/5] Modify list formatting according to GitHub MD rules. --- RELEASES.md | 46 +++++++++++++--------------------------------- 1 file changed, 13 insertions(+), 33 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index 2bb7b783..05da1a88 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -18,26 +18,18 @@ Bug fixes * Indexing with an index or dot expression now works property (it compiled wrongly before). For example, `let s = "hello"; s[s.len-1] = 'x';` now works property instead of causing a runtime error. +* `if` expressions are not supposed to be allowed when compiling for expressions only. This is fixed. Breaking changes ---------------- * `Engine::compile_XXX` functions now return `ParseError` instead of `Box`. -* The `RegisterDynamicFn` trait is merged into the `RegisterResultFn` trait which now always returns - `Result>`. +* The `RegisterDynamicFn` trait is merged into the `RegisterResultFn` trait which now always returns `Result>`. * Default maximum limit on levels of nested function calls is fine-tuned and set to a different value. -* Some operator functions are now built in (see _Speed enhancements_ below), so they are available even - under `Engine::new_raw`. -* Strings are now immutable. The type `rhai::ImmutableString` is used instead of `std::string::String`. - This is to avoid excessive cloning of strings. All native-Rust functions taking string parameters - should switch to `rhai::ImmutableString` (which is either `Rc` or `Arc` depending on - whether the `sync` feature is used). -* Native Rust functions registered with the `Engine` also mutates the first argument when called in - normal function-call style (previously the first argument will be passed by _value_ if not called - in method-call style). Of course, if the first argument is a calculated value (e.g. result of an - expression), then mutating it has no effect, but at least it is not cloned. -* Some built-in methods (e.g. `len` for string, `floor` for `FLOAT`) now have _property_ versions in - addition to methods to simplify coding. +* Some operator functions are now built in (see _Speed enhancements_ below), so they are available even under `Engine::new_raw`. +* Strings are now immutable. The type `rhai::ImmutableString` is used instead of `std::string::String`. This is to avoid excessive cloning of strings. All native-Rust functions taking string parameters should switch to `rhai::ImmutableString` (which is either `Rc` or `Arc` depending on whether the `sync` feature is used). +* Native Rust functions registered with the `Engine` also mutates the first argument when called in normal function-call style (previously the first argument will be passed by _value_ if not called in method-call style). Of course, if the first argument is a calculated value (e.g. result of an expression), then mutating it has no effect, but at least it is not cloned. +* Some built-in methods (e.g. `len` for string, `floor` for `FLOAT`) now have _property_ versions in addition to methods to simplify coding. New features ------------ @@ -50,23 +42,13 @@ New features Speed enhancements ------------------ -* Common operators (e.g. `+`, `>`, `==`) now call into highly efficient built-in implementations for standard types - (i.e. `INT`, `FLOAT`, `bool`, `char`, `()` and `ImmutableString`) if not overridden by a registered function. - This yields a 5-10% speed benefit depending on script operator usage. Scripts running tight loops will see - significant speed-up. -* Common assignment operators (e.g. `+=`, `%=`) now call into highly efficient built-in implementations for - standard types (i.e. `INT`, `FLOAT`, `bool`, `char`, `()` and `ImmutableString`) if not overridden by a registered function. -* Implementations of common operators for standard types are removed from the `ArithmeticPackage` and `LogicPackage` - (and therefore the `CorePackage`) because they are now always available, even under `Engine::new_raw`. +* Common operators (e.g. `+`, `>`, `==`) now call into highly efficient built-in implementations for standard types (i.e. `INT`, `FLOAT`, `bool`, `char`, `()` and `ImmutableString`) if not overridden by a registered function. This yields a 5-10% speed benefit depending on script operator usage. Scripts running tight loops will see significant speed-up. +* Common assignment operators (e.g. `+=`, `%=`) now call into highly efficient built-in implementations for standard types (i.e. `INT`, `FLOAT`, `bool`, `char`, `()` and `ImmutableString`) if not overridden by a registered function. +* Implementations of common operators for standard types are removed from the `ArithmeticPackage` and `LogicPackage` (and therefore the `CorePackage`) because they are now always available, even under `Engine::new_raw`. * Operator-assignment statements (e.g. `+=`) are now handled directly and much faster. * Strings are now _immutable_ and use the `rhai::ImmutableString` type, eliminating large amounts of cloning. -* For Native Rust functions taking a first `&mut` parameter, the first argument is passed by reference instead of - by value, even if not called in method-call style. This allows many functions declared with `&mut` parameter to avoid - excessive cloning. For example, if `a` is a large array, getting its length in this manner: `len(a)` used to result - in a full clone of `a` before taking the length and throwing the copy away. Now, `a` is simply passed by reference, - avoiding the cloning altogether. -* A custom hasher simply passes through `u64` keys without hashing to avoid function call hash keys - (which are by themselves `u64`) being hashed twice. +* For Native Rust functions taking a first `&mut` parameter, the first argument is passed by reference instead of by value, even if not called in method-call style. This allows many functions declared with `&mut` parameter to avoid excessive cloning. For example, if `a` is a large array, getting its length in this manner: `len(a)` used to result in a full clone of `a` before taking the length and throwing the copy away. Now, `a` is simply passed by reference, avoiding the cloning altogether. +* A custom hasher simply passes through `u64` keys without hashing to avoid function call hash keys (which are by themselves `u64`) being hashed twice. Version 0.14.1 @@ -78,15 +60,13 @@ The major features for this release is modules, script resource limits, and spee New features ------------ -* Modules and _module resolvers_ allow loading external scripts under a module namespace. - A module can contain constant variables, Rust functions and Rhai functions. +* Modules and _module resolvers_ allow loading external scripts under a module namespace. A module can contain constant variables, Rust functions and Rhai functions. * `export` variables and `private` functions. * _Indexers_ for Rust types. * Track script evaluation progress and terminate script run. * Set limit on maximum number of operations allowed per script run. * Set limit on maximum number of modules loaded per script run. -* A new API, `Engine::compile_scripts_with_scope`, can compile a list of script segments without needing to - first concatenate them together into one large string. +* A new API, `Engine::compile_scripts_with_scope`, can compile a list of script segments without needing to first concatenate them together into one large string. * Stepped `range` function with a custom step. Speed improvements From b70d38e820cc29a367d08232a7b03a57c02f273f Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 1 Jun 2020 15:25:22 +0800 Subject: [PATCH 2/5] Avoid passing position until error. --- src/api.rs | 10 +- src/engine.rs | 242 ++++++++++++++++++++++++++---------------------- src/optimize.rs | 40 ++++---- src/result.rs | 8 +- 4 files changed, 163 insertions(+), 137 deletions(-) diff --git a/src/api.rs b/src/api.rs index 1d593023..6afc7a35 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1104,16 +1104,20 @@ impl Engine { ) -> Result> { let mut args: StaticVec<_> = arg_values.iter_mut().collect(); let lib = ast.lib(); - let pos = Position::none(); let fn_def = lib .get_function_by_signature(name, args.len(), true) - .ok_or_else(|| Box::new(EvalAltResult::ErrorFunctionNotFound(name.into(), pos)))?; + .ok_or_else(|| { + Box::new(EvalAltResult::ErrorFunctionNotFound( + name.into(), + Position::none(), + )) + })?; let mut state = State::new(); let args = args.as_mut(); - self.call_script_fn(scope, &mut state, &lib, name, fn_def, args, pos, 0) + self.call_script_fn(scope, &mut state, &lib, name, fn_def, args, 0) } /// Optimize the `AST` with constants defined in an external Scope. diff --git a/src/engine.rs b/src/engine.rs index 453ee184..21561e70 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -116,17 +116,20 @@ impl Target<'_> { } /// Update the value of the `Target`. - pub fn set_value(&mut self, new_val: Dynamic, pos: Position) -> Result<(), Box> { + /// Position in `EvalAltResult` is None and must be set afterwards. + pub fn set_value(&mut self, new_val: Dynamic) -> Result<(), Box> { match self { Target::Ref(r) => **r = new_val, Target::Value(_) => { - return Err(Box::new(EvalAltResult::ErrorAssignmentToUnknownLHS(pos))) + return Err(Box::new(EvalAltResult::ErrorAssignmentToUnknownLHS( + Position::none(), + ))) } Target::StringChar(Dynamic(Union::Str(ref mut s)), index, _) => { // Replace the character at the specified index position let new_ch = new_val .as_char() - .map_err(|_| EvalAltResult::ErrorCharMismatch(pos))?; + .map_err(|_| EvalAltResult::ErrorCharMismatch(Position::none()))?; let mut chars: StaticVec = s.chars().collect(); let ch = chars[*index]; @@ -575,6 +578,7 @@ impl Engine { } /// Universal method for calling functions either registered with the `Engine` or written in Rhai. + /// Position in `EvalAltResult` is None and must be set afterwards. /// /// ## WARNING /// @@ -591,10 +595,9 @@ impl Engine { args: &mut FnCallArgs, is_ref: bool, def_val: Option<&Dynamic>, - pos: Position, level: usize, ) -> Result<(Dynamic, bool), Box> { - self.inc_operations(state, pos)?; + self.inc_operations(state)?; let native_only = hashes.1 == 0; @@ -603,7 +606,9 @@ impl Engine { #[cfg(not(feature = "unchecked"))] { if level > self.max_call_stack_depth { - return Err(Box::new(EvalAltResult::ErrorStackOverflow(pos))); + return Err(Box::new( + EvalAltResult::ErrorStackOverflow(Position::none()), + )); } } @@ -650,7 +655,7 @@ impl Engine { // Run scripted function let result = - self.call_script_fn(scope, state, lib, fn_name, fn_def, args, pos, level)?; + self.call_script_fn(scope, state, lib, fn_name, fn_def, args, level)?; // Restore the original reference restore_first_arg(old_this_ptr, args); @@ -674,20 +679,18 @@ impl Engine { ); // Run external function - let result = func.get_native_fn()(args); + let result = func.get_native_fn()(args)?; // Restore the original reference restore_first_arg(old_this_ptr, args); - let result = result.map_err(|err| err.new_position(pos))?; - // See if the function match print/debug (which requires special processing) return Ok(match fn_name { KEYWORD_PRINT => ( (self.print)(result.as_str().map_err(|type_name| { Box::new(EvalAltResult::ErrorMismatchOutputType( type_name.into(), - pos, + Position::none(), )) })?) .into(), @@ -697,7 +700,7 @@ impl Engine { (self.debug)(result.as_str().map_err(|type_name| { Box::new(EvalAltResult::ErrorMismatchOutputType( type_name.into(), - pos, + Position::none(), )) })?) .into(), @@ -724,7 +727,7 @@ impl Engine { if let Some(prop) = extract_prop_from_getter(fn_name) { return Err(Box::new(EvalAltResult::ErrorDotExpr( format!("- property '{}' unknown or write-only", prop), - pos, + Position::none(), ))); } @@ -732,7 +735,7 @@ impl Engine { if let Some(prop) = extract_prop_from_setter(fn_name) { return Err(Box::new(EvalAltResult::ErrorDotExpr( format!("- property '{}' unknown or read-only", prop), - pos, + Position::none(), ))); } @@ -745,18 +748,19 @@ impl Engine { if fn_name == FUNC_INDEXER { return Err(Box::new(EvalAltResult::ErrorFunctionNotFound( format!("[]({})", types_list.join(", ")), - pos, + Position::none(), ))); } // Raise error Err(Box::new(EvalAltResult::ErrorFunctionNotFound( format!("{} ({})", fn_name, types_list.join(", ")), - pos, + Position::none(), ))) } /// Call a script-defined function. + /// Position in `EvalAltResult` is None and must be set afterwards. /// /// ## WARNING /// @@ -771,7 +775,6 @@ impl Engine { fn_name: &str, fn_def: &FnDef, args: &mut FnCallArgs, - pos: Position, level: usize, ) -> Result> { let orig_scope_level = state.scope_level; @@ -798,13 +801,17 @@ impl Engine { .or_else(|err| match *err { // Convert return statement to return value EvalAltResult::Return(x, _) => Ok(x), - EvalAltResult::ErrorInFunctionCall(name, err, _) => Err(Box::new( - EvalAltResult::ErrorInFunctionCall(format!("{} > {}", fn_name, name), err, pos), - )), + EvalAltResult::ErrorInFunctionCall(name, err, _) => { + Err(Box::new(EvalAltResult::ErrorInFunctionCall( + format!("{} > {}", fn_name, name), + err, + Position::none(), + ))) + } _ => Err(Box::new(EvalAltResult::ErrorInFunctionCall( fn_name.to_string(), err, - pos, + Position::none(), ))), }); @@ -825,7 +832,8 @@ impl Engine { || lib.contains_key(&hashes.1) } - // Perform an actual function call, taking care of special functions + /// Perform an actual function call, taking care of special functions + /// Position in `EvalAltResult` is None and must be set afterwards. /// /// ## WARNING /// @@ -842,7 +850,6 @@ impl Engine { args: &mut FnCallArgs, is_ref: bool, def_val: Option<&Dynamic>, - pos: Position, level: usize, ) -> Result<(Dynamic, bool), Box> { // Qualifiers (none) + function name + number of arguments + argument `TypeId`'s. @@ -865,7 +872,7 @@ impl Engine { KEYWORD_EVAL if args.len() == 1 && !self.has_override(lib, hashes) => { Err(Box::new(EvalAltResult::ErrorRuntime( "'eval' should not be called in method style. Try eval(...);".into(), - pos, + Position::none(), ))) } @@ -873,24 +880,24 @@ impl Engine { _ => { let mut scope = Scope::new(); self.call_fn_raw( - &mut scope, state, lib, fn_name, hashes, args, is_ref, def_val, pos, level, + &mut scope, state, lib, fn_name, hashes, args, is_ref, def_val, level, ) } } } /// Evaluate a text string as a script - used primarily for 'eval'. + /// Position in `EvalAltResult` is None and must be set afterwards. fn eval_script_expr( &self, scope: &mut Scope, state: &mut State, lib: &FunctionsLib, script: &Dynamic, - pos: Position, ) -> Result> { - let script = script - .as_str() - .map_err(|type_name| EvalAltResult::ErrorMismatchOutputType(type_name.into(), pos))?; + let script = script.as_str().map_err(|type_name| { + EvalAltResult::ErrorMismatchOutputType(type_name.into(), Position::none()) + })?; // Compile the script text // No optimizations because we only run it once @@ -903,7 +910,7 @@ impl Engine { // If new functions are defined within the eval string, it is an error if !ast.lib().is_empty() { return Err(Box::new(EvalAltResult::ErrorParsing( - ParseErrorType::WrongFnDefinition.into_err(pos), + ParseErrorType::WrongFnDefinition.into_err(Position::none()), ))); } @@ -911,17 +918,16 @@ impl Engine { let ast = AST::new(statements, lib.clone()); // Evaluate the AST - let (result, operations) = self - .eval_ast_with_scope_raw(scope, &ast) - .map_err(|err| err.new_position(pos))?; + let (result, operations) = self.eval_ast_with_scope_raw(scope, &ast)?; state.operations += operations; - self.inc_operations(state, pos)?; + self.inc_operations(state)?; return Ok(result); } /// Chain-evaluate a dot/index chain. + /// Position in `EvalAltResult` is None and must be set afterwards. fn eval_dot_index_chain_helper( &self, state: &mut State, @@ -930,7 +936,6 @@ impl Engine { rhs: &Expr, idx_values: &mut StaticVec, is_index: bool, - op_pos: Position, level: usize, mut new_val: Option, ) -> Result<(Dynamic, bool), Box> { @@ -951,25 +956,27 @@ impl Engine { let (idx, expr, pos) = x.as_ref(); let is_idx = matches!(rhs, Expr::Index(_)); let idx_pos = idx.position(); - let this_ptr = &mut self.get_indexed_mut( - state, lib, obj, is_ref, idx_val, idx_pos, op_pos, false, - )?; + let this_ptr = &mut self + .get_indexed_mut(state, lib, obj, is_ref, idx_val, idx_pos, false)?; self.eval_dot_index_chain_helper( - state, lib, this_ptr, expr, idx_values, is_idx, *pos, level, new_val, + state, lib, this_ptr, expr, idx_values, is_idx, level, new_val, ) + .map_err(|err| EvalAltResult::new_position(err, *pos)) } // xxx[rhs] = new_val _ if new_val.is_some() => { - let this_ptr = &mut self - .get_indexed_mut(state, lib, obj, is_ref, idx_val, pos, op_pos, true)?; + let this_ptr = + &mut self.get_indexed_mut(state, lib, obj, is_ref, idx_val, pos, true)?; - this_ptr.set_value(new_val.unwrap(), rhs.position())?; + this_ptr + .set_value(new_val.unwrap()) + .map_err(|err| EvalAltResult::new_position(err, rhs.position()))?; Ok((Default::default(), true)) } // xxx[rhs] _ => self - .get_indexed_mut(state, lib, obj, is_ref, idx_val, pos, op_pos, false) + .get_indexed_mut(state, lib, obj, is_ref, idx_val, pos, false) .map(|v| (v.clone_into_dynamic(), false)), } } else { @@ -989,9 +996,8 @@ impl Engine { .collect(); let args = arg_values.as_mut(); - self.exec_fn_call( - state, lib, name, *native, *hash, args, is_ref, def_val, *pos, 0, - ) + self.exec_fn_call(state, lib, name, *native, *hash, args, is_ref, def_val, 0) + .map_err(|err| EvalAltResult::new_position(err, *pos)) } // xxx.module::fn_name(...) - syntax error Expr::FnCall(_) => unreachable!(), @@ -1001,9 +1007,10 @@ impl Engine { let ((prop, _, _), pos) = x.as_ref(); let index = prop.clone().into(); let mut val = - self.get_indexed_mut(state, lib, obj, is_ref, index, *pos, op_pos, true)?; + self.get_indexed_mut(state, lib, obj, is_ref, index, *pos, true)?; - val.set_value(new_val.unwrap(), rhs.position())?; + val.set_value(new_val.unwrap()) + .map_err(|err| EvalAltResult::new_position(err, rhs.position()))?; Ok((Default::default(), true)) } // {xxx:map}.id @@ -1011,8 +1018,7 @@ impl Engine { Expr::Property(x) if obj.is::() => { let ((prop, _, _), pos) = x.as_ref(); let index = prop.clone().into(); - let val = - self.get_indexed_mut(state, lib, obj, is_ref, index, *pos, op_pos, false)?; + let val = self.get_indexed_mut(state, lib, obj, is_ref, index, *pos, false)?; Ok((val.clone_into_dynamic(), false)) } @@ -1020,19 +1026,17 @@ impl Engine { Expr::Property(x) if new_val.is_some() => { let ((_, _, setter), pos) = x.as_ref(); let mut args = [obj, new_val.as_mut().unwrap()]; - self.exec_fn_call( - state, lib, setter, true, 0, &mut args, is_ref, None, *pos, 0, - ) - .map(|(v, _)| (v, true)) + self.exec_fn_call(state, lib, setter, true, 0, &mut args, is_ref, None, 0) + .map(|(v, _)| (v, true)) + .map_err(|err| EvalAltResult::new_position(err, *pos)) } // xxx.id Expr::Property(x) => { let ((_, getter, _), pos) = x.as_ref(); let mut args = [obj]; - self.exec_fn_call( - state, lib, getter, true, 0, &mut args, is_ref, None, *pos, 0, - ) - .map(|(v, _)| (v, false)) + self.exec_fn_call(state, lib, getter, true, 0, &mut args, is_ref, None, 0) + .map(|(v, _)| (v, false)) + .map_err(|err| EvalAltResult::new_position(err, *pos)) } #[cfg(not(feature = "no_object"))] // {xxx:map}.prop[expr] | {xxx:map}.prop.expr @@ -1043,14 +1047,15 @@ impl Engine { let mut val = if let Expr::Property(p) = prop { let ((prop, _, _), _) = p.as_ref(); let index = prop.clone().into(); - self.get_indexed_mut(state, lib, obj, is_ref, index, *pos, op_pos, false)? + self.get_indexed_mut(state, lib, obj, is_ref, index, *pos, false)? } else { unreachable!(); }; self.eval_dot_index_chain_helper( - state, lib, &mut val, expr, idx_values, is_idx, *pos, level, new_val, + state, lib, &mut val, expr, idx_values, is_idx, level, new_val, ) + .map_err(|err| EvalAltResult::new_position(err, *pos)) } // xxx.prop[expr] | xxx.prop.expr Expr::Index(x) | Expr::Dot(x) => { @@ -1061,16 +1066,19 @@ impl Engine { let (mut val, updated) = if let Expr::Property(p) = prop { let ((_, getter, _), _) = p.as_ref(); let args = &mut args[..1]; - self.exec_fn_call(state, lib, getter, true, 0, args, is_ref, None, *pos, 0)? + self.exec_fn_call(state, lib, getter, true, 0, args, is_ref, None, 0) + .map_err(|err| EvalAltResult::new_position(err, *pos))? } else { unreachable!(); }; let val = &mut val; let target = &mut val.into(); - let (result, may_be_changed) = self.eval_dot_index_chain_helper( - state, lib, target, expr, idx_values, is_idx, *pos, level, new_val, - )?; + let (result, may_be_changed) = self + .eval_dot_index_chain_helper( + state, lib, target, expr, idx_values, is_idx, level, new_val, + ) + .map_err(|err| EvalAltResult::new_position(err, *pos))?; // Feed the value back via a setter just in case it has been updated if updated || may_be_changed { @@ -1078,14 +1086,12 @@ impl Engine { let ((_, _, setter), _) = p.as_ref(); // Re-use args because the first &mut parameter will not be consumed args[1] = val; - self.exec_fn_call( - state, lib, setter, true, 0, args, is_ref, None, *pos, 0, - ) - .or_else(|err| match *err { - // If there is no setter, no need to feed it back because the property is read-only - EvalAltResult::ErrorDotExpr(_, _) => Ok(Default::default()), - err => Err(Box::new(err)), - })?; + self.exec_fn_call(state, lib, setter, true, 0, args, is_ref, None, 0) + .or_else(|err| match *err { + // If there is no setter, no need to feed it back because the property is read-only + EvalAltResult::ErrorDotExpr(_, _) => Ok(Default::default()), + err => Err(EvalAltResult::new_position(Box::new(err), *pos)), + })?; } } @@ -1124,7 +1130,8 @@ impl Engine { // id.??? or id[???] Expr::Variable(_) => { let (target, name, typ, pos) = search_scope(scope, state, dot_lhs)?; - self.inc_operations(state, pos)?; + self.inc_operations(state) + .map_err(|err| EvalAltResult::new_position(err, pos))?; // Constants cannot be modified match typ { @@ -1140,9 +1147,10 @@ impl Engine { let this_ptr = &mut target.into(); self.eval_dot_index_chain_helper( - state, lib, this_ptr, dot_rhs, idx_values, is_index, *op_pos, level, new_val, + state, lib, this_ptr, dot_rhs, idx_values, is_index, level, new_val, ) .map(|(v, _)| v) + .map_err(|err| EvalAltResult::new_position(err, *op_pos)) } // {expr}.??? = ??? or {expr}[???] = ??? expr if new_val.is_some() => { @@ -1155,9 +1163,10 @@ impl Engine { let val = self.eval_expr(scope, state, lib, expr, level)?; let this_ptr = &mut val.into(); self.eval_dot_index_chain_helper( - state, lib, this_ptr, dot_rhs, idx_values, is_index, *op_pos, level, new_val, + state, lib, this_ptr, dot_rhs, idx_values, is_index, level, new_val, ) .map(|(v, _)| v) + .map_err(|err| EvalAltResult::new_position(err, *op_pos)) } } } @@ -1177,7 +1186,8 @@ impl Engine { size: usize, level: usize, ) -> Result<(), Box> { - self.inc_operations(state, expr.position())?; + self.inc_operations(state) + .map_err(|err| EvalAltResult::new_position(err, expr.position()))?; match expr { Expr::FnCall(x) if x.1.is_none() => { @@ -1211,6 +1221,7 @@ impl Engine { } /// Get the value at the indexed position of a base type + /// Position in `EvalAltResult` may be None and should be set afterwards. fn get_indexed_mut<'a>( &self, state: &mut State, @@ -1219,10 +1230,9 @@ impl Engine { is_ref: bool, mut idx: Dynamic, idx_pos: Position, - op_pos: Position, create: bool, ) -> Result, Box> { - self.inc_operations(state, op_pos)?; + self.inc_operations(state)?; match val { #[cfg(not(feature = "no_index"))] @@ -1292,10 +1302,13 @@ impl Engine { let fn_name = FUNC_INDEXER; let type_name = self.map_type_name(val.type_name()); let args = &mut [val, &mut idx]; - self.exec_fn_call(state, lib, fn_name, true, 0, args, is_ref, None, op_pos, 0) + self.exec_fn_call(state, lib, fn_name, true, 0, args, is_ref, None, 0) .map(|(v, _)| v.into()) .map_err(|_| { - Box::new(EvalAltResult::ErrorIndexingType(type_name.into(), op_pos)) + Box::new(EvalAltResult::ErrorIndexingType( + type_name.into(), + Position::none(), + )) }) } } @@ -1311,7 +1324,8 @@ impl Engine { rhs: &Expr, level: usize, ) -> Result> { - self.inc_operations(state, rhs.position())?; + self.inc_operations(state) + .map_err(|err| EvalAltResult::new_position(err, rhs.position()))?; let lhs_value = self.eval_expr(scope, state, lib, lhs, level)?; let rhs_value = self.eval_expr(scope, state, lib, rhs, level)?; @@ -1327,7 +1341,6 @@ impl Engine { for value in rhs_value.iter_mut() { let args = &mut [&mut lhs_value.clone(), value]; let def_value = Some(&def_value); - let pos = rhs.position(); let hashes = ( // Qualifiers (none) + function name + number of arguments + argument `TypeId`'s. @@ -1335,9 +1348,11 @@ impl Engine { 0, ); - let (r, _) = self.call_fn_raw( - &mut scope, state, lib, op, hashes, args, false, def_value, pos, level, - )?; + let (r, _) = self + .call_fn_raw( + &mut scope, state, lib, op, hashes, args, false, def_value, level, + ) + .map_err(|err| EvalAltResult::new_position(err, rhs.position()))?; if r.as_bool().unwrap_or(false) { return Ok(true.into()); } @@ -1371,7 +1386,8 @@ impl Engine { expr: &Expr, level: usize, ) -> Result> { - self.inc_operations(state, expr.position())?; + self.inc_operations(state) + .map_err(|err| EvalAltResult::new_position(err, expr.position()))?; match expr { Expr::Expr(x) => self.eval_expr(scope, state, lib, x.as_ref(), level), @@ -1395,7 +1411,8 @@ impl Engine { let (lhs_expr, op, rhs_expr, op_pos) = x.as_ref(); let mut rhs_val = self.eval_expr(scope, state, lib, rhs_expr, level)?; let (lhs_ptr, name, typ, pos) = search_scope(scope, state, lhs_expr)?; - self.inc_operations(state, pos)?; + self.inc_operations(state) + .map_err(|err| EvalAltResult::new_position(err, pos))?; match typ { // Assignment to constant variable @@ -1432,10 +1449,9 @@ impl Engine { // Set variable value *lhs_ptr = self - .exec_fn_call( - state, lib, op, true, hash, args, false, None, *op_pos, level, - ) - .map(|(v, _)| v)?; + .exec_fn_call(state, lib, op, true, hash, args, false, None, level) + .map(|(v, _)| v) + .map_err(|err| EvalAltResult::new_position(err, *op_pos))?; } Ok(Default::default()) } @@ -1460,10 +1476,9 @@ impl Engine { &mut self.eval_expr(scope, state, lib, lhs_expr, level)?, &mut rhs_val, ]; - self.exec_fn_call( - state, lib, op, true, hash, args, false, None, *op_pos, level, - ) - .map(|(v, _)| v)? + self.exec_fn_call(state, lib, op, true, hash, args, false, None, level) + .map(|(v, _)| v) + .map_err(|err| EvalAltResult::new_position(err, *op_pos))? }); match lhs_expr { @@ -1531,11 +1546,11 @@ impl Engine { if !self.has_override(lib, (hash_fn, *hash)) { // eval - only in function call style let prev_len = scope.len(); - let pos = args_expr.get(0).position(); - - // Evaluate the text string as a script - let script = self.eval_expr(scope, state, lib, args_expr.get(0), level)?; - let result = self.eval_script_expr(scope, state, lib, &script, pos); + let expr = args_expr.get(0); + let script = self.eval_expr(scope, state, lib, expr, level)?; + let result = self + .eval_script_expr(scope, state, lib, &script) + .map_err(|err| EvalAltResult::new_position(err, expr.position())); if scope.len() != prev_len { // IMPORTANT! If the eval defines new variables in the current scope, @@ -1568,7 +1583,8 @@ impl Engine { .collect::>()?; let (target, _, typ, pos) = search_scope(scope, state, lhs)?; - self.inc_operations(state, pos)?; + self.inc_operations(state) + .map_err(|err| EvalAltResult::new_position(err, pos))?; match typ { ScopeEntryType::Module => unreachable!(), @@ -1593,9 +1609,10 @@ impl Engine { let args = args.as_mut(); self.exec_fn_call( - state, lib, name, *native, *hash, args, is_ref, def_val, *pos, level, + state, lib, name, *native, *hash, args, is_ref, def_val, level, ) .map(|(v, _)| v) + .map_err(|err| EvalAltResult::new_position(err, *pos)) } // Module-qualified function call @@ -1628,7 +1645,8 @@ impl Engine { let func = match module.get_qualified_fn(name, *hash_fn_def) { Err(err) if matches!(*err, EvalAltResult::ErrorFunctionNotFound(_, _)) => { // Then search in Rust functions - self.inc_operations(state, *pos)?; + self.inc_operations(state) + .map_err(|err| EvalAltResult::new_position(err, *pos))?; // Rust functions are indexed in two steps: // 1) Calculate a hash in a similar manner to script-defined functions, @@ -1650,7 +1668,8 @@ impl Engine { let args = args.as_mut(); let fn_def = x.get_fn_def(); let mut scope = Scope::new(); - self.call_script_fn(&mut scope, state, lib, name, fn_def, args, *pos, level) + self.call_script_fn(&mut scope, state, lib, name, fn_def, args, level) + .map_err(|err| EvalAltResult::new_position(err, *pos)) } Ok(x) => x.get_native_fn()(args.as_mut()).map_err(|err| err.new_position(*pos)), Err(err) @@ -1718,7 +1737,8 @@ impl Engine { stmt: &Stmt, level: usize, ) -> Result> { - self.inc_operations(state, stmt.position())?; + self.inc_operations(state) + .map_err(|err| EvalAltResult::new_position(err, stmt.position()))?; match stmt { // No-op @@ -1823,7 +1843,8 @@ impl Engine { for loop_var in func(iter_type) { *scope.get_mut(index).0 = loop_var; - self.inc_operations(state, stmt.position())?; + self.inc_operations(state) + .map_err(|err| EvalAltResult::new_position(err, stmt.position()))?; match self.eval_stmt(scope, state, lib, stmt, level) { Ok(_) => (), @@ -1975,14 +1996,17 @@ impl Engine { } /// Check if the number of operations stay within limit. - fn inc_operations(&self, state: &mut State, pos: Position) -> Result<(), Box> { + /// Position in `EvalAltResult` is None and must be set afterwards. + fn inc_operations(&self, state: &mut State) -> Result<(), Box> { state.operations += 1; #[cfg(not(feature = "unchecked"))] { // Guard against too many operations if state.operations > self.max_operations { - return Err(Box::new(EvalAltResult::ErrorTooManyOperations(pos))); + return Err(Box::new(EvalAltResult::ErrorTooManyOperations( + Position::none(), + ))); } } @@ -1990,7 +2014,7 @@ impl Engine { if let Some(progress) = &self.progress { if !progress(state.operations) { // Terminate script if progress returns false - return Err(Box::new(EvalAltResult::ErrorTerminated(pos))); + return Err(Box::new(EvalAltResult::ErrorTerminated(Position::none()))); } } diff --git a/src/optimize.rs b/src/optimize.rs index 78613ace..ea27620d 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -5,9 +5,7 @@ use crate::engine::{ KEYWORD_TYPE_OF, }; use crate::parser::{map_dynamic_to_expr, Expr, FnDef, ReturnType, Stmt, AST}; -use crate::result::EvalAltResult; use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope}; -use crate::token::Position; use crate::utils::StaticVec; use crate::stdlib::{ @@ -113,8 +111,7 @@ fn call_fn_with_constant_arguments( state: &State, fn_name: &str, arg_values: &mut [Dynamic], - pos: Position, -) -> Result, Box> { +) -> Option { // Search built-in's and external functions let hash_fn = calc_fn_hash( empty(), @@ -134,11 +131,10 @@ fn call_fn_with_constant_arguments( arg_values.iter_mut().collect::>().as_mut(), false, None, - pos, 0, ) .map(|(v, _)| Some(v)) - .or_else(|_| Ok(None)) + .unwrap_or_else(|_| None) } /// Optimize a statement. @@ -574,22 +570,22 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { "" }; - call_fn_with_constant_arguments(&state, name, arg_values.as_mut(), *pos).ok() - .and_then(|result| - result.or_else(|| { - if !arg_for_type_of.is_empty() { - // Handle `type_of()` - Some(arg_for_type_of.to_string().into()) - } else { - // Otherwise use the default value, if any - def_value.clone() - } - }).and_then(|result| map_dynamic_to_expr(result, *pos)) - .map(|expr| { - state.set_dirty(); - expr - }) - ).unwrap_or_else(|| { + call_fn_with_constant_arguments(&state, name, arg_values.as_mut()) + .or_else(|| { + if !arg_for_type_of.is_empty() { + // Handle `type_of()` + Some(arg_for_type_of.to_string().into()) + } else { + // Otherwise use the default value, if any + def_value.clone() + } + }) + .and_then(|result| map_dynamic_to_expr(result, *pos)) + .map(|expr| { + state.set_dirty(); + expr + }) + .unwrap_or_else(|| { // Optimize function call arguments x.3 = x.3.into_iter().map(|a| optimize_expr(a, state)).collect(); Expr::FnCall(x) diff --git a/src/result.rs b/src/result.rs index 2589016b..77102085 100644 --- a/src/result.rs +++ b/src/result.rs @@ -331,10 +331,12 @@ impl EvalAltResult { } } - /// Consume the current `EvalAltResult` and return a new one - /// with the specified `Position`. + /// Consume the current `EvalAltResult` and return a new one with the specified `Position` + /// if the current position is `Position::None`. pub(crate) fn new_position(mut self: Box, new_position: Position) -> Box { - self.set_position(new_position); + if self.position().is_none() { + self.set_position(new_position); + } self } } From b8da1691d3ba06f2e0d7440d1b8979f7e0961954 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 2 Jun 2020 13:33:16 +0800 Subject: [PATCH 3/5] Consolidate callbacks. --- README.md | 2 +- RELEASES.md | 5 +++++ src/api.rs | 4 ++-- src/engine.rs | 30 ++++++++---------------------- src/fn_native.rs | 5 +++++ src/scope.rs | 2 +- src/token.rs | 3 +++ tests/operations.rs | 6 +++--- 8 files changed, 28 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index aaf394fc..1333579b 100644 --- a/README.md +++ b/README.md @@ -2439,7 +2439,7 @@ provide a closure to the `Engine::on_progress` method: ```rust let mut engine = Engine::new(); -engine.on_progress(|count| { // 'count' is the number of operations performed +engine.on_progress(|&count| { // 'count' is the number of operations performed if count % 1000 == 0 { println!("{}", count); // print out a progress log every 1,000 operations } diff --git a/RELEASES.md b/RELEASES.md index 05da1a88..7ba44391 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -4,6 +4,11 @@ Rhai Release Notes Version 0.16.0 ============== +Breaking changes +---------------- + +* Callback closure passed to `Engine::on_progress` now takes `&u64` instead of `u64` to be consistent with other callback signatures. + Version 0.15.0 ============== diff --git a/src/api.rs b/src/api.rs index 6afc7a35..dc0a26d0 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1163,7 +1163,7 @@ impl Engine { /// /// let mut engine = Engine::new(); /// - /// engine.on_progress(move |ops| { + /// engine.on_progress(move |&ops| { /// if ops > 10000 { /// false /// } else if ops % 800 == 0 { @@ -1182,7 +1182,7 @@ impl Engine { /// # Ok(()) /// # } /// ``` - pub fn on_progress(&mut self, callback: impl Fn(u64) -> bool + SendSync + 'static) { + pub fn on_progress(&mut self, callback: impl Fn(&u64) -> bool + SendSync + 'static) { self.progress = Some(Box::new(callback)); } diff --git a/src/engine.rs b/src/engine.rs index 21561e70..a5001df3 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -3,7 +3,7 @@ use crate::any::{Dynamic, Union}; use crate::calc_fn_hash; use crate::error::ParseErrorType; -use crate::fn_native::{CallableFunction, FnCallArgs, Shared}; +use crate::fn_native::{CallableFunction, Callback, FnCallArgs, Shared}; use crate::module::{resolvers, Module, ModuleResolver}; use crate::optimize::OptimizationLevel; use crate::packages::{CorePackage, Package, PackageLibrary, PackagesCollection, StandardPackage}; @@ -302,26 +302,12 @@ pub struct Engine { /// A hashmap mapping type names to pretty-print names. pub(crate) type_names: HashMap, - /// Closure for implementing the `print` command. - #[cfg(not(feature = "sync"))] - pub(crate) print: Box, - /// Closure for implementing the `print` command. - #[cfg(feature = "sync")] - pub(crate) print: Box, - - /// Closure for implementing the `debug` command. - #[cfg(not(feature = "sync"))] - pub(crate) debug: Box, - /// Closure for implementing the `debug` command. - #[cfg(feature = "sync")] - pub(crate) debug: Box, - - /// Closure for progress reporting. - #[cfg(not(feature = "sync"))] - pub(crate) progress: Option bool + 'static>>, - /// Closure for progress reporting. - #[cfg(feature = "sync")] - pub(crate) progress: Option bool + Send + Sync + 'static>>, + /// Callback closure for implementing the `print` command. + pub(crate) print: Callback, + /// Callback closure for implementing the `debug` command. + pub(crate) debug: Callback, + /// Callback closure for progress reporting. + pub(crate) progress: Option>, /// Optimize the AST after compilation. pub(crate) optimization_level: OptimizationLevel, @@ -2012,7 +1998,7 @@ impl Engine { // Report progress - only in steps if let Some(progress) = &self.progress { - if !progress(state.operations) { + if !progress(&state.operations) { // Terminate script if progress returns false return Err(Box::new(EvalAltResult::ErrorTerminated(Position::none()))); } diff --git a/src/fn_native.rs b/src/fn_native.rs index 6c43adc6..334068b5 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -57,6 +57,11 @@ pub type FnAny = dyn Fn(&mut FnCallArgs) -> Result> pub type IteratorFn = fn(Dynamic) -> Box>; +#[cfg(not(feature = "sync"))] +pub type Callback = Box R + 'static>; +#[cfg(feature = "sync")] +pub type Callback = Box R + Send + Sync + 'static>; + /// A type encapsulating a function callable by Rhai. #[derive(Clone)] pub enum CallableFunction { diff --git a/src/scope.rs b/src/scope.rs index 50a5694f..bb15b777 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -175,7 +175,7 @@ impl<'a> Scope<'a> { /// /// Modules are used for accessing member variables, functions and plugins under a namespace. #[cfg(not(feature = "no_module"))] - pub fn push_module>>(&mut self, name: K, mut value: Module) { + pub fn push_module>>(&mut self, name: K, value: Module) { self.push_module_internal(name, value); } diff --git a/src/token.rs b/src/token.rs index 9e6613b0..2967fc02 100644 --- a/src/token.rs +++ b/src/token.rs @@ -198,6 +198,7 @@ pub enum Token { XOrAssign, ModuloAssign, PowerOfAssign, + #[cfg(not(feature = "no_function"))] Private, Import, #[cfg(not(feature = "no_module"))] @@ -284,6 +285,7 @@ impl Token { ModuloAssign => "%=", PowerOf => "~", PowerOfAssign => "~=", + #[cfg(not(feature = "no_function"))] Private => "private", Import => "import", #[cfg(not(feature = "no_module"))] @@ -757,6 +759,7 @@ impl<'a> TokenIterator<'a> { "throw" => Token::Throw, "for" => Token::For, "in" => Token::In, + #[cfg(not(feature = "no_function"))] "private" => Token::Private, "import" => Token::Import, #[cfg(not(feature = "no_module"))] diff --git a/tests/operations.rs b/tests/operations.rs index c02e2381..91df26c9 100644 --- a/tests/operations.rs +++ b/tests/operations.rs @@ -6,7 +6,7 @@ fn test_max_operations() -> Result<(), Box> { let mut engine = Engine::new(); engine.set_max_operations(500); - engine.on_progress(|count| { + engine.on_progress(|&count| { if count % 100 == 0 { println!("{}", count); } @@ -34,7 +34,7 @@ fn test_max_operations_functions() -> Result<(), Box> { let mut engine = Engine::new(); engine.set_max_operations(500); - engine.on_progress(|count| { + engine.on_progress(|&count| { if count % 100 == 0 { println!("{}", count); } @@ -90,7 +90,7 @@ fn test_max_operations_eval() -> Result<(), Box> { let mut engine = Engine::new(); engine.set_max_operations(500); - engine.on_progress(|count| { + engine.on_progress(|&count| { if count % 100 == 0 { println!("{}", count); } From ec6e3daabbe688b582390cad120f3b15446fd944 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 3 Jun 2020 10:44:36 +0800 Subject: [PATCH 4/5] Refactor. --- src/api.rs | 40 ++++------------------ src/parser.rs | 92 +++++++++++++++++++++++++-------------------------- 2 files changed, 53 insertions(+), 79 deletions(-) diff --git a/src/api.rs b/src/api.rs index dc0a26d0..5a683d4a 100644 --- a/src/api.rs +++ b/src/api.rs @@ -7,7 +7,7 @@ use crate::fn_call::FuncArgs; use crate::fn_native::{IteratorFn, SendSync}; use crate::fn_register::RegisterFn; use crate::optimize::{optimize_into_ast, OptimizationLevel}; -use crate::parser::{parse, parse_global_expr, AST}; +use crate::parser::AST; use crate::result::EvalAltResult; use crate::scope::Scope; use crate::token::{lex, Position}; @@ -449,14 +449,7 @@ impl Engine { optimization_level: OptimizationLevel, ) -> Result { let stream = lex(scripts); - - parse( - &mut stream.peekable(), - self, - scope, - optimization_level, - (self.max_expr_depth, self.max_function_expr_depth), - ) + self.parse(&mut stream.peekable(), scope, optimization_level) } /// Read the contents of a file into a string. @@ -578,13 +571,8 @@ impl Engine { // Trims the JSON string and add a '#' in front let scripts = ["#", json.trim()]; let stream = lex(&scripts); - let ast = parse_global_expr( - &mut stream.peekable(), - self, - &scope, - OptimizationLevel::None, - self.max_expr_depth, - )?; + let ast = + self.parse_global_expr(&mut stream.peekable(), &scope, OptimizationLevel::None)?; // Handle null - map to () if has_null { @@ -667,13 +655,7 @@ impl Engine { { let mut peekable = stream.peekable(); - parse_global_expr( - &mut peekable, - self, - scope, - self.optimization_level, - self.max_expr_depth, - ) + self.parse_global_expr(&mut peekable, scope, self.optimization_level) } } @@ -825,12 +807,10 @@ impl Engine { let scripts = [script]; let stream = lex(&scripts); - let ast = parse_global_expr( + let ast = self.parse_global_expr( &mut stream.peekable(), - self, scope, OptimizationLevel::None, // No need to optimize a lone expression - self.max_expr_depth, )?; self.eval_ast_with_scope(scope, &ast) @@ -957,13 +937,7 @@ impl Engine { let scripts = [script]; let stream = lex(&scripts); - let ast = parse( - &mut stream.peekable(), - self, - scope, - self.optimization_level, - (self.max_expr_depth, self.max_function_expr_depth), - )?; + let ast = self.parse(&mut stream.peekable(), scope, self.optimization_level)?; self.consume_ast_with_scope(scope, &ast) } diff --git a/src/parser.rs b/src/parser.rs index 5ff5e692..b2c4c1cf 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -2478,44 +2478,15 @@ fn parse_fn<'a>( }) } -pub fn parse_global_expr<'a>( - input: &mut Peekable>, - engine: &Engine, - scope: &Scope, - optimization_level: OptimizationLevel, - max_expr_depth: usize, -) -> Result { - let mut state = ParseState::new(max_expr_depth); - let expr = parse_expr(input, &mut state, 0, false, false)?; - - match input.peek().unwrap() { - (Token::EOF, _) => (), - // Return error if the expression doesn't end - (token, pos) => { - return Err(PERR::BadInput(format!("Unexpected '{}'", token.syntax())).into_err(*pos)) - } - } - - Ok( - // Optimize AST - optimize_into_ast( - engine, - scope, - vec![Stmt::Expr(Box::new(expr))], - vec![], - optimization_level, - ), - ) -} - /// Parse the global level statements. fn parse_global_level<'a>( input: &mut Peekable>, - max_expr_depth: (usize, usize), + max_expr_depth: usize, + max_function_expr_depth: usize, ) -> Result<(Vec, Vec), ParseError> { let mut statements = Vec::::new(); let mut functions = HashMap::::with_hasher(StraightHasherBuilder); - let mut state = ParseState::new(max_expr_depth.0); + let mut state = ParseState::new(max_expr_depth); while !input.peek().unwrap().0.is_eof() { // Collect all the function definitions @@ -2530,7 +2501,7 @@ fn parse_global_level<'a>( match input.peek().unwrap() { #[cfg(not(feature = "no_function"))] (Token::Fn, _) => { - let mut state = ParseState::new(max_expr_depth.1); + let mut state = ParseState::new(max_function_expr_depth); let func = parse_fn(input, &mut state, access, 0, true, true)?; // Qualifiers (none) + function name + number of arguments. @@ -2586,20 +2557,49 @@ fn parse_global_level<'a>( Ok((statements, functions.into_iter().map(|(_, v)| v).collect())) } -/// Run the parser on an input stream, returning an AST. -pub fn parse<'a>( - input: &mut Peekable>, - engine: &Engine, - scope: &Scope, - optimization_level: OptimizationLevel, - max_expr_depth: (usize, usize), -) -> Result { - let (statements, lib) = parse_global_level(input, max_expr_depth)?; +impl Engine { + pub(crate) fn parse_global_expr<'a>( + &self, + input: &mut Peekable>, + 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)?; - Ok( - // Optimize AST - optimize_into_ast(engine, scope, statements, lib, optimization_level), - ) + match input.peek().unwrap() { + (Token::EOF, _) => (), + // Return error if the expression doesn't end + (token, pos) => { + return Err( + PERR::BadInput(format!("Unexpected '{}'", token.syntax())).into_err(*pos) + ) + } + } + + let expr = vec![Stmt::Expr(Box::new(expr))]; + + Ok( + // Optimize AST + optimize_into_ast(self, scope, expr, Default::default(), optimization_level), + ) + } + + /// Run the parser on an input stream, returning an AST. + pub(crate) fn parse<'a>( + &self, + input: &mut Peekable>, + scope: &Scope, + optimization_level: OptimizationLevel, + ) -> Result { + let (statements, lib) = + parse_global_level(input, self.max_expr_depth, self.max_function_expr_depth)?; + + Ok( + // Optimize AST + optimize_into_ast(self, scope, statements, lib, optimization_level), + ) + } } /// Map a `Dynamic` value to an expression. From e21d25a0c57bb0b0d5dce6e31a2a22e297ba550a Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 3 Jun 2020 11:13:19 +0800 Subject: [PATCH 5/5] Add section on printing custom types. --- README.md | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 1333579b..7021b924 100644 --- a/README.md +++ b/README.md @@ -472,8 +472,7 @@ The follow packages are available: Packages typically contain Rust functions that are callable within a Rhai script. All functions registered in a package is loaded under the _global namespace_ (i.e. they're available without module qualifiers). Once a package is created (e.g. via `new`), it can be _shared_ (via `get`) among multiple instances of [`Engine`], -even across threads (if the [`sync`] feature is turned on). -Therefore, a package only has to be created _once_. +even across threads (under the [`sync`] feature). Therefore, a package only has to be created _once_. Packages are actually implemented as [modules], so they share a lot of behavior and characteristics. The main difference is that a package loads under the _global_ namespace, while a module loads under its own @@ -573,7 +572,7 @@ if type_of(x) == "string" { [`Dynamic`]: #dynamic-values -A `Dynamic` value can be _any_ type. However, if the [`sync`] feature is used, then all types must be `Send + Sync`. +A `Dynamic` value can be _any_ type. However, under the [`sync`] feature, all types must be `Send + Sync`. Because [`type_of()`] a `Dynamic` value returns the type of the actual value, it is usually used to perform type-specific actions based on the actual value's type. @@ -976,7 +975,7 @@ let result = engine.eval::( println!("result: {}", result); // prints 1 ``` -If the [`no_object`] feature is turned on, however, the _method_ style of function calls +Under the [`no_object`] feature, however, the _method_ style of function calls (i.e. calling a function as an object-method) is no longer supported. ```rust @@ -1077,8 +1076,23 @@ println!("Answer: {}", result); // prints 42 ``` Needless to say, `register_type`, `register_type_with_name`, `register_get`, `register_set`, `register_get_set` -and `register_indexer` are not available when the [`no_object`] feature is turned on. -`register_indexer` is also not available when the [`no_index`] feature is turned on. +and `register_indexer` are not available under the [`no_object`] feature. +`register_indexer` is also not available under the [`no_index`] feature. + +Printing for custom types +------------------------- + +To use custom types for `print` and `debug`, or format its value into a [string], it is necessary that the following +functions be registered (assuming the custom type is `T` and it is `Display + Debug`): + +| Function | Signature | Typical implementation | Usage | +| ----------- | ------------------------------------------------ | ------------------------------ | --------------------------------------------------------------------------------------- | +| `to_string` | `|s: &mut T| -> String` | `s.to_string()` | Converts the custom type into a [string] | +| `print` | `|s: &mut T| -> String` | `s.to_string()` | Converts the custom type into a [string] for the [`print`](#print-and-debug) statement | +| `debug` | `|s: &mut T| -> String` | `format!("{:?}", s)` | Converts the custom type into a [string] for the [`debug`](#print-and-debug) statement | +| `+` | `|s1: ImmutableString, s: T| -> ImmutableString` | `s1 + s` | Append the custom type to another [string], for `print("Answer: " + type);` usage | +| `+` | `|s: T, s2: ImmutableString| -> String` | `s.to_string().push_str(&s2);` | Append another [string] to the custom type, for `print(type + " is the answer");` usage | +| `+=` | `|s1: &mut ImmutableString, s: T|` | `s1 += s.to_string()` | Append the custom type to an existing [string], for `s += type;` usage | `Scope` - Initializing and maintaining state ------------------------------------------- @@ -1089,8 +1103,8 @@ By default, Rhai treats each [`Engine`] invocation as a fresh one, persisting on but no global state. This gives each evaluation a clean starting slate. In order to continue using the same global state from one invocation to the next, such a state must be manually created and passed in. -All `Scope` variables are [`Dynamic`], meaning they can store values of any type. If the [`sync`] feature is used, however, -then only types that are `Send + Sync` are supported, and the entire `Scope` itself will also be `Send + Sync`. +All `Scope` variables are [`Dynamic`], meaning they can store values of any type. Under the [`sync`] feature, however, +only types that are `Send + Sync` are supported, and the entire `Scope` itself will also be `Send + Sync`. This is extremely useful in multi-threaded applications. In this example, a global state object (a `Scope`) is created with a few initialized variables, then the same state is @@ -1191,7 +1205,7 @@ The following are reserved keywords in Rhai: | `import`, `export`, `as` | Modules | [`no_module`] | Keywords cannot be the name of a [function] or [variable], unless the relevant exclusive feature is enabled. -For example, `fn` is a valid variable name if the [`no_function`] feature is used. +For example, `fn` is a valid variable name under the [`no_function`] feature. Statements ---------- @@ -1729,7 +1743,7 @@ technically be mapped to [`()`]. A valid JSON string does not start with a hash Rhai object map does - that's the major difference! JSON numbers are all floating-point while Rhai supports integers (`INT`) and floating-point (`FLOAT`) if -the [`no_float`] feature is not turned on. Most common generators of JSON data distinguish between +the [`no_float`] feature is not enabled. Most common generators of JSON data distinguish between integer and floating-point values by always serializing a floating-point number with a decimal point (i.e. `123.0` instead of `123` which is assumed to be an integer). This style can be used successfully with Rhai object maps. @@ -2364,7 +2378,7 @@ Built-in module resolvers are grouped under the `rhai::module_resolvers` module | Module Resolver | Description | | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `FileModuleResolver` | The default module resolution service, not available under the [`no_std`] feature. Loads a script file (based off the current directory) with `.rhai` extension.
The base directory can be changed via the `FileModuleResolver::new_with_path()` constructor function.
`FileModuleResolver::create_module()` loads a script file and returns a module. | -| `StaticModuleResolver` | Loads modules that are statically added. This can be used when the [`no_std`] feature is turned on. | +| `StaticModuleResolver` | Loads modules that are statically added. This can be used under the [`no_std`] feature. | An [`Engine`]'s module resolver is set via a call to `Engine::set_module_resolver`: