FIXED - method calls inside dot chain.
This commit is contained in:
parent
99164ebceb
commit
f36b4a69ae
@ -11,6 +11,11 @@ This version adds:
|
|||||||
* Ability to define custom operators (which must be valid identifiers).
|
* Ability to define custom operators (which must be valid identifiers).
|
||||||
* Low-level API to register functions.
|
* Low-level API to register functions.
|
||||||
|
|
||||||
|
Bug fixes
|
||||||
|
---------
|
||||||
|
|
||||||
|
* Fixed method calls in the middle of a dot chain.
|
||||||
|
|
||||||
Breaking changes
|
Breaking changes
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
|
@ -105,9 +105,11 @@ The Rhai Scripting Language
|
|||||||
5. [Volatility Considerations](engine/optimize/volatility.md)
|
5. [Volatility Considerations](engine/optimize/volatility.md)
|
||||||
6. [Subtle Semantic Changes](engine/optimize/semantics.md)
|
6. [Subtle Semantic Changes](engine/optimize/semantics.md)
|
||||||
4. [Low-Level API](rust/register-raw.md)
|
4. [Low-Level API](rust/register-raw.md)
|
||||||
5. [Disable Keywords and/or Operators](engine/disable.md)
|
5. [Use as DSL](engine/dsl.md)
|
||||||
6. [Custom Operators](engine/custom-op.md)
|
1. [Disable Keywords and/or Operators](engine/disable.md)
|
||||||
7. [Eval Statement](language/eval.md)
|
2. [Custom Operators](engine/custom-op.md)
|
||||||
|
3. [Custom Syntax](engine/custom-syntax.md)
|
||||||
|
6. [Eval Statement](language/eval.md)
|
||||||
9. [Appendix](appendix/index.md)
|
9. [Appendix](appendix/index.md)
|
||||||
1. [Keywords](appendix/keywords.md)
|
1. [Keywords](appendix/keywords.md)
|
||||||
2. [Operators and Symbols](appendix/operators.md)
|
2. [Operators and Symbols](appendix/operators.md)
|
||||||
|
@ -67,4 +67,4 @@ Flexible
|
|||||||
|
|
||||||
* Surgically [disable keywords and operators] to restrict the language.
|
* Surgically [disable keywords and operators] to restrict the language.
|
||||||
|
|
||||||
* [Custom operators].
|
* Use as a [DSL] by [disabling keywords/operators][disable keywords and operators], [custom operators] and defining [custom syntax].
|
||||||
|
@ -41,6 +41,7 @@ Symbols
|
|||||||
| ------------ | ------------------------ |
|
| ------------ | ------------------------ |
|
||||||
| `:` | Property value separator |
|
| `:` | Property value separator |
|
||||||
| `::` | Module path separator |
|
| `::` | Module path separator |
|
||||||
|
| `#` | _Reserved_ |
|
||||||
| `=>` | _Reserved_ |
|
| `=>` | _Reserved_ |
|
||||||
| `->` | _Reserved_ |
|
| `->` | _Reserved_ |
|
||||||
| `<-` | _Reserved_ |
|
| `<-` | _Reserved_ |
|
||||||
|
5
doc/src/engine/custom-syntax.md
Normal file
5
doc/src/engine/custom-syntax.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
Custom Syntax
|
||||||
|
=============
|
||||||
|
|
||||||
|
{{#include ../links.md}}
|
||||||
|
|
80
doc/src/engine/dsl.md
Normal file
80
doc/src/engine/dsl.md
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
Use Rhai as a Domain-Specific Language (DSL)
|
||||||
|
===========================================
|
||||||
|
|
||||||
|
{{#include ../links.md}}
|
||||||
|
|
||||||
|
Rhai can be successfully used as a domain-specific language (DSL).
|
||||||
|
|
||||||
|
|
||||||
|
Expressions Only
|
||||||
|
----------------
|
||||||
|
|
||||||
|
In many DSL scenarios, only evaluation of expressions is needed.
|
||||||
|
|
||||||
|
The `Engine::eval_expression_XXX`[`eval_expression`] API can be used to restrict
|
||||||
|
a script to expressions only.
|
||||||
|
|
||||||
|
|
||||||
|
Disable Keywords and/or Operators
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
|
In some DSL scenarios, it is necessary to further restrict the language to exclude certain
|
||||||
|
language features that are not necessary or dangerous to the application.
|
||||||
|
|
||||||
|
For example, a DSL may disable the `while` loop altogether while keeping all other statement
|
||||||
|
types intact.
|
||||||
|
|
||||||
|
It is possible, in Rhai, to surgically [disable keywords and operators].
|
||||||
|
|
||||||
|
|
||||||
|
Custom Operators
|
||||||
|
----------------
|
||||||
|
|
||||||
|
On the other hand, some DSL scenarios require special operators that make sense only for
|
||||||
|
that specific environment. In such cases, it is possible to define [custom operators] in Rhai.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let animal = "rabbit";
|
||||||
|
let food = "carrot";
|
||||||
|
|
||||||
|
animal eats food // custom operator - 'eats'
|
||||||
|
|
||||||
|
eats(animal, food) // <- the above really de-sugars to this
|
||||||
|
```
|
||||||
|
|
||||||
|
Although a [custom operator] always de-sugars to a simple function call,
|
||||||
|
nevertheless it makes the DSL syntax much simpler and expressive.
|
||||||
|
|
||||||
|
|
||||||
|
Custom Syntax
|
||||||
|
-------------
|
||||||
|
|
||||||
|
For advanced DSL scenarios, it is possible to define entire expression [_syntax_][custom syntax] -
|
||||||
|
essentially custom statement types.
|
||||||
|
|
||||||
|
The [`internals`] feature is needed to be able to define [custom syntax] in Rhai.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let table = [..., ..., ..., ...];
|
||||||
|
|
||||||
|
// Syntax = "select" $ident$ $ident$ "from" $expr$ "->" $ident$ ":" $expr$
|
||||||
|
let total = select sum price from table -> row : row.weight > 50;
|
||||||
|
```
|
||||||
|
|
||||||
|
After registering this custom syntax with Rhai, it can be used anywhere inside a script as
|
||||||
|
a normal expression.
|
||||||
|
|
||||||
|
For its evaluation, the callback function will receive the following list of parameters:
|
||||||
|
|
||||||
|
`exprs[0] = "sum"` - math operator
|
||||||
|
`exprs[1] = "price"` - field name
|
||||||
|
`exprs[2] = Expr(table)` - data source
|
||||||
|
`exprs[3] = "row"` - loop variable name
|
||||||
|
`exprs[4] = Expr(row.wright > 50)` - expression
|
||||||
|
|
||||||
|
The other identified, such as `"select"`, `"from"`, as as as symbols `->` and `:` are
|
||||||
|
parsed in the order defined within the custom syntax.
|
@ -89,6 +89,7 @@
|
|||||||
[`eval`]: {{rootUrl}}/language/eval.md
|
[`eval`]: {{rootUrl}}/language/eval.md
|
||||||
|
|
||||||
[OOP]: {{rootUrl}}/language/oop.md
|
[OOP]: {{rootUrl}}/language/oop.md
|
||||||
|
[DSL]: {{rootUrl}}/engine/dsl.md
|
||||||
|
|
||||||
[maximum statement depth]: {{rootUrl}}/safety/max-stmt-depth.md
|
[maximum statement depth]: {{rootUrl}}/safety/max-stmt-depth.md
|
||||||
[maximum call stack depth]: {{rootUrl}}/safety/max-call-stack.md
|
[maximum call stack depth]: {{rootUrl}}/safety/max-call-stack.md
|
||||||
@ -107,3 +108,4 @@
|
|||||||
[disable keywords and operators]: {{rootUrl}}/engine/disable.md
|
[disable keywords and operators]: {{rootUrl}}/engine/disable.md
|
||||||
[custom operator]: {{rootUrl}}/engine/custom-op.md
|
[custom operator]: {{rootUrl}}/engine/custom-op.md
|
||||||
[custom operators]: {{rootUrl}}/engine/custom-op.md
|
[custom operators]: {{rootUrl}}/engine/custom-op.md
|
||||||
|
[custom syntax]: {{rootUrl}}/engine/custom-syntax.md
|
||||||
|
@ -25,7 +25,7 @@ more control over what a script can (or cannot) do.
|
|||||||
| `no_module` | Disable loading external [modules]. |
|
| `no_module` | Disable loading external [modules]. |
|
||||||
| `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. |
|
| `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. |
|
||||||
| `serde` | Enable serialization/deserialization via [`serde`]. Notice that the [`serde`](https://crates.io/crates/serde) crate will be pulled in together with its dependencies. |
|
| `serde` | Enable serialization/deserialization via [`serde`]. Notice that the [`serde`](https://crates.io/crates/serde) crate will be pulled in together with its dependencies. |
|
||||||
| `internals` | Expose internal data structures (e.g. [`AST`] nodes). Beware that Rhai internals are volatile and may change from version to version. |
|
| `internals` | Expose internal data structures (e.g. [`AST`] nodes) and enable defining [custom syntax]. Beware that Rhai internals are volatile and may change from version to version. |
|
||||||
|
|
||||||
|
|
||||||
Example
|
Example
|
||||||
|
287
src/engine.rs
287
src/engine.rs
@ -1012,6 +1012,81 @@ impl Engine {
|
|||||||
return Ok(result);
|
return Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Call a dot method.
|
||||||
|
fn call_method(
|
||||||
|
&self,
|
||||||
|
state: &mut State,
|
||||||
|
lib: &Module,
|
||||||
|
target: &mut Target,
|
||||||
|
expr: &Expr,
|
||||||
|
mut idx_val: Dynamic,
|
||||||
|
level: usize,
|
||||||
|
) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
|
||||||
|
let ((name, native, pos), _, hash, _, def_val) = match expr {
|
||||||
|
Expr::FnCall(x) => x.as_ref(),
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let is_ref = target.is_ref();
|
||||||
|
let is_value = target.is_value();
|
||||||
|
let def_val = def_val.as_ref();
|
||||||
|
|
||||||
|
// Get a reference to the mutation target Dynamic
|
||||||
|
let obj = target.as_mut();
|
||||||
|
let idx = idx_val.downcast_mut::<StaticVec<Dynamic>>().unwrap();
|
||||||
|
let mut fn_name = name.as_ref();
|
||||||
|
|
||||||
|
// Check if it is a FnPtr call
|
||||||
|
let (result, updated) = if fn_name == KEYWORD_FN_PTR_CALL && obj.is::<FnPtr>() {
|
||||||
|
// Redirect function name
|
||||||
|
fn_name = obj.as_str().unwrap();
|
||||||
|
// Recalculate hash
|
||||||
|
let hash = calc_fn_hash(empty(), fn_name, idx.len(), empty());
|
||||||
|
// Arguments are passed as-is
|
||||||
|
let mut arg_values = idx.iter_mut().collect::<StaticVec<_>>();
|
||||||
|
let args = arg_values.as_mut();
|
||||||
|
|
||||||
|
// Map it to name(args) in function-call style
|
||||||
|
self.exec_fn_call(
|
||||||
|
state, lib, fn_name, *native, hash, args, false, false, def_val, level,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
let redirected: Option<ImmutableString>;
|
||||||
|
let mut hash = *hash;
|
||||||
|
|
||||||
|
// Check if it is a map method call in OOP style
|
||||||
|
if let Some(map) = obj.downcast_ref::<Map>() {
|
||||||
|
if let Some(val) = map.get(fn_name) {
|
||||||
|
if let Some(f) = val.downcast_ref::<FnPtr>() {
|
||||||
|
// Remap the function name
|
||||||
|
redirected = Some(f.get_fn_name().clone());
|
||||||
|
fn_name = redirected.as_ref().unwrap();
|
||||||
|
|
||||||
|
// Recalculate the hash based on the new function name
|
||||||
|
hash = calc_fn_hash(empty(), fn_name, idx.len(), empty());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Attached object pointer in front of the arguments
|
||||||
|
let mut arg_values = once(obj).chain(idx.iter_mut()).collect::<StaticVec<_>>();
|
||||||
|
let args = arg_values.as_mut();
|
||||||
|
|
||||||
|
self.exec_fn_call(
|
||||||
|
state, lib, fn_name, *native, hash, args, is_ref, true, def_val, level,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.map_err(|err| err.new_position(*pos))?;
|
||||||
|
|
||||||
|
// Feed the changed temp value back
|
||||||
|
if updated && !is_ref && !is_value {
|
||||||
|
let new_val = target.as_mut().clone();
|
||||||
|
target.set_value(new_val)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((result, updated))
|
||||||
|
}
|
||||||
|
|
||||||
/// Chain-evaluate a dot/index chain.
|
/// Chain-evaluate a dot/index chain.
|
||||||
/// Position in `EvalAltResult` is None and must be set afterwards.
|
/// Position in `EvalAltResult` is None and must be set afterwards.
|
||||||
fn eval_dot_index_chain_helper(
|
fn eval_dot_index_chain_helper(
|
||||||
@ -1031,7 +1106,6 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let is_ref = target.is_ref();
|
let is_ref = target.is_ref();
|
||||||
let is_value = target.is_value();
|
|
||||||
|
|
||||||
let next_chain = match rhs {
|
let next_chain = match rhs {
|
||||||
Expr::Index(_) => ChainType::Index,
|
Expr::Index(_) => ChainType::Index,
|
||||||
@ -1040,7 +1114,7 @@ impl Engine {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Pop the last index value
|
// Pop the last index value
|
||||||
let mut idx_val = idx_values.pop();
|
let idx_val = idx_values.pop();
|
||||||
|
|
||||||
match chain_type {
|
match chain_type {
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
@ -1124,69 +1198,7 @@ impl Engine {
|
|||||||
match rhs {
|
match rhs {
|
||||||
// xxx.fn_name(arg_expr_list)
|
// xxx.fn_name(arg_expr_list)
|
||||||
Expr::FnCall(x) if x.1.is_none() => {
|
Expr::FnCall(x) if x.1.is_none() => {
|
||||||
let ((name, native, pos), _, hash, _, def_val) = x.as_ref();
|
self.call_method(state, lib, target, rhs, idx_val, level)
|
||||||
let def_val = def_val.as_ref();
|
|
||||||
|
|
||||||
// Get a reference to the mutation target Dynamic
|
|
||||||
let (result, updated) = {
|
|
||||||
let obj = target.as_mut();
|
|
||||||
let idx = idx_val.downcast_mut::<StaticVec<Dynamic>>().unwrap();
|
|
||||||
let mut fn_name = name.as_ref();
|
|
||||||
|
|
||||||
// Check if it is a FnPtr call
|
|
||||||
if fn_name == KEYWORD_FN_PTR_CALL && obj.is::<FnPtr>() {
|
|
||||||
// Redirect function name
|
|
||||||
fn_name = obj.as_str().unwrap();
|
|
||||||
// Recalculate hash
|
|
||||||
let hash = calc_fn_hash(empty(), fn_name, idx.len(), empty());
|
|
||||||
// Arguments are passed as-is
|
|
||||||
let mut arg_values = idx.iter_mut().collect::<StaticVec<_>>();
|
|
||||||
let args = arg_values.as_mut();
|
|
||||||
|
|
||||||
// Map it to name(args) in function-call style
|
|
||||||
self.exec_fn_call(
|
|
||||||
state, lib, fn_name, *native, hash, args, false, false,
|
|
||||||
def_val, level,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
let redirected: Option<ImmutableString>;
|
|
||||||
let mut hash = *hash;
|
|
||||||
|
|
||||||
// Check if it is a map method call in OOP style
|
|
||||||
if let Some(map) = obj.downcast_ref::<Map>() {
|
|
||||||
if let Some(val) = map.get(fn_name) {
|
|
||||||
if let Some(f) = val.downcast_ref::<FnPtr>() {
|
|
||||||
// Remap the function name
|
|
||||||
redirected = Some(f.get_fn_name().clone());
|
|
||||||
fn_name = redirected.as_ref().unwrap();
|
|
||||||
|
|
||||||
// Recalculate the hash based on the new function name
|
|
||||||
hash =
|
|
||||||
calc_fn_hash(empty(), fn_name, idx.len(), empty());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Attached object pointer in front of the arguments
|
|
||||||
let mut arg_values =
|
|
||||||
once(obj).chain(idx.iter_mut()).collect::<StaticVec<_>>();
|
|
||||||
let args = arg_values.as_mut();
|
|
||||||
|
|
||||||
self.exec_fn_call(
|
|
||||||
state, lib, fn_name, *native, hash, args, is_ref, true,
|
|
||||||
def_val, level,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.map_err(|err| err.new_position(*pos))?
|
|
||||||
};
|
|
||||||
|
|
||||||
// Feed the changed temp value back
|
|
||||||
if updated && !is_ref && !is_value {
|
|
||||||
let new_val = target.as_mut().clone();
|
|
||||||
target.set_value(new_val)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok((result, updated))
|
|
||||||
}
|
}
|
||||||
// xxx.module::fn_name(...) - syntax error
|
// xxx.module::fn_name(...) - syntax error
|
||||||
Expr::FnCall(_) => unreachable!(),
|
Expr::FnCall(_) => unreachable!(),
|
||||||
@ -1230,16 +1242,26 @@ impl Engine {
|
|||||||
.map(|(v, _)| (v, false))
|
.map(|(v, _)| (v, false))
|
||||||
.map_err(|err| err.new_position(*pos))
|
.map_err(|err| err.new_position(*pos))
|
||||||
}
|
}
|
||||||
// {xxx:map}.prop[expr] | {xxx:map}.prop.expr
|
// {xxx:map}.sub_lhs[expr] | {xxx:map}.sub_lhs.expr
|
||||||
Expr::Index(x) | Expr::Dot(x) if target.is::<Map>() => {
|
Expr::Index(x) | Expr::Dot(x) if target.is::<Map>() => {
|
||||||
let (prop, expr, pos) = x.as_ref();
|
let (sub_lhs, expr, pos) = x.as_ref();
|
||||||
|
|
||||||
let mut val = if let Expr::Property(p) = prop {
|
let mut val = match sub_lhs {
|
||||||
let ((prop, _, _), _) = p.as_ref();
|
Expr::Property(p) => {
|
||||||
let index = prop.clone().into();
|
let ((prop, _, _), _) = p.as_ref();
|
||||||
self.get_indexed_mut(state, lib, target, index, *pos, false, level)?
|
let index = prop.clone().into();
|
||||||
} else {
|
self.get_indexed_mut(state, lib, target, index, *pos, false, level)?
|
||||||
unreachable!();
|
}
|
||||||
|
// {xxx:map}.fn_name(arg_expr_list)[expr] | {xxx:map}.fn_name(arg_expr_list).expr
|
||||||
|
Expr::FnCall(x) if x.1.is_none() => {
|
||||||
|
let (val, _) =
|
||||||
|
self.call_method(state, lib, target, sub_lhs, idx_val, level)?;
|
||||||
|
val.into()
|
||||||
|
}
|
||||||
|
// {xxx:map}.module::fn_name(...) - syntax error
|
||||||
|
Expr::FnCall(_) => unreachable!(),
|
||||||
|
// Others - syntax error
|
||||||
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.eval_dot_index_chain_helper(
|
self.eval_dot_index_chain_helper(
|
||||||
@ -1248,49 +1270,72 @@ impl Engine {
|
|||||||
)
|
)
|
||||||
.map_err(|err| err.new_position(*pos))
|
.map_err(|err| err.new_position(*pos))
|
||||||
}
|
}
|
||||||
// xxx.prop[expr] | xxx.prop.expr
|
// xxx.sub_lhs[expr] | xxx.sub_lhs.expr
|
||||||
Expr::Index(x) | Expr::Dot(x) => {
|
Expr::Index(x) | Expr::Dot(x) => {
|
||||||
let (prop, expr, pos) = x.as_ref();
|
let (sub_lhs, expr, pos) = x.as_ref();
|
||||||
let args = &mut [target.as_mut(), &mut Default::default()];
|
|
||||||
|
|
||||||
let (mut val, updated) = if let Expr::Property(p) = prop {
|
match sub_lhs {
|
||||||
let ((_, getter, _), _) = p.as_ref();
|
// xxx.prop[expr] | xxx.prop.expr
|
||||||
let args = &mut args[..1];
|
Expr::Property(p) => {
|
||||||
self.exec_fn_call(
|
let ((_, getter, setter), _) = p.as_ref();
|
||||||
state, lib, getter, true, 0, args, is_ref, true, None, level,
|
let arg_values = &mut [target.as_mut(), &mut Default::default()];
|
||||||
)
|
let args = &mut arg_values[..1];
|
||||||
.map_err(|err| err.new_position(*pos))?
|
let (mut val, updated) = self
|
||||||
} else {
|
.exec_fn_call(
|
||||||
unreachable!();
|
state, lib, getter, true, 0, args, is_ref, true, None,
|
||||||
};
|
level,
|
||||||
let val = &mut val;
|
)
|
||||||
let target = &mut val.into();
|
.map_err(|err| err.new_position(*pos))?;
|
||||||
|
|
||||||
let (result, may_be_changed) = self
|
let val = &mut val;
|
||||||
.eval_dot_index_chain_helper(
|
let target = &mut val.into();
|
||||||
state, lib, this_ptr, target, expr, idx_values, next_chain, level,
|
|
||||||
new_val,
|
|
||||||
)
|
|
||||||
.map_err(|err| err.new_position(*pos))?;
|
|
||||||
|
|
||||||
// Feed the value back via a setter just in case it has been updated
|
let (result, may_be_changed) = self
|
||||||
if updated || may_be_changed {
|
.eval_dot_index_chain_helper(
|
||||||
if let Expr::Property(p) = prop {
|
state, lib, this_ptr, target, expr, idx_values, next_chain,
|
||||||
let ((_, _, setter), _) = p.as_ref();
|
level, new_val,
|
||||||
// Re-use args because the first &mut parameter will not be consumed
|
)
|
||||||
args[1] = val;
|
.map_err(|err| err.new_position(*pos))?;
|
||||||
self.exec_fn_call(
|
|
||||||
state, lib, setter, true, 0, args, is_ref, true, None, level,
|
// Feed the value back via a setter just in case it has been updated
|
||||||
)
|
if updated || may_be_changed {
|
||||||
.or_else(|err| match *err {
|
// Re-use args because the first &mut parameter will not be consumed
|
||||||
// If there is no setter, no need to feed it back because the property is read-only
|
arg_values[1] = val;
|
||||||
EvalAltResult::ErrorDotExpr(_, _) => Ok(Default::default()),
|
self.exec_fn_call(
|
||||||
_ => Err(err.new_position(*pos)),
|
state, lib, setter, true, 0, arg_values, is_ref, true,
|
||||||
})?;
|
None, level,
|
||||||
|
)
|
||||||
|
.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.new_position(*pos)),
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((result, may_be_changed))
|
||||||
}
|
}
|
||||||
}
|
// xxx.fn_name(arg_expr_list)[expr] | xxx.fn_name(arg_expr_list).expr
|
||||||
|
Expr::FnCall(x) if x.1.is_none() => {
|
||||||
|
let (mut val, _) =
|
||||||
|
self.call_method(state, lib, target, sub_lhs, idx_val, level)?;
|
||||||
|
let val = &mut val;
|
||||||
|
let target = &mut val.into();
|
||||||
|
|
||||||
Ok((result, may_be_changed))
|
self.eval_dot_index_chain_helper(
|
||||||
|
state, lib, this_ptr, target, expr, idx_values, next_chain,
|
||||||
|
level, new_val,
|
||||||
|
)
|
||||||
|
.map_err(|err| err.new_position(*pos))
|
||||||
|
}
|
||||||
|
// xxx.module::fn_name(...) - syntax error
|
||||||
|
Expr::FnCall(_) => unreachable!(),
|
||||||
|
// Others - syntax error
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Syntax error
|
// Syntax error
|
||||||
_ => Err(Box::new(EvalAltResult::ErrorDotExpr(
|
_ => Err(Box::new(EvalAltResult::ErrorDotExpr(
|
||||||
@ -1325,7 +1370,7 @@ impl Engine {
|
|||||||
let idx_values = &mut StaticVec::new();
|
let idx_values = &mut StaticVec::new();
|
||||||
|
|
||||||
self.eval_indexed_chain(
|
self.eval_indexed_chain(
|
||||||
scope, mods, state, lib, this_ptr, dot_rhs, idx_values, 0, level,
|
scope, mods, state, lib, this_ptr, dot_rhs, chain_type, idx_values, 0, level,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
match dot_lhs {
|
match dot_lhs {
|
||||||
@ -1389,6 +1434,7 @@ impl Engine {
|
|||||||
lib: &Module,
|
lib: &Module,
|
||||||
this_ptr: &mut Option<&mut Dynamic>,
|
this_ptr: &mut Option<&mut Dynamic>,
|
||||||
expr: &Expr,
|
expr: &Expr,
|
||||||
|
chain_type: ChainType,
|
||||||
idx_values: &mut StaticVec<Dynamic>,
|
idx_values: &mut StaticVec<Dynamic>,
|
||||||
size: usize,
|
size: usize,
|
||||||
level: usize,
|
level: usize,
|
||||||
@ -1415,12 +1461,29 @@ impl Engine {
|
|||||||
// Evaluate in left-to-right order
|
// Evaluate in left-to-right order
|
||||||
let lhs_val = match lhs {
|
let lhs_val = match lhs {
|
||||||
Expr::Property(_) => Default::default(), // Store a placeholder in case of a property
|
Expr::Property(_) => Default::default(), // Store a placeholder in case of a property
|
||||||
|
Expr::FnCall(x) if chain_type == ChainType::Dot && x.1.is_none() => {
|
||||||
|
let arg_values = x
|
||||||
|
.3
|
||||||
|
.iter()
|
||||||
|
.map(|arg_expr| {
|
||||||
|
self.eval_expr(scope, mods, state, lib, this_ptr, arg_expr, level)
|
||||||
|
})
|
||||||
|
.collect::<Result<StaticVec<Dynamic>, _>>()?;
|
||||||
|
|
||||||
|
Dynamic::from(arg_values)
|
||||||
|
}
|
||||||
|
Expr::FnCall(_) => unreachable!(),
|
||||||
_ => self.eval_expr(scope, mods, state, lib, this_ptr, lhs, level)?,
|
_ => self.eval_expr(scope, mods, state, lib, this_ptr, lhs, level)?,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Push in reverse order
|
// Push in reverse order
|
||||||
|
let chain_type = match expr {
|
||||||
|
Expr::Index(_) => ChainType::Index,
|
||||||
|
Expr::Dot(_) => ChainType::Dot,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
self.eval_indexed_chain(
|
self.eval_indexed_chain(
|
||||||
scope, mods, state, lib, this_ptr, rhs, idx_values, size, level,
|
scope, mods, state, lib, this_ptr, rhs, chain_type, idx_values, size, level,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
idx_values.push(lhs_val);
|
idx_values.push(lhs_val);
|
||||||
|
Loading…
Reference in New Issue
Block a user