Merge branch 'master' into plugins
This commit is contained in:
commit
a2ddd2175e
@ -7,6 +7,7 @@ Version 0.18.0
|
|||||||
This version adds:
|
This version adds:
|
||||||
|
|
||||||
* Anonymous functions (in closure syntax). Simplifies creation of ad hoc functions.
|
* Anonymous functions (in closure syntax). Simplifies creation of ad hoc functions.
|
||||||
|
* Currying of function pointers.
|
||||||
|
|
||||||
New features
|
New features
|
||||||
------------
|
------------
|
||||||
@ -15,6 +16,8 @@ New features
|
|||||||
* Disallow many keywords as variables, such as `print`, `eval`, `call`, `this` etc.
|
* Disallow many keywords as variables, such as `print`, `eval`, `call`, `this` etc.
|
||||||
* `x.call(f, ...)` allows binding `x` to `this` for the function referenced by the function pointer `f`.
|
* `x.call(f, ...)` allows binding `x` to `this` for the function referenced by the function pointer `f`.
|
||||||
* Anonymous functions in the syntax of a closure, e.g. `|x, y, z| x + y - z`.
|
* Anonymous functions in the syntax of a closure, e.g. `|x, y, z| x + y - z`.
|
||||||
|
* Custom syntax now works even without the `internals` feature.
|
||||||
|
* Currying of function pointers is supported via the `curry` keyword.
|
||||||
|
|
||||||
Breaking changes
|
Breaking changes
|
||||||
----------------
|
----------------
|
||||||
@ -48,7 +51,7 @@ Breaking changes
|
|||||||
New features
|
New features
|
||||||
------------
|
------------
|
||||||
|
|
||||||
* New `serde` feature to allow serializating/deserializating to/from `Dynamic` values using [`serde`](https://crates.io/crates/serde).
|
* New `serde` feature to allow serializing/deserializing to/from `Dynamic` values using [`serde`](https://crates.io/crates/serde).
|
||||||
This is particularly useful when converting a Rust `struct` to a `Dynamic` _object map_ and back.
|
This is particularly useful when converting a Rust `struct` to a `Dynamic` _object map_ and back.
|
||||||
* `Engine::disable_symbol` to surgically disable keywords and/or operators.
|
* `Engine::disable_symbol` to surgically disable keywords and/or operators.
|
||||||
* `Engine::register_custom_operator` to define a custom operator.
|
* `Engine::register_custom_operator` to define a custom operator.
|
||||||
|
@ -76,6 +76,7 @@ The Rhai Scripting Language
|
|||||||
3. [Namespaces](language/fn-namespaces.md)
|
3. [Namespaces](language/fn-namespaces.md)
|
||||||
4. [Function Pointers](language/fn-ptr.md)
|
4. [Function Pointers](language/fn-ptr.md)
|
||||||
5. [Anonymous Functions](language/fn-anon.md)
|
5. [Anonymous Functions](language/fn-anon.md)
|
||||||
|
6. [Currying](language/fn-curry.md)
|
||||||
15. [Print and Debug](language/print-debug.md)
|
15. [Print and Debug](language/print-debug.md)
|
||||||
16. [Modules](language/modules/index.md)
|
16. [Modules](language/modules/index.md)
|
||||||
1. [Export Variables, Functions and Sub-Modules](language/modules/export.md)
|
1. [Export Variables, Functions and Sub-Modules](language/modules/export.md)
|
||||||
|
@ -26,6 +26,7 @@ Keywords List
|
|||||||
| `fn` (lower-case `f`) | Function definition | [`no_function`] |
|
| `fn` (lower-case `f`) | Function definition | [`no_function`] |
|
||||||
| `Fn` (capital `F`) | Function to create a [function pointer] | |
|
| `Fn` (capital `F`) | Function to create a [function pointer] | |
|
||||||
| `call` | Call a [function pointer] | |
|
| `call` | Call a [function pointer] | |
|
||||||
|
| `curry` | Curry a [function pointer] | |
|
||||||
| `this` | Reference to base object for method call | [`no_function`] |
|
| `this` | Reference to base object for method call | [`no_function`] |
|
||||||
| `type_of` | Get type name of value | |
|
| `type_of` | Get type name of value | |
|
||||||
| `print` | Print value | |
|
| `print` | Print value | |
|
||||||
|
@ -33,18 +33,7 @@ Where This Might Be Useful
|
|||||||
* Where you just want to confuse your user and make their lives miserable, because you can.
|
* Where you just want to confuse your user and make their lives miserable, because you can.
|
||||||
|
|
||||||
|
|
||||||
Step One - Start With `internals`
|
Step One - Design The Syntax
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
Since a custom syntax taps deeply into the `AST` and evaluation process of the `Engine`,
|
|
||||||
the [`internals`] feature must be on in order to expose these necessary internal data structures.
|
|
||||||
|
|
||||||
Beware that Rhai internal data structures are _volatile_ and may change without warning.
|
|
||||||
|
|
||||||
Caveat emptor.
|
|
||||||
|
|
||||||
|
|
||||||
Step Two - Design The Syntax
|
|
||||||
---------------------------
|
---------------------------
|
||||||
|
|
||||||
A custom syntax is simply a list of symbols.
|
A custom syntax is simply a list of symbols.
|
||||||
@ -116,8 +105,8 @@ print(hello); // variable declared by a custom syntax persists!
|
|||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
Step Three - Implementation
|
Step Two - Implementation
|
||||||
--------------------------
|
-------------------------
|
||||||
|
|
||||||
Any custom syntax must include an _implementation_ of it.
|
Any custom syntax must include an _implementation_ of it.
|
||||||
|
|
||||||
@ -164,8 +153,6 @@ let expr = inputs.get(0).unwrap();
|
|||||||
let result = engine.eval_expression_tree(context, scope, expr)?;
|
let result = engine.eval_expression_tree(context, scope, expr)?;
|
||||||
```
|
```
|
||||||
|
|
||||||
As can be seem above, most arguments are simply passed straight-through to `engine::eval_expression_tree`.
|
|
||||||
|
|
||||||
### Declare Variables
|
### Declare Variables
|
||||||
|
|
||||||
New variables maybe declared (usually with a variable name that is passed in via `$ident$).
|
New variables maybe declared (usually with a variable name that is passed in via `$ident$).
|
||||||
@ -179,14 +166,14 @@ In other words, any `scope.push(...)` calls must come _before_ any `engine::eval
|
|||||||
let var_name = inputs[0].get_variable_name().unwrap().to_string();
|
let var_name = inputs[0].get_variable_name().unwrap().to_string();
|
||||||
let expr = inputs.get(1).unwrap();
|
let expr = inputs.get(1).unwrap();
|
||||||
|
|
||||||
scope.push(var_name, 0 as INT); // do this BEFORE engine.eval_expression_tree!
|
scope.push(var_name, 0 as INT); // do this BEFORE 'engine.eval_expression_tree'!
|
||||||
|
|
||||||
let result = engine.eval_expression_tree(scope, mods, state, lib, this_ptr, expr, level)?;
|
let result = engine.eval_expression_tree(context, scope, expr)?;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
Step Four - Register the Custom Syntax
|
Step Three - Register the Custom Syntax
|
||||||
-------------------------------------
|
--------------------------------------
|
||||||
|
|
||||||
Use `Engine::register_custom_syntax` to register a custom syntax.
|
Use `Engine::register_custom_syntax` to register a custom syntax.
|
||||||
|
|
||||||
@ -236,7 +223,7 @@ engine.register_custom_syntax(
|
|||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
Step Five - Disable Unneeded Statement Types
|
Step Four - Disable Unneeded Statement Types
|
||||||
-------------------------------------------
|
-------------------------------------------
|
||||||
|
|
||||||
When a DSL needs a custom syntax, most likely than not it is extremely specialized.
|
When a DSL needs a custom syntax, most likely than not it is extremely specialized.
|
||||||
@ -254,13 +241,13 @@ custom syntax (plus possibly expressions). But again, Don't Do It™ - unless y
|
|||||||
of what you're doing.
|
of what you're doing.
|
||||||
|
|
||||||
|
|
||||||
Step Six - Document
|
Step Five - Document
|
||||||
-------------------
|
--------------------
|
||||||
|
|
||||||
For custom syntax, documentation is crucial.
|
For custom syntax, documentation is crucial.
|
||||||
|
|
||||||
Make sure there are _lots_ of examples for users to follow.
|
Make sure there are _lots_ of examples for users to follow.
|
||||||
|
|
||||||
|
|
||||||
Step Seven - Profit!
|
Step Six - Profit!
|
||||||
--------------------
|
------------------
|
||||||
|
@ -11,7 +11,7 @@ Expressions Only
|
|||||||
|
|
||||||
In many DSL scenarios, only evaluation of expressions is needed.
|
In many DSL scenarios, only evaluation of expressions is needed.
|
||||||
|
|
||||||
The `Engine::eval_expression_XXX`[`eval_expression`] API can be used to restrict
|
The [`Engine::eval_expression_XXX`][`eval_expression`] API can be used to restrict
|
||||||
a script to expressions only.
|
a script to expressions only.
|
||||||
|
|
||||||
|
|
||||||
@ -21,8 +21,7 @@ Disable Keywords and/or Operators
|
|||||||
In some DSL scenarios, it is necessary to further restrict the language to exclude certain
|
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.
|
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
|
For example, a DSL may disable the `while` loop while keeping all other statement types intact.
|
||||||
types intact.
|
|
||||||
|
|
||||||
It is possible, in Rhai, to surgically [disable keywords and operators].
|
It is possible, in Rhai, to surgically [disable keywords and operators].
|
||||||
|
|
||||||
@ -54,31 +53,29 @@ Custom Syntax
|
|||||||
For advanced DSL scenarios, it is possible to define entire expression [_syntax_][custom syntax] -
|
For advanced DSL scenarios, it is possible to define entire expression [_syntax_][custom syntax] -
|
||||||
essentially custom statement types.
|
essentially custom statement types.
|
||||||
|
|
||||||
The [`internals`] feature is needed to be able to define [custom syntax] in Rhai.
|
For example, the following is a SQL-like syntax for some obscure DSL operation:
|
||||||
|
|
||||||
For example, the following is a SQL like syntax for some obscure DSL operation:
|
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let table = [..., ..., ..., ...];
|
let table = [..., ..., ..., ...];
|
||||||
|
|
||||||
// Syntax = "calculate" $ident$ $ident$ "from" $expr$ "->" $ident$ ":" $expr$
|
// Syntax = calculate $ident$ $ident$ from $expr$ -> $ident$ : $expr$
|
||||||
let total = calculate sum price from table -> row : row.weight > 50;
|
let total = calculate sum price from table -> row : row.weight > 50;
|
||||||
|
|
||||||
// Note: There is nothing special about the use of symbols; to make it look exactly like SQL:
|
// Note: There is nothing special about those symbols; to make it look exactly like SQL:
|
||||||
// Syntax = "SELECT" $ident$ "(" $ident$ ")" "FROM" $expr$ "AS" $ident$ "WHERE" $expr$
|
// Syntax = SELECT $ident$ ( $ident$ ) FROM $expr$ AS $ident$ WHERE $expr$
|
||||||
let total = SELECT sum(price) FROM table AS row WHERE row.weight > 50;
|
let total = SELECT sum(price) FROM table AS row WHERE row.weight > 50;
|
||||||
```
|
```
|
||||||
|
|
||||||
After registering this custom syntax with Rhai, it can be used anywhere inside a script as
|
After registering this custom syntax with Rhai, it can be used anywhere inside a script as
|
||||||
a normal expression.
|
a normal expression.
|
||||||
|
|
||||||
For its evaluation, the callback function will receive the following list of parameters:
|
For its evaluation, the callback function will receive the following list of inputs:
|
||||||
|
|
||||||
`exprs[0] = "sum"` - math operator
|
* `inputs[0] = "sum"` - math operator
|
||||||
`exprs[1] = "price"` - field name
|
* `inputs[1] = "price"` - field name
|
||||||
`exprs[2] = Expression(table)` - data source
|
* `inputs[2] = Expression(table)` - data source
|
||||||
`exprs[3] = "row"` - loop variable name
|
* `inputs[3] = "row"` - loop variable name
|
||||||
`exprs[4] = Expression(row.wright > 50)` - expression
|
* `inputs[4] = Expression(row.wright > 50)` - filter predicate
|
||||||
|
|
||||||
The other identified, such as `"select"`, `"from"`, as as as symbols `->` and `:` are
|
Other identifiers, such as `"calculate"`, `"from"`, as well as symbols such as `->` and `:`,
|
||||||
parsed in the order defined within the custom syntax.
|
are parsed in the order defined within the custom syntax.
|
||||||
|
30
doc/src/language/fn-curry.md
Normal file
30
doc/src/language/fn-curry.md
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
Function Pointer Currying
|
||||||
|
========================
|
||||||
|
|
||||||
|
{{#include ../links.md}}
|
||||||
|
|
||||||
|
It is possible to _curry_ a [function pointer] by providing partial (or all) arguments.
|
||||||
|
|
||||||
|
Currying is done via the `curry` keyword and produces a new [function pointer] which carries
|
||||||
|
the curried arguments.
|
||||||
|
|
||||||
|
When the curried [function pointer] is called, the curried arguments are inserted starting from the left.
|
||||||
|
The actual call arguments should be reduced by the number of curried arguments.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn mul(x, y) { // function with two parameters
|
||||||
|
x * y
|
||||||
|
}
|
||||||
|
|
||||||
|
let func = Fn("mul");
|
||||||
|
|
||||||
|
func.call(21, 2) == 42; // two arguments are required for 'mul'
|
||||||
|
|
||||||
|
let curried = func.curry(21); // currying produces a new function pointer which
|
||||||
|
// carries 21 as the first argument
|
||||||
|
|
||||||
|
let curried = curry(func, 21); // function-call style also works
|
||||||
|
|
||||||
|
curried.call(2) == 42; // <- de-sugars to 'func.call(21, 2)'
|
||||||
|
// only one argument is now required
|
||||||
|
```
|
@ -20,9 +20,14 @@ The `ImmutableString` Type
|
|||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
All strings in Rhai are implemented as `ImmutableString` (see [standard types]).
|
All strings in Rhai are implemented as `ImmutableString` (see [standard types]).
|
||||||
|
An `ImmutableString` does not change and can be shared.
|
||||||
|
|
||||||
`ImmutableString` should be used in place of the standard Rust type `String` when registering functions
|
Modifying an `ImmutableString` causes it first to be cloned, and then the modification made to the copy.
|
||||||
because using `String` is very inefficient (the `String` must always be cloned).
|
|
||||||
|
### **IMPORTANT** - Avoid `String` Parameters
|
||||||
|
|
||||||
|
`ImmutableString` should be used in place of `String` for function parameters because using
|
||||||
|
`String` is very inefficient (the `String` argument is cloned during every call).
|
||||||
|
|
||||||
A alternative is to use `&str` which maps straight to `ImmutableString`.
|
A alternative is to use `&str` which maps straight to `ImmutableString`.
|
||||||
|
|
||||||
@ -67,14 +72,6 @@ Individual characters within a Rhai string can also be replaced just as if the s
|
|||||||
In Rhai, there are also no separate concepts of `String` and `&str` as in Rust.
|
In Rhai, there are also no separate concepts of `String` and `&str` as in Rust.
|
||||||
|
|
||||||
|
|
||||||
Immutable Strings
|
|
||||||
----------------
|
|
||||||
|
|
||||||
Rhai use _immutable_ strings (type `ImmutableString`) and can be shared.
|
|
||||||
|
|
||||||
Modifying a Rhai string actually causes it first to be cloned, and then the modification made to the copy.
|
|
||||||
|
|
||||||
|
|
||||||
Examples
|
Examples
|
||||||
--------
|
--------
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ The following primitive types are supported natively:
|
|||||||
| **Immutable Unicode [string]** | `rhai::ImmutableString` (implemented as `Rc<String>` or `Arc<String>`) | `"string"` | `"hello"` etc. |
|
| **Immutable Unicode [string]** | `rhai::ImmutableString` (implemented as `Rc<String>` or `Arc<String>`) | `"string"` | `"hello"` etc. |
|
||||||
| **[`Array`]** (disabled with [`no_index`]) | `rhai::Array` | `"array"` | `"[ ?, ?, ? ]"` |
|
| **[`Array`]** (disabled with [`no_index`]) | `rhai::Array` | `"array"` | `"[ ?, ?, ? ]"` |
|
||||||
| **[Object map]** (disabled with [`no_object`]) | `rhai::Map` | `"map"` | `"#{ "a": 1, "b": 2 }"` |
|
| **[Object map]** (disabled with [`no_object`]) | `rhai::Map` | `"map"` | `"#{ "a": 1, "b": 2 }"` |
|
||||||
| **[Timestamp]** (implemented in the [`BasicTimePackage`][packages], disabled with [`no_std`]) | `std::time::Instant` ([`instant::Instant`] if not [WASM] build) | `"timestamp"` | _not supported_ |
|
| **[Timestamp]** (implemented in the [`BasicTimePackage`][packages], disabled with [`no_std`]) | `std::time::Instant` ([`instant::Instant`] if [WASM] build) | `"timestamp"` | _not supported_ |
|
||||||
| **[Function pointer]** | `rhai::FnPtr` | `Fn` | `"Fn(foo)"` |
|
| **[Function pointer]** | `rhai::FnPtr` | `Fn` | `"Fn(foo)"` |
|
||||||
| **[`Dynamic`] value** (i.e. can be anything) | `rhai::Dynamic` | _the actual type_ | _actual value_ |
|
| **[`Dynamic`] value** (i.e. can be anything) | `rhai::Dynamic` | _the actual type_ | _actual value_ |
|
||||||
| **System integer** (current configuration) | `rhai::INT` (`i32` or `i64`) | `"i32"` or `"i64"` | `"42"`, `"123"` etc. |
|
| **System integer** (current configuration) | `rhai::INT` (`i32` or `i64`) | `"i32"` or `"i64"` | `"42"`, `"123"` etc. |
|
||||||
|
@ -76,6 +76,7 @@
|
|||||||
[functions]: {{rootUrl}}/language/functions.md
|
[functions]: {{rootUrl}}/language/functions.md
|
||||||
[function pointer]: {{rootUrl}}/language/fn-ptr.md
|
[function pointer]: {{rootUrl}}/language/fn-ptr.md
|
||||||
[function pointers]: {{rootUrl}}/language/fn-ptr.md
|
[function pointers]: {{rootUrl}}/language/fn-ptr.md
|
||||||
|
[currying]: {{rootUrl}}/language/fn-curry.md
|
||||||
[function namespace]: {{rootUrl}}/language/fn-namespaces.md
|
[function namespace]: {{rootUrl}}/language/fn-namespaces.md
|
||||||
[function namespaces]: {{rootUrl}}/language/fn-namespaces.md
|
[function namespaces]: {{rootUrl}}/language/fn-namespaces.md
|
||||||
[anonymous function]: {{rootUrl}}/language/fn-anon.md
|
[anonymous function]: {{rootUrl}}/language/fn-anon.md
|
||||||
|
@ -4,19 +4,32 @@
|
|||||||
{{#include ../links.md}}
|
{{#include ../links.md}}
|
||||||
|
|
||||||
|
|
||||||
|
Avoid `String`
|
||||||
|
--------------
|
||||||
|
|
||||||
|
As must as possible, avoid using `String` parameters in functions.
|
||||||
|
|
||||||
|
Each `String` argument is cloned during every single call to that function - and the copy
|
||||||
|
immediately thrown away right after the call.
|
||||||
|
|
||||||
|
Needless to say, it is _extremely_ inefficient to use `String` parameters.
|
||||||
|
|
||||||
|
|
||||||
`&str` Maps to `ImmutableString`
|
`&str` Maps to `ImmutableString`
|
||||||
-------------------------------
|
-------------------------------
|
||||||
|
|
||||||
Rust functions accepting parameters of `String` should use `&str` instead because it maps directly to
|
Rust functions accepting parameters of `String` should use `&str` instead because it maps directly to
|
||||||
[`ImmutableString`][string] which is the type that Rhai uses to represent [strings] internally.
|
[`ImmutableString`][string] which is the type that Rhai uses to represent [strings] internally.
|
||||||
|
|
||||||
The parameter type `String` is discouraged because it involves converting an [`ImmutableString`] into a `String`.
|
The parameter type `String` involves always converting an [`ImmutableString`][string] into a `String`
|
||||||
|
which mandates cloning it.
|
||||||
|
|
||||||
Using `ImmutableString` or `&str` is much more efficient.
|
Using `ImmutableString` or `&str` is much more efficient.
|
||||||
A common mistake made by novice Rhai users is to register functions with `String` parameters.
|
A common mistake made by novice Rhai users is to register functions with `String` parameters.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
fn get_len1(s: String) -> i64 { s.len() as i64 } // <- Rhai finds this function, but very inefficient
|
fn get_len1(s: String) -> i64 { s.len() as i64 } // <- Very inefficient!!!
|
||||||
fn get_len2(s: &str) -> i64 { s.len() as i64 } // <- Rhai finds this function fine
|
fn get_len2(s: &str) -> i64 { s.len() as i64 } // <- This is better
|
||||||
fn get_len3(s: ImmutableString) -> i64 { s.len() as i64 } // <- the above is equivalent to this
|
fn get_len3(s: ImmutableString) -> i64 { s.len() as i64 } // <- the above is equivalent to this
|
||||||
|
|
||||||
engine
|
engine
|
||||||
@ -24,9 +37,9 @@ engine
|
|||||||
.register_fn("len2", get_len2)
|
.register_fn("len2", get_len2)
|
||||||
.register_fn("len3", get_len3);
|
.register_fn("len3", get_len3);
|
||||||
|
|
||||||
let len = engine.eval::<i64>("x.len1()")?; // error: function 'len1 (&str | ImmutableString)' not found
|
let len = engine.eval::<i64>("x.len1()")?; // 'x' is cloned, very inefficient!
|
||||||
let len = engine.eval::<i64>("x.len2()")?; // works fine
|
let len = engine.eval::<i64>("x.len2()")?; // 'x' is shared
|
||||||
let len = engine.eval::<i64>("x.len3()")?; // works fine
|
let len = engine.eval::<i64>("x.len3()")?; // 'x' is shared
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
@ -37,9 +50,11 @@ Rhai functions can take a first `&mut` parameter. Usually this is a good idea b
|
|||||||
cloning of the argument (except for primary types where cloning is cheap), so its use is encouraged
|
cloning of the argument (except for primary types where cloning is cheap), so its use is encouraged
|
||||||
even though there is no intention to ever mutate that argument.
|
even though there is no intention to ever mutate that argument.
|
||||||
|
|
||||||
`ImmutableString` is an exception to this rule. While `ImmutableString` is cheap to clone (only
|
[`ImmutableString`][string] is an exception to this rule.
|
||||||
incrementing a reference count), taking a mutable reference to it involves making a private clone
|
|
||||||
of the underlying string because Rhai has no way to find out whether that parameter will be mutated.
|
While `ImmutableString` is cheap to clone (only incrementing a reference count), taking a mutable
|
||||||
|
reference to it involves making a private clone of the underlying string because Rhai has no way
|
||||||
|
to find out whether that parameter will be mutated.
|
||||||
|
|
||||||
If the `ImmutableString` is not shared by any other variables, then Rhai just returns a mutable
|
If the `ImmutableString` is not shared by any other variables, then Rhai just returns a mutable
|
||||||
reference to it since nobody else is watching! Otherwise a private copy is made first,
|
reference to it since nobody else is watching! Otherwise a private copy is made first,
|
||||||
|
17
src/any.rs
17
src/any.rs
@ -137,7 +137,7 @@ pub enum Union {
|
|||||||
Array(Box<Array>),
|
Array(Box<Array>),
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
Map(Box<Map>),
|
Map(Box<Map>),
|
||||||
FnPtr(FnPtr),
|
FnPtr(Box<FnPtr>),
|
||||||
Variant(Box<Box<dyn Variant>>),
|
Variant(Box<Box<dyn Variant>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -274,7 +274,7 @@ impl fmt::Debug for Dynamic {
|
|||||||
f.write_str("#")?;
|
f.write_str("#")?;
|
||||||
fmt::Debug::fmt(value, f)
|
fmt::Debug::fmt(value, f)
|
||||||
}
|
}
|
||||||
Union::FnPtr(value) => fmt::Display::fmt(value, f),
|
Union::FnPtr(value) => fmt::Debug::fmt(value, f),
|
||||||
|
|
||||||
#[cfg(not(feature = "no_std"))]
|
#[cfg(not(feature = "no_std"))]
|
||||||
Union::Variant(value) if value.is::<Instant>() => write!(f, "<timestamp>"),
|
Union::Variant(value) if value.is::<Instant>() => write!(f, "<timestamp>"),
|
||||||
@ -481,7 +481,7 @@ impl Dynamic {
|
|||||||
}
|
}
|
||||||
if type_id == TypeId::of::<FnPtr>() {
|
if type_id == TypeId::of::<FnPtr>() {
|
||||||
return match self.0 {
|
return match self.0 {
|
||||||
Union::FnPtr(value) => unsafe_try_cast(value),
|
Union::FnPtr(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v),
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -582,7 +582,7 @@ impl Dynamic {
|
|||||||
}
|
}
|
||||||
if type_id == TypeId::of::<FnPtr>() {
|
if type_id == TypeId::of::<FnPtr>() {
|
||||||
return match &self.0 {
|
return match &self.0 {
|
||||||
Union::FnPtr(value) => <dyn Any>::downcast_ref::<T>(value),
|
Union::FnPtr(value) => <dyn Any>::downcast_ref::<T>(value.as_ref()),
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -656,7 +656,7 @@ impl Dynamic {
|
|||||||
}
|
}
|
||||||
if type_id == TypeId::of::<FnPtr>() {
|
if type_id == TypeId::of::<FnPtr>() {
|
||||||
return match &mut self.0 {
|
return match &mut self.0 {
|
||||||
Union::FnPtr(value) => <dyn Any>::downcast_mut::<T>(value),
|
Union::FnPtr(value) => <dyn Any>::downcast_mut::<T>(value.as_mut()),
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -735,7 +735,7 @@ impl Dynamic {
|
|||||||
pub(crate) fn take_immutable_string(self) -> Result<ImmutableString, &'static str> {
|
pub(crate) fn take_immutable_string(self) -> Result<ImmutableString, &'static str> {
|
||||||
match self.0 {
|
match self.0 {
|
||||||
Union::Str(s) => Ok(s),
|
Union::Str(s) => Ok(s),
|
||||||
Union::FnPtr(f) => Ok(f.take_fn_name()),
|
Union::FnPtr(f) => Ok(f.take_data().0),
|
||||||
_ => Err(self.type_name()),
|
_ => Err(self.type_name()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -801,6 +801,11 @@ impl<K: Into<ImmutableString>, T: Variant + Clone> From<HashMap<K, T>> for Dynam
|
|||||||
}
|
}
|
||||||
impl From<FnPtr> for Dynamic {
|
impl From<FnPtr> for Dynamic {
|
||||||
fn from(value: FnPtr) -> Self {
|
fn from(value: FnPtr) -> Self {
|
||||||
|
Box::new(value).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<Box<FnPtr>> for Dynamic {
|
||||||
|
fn from(value: Box<FnPtr>) -> Self {
|
||||||
Self(Union::FnPtr(value))
|
Self(Union::FnPtr(value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
161
src/engine.rs
161
src/engine.rs
@ -11,15 +11,13 @@ use crate::parser::{Expr, FnAccess, ImmutableString, ReturnType, ScriptFnDef, St
|
|||||||
use crate::r#unsafe::unsafe_cast_var_name_to_lifetime;
|
use crate::r#unsafe::unsafe_cast_var_name_to_lifetime;
|
||||||
use crate::result::EvalAltResult;
|
use crate::result::EvalAltResult;
|
||||||
use crate::scope::{EntryType as ScopeEntryType, Scope};
|
use crate::scope::{EntryType as ScopeEntryType, Scope};
|
||||||
|
use crate::syntax::{CustomSyntax, EvalContext, Expression};
|
||||||
use crate::token::Position;
|
use crate::token::Position;
|
||||||
use crate::utils::StaticVec;
|
use crate::utils::StaticVec;
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
use crate::parser::FLOAT;
|
use crate::parser::FLOAT;
|
||||||
|
|
||||||
#[cfg(feature = "internals")]
|
|
||||||
use crate::syntax::{CustomSyntax, EvalContext};
|
|
||||||
|
|
||||||
use crate::stdlib::{
|
use crate::stdlib::{
|
||||||
any::{type_name, TypeId},
|
any::{type_name, TypeId},
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
@ -81,6 +79,7 @@ pub const KEYWORD_TYPE_OF: &str = "type_of";
|
|||||||
pub const KEYWORD_EVAL: &str = "eval";
|
pub const KEYWORD_EVAL: &str = "eval";
|
||||||
pub const KEYWORD_FN_PTR: &str = "Fn";
|
pub const KEYWORD_FN_PTR: &str = "Fn";
|
||||||
pub const KEYWORD_FN_PTR_CALL: &str = "call";
|
pub const KEYWORD_FN_PTR_CALL: &str = "call";
|
||||||
|
pub const KEYWORD_FN_PTR_CURRY: &str = "curry";
|
||||||
pub const KEYWORD_THIS: &str = "this";
|
pub const KEYWORD_THIS: &str = "this";
|
||||||
pub const FN_TO_STRING: &str = "to_string";
|
pub const FN_TO_STRING: &str = "to_string";
|
||||||
pub const FN_GET: &str = "get$";
|
pub const FN_GET: &str = "get$";
|
||||||
@ -88,45 +87,10 @@ pub const FN_SET: &str = "set$";
|
|||||||
pub const FN_IDX_GET: &str = "index$get$";
|
pub const FN_IDX_GET: &str = "index$get$";
|
||||||
pub const FN_IDX_SET: &str = "index$set$";
|
pub const FN_IDX_SET: &str = "index$set$";
|
||||||
pub const FN_ANONYMOUS: &str = "anon$";
|
pub const FN_ANONYMOUS: &str = "anon$";
|
||||||
|
|
||||||
#[cfg(feature = "internals")]
|
|
||||||
pub const MARKER_EXPR: &str = "$expr$";
|
pub const MARKER_EXPR: &str = "$expr$";
|
||||||
#[cfg(feature = "internals")]
|
|
||||||
pub const MARKER_BLOCK: &str = "$block$";
|
pub const MARKER_BLOCK: &str = "$block$";
|
||||||
#[cfg(feature = "internals")]
|
|
||||||
pub const MARKER_IDENT: &str = "$ident$";
|
pub const MARKER_IDENT: &str = "$ident$";
|
||||||
|
|
||||||
#[cfg(feature = "internals")]
|
|
||||||
#[derive(Debug, Clone, Hash)]
|
|
||||||
pub struct Expression<'a>(&'a Expr);
|
|
||||||
|
|
||||||
#[cfg(feature = "internals")]
|
|
||||||
impl<'a> From<&'a Expr> for Expression<'a> {
|
|
||||||
fn from(expr: &'a Expr) -> Self {
|
|
||||||
Self(expr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "internals")]
|
|
||||||
impl Expression<'_> {
|
|
||||||
/// If this expression is a variable name, return it. Otherwise `None`.
|
|
||||||
#[cfg(feature = "internals")]
|
|
||||||
pub fn get_variable_name(&self) -> Option<&str> {
|
|
||||||
match self.0 {
|
|
||||||
Expr::Variable(x) => Some((x.0).0.as_str()),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Get the expression.
|
|
||||||
pub(crate) fn expr(&self) -> &Expr {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
/// Get the position of this expression.
|
|
||||||
pub fn position(&self) -> Position {
|
|
||||||
self.0.position()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A type specifying the method of chaining.
|
/// A type specifying the method of chaining.
|
||||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
|
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
|
||||||
enum ChainType {
|
enum ChainType {
|
||||||
@ -203,7 +167,7 @@ impl Target<'_> {
|
|||||||
.as_char()
|
.as_char()
|
||||||
.map_err(|_| EvalAltResult::ErrorCharMismatch(Position::none()))?;
|
.map_err(|_| EvalAltResult::ErrorCharMismatch(Position::none()))?;
|
||||||
|
|
||||||
let mut chars: StaticVec<char> = s.chars().collect();
|
let mut chars = s.chars().collect::<StaticVec<_>>();
|
||||||
let ch = chars[*index];
|
let ch = chars[*index];
|
||||||
|
|
||||||
// See if changed - if so, update the String
|
// See if changed - if so, update the String
|
||||||
@ -316,7 +280,6 @@ pub struct Engine {
|
|||||||
/// A hashset containing custom keywords and precedence to recognize.
|
/// A hashset containing custom keywords and precedence to recognize.
|
||||||
pub(crate) custom_keywords: Option<HashMap<String, u8>>,
|
pub(crate) custom_keywords: Option<HashMap<String, u8>>,
|
||||||
/// Custom syntax.
|
/// Custom syntax.
|
||||||
#[cfg(feature = "internals")]
|
|
||||||
pub(crate) custom_syntax: Option<HashMap<String, CustomSyntax>>,
|
pub(crate) custom_syntax: Option<HashMap<String, CustomSyntax>>,
|
||||||
|
|
||||||
/// Callback closure for implementing the `print` command.
|
/// Callback closure for implementing the `print` command.
|
||||||
@ -376,8 +339,6 @@ impl Default for Engine {
|
|||||||
type_names: None,
|
type_names: None,
|
||||||
disabled_symbols: None,
|
disabled_symbols: None,
|
||||||
custom_keywords: None,
|
custom_keywords: None,
|
||||||
|
|
||||||
#[cfg(feature = "internals")]
|
|
||||||
custom_syntax: None,
|
custom_syntax: None,
|
||||||
|
|
||||||
// default print/debug implementations
|
// default print/debug implementations
|
||||||
@ -605,8 +566,6 @@ impl Engine {
|
|||||||
type_names: None,
|
type_names: None,
|
||||||
disabled_symbols: None,
|
disabled_symbols: None,
|
||||||
custom_keywords: None,
|
custom_keywords: None,
|
||||||
|
|
||||||
#[cfg(feature = "internals")]
|
|
||||||
custom_syntax: None,
|
custom_syntax: None,
|
||||||
|
|
||||||
print: Box::new(|_| {}),
|
print: Box::new(|_| {}),
|
||||||
@ -1066,7 +1025,7 @@ impl Engine {
|
|||||||
lib: &Module,
|
lib: &Module,
|
||||||
target: &mut Target,
|
target: &mut Target,
|
||||||
expr: &Expr,
|
expr: &Expr,
|
||||||
mut idx_val: Dynamic,
|
idx_val: Dynamic,
|
||||||
level: usize,
|
level: usize,
|
||||||
) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
|
) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
|
||||||
let ((name, native, pos), _, hash, _, def_val) = match expr {
|
let ((name, native, pos), _, hash, _, def_val) = match expr {
|
||||||
@ -1079,17 +1038,22 @@ impl Engine {
|
|||||||
|
|
||||||
// Get a reference to the mutation target Dynamic
|
// Get a reference to the mutation target Dynamic
|
||||||
let obj = target.as_mut();
|
let obj = target.as_mut();
|
||||||
let idx = idx_val.downcast_mut::<StaticVec<Dynamic>>().unwrap();
|
let mut idx = idx_val.cast::<StaticVec<Dynamic>>();
|
||||||
let mut fn_name = name.as_ref();
|
let mut fn_name = name.as_ref();
|
||||||
|
|
||||||
let (result, updated) = if fn_name == KEYWORD_FN_PTR_CALL && obj.is::<FnPtr>() {
|
let (result, updated) = if fn_name == KEYWORD_FN_PTR_CALL && obj.is::<FnPtr>() {
|
||||||
// FnPtr call
|
// FnPtr call
|
||||||
|
let fn_ptr = obj.downcast_ref::<FnPtr>().unwrap();
|
||||||
|
let mut curry = fn_ptr.curry().iter().cloned().collect::<StaticVec<_>>();
|
||||||
// Redirect function name
|
// Redirect function name
|
||||||
let fn_name = obj.as_str().unwrap();
|
let fn_name = fn_ptr.fn_name();
|
||||||
// Recalculate hash
|
// Recalculate hash
|
||||||
let hash = calc_fn_hash(empty(), fn_name, idx.len(), empty());
|
let hash = calc_fn_hash(empty(), fn_name, curry.len() + idx.len(), empty());
|
||||||
// Arguments are passed as-is
|
// Arguments are passed as-is, adding the curried arguments
|
||||||
let mut arg_values = idx.iter_mut().collect::<StaticVec<_>>();
|
let mut arg_values = curry
|
||||||
|
.iter_mut()
|
||||||
|
.chain(idx.iter_mut())
|
||||||
|
.collect::<StaticVec<_>>();
|
||||||
let args = arg_values.as_mut();
|
let args = arg_values.as_mut();
|
||||||
|
|
||||||
// Map it to name(args) in function-call style
|
// Map it to name(args) in function-call style
|
||||||
@ -1098,17 +1062,16 @@ impl Engine {
|
|||||||
)
|
)
|
||||||
} else if fn_name == KEYWORD_FN_PTR_CALL && idx.len() > 0 && idx[0].is::<FnPtr>() {
|
} else if fn_name == KEYWORD_FN_PTR_CALL && idx.len() > 0 && idx[0].is::<FnPtr>() {
|
||||||
// FnPtr call on object
|
// FnPtr call on object
|
||||||
|
let fn_ptr = idx.remove(0).cast::<FnPtr>();
|
||||||
|
let mut curry = fn_ptr.curry().iter().cloned().collect::<StaticVec<_>>();
|
||||||
// Redirect function name
|
// Redirect function name
|
||||||
let fn_name = idx[0]
|
let fn_name = fn_ptr.get_fn_name().clone();
|
||||||
.downcast_ref::<FnPtr>()
|
|
||||||
.unwrap()
|
|
||||||
.get_fn_name()
|
|
||||||
.clone();
|
|
||||||
// Recalculate hash
|
// Recalculate hash
|
||||||
let hash = calc_fn_hash(empty(), &fn_name, idx.len() - 1, empty());
|
let hash = calc_fn_hash(empty(), &fn_name, curry.len() + idx.len(), empty());
|
||||||
// Replace the first argument with the object pointer
|
// Replace the first argument with the object pointer, adding the curried arguments
|
||||||
let mut arg_values = once(obj)
|
let mut arg_values = once(obj)
|
||||||
.chain(idx.iter_mut().skip(1))
|
.chain(curry.iter_mut())
|
||||||
|
.chain(idx.iter_mut())
|
||||||
.collect::<StaticVec<_>>();
|
.collect::<StaticVec<_>>();
|
||||||
let args = arg_values.as_mut();
|
let args = arg_values.as_mut();
|
||||||
|
|
||||||
@ -1116,8 +1079,24 @@ impl Engine {
|
|||||||
self.exec_fn_call(
|
self.exec_fn_call(
|
||||||
state, lib, &fn_name, *native, hash, args, is_ref, true, *def_val, level,
|
state, lib, &fn_name, *native, hash, args, is_ref, true, *def_val, level,
|
||||||
)
|
)
|
||||||
|
} else if fn_name == KEYWORD_FN_PTR_CURRY && obj.is::<FnPtr>() {
|
||||||
|
// Curry call
|
||||||
|
let fn_ptr = obj.downcast_ref::<FnPtr>().unwrap();
|
||||||
|
Ok((
|
||||||
|
FnPtr::new_unchecked(
|
||||||
|
fn_ptr.get_fn_name().clone(),
|
||||||
|
fn_ptr
|
||||||
|
.curry()
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.chain(idx.into_iter())
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
|
false,
|
||||||
|
))
|
||||||
} else {
|
} else {
|
||||||
let redirected: Option<ImmutableString>;
|
let redirected;
|
||||||
let mut hash = *hash;
|
let mut hash = *hash;
|
||||||
|
|
||||||
// Check if it is a map method call in OOP style
|
// Check if it is a map method call in OOP style
|
||||||
@ -1126,9 +1105,8 @@ impl Engine {
|
|||||||
if let Some(val) = map.get(fn_name) {
|
if let Some(val) = map.get(fn_name) {
|
||||||
if let Some(f) = val.downcast_ref::<FnPtr>() {
|
if let Some(f) = val.downcast_ref::<FnPtr>() {
|
||||||
// Remap the function name
|
// Remap the function name
|
||||||
redirected = Some(f.get_fn_name().clone());
|
redirected = f.get_fn_name().clone();
|
||||||
fn_name = redirected.as_ref().unwrap();
|
fn_name = &redirected;
|
||||||
|
|
||||||
// Recalculate the hash based on the new function name
|
// Recalculate the hash based on the new function name
|
||||||
hash = calc_fn_hash(empty(), fn_name, idx.len(), empty());
|
hash = calc_fn_hash(empty(), fn_name, idx.len(), empty());
|
||||||
}
|
}
|
||||||
@ -1734,8 +1712,6 @@ impl Engine {
|
|||||||
/// ## WARNING - Low Level API
|
/// ## WARNING - Low Level API
|
||||||
///
|
///
|
||||||
/// This function is very low level. It evaluates an expression from an AST.
|
/// This function is very low level. It evaluates an expression from an AST.
|
||||||
#[cfg(feature = "internals")]
|
|
||||||
#[deprecated(note = "this method is volatile and may change")]
|
|
||||||
pub fn eval_expression_tree(
|
pub fn eval_expression_tree(
|
||||||
&self,
|
&self,
|
||||||
context: &mut EvalContext,
|
context: &mut EvalContext,
|
||||||
@ -1775,7 +1751,7 @@ impl Engine {
|
|||||||
Expr::FloatConstant(x) => Ok(x.0.into()),
|
Expr::FloatConstant(x) => Ok(x.0.into()),
|
||||||
Expr::StringConstant(x) => Ok(x.0.to_string().into()),
|
Expr::StringConstant(x) => Ok(x.0.to_string().into()),
|
||||||
Expr::CharConstant(x) => Ok(x.0.into()),
|
Expr::CharConstant(x) => Ok(x.0.into()),
|
||||||
Expr::FnPointer(x) => Ok(FnPtr::new_unchecked(x.0.clone()).into()),
|
Expr::FnPointer(x) => Ok(FnPtr::new_unchecked(x.0.clone(), Default::default()).into()),
|
||||||
Expr::Variable(x) if (x.0).0 == KEYWORD_THIS => {
|
Expr::Variable(x) if (x.0).0 == KEYWORD_THIS => {
|
||||||
if let Some(val) = this_ptr {
|
if let Some(val) = this_ptr {
|
||||||
Ok(val.clone())
|
Ok(val.clone())
|
||||||
@ -1963,6 +1939,34 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle curry()
|
||||||
|
if name == KEYWORD_FN_PTR_CURRY && args_expr.len() > 1 {
|
||||||
|
let expr = args_expr.get(0);
|
||||||
|
let fn_ptr = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
|
||||||
|
|
||||||
|
if !fn_ptr.is::<FnPtr>() {
|
||||||
|
return Err(Box::new(EvalAltResult::ErrorMismatchOutputType(
|
||||||
|
self.map_type_name(type_name::<FnPtr>()).into(),
|
||||||
|
self.map_type_name(fn_ptr.type_name()).into(),
|
||||||
|
expr.position(),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
let (fn_name, fn_curry) = fn_ptr.cast::<FnPtr>().take_data();
|
||||||
|
|
||||||
|
let curry: StaticVec<_> = args_expr
|
||||||
|
.iter()
|
||||||
|
.skip(1)
|
||||||
|
.map(|expr| self.eval_expr(scope, mods, state, lib, this_ptr, expr, level))
|
||||||
|
.collect::<Result<_, _>>()?;
|
||||||
|
|
||||||
|
return Ok(FnPtr::new_unchecked(
|
||||||
|
fn_name,
|
||||||
|
fn_curry.into_iter().chain(curry.into_iter()).collect(),
|
||||||
|
)
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
|
||||||
// Handle eval()
|
// Handle eval()
|
||||||
if name == KEYWORD_EVAL && args_expr.len() == 1 {
|
if name == KEYWORD_EVAL && args_expr.len() == 1 {
|
||||||
let hash_fn =
|
let hash_fn =
|
||||||
@ -1992,6 +1996,7 @@ impl Engine {
|
|||||||
let redirected;
|
let redirected;
|
||||||
let mut name = name.as_ref();
|
let mut name = name.as_ref();
|
||||||
let mut args_expr = args_expr.as_ref();
|
let mut args_expr = args_expr.as_ref();
|
||||||
|
let mut curry: StaticVec<_> = Default::default();
|
||||||
let mut hash = *hash;
|
let mut hash = *hash;
|
||||||
|
|
||||||
if name == KEYWORD_FN_PTR_CALL
|
if name == KEYWORD_FN_PTR_CALL
|
||||||
@ -1999,20 +2004,22 @@ impl Engine {
|
|||||||
&& !self.has_override(lib, 0, hash)
|
&& !self.has_override(lib, 0, hash)
|
||||||
{
|
{
|
||||||
let expr = args_expr.get(0).unwrap();
|
let expr = args_expr.get(0).unwrap();
|
||||||
let fn_ptr = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
|
let fn_name = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
|
||||||
|
|
||||||
if fn_ptr.is::<FnPtr>() {
|
if fn_name.is::<FnPtr>() {
|
||||||
|
let fn_ptr = fn_name.cast::<FnPtr>();
|
||||||
|
curry = fn_ptr.curry().iter().cloned().collect();
|
||||||
// Redirect function name
|
// Redirect function name
|
||||||
redirected = Some(fn_ptr.cast::<FnPtr>().take_fn_name());
|
redirected = fn_ptr.take_data().0;
|
||||||
name = redirected.as_ref().unwrap();
|
name = &redirected;
|
||||||
// Skip the first argument
|
// Skip the first argument
|
||||||
args_expr = &args_expr.as_ref()[1..];
|
args_expr = &args_expr.as_ref()[1..];
|
||||||
// Recalculate hash
|
// Recalculate hash
|
||||||
hash = calc_fn_hash(empty(), name, args_expr.len(), empty());
|
hash = calc_fn_hash(empty(), name, curry.len() + args_expr.len(), empty());
|
||||||
} else {
|
} else {
|
||||||
return Err(Box::new(EvalAltResult::ErrorMismatchOutputType(
|
return Err(Box::new(EvalAltResult::ErrorMismatchOutputType(
|
||||||
self.map_type_name(type_name::<FnPtr>()).into(),
|
self.map_type_name(type_name::<FnPtr>()).into(),
|
||||||
fn_ptr.type_name().into(),
|
fn_name.type_name().into(),
|
||||||
expr.position(),
|
expr.position(),
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
@ -2023,7 +2030,7 @@ impl Engine {
|
|||||||
let mut args: StaticVec<_>;
|
let mut args: StaticVec<_>;
|
||||||
let mut is_ref = false;
|
let mut is_ref = false;
|
||||||
|
|
||||||
if args_expr.is_empty() {
|
if args_expr.is_empty() && curry.is_empty() {
|
||||||
// No arguments
|
// No arguments
|
||||||
args = Default::default();
|
args = Default::default();
|
||||||
} else {
|
} else {
|
||||||
@ -2046,7 +2053,10 @@ impl Engine {
|
|||||||
self.inc_operations(state)
|
self.inc_operations(state)
|
||||||
.map_err(|err| err.new_position(pos))?;
|
.map_err(|err| err.new_position(pos))?;
|
||||||
|
|
||||||
args = once(target).chain(arg_values.iter_mut()).collect();
|
args = once(target)
|
||||||
|
.chain(curry.iter_mut())
|
||||||
|
.chain(arg_values.iter_mut())
|
||||||
|
.collect();
|
||||||
|
|
||||||
is_ref = true;
|
is_ref = true;
|
||||||
}
|
}
|
||||||
@ -2059,7 +2069,7 @@ impl Engine {
|
|||||||
})
|
})
|
||||||
.collect::<Result<_, _>>()?;
|
.collect::<Result<_, _>>()?;
|
||||||
|
|
||||||
args = arg_values.iter_mut().collect();
|
args = curry.iter_mut().chain(arg_values.iter_mut()).collect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2216,10 +2226,9 @@ impl Engine {
|
|||||||
Expr::False(_) => Ok(false.into()),
|
Expr::False(_) => Ok(false.into()),
|
||||||
Expr::Unit(_) => Ok(().into()),
|
Expr::Unit(_) => Ok(().into()),
|
||||||
|
|
||||||
#[cfg(feature = "internals")]
|
|
||||||
Expr::Custom(x) => {
|
Expr::Custom(x) => {
|
||||||
let func = (x.0).1.as_ref();
|
let func = (x.0).1.as_ref();
|
||||||
let ep: StaticVec<_> = (x.0).0.iter().map(|e| e.into()).collect();
|
let ep = (x.0).0.iter().map(|e| e.into()).collect::<StaticVec<_>>();
|
||||||
let mut context = EvalContext {
|
let mut context = EvalContext {
|
||||||
mods,
|
mods,
|
||||||
state,
|
state,
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
//! Module containing interfaces with native-Rust functions.
|
//! Module containing interfaces with native-Rust functions.
|
||||||
use crate::any::Dynamic;
|
use crate::any::Dynamic;
|
||||||
use crate::engine::Engine;
|
use crate::engine::Engine;
|
||||||
use crate::module::Module;
|
use crate::module::{FuncReturn, Module};
|
||||||
use crate::parser::ScriptFnDef;
|
use crate::parser::ScriptFnDef;
|
||||||
use crate::plugin::PluginFunction;
|
use crate::plugin::PluginFunction;
|
||||||
use crate::result::EvalAltResult;
|
use crate::result::EvalAltResult;
|
||||||
use crate::token::{is_valid_identifier, Position};
|
use crate::token::{is_valid_identifier, Position};
|
||||||
use crate::utils::ImmutableString;
|
use crate::utils::{ImmutableString, StaticVec};
|
||||||
|
use crate::Scope;
|
||||||
|
|
||||||
use crate::stdlib::{boxed::Box, convert::TryFrom, fmt, rc::Rc, string::String, sync::Arc};
|
use crate::stdlib::{boxed::Box, convert::TryFrom, fmt, mem, rc::Rc, string::String, sync::Arc};
|
||||||
|
|
||||||
/// Trait that maps to `Send + Sync` only under the `sync` feature.
|
/// Trait that maps to `Send + Sync` only under the `sync` feature.
|
||||||
#[cfg(feature = "sync")]
|
#[cfg(feature = "sync")]
|
||||||
@ -50,14 +51,15 @@ pub fn shared_take<T: Clone>(value: Shared<T>) -> T {
|
|||||||
|
|
||||||
pub type FnCallArgs<'a> = [&'a mut Dynamic];
|
pub type FnCallArgs<'a> = [&'a mut Dynamic];
|
||||||
|
|
||||||
/// A general function pointer.
|
/// A general function pointer, which may carry additional (i.e. curried) argument values
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Hash, Default)]
|
/// to be passed onto a function during a call.
|
||||||
pub struct FnPtr(ImmutableString);
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct FnPtr(ImmutableString, Vec<Dynamic>);
|
||||||
|
|
||||||
impl FnPtr {
|
impl FnPtr {
|
||||||
/// Create a new function pointer.
|
/// Create a new function pointer.
|
||||||
pub(crate) fn new_unchecked<S: Into<ImmutableString>>(name: S) -> Self {
|
pub(crate) fn new_unchecked<S: Into<ImmutableString>>(name: S, curry: Vec<Dynamic>) -> Self {
|
||||||
Self(name.into())
|
Self(name.into(), curry)
|
||||||
}
|
}
|
||||||
/// Get the name of the function.
|
/// Get the name of the function.
|
||||||
pub fn fn_name(&self) -> &str {
|
pub fn fn_name(&self) -> &str {
|
||||||
@ -67,9 +69,38 @@ impl FnPtr {
|
|||||||
pub(crate) fn get_fn_name(&self) -> &ImmutableString {
|
pub(crate) fn get_fn_name(&self) -> &ImmutableString {
|
||||||
&self.0
|
&self.0
|
||||||
}
|
}
|
||||||
/// Get the name of the function.
|
/// Get the underlying data of the function pointer.
|
||||||
pub(crate) fn take_fn_name(self) -> ImmutableString {
|
pub(crate) fn take_data(self) -> (ImmutableString, Vec<Dynamic>) {
|
||||||
self.0
|
(self.0, self.1)
|
||||||
|
}
|
||||||
|
/// Get the curried arguments.
|
||||||
|
pub fn curry(&self) -> &[Dynamic] {
|
||||||
|
&self.1
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Call the function pointer with curried arguments (if any).
|
||||||
|
///
|
||||||
|
/// ## WARNING
|
||||||
|
///
|
||||||
|
/// All the arguments are _consumed_, meaning that they're replaced by `()`.
|
||||||
|
/// This is to avoid unnecessarily cloning the arguments.
|
||||||
|
/// Do not use the arguments after this call. If they are needed afterwards,
|
||||||
|
/// clone them _before_ calling this function.
|
||||||
|
pub fn call_dynamic(
|
||||||
|
&self,
|
||||||
|
engine: &Engine,
|
||||||
|
lib: impl AsRef<Module>,
|
||||||
|
this_ptr: Option<&mut Dynamic>,
|
||||||
|
mut arg_values: impl AsMut<[Dynamic]>,
|
||||||
|
) -> FuncReturn<Dynamic> {
|
||||||
|
let args = self
|
||||||
|
.1
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.chain(arg_values.as_mut().iter_mut().map(|v| mem::take(v)))
|
||||||
|
.collect::<StaticVec<_>>();
|
||||||
|
|
||||||
|
engine.call_fn_dynamic(&mut Scope::new(), lib, self.0.as_str(), this_ptr, args)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,7 +115,7 @@ impl TryFrom<ImmutableString> for FnPtr {
|
|||||||
|
|
||||||
fn try_from(value: ImmutableString) -> Result<Self, Self::Error> {
|
fn try_from(value: ImmutableString) -> Result<Self, Self::Error> {
|
||||||
if is_valid_identifier(value.chars()) {
|
if is_valid_identifier(value.chars()) {
|
||||||
Ok(Self(value))
|
Ok(Self(value, Default::default()))
|
||||||
} else {
|
} else {
|
||||||
Err(Box::new(EvalAltResult::ErrorFunctionNotFound(
|
Err(Box::new(EvalAltResult::ErrorFunctionNotFound(
|
||||||
value.into(),
|
value.into(),
|
||||||
|
@ -95,7 +95,6 @@ mod scope;
|
|||||||
mod serde;
|
mod serde;
|
||||||
mod settings;
|
mod settings;
|
||||||
mod stdlib;
|
mod stdlib;
|
||||||
#[cfg(feature = "internals")]
|
|
||||||
mod syntax;
|
mod syntax;
|
||||||
mod token;
|
mod token;
|
||||||
mod r#unsafe;
|
mod r#unsafe;
|
||||||
@ -110,6 +109,7 @@ pub use module::Module;
|
|||||||
pub use parser::{ImmutableString, AST, INT};
|
pub use parser::{ImmutableString, AST, INT};
|
||||||
pub use result::EvalAltResult;
|
pub use result::EvalAltResult;
|
||||||
pub use scope::Scope;
|
pub use scope::Scope;
|
||||||
|
pub use syntax::{EvalContext, Expression};
|
||||||
pub use token::Position;
|
pub use token::Position;
|
||||||
pub use utils::calc_fn_spec as calc_fn_hash;
|
pub use utils::calc_fn_spec as calc_fn_hash;
|
||||||
|
|
||||||
@ -173,11 +173,7 @@ pub use parser::{CustomExpr, Expr, ReturnType, ScriptFnDef, Stmt};
|
|||||||
|
|
||||||
#[cfg(feature = "internals")]
|
#[cfg(feature = "internals")]
|
||||||
#[deprecated(note = "this type is volatile and may change")]
|
#[deprecated(note = "this type is volatile and may change")]
|
||||||
pub use engine::{Expression, Imports, State as EvalState};
|
pub use engine::{Imports, State as EvalState};
|
||||||
|
|
||||||
#[cfg(feature = "internals")]
|
|
||||||
#[deprecated(note = "this type is volatile and may change")]
|
|
||||||
pub use syntax::EvalContext;
|
|
||||||
|
|
||||||
#[cfg(feature = "internals")]
|
#[cfg(feature = "internals")]
|
||||||
#[deprecated(note = "this type is volatile and may change")]
|
#[deprecated(note = "this type is volatile and may change")]
|
||||||
|
@ -2,23 +2,19 @@
|
|||||||
|
|
||||||
use crate::any::{Dynamic, Union};
|
use crate::any::{Dynamic, Union};
|
||||||
use crate::calc_fn_hash;
|
use crate::calc_fn_hash;
|
||||||
use crate::engine::{make_getter, make_setter, Engine, FN_ANONYMOUS, KEYWORD_THIS};
|
use crate::engine::{
|
||||||
|
make_getter, make_setter, Engine, FN_ANONYMOUS, KEYWORD_THIS, MARKER_BLOCK, MARKER_EXPR,
|
||||||
|
MARKER_IDENT,
|
||||||
|
};
|
||||||
use crate::error::{LexError, ParseError, ParseErrorType};
|
use crate::error::{LexError, ParseError, ParseErrorType};
|
||||||
|
use crate::fn_native::Shared;
|
||||||
use crate::module::{Module, ModuleRef};
|
use crate::module::{Module, ModuleRef};
|
||||||
use crate::optimize::{optimize_into_ast, OptimizationLevel};
|
use crate::optimize::{optimize_into_ast, OptimizationLevel};
|
||||||
use crate::scope::{EntryType as ScopeEntryType, Scope};
|
use crate::scope::{EntryType as ScopeEntryType, Scope};
|
||||||
|
use crate::syntax::FnCustomSyntaxEval;
|
||||||
use crate::token::{is_valid_identifier, Position, Token, TokenStream};
|
use crate::token::{is_valid_identifier, Position, Token, TokenStream};
|
||||||
use crate::utils::{StaticVec, StraightHasherBuilder};
|
use crate::utils::{StaticVec, StraightHasherBuilder};
|
||||||
|
|
||||||
#[cfg(feature = "internals")]
|
|
||||||
use crate::engine::{MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT};
|
|
||||||
|
|
||||||
#[cfg(feature = "internals")]
|
|
||||||
use crate::fn_native::Shared;
|
|
||||||
|
|
||||||
#[cfg(feature = "internals")]
|
|
||||||
use crate::syntax::FnCustomSyntaxEval;
|
|
||||||
|
|
||||||
use crate::stdlib::{
|
use crate::stdlib::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
boxed::Box,
|
boxed::Box,
|
||||||
@ -587,17 +583,14 @@ impl Stmt {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
#[cfg(feature = "internals")]
|
|
||||||
pub struct CustomExpr(pub StaticVec<Expr>, pub Shared<FnCustomSyntaxEval>);
|
pub struct CustomExpr(pub StaticVec<Expr>, pub Shared<FnCustomSyntaxEval>);
|
||||||
|
|
||||||
#[cfg(feature = "internals")]
|
|
||||||
impl fmt::Debug for CustomExpr {
|
impl fmt::Debug for CustomExpr {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
fmt::Debug::fmt(&self.0, f)
|
fmt::Debug::fmt(&self.0, f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "internals")]
|
|
||||||
impl Hash for CustomExpr {
|
impl Hash for CustomExpr {
|
||||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
self.0.hash(state);
|
self.0.hash(state);
|
||||||
@ -683,7 +676,6 @@ pub enum Expr {
|
|||||||
/// ()
|
/// ()
|
||||||
Unit(Position),
|
Unit(Position),
|
||||||
/// Custom syntax
|
/// Custom syntax
|
||||||
#[cfg(feature = "internals")]
|
|
||||||
Custom(Box<(CustomExpr, Position)>),
|
Custom(Box<(CustomExpr, Position)>),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -781,7 +773,6 @@ impl Expr {
|
|||||||
|
|
||||||
Self::Dot(x) | Self::Index(x) => x.0.position(),
|
Self::Dot(x) | Self::Index(x) => x.0.position(),
|
||||||
|
|
||||||
#[cfg(feature = "internals")]
|
|
||||||
Self::Custom(x) => x.1,
|
Self::Custom(x) => x.1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -816,8 +807,6 @@ impl Expr {
|
|||||||
Self::Assignment(x) => x.3 = new_pos,
|
Self::Assignment(x) => x.3 = new_pos,
|
||||||
Self::Dot(x) => x.2 = new_pos,
|
Self::Dot(x) => x.2 = new_pos,
|
||||||
Self::Index(x) => x.2 = new_pos,
|
Self::Index(x) => x.2 = new_pos,
|
||||||
|
|
||||||
#[cfg(feature = "internals")]
|
|
||||||
Self::Custom(x) => x.1 = new_pos,
|
Self::Custom(x) => x.1 = new_pos,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -925,7 +914,6 @@ impl Expr {
|
|||||||
_ => false,
|
_ => false,
|
||||||
},
|
},
|
||||||
|
|
||||||
#[cfg(feature = "internals")]
|
|
||||||
Self::Custom(_) => false,
|
Self::Custom(_) => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2148,7 +2136,6 @@ fn parse_expr(
|
|||||||
settings.ensure_level_within_max_limit(state.max_expr_depth)?;
|
settings.ensure_level_within_max_limit(state.max_expr_depth)?;
|
||||||
|
|
||||||
// Check if it is a custom syntax.
|
// Check if it is a custom syntax.
|
||||||
#[cfg(feature = "internals")]
|
|
||||||
if let Some(ref custom) = state.engine.custom_syntax {
|
if let Some(ref custom) = state.engine.custom_syntax {
|
||||||
let (token, pos) = input.peek().unwrap();
|
let (token, pos) = input.peek().unwrap();
|
||||||
let token_pos = *pos;
|
let token_pos = *pos;
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
//! Module containing implementation for custom syntax.
|
//! Module containing implementation for custom syntax.
|
||||||
#![cfg(feature = "internals")]
|
|
||||||
|
|
||||||
use crate::any::Dynamic;
|
use crate::any::Dynamic;
|
||||||
use crate::engine::{Engine, Expression, Imports, State, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT};
|
use crate::engine::{Engine, Imports, State, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT};
|
||||||
use crate::error::LexError;
|
use crate::error::{LexError, ParseError};
|
||||||
use crate::fn_native::{SendSync, Shared};
|
use crate::fn_native::{SendSync, Shared};
|
||||||
use crate::module::Module;
|
use crate::module::Module;
|
||||||
|
use crate::parser::Expr;
|
||||||
use crate::result::EvalAltResult;
|
use crate::result::EvalAltResult;
|
||||||
use crate::scope::Scope;
|
use crate::scope::Scope;
|
||||||
use crate::token::{is_valid_identifier, Token};
|
use crate::token::{is_valid_identifier, Position, Token};
|
||||||
use crate::utils::StaticVec;
|
use crate::utils::StaticVec;
|
||||||
|
|
||||||
use crate::stdlib::{
|
use crate::stdlib::{
|
||||||
@ -18,7 +17,7 @@ use crate::stdlib::{
|
|||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A general function trail object.
|
/// A general expression evaluation trait object.
|
||||||
#[cfg(not(feature = "sync"))]
|
#[cfg(not(feature = "sync"))]
|
||||||
pub type FnCustomSyntaxEval = dyn Fn(
|
pub type FnCustomSyntaxEval = dyn Fn(
|
||||||
&Engine,
|
&Engine,
|
||||||
@ -26,12 +25,40 @@ pub type FnCustomSyntaxEval = dyn Fn(
|
|||||||
&mut Scope,
|
&mut Scope,
|
||||||
&[Expression],
|
&[Expression],
|
||||||
) -> Result<Dynamic, Box<EvalAltResult>>;
|
) -> Result<Dynamic, Box<EvalAltResult>>;
|
||||||
/// A general function trail object.
|
/// A general expression evaluation trait object.
|
||||||
#[cfg(feature = "sync")]
|
#[cfg(feature = "sync")]
|
||||||
pub type FnCustomSyntaxEval = dyn Fn(&Engine, &mut EvalContext, &mut Scope, &[Expression]) -> Result<Dynamic, Box<EvalAltResult>>
|
pub type FnCustomSyntaxEval = dyn Fn(&Engine, &mut EvalContext, &mut Scope, &[Expression]) -> Result<Dynamic, Box<EvalAltResult>>
|
||||||
+ Send
|
+ Send
|
||||||
+ Sync;
|
+ Sync;
|
||||||
|
|
||||||
|
/// An expression sub-tree in an AST.
|
||||||
|
#[derive(Debug, Clone, Hash)]
|
||||||
|
pub struct Expression<'a>(&'a Expr);
|
||||||
|
|
||||||
|
impl<'a> From<&'a Expr> for Expression<'a> {
|
||||||
|
fn from(expr: &'a Expr) -> Self {
|
||||||
|
Self(expr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Expression<'_> {
|
||||||
|
/// If this expression is a variable name, return it. Otherwise `None`.
|
||||||
|
pub fn get_variable_name(&self) -> Option<&str> {
|
||||||
|
match self.0 {
|
||||||
|
Expr::Variable(x) => Some((x.0).0.as_str()),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Get the expression.
|
||||||
|
pub(crate) fn expr(&self) -> &Expr {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
/// Get the position of this expression.
|
||||||
|
pub fn position(&self) -> Position {
|
||||||
|
self.0.position()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct CustomSyntax {
|
pub struct CustomSyntax {
|
||||||
pub segments: StaticVec<String>,
|
pub segments: StaticVec<String>,
|
||||||
@ -45,6 +72,8 @@ impl fmt::Debug for CustomSyntax {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Context of a script evaluation process.
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct EvalContext<'a, 'b: 'a, 's, 'm, 't, 'd: 't> {
|
pub struct EvalContext<'a, 'b: 'a, 's, 'm, 't, 'd: 't> {
|
||||||
pub(crate) mods: &'a mut Imports<'b>,
|
pub(crate) mods: &'a mut Imports<'b>,
|
||||||
pub(crate) state: &'s mut State,
|
pub(crate) state: &'s mut State,
|
||||||
@ -56,7 +85,7 @@ pub struct EvalContext<'a, 'b: 'a, 's, 'm, 't, 'd: 't> {
|
|||||||
impl Engine {
|
impl Engine {
|
||||||
pub fn register_custom_syntax<S: AsRef<str> + ToString>(
|
pub fn register_custom_syntax<S: AsRef<str> + ToString>(
|
||||||
&mut self,
|
&mut self,
|
||||||
value: &[S],
|
keywords: &[S],
|
||||||
scope_delta: isize,
|
scope_delta: isize,
|
||||||
func: impl Fn(
|
func: impl Fn(
|
||||||
&Engine,
|
&Engine,
|
||||||
@ -66,15 +95,18 @@ impl Engine {
|
|||||||
) -> Result<Dynamic, Box<EvalAltResult>>
|
) -> Result<Dynamic, Box<EvalAltResult>>
|
||||||
+ SendSync
|
+ SendSync
|
||||||
+ 'static,
|
+ 'static,
|
||||||
) -> Result<&mut Self, Box<LexError>> {
|
) -> Result<&mut Self, Box<ParseError>> {
|
||||||
if value.is_empty() {
|
|
||||||
return Err(Box::new(LexError::ImproperSymbol("".to_string())));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut segments: StaticVec<_> = Default::default();
|
let mut segments: StaticVec<_> = Default::default();
|
||||||
|
|
||||||
for s in value {
|
for s in keywords {
|
||||||
let seg = match s.as_ref() {
|
let s = s.as_ref().trim();
|
||||||
|
|
||||||
|
// skip empty keywords
|
||||||
|
if s.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let seg = match s {
|
||||||
// Markers not in first position
|
// Markers not in first position
|
||||||
MARKER_EXPR | MARKER_BLOCK | MARKER_IDENT if !segments.is_empty() => s.to_string(),
|
MARKER_EXPR | MARKER_BLOCK | MARKER_IDENT if !segments.is_empty() => s.to_string(),
|
||||||
// Standard symbols not in first position
|
// Standard symbols not in first position
|
||||||
@ -115,12 +147,25 @@ impl Engine {
|
|||||||
s.into()
|
s.into()
|
||||||
}
|
}
|
||||||
// Anything else is an error
|
// Anything else is an error
|
||||||
_ => return Err(Box::new(LexError::ImproperSymbol(s.to_string()))),
|
_ => {
|
||||||
|
return Err(LexError::ImproperSymbol(format!(
|
||||||
|
"Improper symbol for custom syntax: '{}'",
|
||||||
|
s
|
||||||
|
))
|
||||||
|
.into_err(Position::none())
|
||||||
|
.into());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
segments.push(seg);
|
segments.push(seg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the syntax has no keywords, just ignore the registration
|
||||||
|
if segments.is_empty() {
|
||||||
|
return Ok(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the first keyword as the discriminator
|
||||||
let key = segments.remove(0);
|
let key = segments.remove(0);
|
||||||
|
|
||||||
let syntax = CustomSyntax {
|
let syntax = CustomSyntax {
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
//! Main module defining the lexer and parser.
|
//! Main module defining the lexer and parser.
|
||||||
|
|
||||||
use crate::engine::{
|
use crate::engine::{
|
||||||
Engine, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_PRINT,
|
Engine, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY,
|
||||||
KEYWORD_THIS, KEYWORD_TYPE_OF,
|
KEYWORD_PRINT, KEYWORD_THIS, KEYWORD_TYPE_OF,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::error::LexError;
|
use crate::error::LexError;
|
||||||
@ -404,7 +404,7 @@ impl Token {
|
|||||||
Reserved(syntax.into())
|
Reserved(syntax.into())
|
||||||
}
|
}
|
||||||
KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR
|
KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR
|
||||||
| KEYWORD_FN_PTR_CALL | KEYWORD_THIS => Reserved(syntax.into()),
|
| KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_THIS => Reserved(syntax.into()),
|
||||||
|
|
||||||
_ => return None,
|
_ => return None,
|
||||||
})
|
})
|
||||||
|
@ -387,7 +387,7 @@ impl<T> StaticVec<T> {
|
|||||||
let value = self.extract_from_list(index);
|
let value = self.extract_from_list(index);
|
||||||
|
|
||||||
// Move all items one slot to the left
|
// Move all items one slot to the left
|
||||||
for x in index + 1..self.len - 1 {
|
for x in index + 1..self.len {
|
||||||
let orig_value = self.extract_from_list(x);
|
let orig_value = self.extract_from_list(x);
|
||||||
self.set_into_list(x - 1, orig_value, false);
|
self.set_into_list(x - 1, orig_value, false);
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
use rhai::{
|
use rhai::{
|
||||||
Dynamic, Engine, EvalAltResult, FnPtr, Func, Module, ParseError, ParseErrorType, Scope, INT,
|
Dynamic, Engine, EvalAltResult, FnPtr, Func, Module, ParseError, ParseErrorType, Scope, INT,
|
||||||
};
|
};
|
||||||
|
use std::any::TypeId;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_fn() -> Result<(), Box<EvalAltResult>> {
|
fn test_fn() -> Result<(), Box<EvalAltResult>> {
|
||||||
@ -121,9 +122,9 @@ fn test_fn_ptr_raw() -> Result<(), Box<EvalAltResult>> {
|
|||||||
engine.register_raw_fn(
|
engine.register_raw_fn(
|
||||||
"bar",
|
"bar",
|
||||||
&[
|
&[
|
||||||
std::any::TypeId::of::<INT>(),
|
TypeId::of::<INT>(),
|
||||||
std::any::TypeId::of::<FnPtr>(),
|
TypeId::of::<FnPtr>(),
|
||||||
std::any::TypeId::of::<INT>(),
|
TypeId::of::<INT>(),
|
||||||
],
|
],
|
||||||
move |engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| {
|
move |engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| {
|
||||||
let fp = std::mem::take(args[1]).cast::<FnPtr>();
|
let fp = std::mem::take(args[1]).cast::<FnPtr>();
|
||||||
@ -157,3 +158,35 @@ fn test_fn_ptr_raw() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_fn_ptr_curry_call() -> Result<(), Box<EvalAltResult>> {
|
||||||
|
let mut module = Module::new();
|
||||||
|
|
||||||
|
module.set_raw_fn(
|
||||||
|
"call_with_arg",
|
||||||
|
&[TypeId::of::<FnPtr>(), TypeId::of::<INT>()],
|
||||||
|
|engine: &Engine, module: &Module, args: &mut [&mut Dynamic]| {
|
||||||
|
let fn_ptr = std::mem::take(args[0]).cast::<FnPtr>();
|
||||||
|
fn_ptr.call_dynamic(engine, module, None, [std::mem::take(args[1])])
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut engine = Engine::new();
|
||||||
|
engine.load_package(module.into());
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<INT>(
|
||||||
|
r#"
|
||||||
|
let addition = |x, y| { x + y };
|
||||||
|
let curried = addition.curry(2);
|
||||||
|
|
||||||
|
call_with_arg(curried, 40)
|
||||||
|
"#
|
||||||
|
)?,
|
||||||
|
42
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
@ -78,3 +78,35 @@ fn test_fn_ptr() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_fn_ptr_curry() -> Result<(), Box<EvalAltResult>> {
|
||||||
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
|
engine.register_fn("foo", |x: &mut INT, y: INT| *x + y);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<INT>(
|
||||||
|
r#"
|
||||||
|
let f = Fn("foo");
|
||||||
|
let f2 = f.curry(40);
|
||||||
|
f2.call(2)
|
||||||
|
"#
|
||||||
|
)?,
|
||||||
|
42
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<INT>(
|
||||||
|
r#"
|
||||||
|
let f = Fn("foo");
|
||||||
|
let f2 = curry(f, 40);
|
||||||
|
call(f2, 2)
|
||||||
|
"#
|
||||||
|
)?,
|
||||||
|
42
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
#![cfg(feature = "internals")]
|
use rhai::{
|
||||||
use rhai::{Engine, EvalAltResult, EvalContext, Expression, LexError, ParseErrorType, Scope, INT};
|
Engine, EvalAltResult, EvalContext, Expression, ParseError, ParseErrorType, Scope, INT,
|
||||||
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
|
fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
|
||||||
@ -72,7 +73,7 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
|
|||||||
// The first symbol must be an identifier
|
// The first symbol must be an identifier
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
*engine.register_custom_syntax(&["!"], 0, |_, _, _, _| Ok(().into())).expect_err("should error"),
|
*engine.register_custom_syntax(&["!"], 0, |_, _, _, _| Ok(().into())).expect_err("should error"),
|
||||||
LexError::ImproperSymbol(s) if s == "!"
|
ParseError(err, _) if *err == ParseErrorType::BadInput("Improper symbol for custom syntax: '!'".to_string())
|
||||||
));
|
));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
Loading…
Reference in New Issue
Block a user