diff --git a/README.md b/README.md
index c8f5fb73..a2720f62 100644
--- a/README.md
+++ b/README.md
@@ -1388,19 +1388,19 @@ record == "Bob X. Davis: age 42 ❤\n";
The following standard methods (defined in the [`MoreStringPackage`](#packages) but excluded if using a [raw `Engine`]) operate on strings:
-| Function | Parameter(s) | Description |
-| ------------ | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------- |
-| `len` | _none_ | returns the number of characters (not number of bytes) in the string |
-| `pad` | character to pad, target length | pads the string with an character to at least a specified length |
-| `append` | character/string to append | Adds a character or a string to the end of another string |
-| `clear` | _none_ | empties the string |
-| `truncate` | target length | cuts off the string at exactly a specified number of characters |
-| `contains` | character/sub-string to search for | checks if a certain character or sub-string occurs in the string |
-| `index_of` | character/sub-string to search for, start index _(optional)_ | returns the index that a certain character or sub-string occurs in the string, or -1 if not found |
-| `sub_string` | start index, length _(optional)_ | extracts a sub-string (to the end of the string if length is not specified) |
-| `crop` | start index, length _(optional)_ | retains only a portion of the string (to the end of the string if length is not specified) |
-| `replace` | target character/sub-string, replacement character/string | replaces a sub-string with another |
-| `trim` | _none_ | trims the string of whitespace at the beginning and end |
+| Function | Parameter(s) | Description |
+| ----------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------- |
+| `len` | _none_ | returns the number of characters (not number of bytes) in the string |
+| `pad` | character to pad, target length | pads the string with an character to at least a specified length |
+| `+=` operator, `append` | character/string to append | Adds a character or a string to the end of another string |
+| `clear` | _none_ | empties the string |
+| `truncate` | target length | cuts off the string at exactly a specified number of characters |
+| `contains` | character/sub-string to search for | checks if a certain character or sub-string occurs in the string |
+| `index_of` | character/sub-string to search for, start index _(optional)_ | returns the index that a certain character or sub-string occurs in the string, or -1 if not found |
+| `sub_string` | start index, length _(optional)_ | extracts a sub-string (to the end of the string if length is not specified) |
+| `crop` | start index, length _(optional)_ | retains only a portion of the string (to the end of the string if length is not specified) |
+| `replace` | target character/sub-string, replacement character/string | replaces a sub-string with another |
+| `trim` | _none_ | trims the string of whitespace at the beginning and end |
### Examples
@@ -1463,19 +1463,19 @@ Arrays are disabled via the [`no_index`] feature.
The following methods (defined in the [`BasicArrayPackage`](#packages) but excluded if using a [raw `Engine`]) operate on arrays:
-| Function | Parameter(s) | Description |
-| ------------ | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
-| `push` | element to insert | inserts an element at the end |
-| `append` | array to append | concatenates the second array to the end of the first |
-| `+` operator | first array, second array | concatenates the first array with the second |
-| `insert` | element to insert, position (beginning if <= 0, end if >= length) | insert an element at a certain index |
-| `pop` | _none_ | removes the last element and returns it ([`()`] if empty) |
-| `shift` | _none_ | removes the first element and returns it ([`()`] if empty) |
-| `remove` | index | removes an element at a particular index and returns it, or returns [`()`] if the index is not valid |
-| `len` | _none_ | returns the number of elements |
-| `pad` | element to pad, target length | pads the array with an element to at least a specified length |
-| `clear` | _none_ | empties the array |
-| `truncate` | target length | cuts off the array at exactly a specified length (discarding all subsequent elements) |
+| Function | Parameter(s) | Description |
+| ----------------------- | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
+| `push` | element to insert | inserts an element at the end |
+| `+=` operator, `append` | array to append | concatenates the second array to the end of the first |
+| `+` operator | first array, second array | concatenates the first array with the second |
+| `insert` | element to insert, position (beginning if <= 0, end if >= length) | insert an element at a certain index |
+| `pop` | _none_ | removes the last element and returns it ([`()`] if empty) |
+| `shift` | _none_ | removes the first element and returns it ([`()`] if empty) |
+| `remove` | index | removes an element at a particular index and returns it, or returns [`()`] if the index is not valid |
+| `len` | _none_ | returns the number of elements |
+| `pad` | element to pad, target length | pads the array with an element to at least a specified length |
+| `clear` | _none_ | empties the array |
+| `truncate` | target length | cuts off the array at exactly a specified length (discarding all subsequent elements) |
### Examples
@@ -1585,16 +1585,16 @@ Object maps are disabled via the [`no_object`] feature.
The following methods (defined in the [`BasicMapPackage`](#packages) but excluded if using a [raw `Engine`]) operate on object maps:
-| Function | Parameter(s) | Description |
-| ------------ | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
-| `has` | property name | does the object map contain a property of a particular name? |
-| `len` | _none_ | returns the number of properties |
-| `clear` | _none_ | empties the object map |
-| `remove` | property name | removes a certain property and returns it ([`()`] if the property does not exist) |
-| `mixin` | second object map | mixes in all the properties of the second object map to the first (values of properties with the same names replace the existing values) |
-| `+` operator | first object map, second object map | merges the first object map with the second |
-| `keys` | _none_ | returns an [array] of all the property names (in random order), not available under [`no_index`] |
-| `values` | _none_ | returns an [array] of all the property values (in random order), not available under [`no_index`] |
+| Function | Parameter(s) | Description |
+| ---------------------- | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
+| `has` | property name | does the object map contain a property of a particular name? |
+| `len` | _none_ | returns the number of properties |
+| `clear` | _none_ | empties the object map |
+| `remove` | property name | removes a certain property and returns it ([`()`] if the property does not exist) |
+| `+=` operator, `mixin` | second object map | mixes in all the properties of the second object map to the first (values of properties with the same names replace the existing values) |
+| `+` operator | first object map, second object map | merges the first object map with the second |
+| `keys` | _none_ | returns an [array] of all the property names (in random order), not available under [`no_index`] |
+| `values` | _none_ | returns an [array] of all the property values (in random order), not available under [`no_index`] |
### Examples
diff --git a/benches/iterations.rs b/benches/iterations.rs
index 23eb7621..b70e9432 100644
--- a/benches/iterations.rs
+++ b/benches/iterations.rs
@@ -12,7 +12,7 @@ fn bench_iterations_1000(bench: &mut Bencher) {
let x = 1_000;
while x > 0 {
- x = x - 1;
+ x -= 1;
}
"#;
diff --git a/scripts/loop.rhai b/scripts/loop.rhai
index d173b86f..1b514baf 100644
--- a/scripts/loop.rhai
+++ b/scripts/loop.rhai
@@ -6,7 +6,7 @@ let x = 10;
loop {
print(x);
- x = x - 1;
+ x -= 1;
if x <= 0 { break; }
}
diff --git a/scripts/speed_test.rhai b/scripts/speed_test.rhai
index 5709d6ac..e07bd7ec 100644
--- a/scripts/speed_test.rhai
+++ b/scripts/speed_test.rhai
@@ -7,7 +7,7 @@ let x = 1_000_000;
print("Ready... Go!");
while x > 0 {
- x = x - 1;
+ x -= 1;
}
print("Finished. Run time = " + now.elapsed() + " seconds.");
diff --git a/scripts/while.rhai b/scripts/while.rhai
index 3f58f6b6..e17493b2 100644
--- a/scripts/while.rhai
+++ b/scripts/while.rhai
@@ -4,5 +4,5 @@ let x = 10;
while x > 0 {
print(x);
- x = x - 1;
+ x -= 1;
}
diff --git a/src/engine.rs b/src/engine.rs
index 9e134769..40930d6f 100644
--- a/src/engine.rs
+++ b/src/engine.rs
@@ -1354,10 +1354,11 @@ impl Engine {
let def_value = Some(&def_value);
let pos = rhs.position();
- // Qualifiers (none) + function name + number of arguments + argument `TypeId`'s.
- let hash_fn =
- calc_fn_hash(empty(), op, args.len(), args.iter().map(|a| a.type_id()));
- let hashes = (hash_fn, 0);
+ let hashes = (
+ // Qualifiers (none) + function name + number of arguments + argument `TypeId`'s.
+ calc_fn_hash(empty(), op, args.len(), args.iter().map(|a| a.type_id())),
+ 0,
+ );
let (r, _) = self.call_fn_raw(
None, state, lib, op, hashes, args, false, def_value, pos, level,
@@ -1417,57 +1418,87 @@ impl Engine {
// lhs = rhs
Expr::Assignment(x) => {
- let op_pos = x.2;
- let rhs_val = self.eval_expr(scope, state, lib, &x.1, level)?;
+ let lhs_expr = &x.0;
+ let op = x.1.as_ref();
+ let op_pos = x.3;
+ let mut rhs_val = self.eval_expr(scope, state, lib, &x.2, level)?;
- match &x.0 {
- // name = rhs
- Expr::Variable(x) => {
- let ((name, pos), modules, hash_var, index) = x.as_ref();
- let index = if state.always_search { None } else { *index };
- let mod_and_hash = modules.as_ref().map(|m| (m.as_ref(), *hash_var));
- let (lhs_ptr, typ) = search_scope(scope, name, mod_and_hash, index, *pos)?;
- self.inc_operations(state, *pos)?;
+ // name op= rhs
+ if let Expr::Variable(x) = &x.0 {
+ let ((name, pos), modules, hash_var, index) = x.as_ref();
+ let index = if state.always_search { None } else { *index };
+ let mod_and_hash = modules.as_ref().map(|m| (m.as_ref(), *hash_var));
+ let (lhs_ptr, typ) = search_scope(scope, name, mod_and_hash, index, *pos)?;
+ self.inc_operations(state, *pos)?;
- match typ {
- ScopeEntryType::Constant => Err(Box::new(
- EvalAltResult::ErrorAssignmentToConstant(name.clone(), *pos),
- )),
- ScopeEntryType::Normal => {
- *lhs_ptr = rhs_val;
- Ok(Default::default())
+ match typ {
+ ScopeEntryType::Constant => Err(Box::new(
+ EvalAltResult::ErrorAssignmentToConstant(name.clone(), *pos),
+ )),
+ ScopeEntryType::Normal if !op.is_empty() => {
+ // Complex op-assignment
+ if run_builtin_op_assignment(op, lhs_ptr, &rhs_val)?.is_none() {
+ // Not built in, do function call
+ let mut lhs_val = lhs_ptr.clone();
+ let args = &mut [&mut lhs_val, &mut rhs_val];
+ let hash = calc_fn_hash(empty(), op, 2, empty());
+ *lhs_ptr = self
+ .exec_fn_call(
+ state, lib, op, true, hash, args, false, None, op_pos,
+ level,
+ )
+ .map(|(v, _)| v)?;
}
- // End variable cannot be a module
- ScopeEntryType::Module => unreachable!(),
+ Ok(Default::default())
}
+ ScopeEntryType::Normal => {
+ *lhs_ptr = rhs_val;
+ Ok(Default::default())
+ }
+ // End variable cannot be a module
+ ScopeEntryType::Module => unreachable!(),
}
- // idx_lhs[idx_expr] = rhs
- #[cfg(not(feature = "no_index"))]
- Expr::Index(x) => {
- let new_val = Some(rhs_val);
- self.eval_dot_index_chain(
+ } else {
+ let new_val = Some(if op.is_empty() {
+ rhs_val
+ } else {
+ // Complex op-assignment - always do function call
+ let args = &mut [
+ &mut self.eval_expr(scope, state, lib, lhs_expr, level)?,
+ &mut rhs_val,
+ ];
+ let hash = calc_fn_hash(empty(), op, 2, empty());
+ self.exec_fn_call(
+ state, lib, op, true, hash, args, false, None, op_pos, level,
+ )
+ .map(|(v, _)| v)?
+ });
+
+ match &x.0 {
+ // name op= rhs
+ Expr::Variable(_) => unreachable!(),
+ // idx_lhs[idx_expr] op= rhs
+ #[cfg(not(feature = "no_index"))]
+ Expr::Index(x) => self.eval_dot_index_chain(
scope, state, lib, &x.0, &x.1, true, x.2, level, new_val,
- )
- }
- // dot_lhs.dot_rhs = rhs
- #[cfg(not(feature = "no_object"))]
- Expr::Dot(x) => {
- let new_val = Some(rhs_val);
- self.eval_dot_index_chain(
+ ),
+ // dot_lhs.dot_rhs op= rhs
+ #[cfg(not(feature = "no_object"))]
+ Expr::Dot(x) => self.eval_dot_index_chain(
scope, state, lib, &x.0, &x.1, false, op_pos, level, new_val,
- )
- }
- // Error assignment to constant
- expr if expr.is_constant() => {
- Err(Box::new(EvalAltResult::ErrorAssignmentToConstant(
- expr.get_constant_str(),
+ ),
+ // Error assignment to constant
+ expr if expr.is_constant() => {
+ Err(Box::new(EvalAltResult::ErrorAssignmentToConstant(
+ expr.get_constant_str(),
+ expr.position(),
+ )))
+ }
+ // Syntax error
+ expr => Err(Box::new(EvalAltResult::ErrorAssignmentToUnknownLHS(
expr.position(),
- )))
+ ))),
}
- // Syntax error
- expr => Err(Box::new(EvalAltResult::ErrorAssignmentToUnknownLHS(
- expr.position(),
- ))),
}
}
@@ -1675,7 +1706,7 @@ impl Engine {
let result = self.eval_expr(scope, state, lib, expr, level)?;
Ok(match expr.as_ref() {
- // If it is an assignment, erase the result at the root
+ // If it is a simple assignment, erase the result at the root
Expr::Assignment(_) => Default::default(),
_ => result,
})
@@ -2079,3 +2110,85 @@ fn run_builtin_binary_op(
Ok(None)
}
+
+/// Build in common operator assignment implementations to avoid the cost of calling a registered function.
+fn run_builtin_op_assignment(
+ op: &str,
+ x: &mut Dynamic,
+ y: &Dynamic,
+) -> Result