FIX - fixes panic when constant array is assigned to. Refine README section on constants.
This commit is contained in:
parent
504fd56f1f
commit
26bdc8ba08
77
README.md
77
README.md
@ -66,13 +66,13 @@ Examples
|
|||||||
A number of examples can be found in the `examples` folder:
|
A number of examples can be found in the `examples` folder:
|
||||||
|
|
||||||
| Example | Description |
|
| Example | Description |
|
||||||
| -------------------------- | ------------------------------------------------------------------------- |
|
| -------------------------- | --------------------------------------------------------------------------- |
|
||||||
| `arrays_and_structs` | demonstrates registering a new type to Rhai and the usage of arrays on it |
|
| `arrays_and_structs` | demonstrates registering a new type to Rhai and the usage of arrays on it |
|
||||||
| `custom_types_and_methods` | shows how to register a type and methods for it |
|
| `custom_types_and_methods` | shows how to register a type and methods for it |
|
||||||
| `hello` | simple example that evaluates an expression and prints the result |
|
| `hello` | simple example that evaluates an expression and prints the result |
|
||||||
| `reuse_scope` | evaluates two pieces of code in separate runs, but using a common scope |
|
| `reuse_scope` | evaluates two pieces of code in separate runs, but using a common [`Scope`] |
|
||||||
| `rhai_runner` | runs each filename passed to it as a Rhai script |
|
| `rhai_runner` | runs each filename passed to it as a Rhai script |
|
||||||
| `simple_fn` | shows how to register a Rust function to a Rhai engine |
|
| `simple_fn` | shows how to register a Rust function to a Rhai [`Engine`] |
|
||||||
| `repl` | a simple REPL, interactively evaluate statements from stdin |
|
| `repl` | a simple REPL, interactively evaluate statements from stdin |
|
||||||
|
|
||||||
Examples can be run with the following command:
|
Examples can be run with the following command:
|
||||||
@ -294,7 +294,7 @@ fn main() -> Result<(), EvalAltResult>
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
To return a `Dynamic` value, simply `Box` it and return it.
|
To return a [`Dynamic`] value, simply `Box` it and return it.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
fn decide(yes_no: bool) -> Dynamic {
|
fn decide(yes_no: bool) -> Dynamic {
|
||||||
@ -421,7 +421,7 @@ fn main() -> Result<(), EvalAltResult>
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
First, for each type we use with the engine, we need to be able to Clone. This allows the engine to pass by value and still keep its own state.
|
All custom types must implement `Clone`. This allows the [`Engine`] to pass by value.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -430,7 +430,7 @@ struct TestStruct {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Next, we create a few methods that we'll later use in our scripts. Notice that we register our custom type with the engine.
|
Next, we create a few methods that we'll later use in our scripts. Notice that we register our custom type with the [`Engine`].
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
impl TestStruct {
|
impl TestStruct {
|
||||||
@ -448,9 +448,9 @@ let mut engine = Engine::new();
|
|||||||
engine.register_type::<TestStruct>();
|
engine.register_type::<TestStruct>();
|
||||||
```
|
```
|
||||||
|
|
||||||
To use methods and functions with the engine, we need to register them. There are some convenience functions to help with this. Below I register update and new with the engine.
|
To use methods and functions with the [`Engine`], we need to register them. There are some convenience functions to help with this. Below I register update and new with the [`Engine`].
|
||||||
|
|
||||||
*Note: the engine follows the convention that methods use a &mut first parameter so that invoking methods can update the value in memory.*
|
*Note: [`Engine`] follows the convention that methods use a `&mut` first parameter so that invoking methods can update the value in memory.*
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
engine.register_fn("update", TestStruct::update);
|
engine.register_fn("update", TestStruct::update);
|
||||||
@ -530,7 +530,7 @@ println!("result: {}", result);
|
|||||||
Initializing and maintaining state
|
Initializing and maintaining state
|
||||||
---------------------------------
|
---------------------------------
|
||||||
|
|
||||||
By default, Rhai treats each engine invocation as a fresh one, persisting only the functions that have been defined but no top-level state. This gives each one a fairly clean starting place. Sometimes, though, you want to continue using the same top-level state from one invocation to the next.
|
By default, Rhai treats each [`Engine`] invocation as a fresh one, persisting only the functions that have been defined but no top-level state. This gives each one a fairly clean starting place. Sometimes, though, you want to continue using the same top-level state from one invocation to the next.
|
||||||
|
|
||||||
In this example, we first create a state with a few initialized variables, then thread the same state through multiple invocations:
|
In this example, we first create a state with a few initialized variables, then thread the same state through multiple invocations:
|
||||||
|
|
||||||
@ -608,6 +608,13 @@ print(x * 2); // prints 84
|
|||||||
x = 123; // <- syntax error - cannot assign to constant
|
x = 123; // <- syntax error - cannot assign to constant
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Constants must be assigned a _value_ not an expression.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
const x = 40 + 2; // <- syntax error - cannot assign expression to constant
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
Numbers
|
Numbers
|
||||||
-------
|
-------
|
||||||
|
|
||||||
@ -997,7 +1004,7 @@ fn add(x, y) {
|
|||||||
print(add(2, 3));
|
print(add(2, 3));
|
||||||
```
|
```
|
||||||
|
|
||||||
Functions defined in script always take `Dynamic` parameters (i.e. the parameter can be of any type).
|
Functions defined in script always take [`Dynamic`] parameters (i.e. the parameter can be of any type).
|
||||||
It is important to remember that all parameters are passed by _value_, so all functions are _pure_ (i.e. they never modify their parameters).
|
It is important to remember that all parameters are passed by _value_, so all functions are _pure_ (i.e. they never modify their parameters).
|
||||||
Any update to an argument will **not** be reflected back to the caller. This can introduce subtle bugs, if you are not careful.
|
Any update to an argument will **not** be reflected back to the caller. This can introduce subtle bugs, if you are not careful.
|
||||||
|
|
||||||
@ -1029,7 +1036,7 @@ fn do_addition(x) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Functions can be _overloaded_ based on the number of parameters (but not parameter types, since all parameters are `Dynamic`).
|
Functions can be _overloaded_ based on the number of parameters (but not parameter types, since all parameters are [`Dynamic`]).
|
||||||
New definitions of the same name and number of parameters overwrite previous definitions.
|
New definitions of the same name and number of parameters overwrite previous definitions.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
@ -1122,24 +1129,56 @@ The above script optimizes to:
|
|||||||
Constants propagation is used to remove dead code:
|
Constants propagation is used to remove dead code:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
const abc = true;
|
const ABC = true;
|
||||||
if abc || some_work() { print("done!"); } // 'abc' is constant so it is replaced by 'true'...
|
if ABC || some_work() { print("done!"); } // 'ABC' is constant so it is replaced by 'true'...
|
||||||
if true || some_work() { print("done!"); } // since '||' short-circuits, 'some_work' is never called because the LHS is 'true'
|
if true || some_work() { print("done!"); } // since '||' short-circuits, 'some_work' is never called
|
||||||
if true { print("done!"); } // <-- the line above is equivalent to this
|
if true { print("done!"); } // <-- the line above is equivalent to this
|
||||||
print("done!"); // <-- the line above is further simplified to this
|
print("done!"); // <-- the line above is further simplified to this
|
||||||
// because the condition is always true
|
// because the condition is always true
|
||||||
```
|
```
|
||||||
|
|
||||||
These are quite effective for template-based machine-generated scripts where certain constant values are spliced into the script text in order to turn on/off certain sections.
|
These are quite effective for template-based machine-generated scripts where certain constant values are spliced into the script text in order to turn on/off certain sections.
|
||||||
|
For fixed script texts, the constant values can be provided in a user-defined `Scope` object to the `Engine` for use in compilation and evaluation.
|
||||||
|
|
||||||
Beware, however, that most operators are actually function calls, and those functions can be overridden, so they are not optimized away:
|
Beware, however, that most operators are actually function calls, and those functions can be overridden, so they are not optimized away:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
if 1 == 1 { ... } // '1==1' is NOT optimized away because you can define
|
const DECISION = 1;
|
||||||
// your own '==' function to override the built-in default!
|
|
||||||
|
if DECISION == 1 { // NOT optimized away because you can define
|
||||||
|
: // your own '==' function to override the built-in default!
|
||||||
|
:
|
||||||
|
} else if DECISION == 2 { // same here, NOT optimized away
|
||||||
|
:
|
||||||
|
} else if DECISION == 3 { // same here, NOT optimized away
|
||||||
|
:
|
||||||
|
} else {
|
||||||
|
:
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Here be dragons!
|
So, instead, do this:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
const DECISION_1 = true;
|
||||||
|
const DECISION_2 = false;
|
||||||
|
const DECISION_3 = false;
|
||||||
|
|
||||||
|
if DECISION_1 {
|
||||||
|
: // this branch is kept
|
||||||
|
} else if DECISION_2 {
|
||||||
|
: // this branch is eliminated
|
||||||
|
} else if DECISION_3 {
|
||||||
|
: // this branch is eliminated
|
||||||
|
} else {
|
||||||
|
: // this branch is eliminated
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In general, boolean constants are most effective if you want the optimizer to automatically prune large `if`-`else` branches because they do not depend on operators.
|
||||||
|
|
||||||
|
Here be dragons!
|
||||||
|
----------------
|
||||||
|
|
||||||
Some optimizations can be quite aggressive and can alter subtle semantics of the script. For example:
|
Some optimizations can be quite aggressive and can alter subtle semantics of the script. For example:
|
||||||
|
|
||||||
@ -1191,3 +1230,7 @@ engine.set_optimization(false); // turn off the optimizer
|
|||||||
[`no_function`]: #optional-features
|
[`no_function`]: #optional-features
|
||||||
[`only_i32`]: #optional-features
|
[`only_i32`]: #optional-features
|
||||||
[`only_i64`]: #optional-features
|
[`only_i64`]: #optional-features
|
||||||
|
|
||||||
|
[`Engine`]: #hello-world
|
||||||
|
[`Scope`]: #initializing-and-maintaining-state
|
||||||
|
[`Dynamic`]: #values-and-types
|
||||||
|
@ -372,7 +372,15 @@ impl Engine<'_> {
|
|||||||
let val = self.get_dot_val_helper(scope, target.as_mut(), dot_rhs);
|
let val = self.get_dot_val_helper(scope, target.as_mut(), dot_rhs);
|
||||||
|
|
||||||
// In case the expression mutated `target`, we need to update it back into the scope because it is cloned.
|
// In case the expression mutated `target`, we need to update it back into the scope because it is cloned.
|
||||||
if let Some((id, src_idx)) = src {
|
if let Some((id, var_type, src_idx)) = src {
|
||||||
|
match var_type {
|
||||||
|
VariableType::Constant => {
|
||||||
|
return Err(EvalAltResult::ErrorAssignmentToConstant(
|
||||||
|
id.to_string(),
|
||||||
|
idx_lhs.position(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
VariableType::Normal => {
|
||||||
Self::update_indexed_var_in_scope(
|
Self::update_indexed_var_in_scope(
|
||||||
src_type,
|
src_type,
|
||||||
scope,
|
scope,
|
||||||
@ -380,9 +388,11 @@ impl Engine<'_> {
|
|||||||
src_idx,
|
src_idx,
|
||||||
idx,
|
idx,
|
||||||
target,
|
target,
|
||||||
idx_lhs.position(),
|
dot_rhs.position(),
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val
|
val
|
||||||
}
|
}
|
||||||
@ -477,7 +487,15 @@ impl Engine<'_> {
|
|||||||
lhs: &'a Expr,
|
lhs: &'a Expr,
|
||||||
idx_expr: &Expr,
|
idx_expr: &Expr,
|
||||||
idx_pos: Position,
|
idx_pos: Position,
|
||||||
) -> Result<(IndexSourceType, Option<(&'a str, usize)>, usize, Dynamic), EvalAltResult> {
|
) -> Result<
|
||||||
|
(
|
||||||
|
IndexSourceType,
|
||||||
|
Option<(&'a str, VariableType, usize)>,
|
||||||
|
usize,
|
||||||
|
Dynamic,
|
||||||
|
),
|
||||||
|
EvalAltResult,
|
||||||
|
> {
|
||||||
let idx = self.eval_index_value(scope, idx_expr)?;
|
let idx = self.eval_index_value(scope, idx_expr)?;
|
||||||
|
|
||||||
match lhs {
|
match lhs {
|
||||||
@ -488,8 +506,13 @@ impl Engine<'_> {
|
|||||||
|val| self.get_indexed_value(&val, idx, idx_expr.position(), idx_pos),
|
|val| self.get_indexed_value(&val, idx, idx_expr.position(), idx_pos),
|
||||||
lhs.position(),
|
lhs.position(),
|
||||||
)
|
)
|
||||||
.map(|(src_idx, _, (val, src_type))| {
|
.map(|(src_idx, var_type, (val, src_type))| {
|
||||||
(src_type, Some((id.as_str(), src_idx)), idx as usize, val)
|
(
|
||||||
|
src_type,
|
||||||
|
Some((id.as_str(), var_type, src_idx)),
|
||||||
|
idx as usize,
|
||||||
|
val,
|
||||||
|
)
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// (expr)[idx_expr]
|
// (expr)[idx_expr]
|
||||||
@ -739,17 +762,21 @@ impl Engine<'_> {
|
|||||||
self.set_dot_val_helper(scope, target.as_mut(), dot_rhs, new_val, val_pos);
|
self.set_dot_val_helper(scope, target.as_mut(), dot_rhs, new_val, val_pos);
|
||||||
|
|
||||||
// In case the expression mutated `target`, we need to update it back into the scope because it is cloned.
|
// In case the expression mutated `target`, we need to update it back into the scope because it is cloned.
|
||||||
if let Some((id, src_idx)) = src {
|
if let Some((id, var_type, src_idx)) = src {
|
||||||
Self::update_indexed_var_in_scope(
|
match var_type {
|
||||||
src_type,
|
VariableType::Constant => {
|
||||||
scope,
|
return Err(EvalAltResult::ErrorAssignmentToConstant(
|
||||||
id,
|
id.to_string(),
|
||||||
src_idx,
|
|
||||||
idx,
|
|
||||||
target,
|
|
||||||
lhs.position(),
|
lhs.position(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
VariableType::Normal => {
|
||||||
|
Self::update_indexed_var_in_scope(
|
||||||
|
src_type, scope, id, src_idx, idx, target, val_pos,
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val
|
val
|
||||||
}
|
}
|
||||||
@ -811,8 +838,15 @@ impl Engine<'_> {
|
|||||||
let (src_type, src, idx, _) =
|
let (src_type, src, idx, _) =
|
||||||
self.eval_index_expr(scope, idx_lhs, idx_expr, *idx_pos)?;
|
self.eval_index_expr(scope, idx_lhs, idx_expr, *idx_pos)?;
|
||||||
|
|
||||||
if let Some((id, src_idx)) = src {
|
if let Some((id, var_type, src_idx)) = src {
|
||||||
Ok(Self::update_indexed_var_in_scope(
|
match var_type {
|
||||||
|
VariableType::Constant => {
|
||||||
|
return Err(EvalAltResult::ErrorAssignmentToConstant(
|
||||||
|
id.to_string(),
|
||||||
|
idx_lhs.position(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
VariableType::Normal => Ok(Self::update_indexed_var_in_scope(
|
||||||
src_type,
|
src_type,
|
||||||
scope,
|
scope,
|
||||||
&id,
|
&id,
|
||||||
@ -820,7 +854,8 @@ impl Engine<'_> {
|
|||||||
idx,
|
idx,
|
||||||
rhs_val,
|
rhs_val,
|
||||||
rhs.position(),
|
rhs.position(),
|
||||||
)?)
|
)?),
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(EvalAltResult::ErrorAssignmentToUnknownLHS(
|
Err(EvalAltResult::ErrorAssignmentToUnknownLHS(
|
||||||
idx_lhs.position(),
|
idx_lhs.position(),
|
||||||
|
@ -343,6 +343,8 @@ impl Expr {
|
|||||||
| Expr::False(_)
|
| Expr::False(_)
|
||||||
| Expr::Unit(_) => true,
|
| Expr::Unit(_) => true,
|
||||||
|
|
||||||
|
Expr::Array(expressions, _) => expressions.iter().all(Expr::is_constant),
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
Expr::FloatConstant(_, _) => true,
|
Expr::FloatConstant(_, _) => true,
|
||||||
|
|
||||||
@ -1635,12 +1637,12 @@ fn parse_assignment(lhs: Expr, rhs: Expr, pos: Position) -> Result<Expr, ParseEr
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Index(idx_lhs, _, _) if is_top => valid_assignment_chain(idx_lhs, true),
|
Expr::Index(idx_lhs, _, pos) => Some(ParseError::new(
|
||||||
|
match idx_lhs.as_ref() {
|
||||||
#[cfg(not(feature = "no_index"))]
|
Expr::Index(_, _, _) => ParseErrorType::AssignmentToCopy,
|
||||||
Expr::Index(idx_lhs, _, _) if !is_top => Some(ParseError::new(
|
_ => ParseErrorType::AssignmentToInvalidLHS,
|
||||||
ParseErrorType::AssignmentToInvalidLHS,
|
},
|
||||||
idx_lhs.position(),
|
*pos,
|
||||||
)),
|
)),
|
||||||
|
|
||||||
Expr::Dot(dot_lhs, dot_rhs, _) => match dot_lhs.as_ref() {
|
Expr::Dot(dot_lhs, dot_rhs, _) => match dot_lhs.as_ref() {
|
||||||
|
@ -7,6 +7,10 @@ fn test_arrays() -> Result<(), EvalAltResult> {
|
|||||||
|
|
||||||
assert_eq!(engine.eval::<INT>("let x = [1, 2, 3]; x[1]")?, 2);
|
assert_eq!(engine.eval::<INT>("let x = [1, 2, 3]; x[1]")?, 2);
|
||||||
assert_eq!(engine.eval::<INT>("let y = [1, 2, 3]; y[1] = 5; y[1]")?, 5);
|
assert_eq!(engine.eval::<INT>("let y = [1, 2, 3]; y[1] = 5; y[1]")?, 5);
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<char>(r#"let y = [1, [ 42, 88, "93" ], 3]; y[1][2][1]"#)?,
|
||||||
|
'3'
|
||||||
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -48,10 +52,12 @@ fn test_array_with_structs() -> Result<(), EvalAltResult> {
|
|||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<INT>(
|
engine.eval::<INT>(
|
||||||
"let a = [new_ts()]; \
|
r"
|
||||||
a[0].x = 100; \
|
let a = [new_ts()];
|
||||||
a[0].update(); \
|
a[0].x = 100;
|
||||||
a[0].x",
|
a[0].update();
|
||||||
|
a[0].x
|
||||||
|
"
|
||||||
)?,
|
)?,
|
||||||
1100
|
1100
|
||||||
);
|
);
|
||||||
|
@ -6,7 +6,13 @@ fn test_constant() -> Result<(), EvalAltResult> {
|
|||||||
|
|
||||||
assert_eq!(engine.eval::<i64>("const x = 123; x")?, 123);
|
assert_eq!(engine.eval::<i64>("const x = 123; x")?, 123);
|
||||||
|
|
||||||
match engine.eval::<i64>("const x = 123; x = 42; x") {
|
match engine.eval::<i64>("const x = 123; x = 42;") {
|
||||||
|
Err(EvalAltResult::ErrorAssignmentToConstant(var, _)) if var == "x" => (),
|
||||||
|
Err(err) => return Err(err),
|
||||||
|
Ok(_) => panic!("expecting compilation error"),
|
||||||
|
}
|
||||||
|
|
||||||
|
match engine.eval::<i64>("const x = [1, 2, 3, 4, 5]; x[2] = 42;") {
|
||||||
Err(EvalAltResult::ErrorAssignmentToConstant(var, _)) if var == "x" => (),
|
Err(EvalAltResult::ErrorAssignmentToConstant(var, _)) if var == "x" => (),
|
||||||
Err(err) => return Err(err),
|
Err(err) => return Err(err),
|
||||||
Ok(_) => panic!("expecting compilation error"),
|
Ok(_) => panic!("expecting compilation error"),
|
||||||
|
Loading…
Reference in New Issue
Block a user