Refine docs and add custom syntax.
This commit is contained in:
parent
7436fc1c05
commit
ebffbf0f98
@ -21,7 +21,7 @@ num-traits = { version = "0.2.11", default-features = false }
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
#default = ["unchecked", "sync", "no_optimize", "no_float", "only_i32", "no_index", "no_object", "no_function", "no_module"]
|
#default = ["unchecked", "sync", "no_optimize", "no_float", "only_i32", "no_index", "no_object", "no_function", "no_module"]
|
||||||
default = []
|
default = ["internals"]
|
||||||
plugins = []
|
plugins = []
|
||||||
unchecked = [] # unchecked arithmetic
|
unchecked = [] # unchecked arithmetic
|
||||||
sync = [] # restrict to only types that implement Send + Sync
|
sync = [] # restrict to only types that implement Send + Sync
|
||||||
|
@ -35,7 +35,7 @@ Features
|
|||||||
* Track script evaluation [progress](https://schungx.github.io/rhai/safety/progress.html) and manually terminate a script run.
|
* Track script evaluation [progress](https://schungx.github.io/rhai/safety/progress.html) and manually terminate a script run.
|
||||||
* [Function overloading](https://schungx.github.io/rhai/language/overload.html).
|
* [Function overloading](https://schungx.github.io/rhai/language/overload.html).
|
||||||
* [Operator overloading](https://schungx.github.io/rhai/rust/operators.html).
|
* [Operator overloading](https://schungx.github.io/rhai/rust/operators.html).
|
||||||
* Support for use as a [DSL](https://schungx.github.io/rhai/engine/dsl.html) - [disabling keywords/operators](https://schungx.github.io/rhai/engine/disable.html), [custom operators](https://schungx.github.io/rhai/engine/custom-op.html).
|
* Support for use as a [DSL](https://schungx.github.io/rhai/engine/dsl.html) - [disabling keywords/operators](https://schungx.github.io/rhai/engine/disable.html), [custom operators](https://schungx.github.io/rhai/engine/custom-op.html) and extending the language with [custom syntax](https://schungx.github.io/rhai/engine/custom-syntax.html).
|
||||||
* Dynamic dispatch via [function pointers](https://schungx.github.io/rhai/language/fn-ptr.html).
|
* Dynamic dispatch via [function pointers](https://schungx.github.io/rhai/language/fn-ptr.html).
|
||||||
* Some support for [object-oriented programming (OOP)](https://schungx.github.io/rhai/language/oop.html).
|
* Some support for [object-oriented programming (OOP)](https://schungx.github.io/rhai/language/oop.html).
|
||||||
* Organize code base with dynamically-loadable [modules](https://schungx.github.io/rhai/language/modules.html).
|
* Organize code base with dynamically-loadable [modules](https://schungx.github.io/rhai/language/modules.html).
|
||||||
|
@ -108,7 +108,7 @@ The Rhai Scripting Language
|
|||||||
5. [Use as DSL](engine/dsl.md)
|
5. [Use as DSL](engine/dsl.md)
|
||||||
1. [Disable Keywords and/or Operators](engine/disable.md)
|
1. [Disable Keywords and/or Operators](engine/disable.md)
|
||||||
2. [Custom Operators](engine/custom-op.md)
|
2. [Custom Operators](engine/custom-op.md)
|
||||||
3. [Custom Syntax](engine/custom-syntax.md)
|
3. [Extending with Custom Syntax](engine/custom-syntax.md)
|
||||||
6. [Eval Statement](language/eval.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)
|
||||||
|
@ -67,4 +67,5 @@ Flexible
|
|||||||
|
|
||||||
* Surgically [disable keywords and operators] to restrict the language.
|
* Surgically [disable keywords and operators] to restrict the language.
|
||||||
|
|
||||||
* Use as a [DSL] by [disabling keywords/operators][disable keywords and operators], [custom operators] and defining [custom syntax].
|
* Use as a [DSL] by [disabling keywords/operators][disable keywords and operators], [custom operators]
|
||||||
|
and extending the language with [custom syntax].
|
||||||
|
@ -1,5 +1,282 @@
|
|||||||
Custom Syntax
|
Extending Rhai with Custom Syntax
|
||||||
=============
|
================================
|
||||||
|
|
||||||
{{#include ../links.md}}
|
{{#include ../links.md}}
|
||||||
|
|
||||||
|
|
||||||
|
For the ultimate advantageous, there is a built-in facility to _extend_ the Rhai language
|
||||||
|
with custom-defined _syntax_.
|
||||||
|
|
||||||
|
But before going off to define the next weird statement type, heed this warning:
|
||||||
|
|
||||||
|
|
||||||
|
Don't Do It™
|
||||||
|
------------
|
||||||
|
|
||||||
|
Stick with standard language syntax as much as possible.
|
||||||
|
|
||||||
|
Having to learn Rhai is bad enough, no sane user would ever want to learn _yet_ another
|
||||||
|
obscure language syntax just to do something.
|
||||||
|
|
||||||
|
Try to use [custom operators] first. Defining a custom syntax should be considered a _last resort_.
|
||||||
|
|
||||||
|
|
||||||
|
Where This Might Be Useful
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
* Where an operation is used a _LOT_ and a custom syntax saves a lot of typing.
|
||||||
|
|
||||||
|
* Where a custom syntax _significantly_ simplifies the code and _significantly_ enhances understanding of the code's intent.
|
||||||
|
|
||||||
|
* Where certain logic cannot be easily encapsulated inside a function. This is usually the case where _closures_ are required, because Rhai does not have closures.
|
||||||
|
|
||||||
|
* Where you just want to confuse your user and make their lives miserable, because you can.
|
||||||
|
|
||||||
|
|
||||||
|
Step One - Start With `internals`
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
These symbol types can be used:
|
||||||
|
|
||||||
|
* Standard [keywords]({{rootUrl}}/appendix/keywords.md)
|
||||||
|
|
||||||
|
* Standard [operators]({{rootUrl}}/appendix/operators.md#operators).
|
||||||
|
|
||||||
|
* Reserved [symbols]({{rootUrl}}/appendix/operators.md#symbols).
|
||||||
|
|
||||||
|
* Identifiers following the [variable] naming rules.
|
||||||
|
|
||||||
|
* `$expr$` - any valid expression, statement or statement block.
|
||||||
|
|
||||||
|
* `$block$` - any valid statement block (i.e. must be enclosed by `'{'` .. `'}'`).
|
||||||
|
|
||||||
|
* `$ident$` - any [variable] name.
|
||||||
|
|
||||||
|
### The First Symbol Must be a Keyword
|
||||||
|
|
||||||
|
There is no specific limit on the combination and sequencing of each symbol type,
|
||||||
|
except the _first_ symbol which must be a [custom keyword].
|
||||||
|
|
||||||
|
It _cannot_ be a [built-in keyword]({{rootUrl}}/appendix/keywords.md).
|
||||||
|
|
||||||
|
However, it _may_ be a built-in keyword that has been [disabled][disable keywords and operators].
|
||||||
|
|
||||||
|
### The First Symbol Must be Unique
|
||||||
|
|
||||||
|
Rhai uses the _first_ symbol as a clue to parse custom syntax.
|
||||||
|
|
||||||
|
Therefore, at any one time, there can only be _one_ custom syntax starting with each unique symbol.
|
||||||
|
|
||||||
|
Any new custom syntax definition using the same first symbol simply _overwrites_ the previous one.
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
```rust
|
||||||
|
exec $ident$ <- $expr$ : $block$
|
||||||
|
```
|
||||||
|
|
||||||
|
The above syntax is made up of a stream of symbols:
|
||||||
|
|
||||||
|
| Position | Input | Symbol | Description |
|
||||||
|
| :------: | :---: | :-------: | -------------------------------------------------------------------------------------------------------- |
|
||||||
|
| 1 | | `exec` | custom keyword |
|
||||||
|
| 2 | 1 | `$ident$` | a variable name |
|
||||||
|
| 3 | | `<-` | the left-arrow symbol (which is a [reserved symbol]({{rootUrl}}/appendix/operators.md#symbols) in Rhai). |
|
||||||
|
| 4 | 2 | `$expr$` | an expression, which may be enclosed with `{` .. `}`, or not. |
|
||||||
|
| 5 | | `:` | the colon symbol |
|
||||||
|
| 6 | 3 | `$block$` | a statement block, which must be enclosed with `{` .. `}`. |
|
||||||
|
|
||||||
|
This syntax matches the following sample code and generates three inputs (one for each non-keyword):
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Assuming the 'exec' custom syntax implementation declares the variable 'hello':
|
||||||
|
let x = exec hello <- foo(1, 2) : {
|
||||||
|
hello += bar(hello);
|
||||||
|
baz(hello);
|
||||||
|
};
|
||||||
|
|
||||||
|
print(x); // variable 'x' has a value returned by the custom syntax
|
||||||
|
|
||||||
|
print(hello); // variable declared by a custom syntax persists!
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Step Three - Implementation
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
Any custom syntax must include an _implementation_ of it.
|
||||||
|
|
||||||
|
### Function Signature
|
||||||
|
|
||||||
|
The function signature of an implementation is:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
Fn(
|
||||||
|
engine: &Engine,
|
||||||
|
scope: &mut Scope,
|
||||||
|
mods: &mut Imports,
|
||||||
|
state: &mut State,
|
||||||
|
lib: &Module,
|
||||||
|
this_ptr: &mut Option<&mut Dynamic>,
|
||||||
|
inputs: &[Expr],
|
||||||
|
level: usize
|
||||||
|
) -> Result<Dynamic, Box<EvalAltResult>>
|
||||||
|
```
|
||||||
|
|
||||||
|
where:
|
||||||
|
|
||||||
|
* `engine : &Engine` - reference to the current [`Engine`].
|
||||||
|
* `scope : &mut Scope` - mutable reference to the current [`Scope`]; variables can be added to it.
|
||||||
|
* `mods : &mut Imports` - mutable reference to the current collection of imported [`Module`]'s; **do not touch**.
|
||||||
|
* `state : &mut State` - mutable reference to the current evaluation state; **do not touch**.
|
||||||
|
* `lib : &Module` - reference to the current collection of script-defined functions.
|
||||||
|
* `this_ptr : &mut Option<&mut Dynamic>` - mutable reference to the current binding of the `this` pointer; **do not touch**.
|
||||||
|
* `inputs : &[Expr]` - a list of input expression trees.
|
||||||
|
* `level : usize` - the current function call level.
|
||||||
|
|
||||||
|
There are a lot of parameters, most of which should not be touched or Bad Things Happen™.
|
||||||
|
They represent the running _content_ of a script evaluation and should simply be passed
|
||||||
|
straight-through the the [`Engine`].
|
||||||
|
|
||||||
|
### Access Arguments
|
||||||
|
|
||||||
|
The most important argument is `inputs` where the matched identifiers (`$ident$`), expressions/statements (`$expr$`)
|
||||||
|
and statement blocks (`$block$) are provided.
|
||||||
|
|
||||||
|
To access a particular argument, use the following patterns:
|
||||||
|
|
||||||
|
| Argument type | Pattern (`n` = slot in `inputs`) | Result type | Description |
|
||||||
|
| :-----------: | ---------------------------------------- | :---------: | ------------------ |
|
||||||
|
| `$ident$` | `inputs[n].get_variable_name().unwrap()` | `&str` | name of a variable |
|
||||||
|
| `$expr$` | `inputs.get(n).unwrap()` | `Expr` | an expression tree |
|
||||||
|
| `$block$` | `inputs.get(n).unwrap()` | `Expr` | an expression tree |
|
||||||
|
|
||||||
|
### Evaluate an Expression Tree
|
||||||
|
|
||||||
|
Use the `engine::eval_expression_tree` method to evaluate an expression tree.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let expr = inputs.get(0).unwrap();
|
||||||
|
let result = engine.eval_expression_tree(scope, mods, state, lib, this_ptr, expr, level)?;
|
||||||
|
```
|
||||||
|
|
||||||
|
As can be seem above, most arguments are simply passed straight-through to `engine::eval_expression_tree`.
|
||||||
|
|
||||||
|
### Declare Variables
|
||||||
|
|
||||||
|
New variables maybe declared (usually with a variable name that is passed in via `$ident$).
|
||||||
|
|
||||||
|
It can simply be pushed into the [`scope`].
|
||||||
|
|
||||||
|
However, beware that all new variables must be declared _prior_ to evaluating any expression tree.
|
||||||
|
In other words, any `scope.push(...)` calls must come _before_ any `engine::eval_expression_tree(...)` calls.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let var_name = inputs[0].get_variable_name().unwrap().to_string();
|
||||||
|
let expr = inputs.get(1).unwrap();
|
||||||
|
|
||||||
|
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)?;
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Step Four - Register the Custom Syntax
|
||||||
|
-------------------------------------
|
||||||
|
|
||||||
|
Use `Engine::register_custom_syntax` to register a custom syntax.
|
||||||
|
|
||||||
|
Again, beware that the _first_ symbol must be unique. If there already exists a custom syntax starting
|
||||||
|
with that symbol, the previous syntax will be overwritten.
|
||||||
|
|
||||||
|
The syntax is passed simply as a slice of `&str`.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Custom syntax implementation
|
||||||
|
fn implementation_func(
|
||||||
|
engine: &Engine,
|
||||||
|
scope: &mut Scope,
|
||||||
|
mods: &mut Imports,
|
||||||
|
state: &mut State,
|
||||||
|
lib: &Module,
|
||||||
|
this_ptr: &mut Option<&mut Dynamic>,
|
||||||
|
inputs: &[Expr],
|
||||||
|
level: usize
|
||||||
|
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||||
|
let var_name = inputs[0].get_variable_name().unwrap().to_string();
|
||||||
|
let stmt = inputs.get(1).unwrap();
|
||||||
|
let condition = inputs.get(2).unwrap();
|
||||||
|
|
||||||
|
// Push one new variable into the 'scope' BEFORE 'eval_expression_tree'
|
||||||
|
scope.push(var_name, 0 as INT);
|
||||||
|
|
||||||
|
loop {
|
||||||
|
// Evaluate the statement block
|
||||||
|
engine.eval_expression_tree(scope, mods, state, lib, this_ptr, stmt, level)?;
|
||||||
|
|
||||||
|
// Evaluate the condition expression
|
||||||
|
let stop = !engine
|
||||||
|
.eval_expression_tree(scope, mods, state, lib, this_ptr, condition, level)?
|
||||||
|
.as_bool()
|
||||||
|
.map_err(|_| EvalAltResult::ErrorBooleanArgMismatch(
|
||||||
|
"do-while".into(), expr.position()
|
||||||
|
))?;
|
||||||
|
|
||||||
|
if stop {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(().into())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the custom syntax (sample): do |x| -> { x += 1 } while x < 0;
|
||||||
|
engine.register_custom_syntax(
|
||||||
|
&[ "do", "|", "$ident$", "|", "->", "$block$", "while", "$expr$" ], // the custom syntax
|
||||||
|
1, // the number of new variables declared within this custom syntax
|
||||||
|
implementation_func
|
||||||
|
)?;
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Step Five - Disable Unneeded Statement Types
|
||||||
|
-------------------------------------------
|
||||||
|
|
||||||
|
When a DSL needs a custom syntax, most likely than not it is extremely specialized.
|
||||||
|
Therefore, many statement types actually may not make sense under the same usage scenario.
|
||||||
|
|
||||||
|
So, while at it, better [disable][disable keywords and operators] those built-in keywords
|
||||||
|
and operators that should not be used by the user. The would leave only the bare minimum
|
||||||
|
language surface exposed, together with the custom syntax that is tailor-designed for
|
||||||
|
the scenario.
|
||||||
|
|
||||||
|
A keyword or operator that is disabled can still be used in a custom syntax.
|
||||||
|
|
||||||
|
In an extreme case, it is possible to disable _every_ keyword in the language, leaving only
|
||||||
|
custom syntax (plus possibly expressions). But again, Don't Do It™ - unless you are certain
|
||||||
|
of what you're doing.
|
||||||
|
|
||||||
|
|
||||||
|
Step Six - Document
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
For custom syntax, documentation is crucial.
|
||||||
|
|
||||||
|
Make sure there are _lots_ of examples for users to follow.
|
||||||
|
|
||||||
|
|
||||||
|
Step Seven - Profit!
|
||||||
|
--------------------
|
||||||
|
@ -56,13 +56,17 @@ essentially custom statement types.
|
|||||||
|
|
||||||
The [`internals`] feature is needed to be able to define [custom syntax] in Rhai.
|
The [`internals`] feature is needed to be able to define [custom syntax] in Rhai.
|
||||||
|
|
||||||
For example:
|
For example, the following is a SQL like syntax for some obscure DSL operation:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let table = [..., ..., ..., ...];
|
let table = [..., ..., ..., ...];
|
||||||
|
|
||||||
// Syntax = "select" $ident$ $ident$ "from" $expr$ "->" $ident$ ":" $expr$
|
// Syntax = "calculate" $ident$ $ident$ "from" $expr$ "->" $ident$ ":" $expr$
|
||||||
let total = select 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:
|
||||||
|
// Syntax = "SELECT" $ident$ "(" $ident$ ")" "FROM" $expr$ "AS" $ident$ "WHERE" $expr$
|
||||||
|
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
|
||||||
|
@ -3,9 +3,9 @@ Infinite `loop`
|
|||||||
|
|
||||||
{{#include ../links.md}}
|
{{#include ../links.md}}
|
||||||
|
|
||||||
Infinite loops follow C syntax.
|
Infinite loops follow Rust syntax.
|
||||||
|
|
||||||
Like C, `continue` can be used to skip to the next iteration, by-passing all following statements;
|
Like Rust, `continue` can be used to skip to the next iteration, by-passing all following statements;
|
||||||
`break` can be used to break out of the loop unconditionally.
|
`break` can be used to break out of the loop unconditionally.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
@ -11,13 +11,15 @@ 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;
|
|
||||||
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;
|
||||||
|
|
||||||
use crate::stdlib::{
|
use crate::stdlib::{
|
||||||
any::{type_name, TypeId},
|
any::{type_name, TypeId},
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
@ -85,9 +87,12 @@ pub const FN_GET: &str = "get$";
|
|||||||
pub const FN_SET: &str = "set$";
|
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$";
|
||||||
|
|
||||||
|
#[cfg(feature = "internals")]
|
||||||
pub const MARKER_EXPR: &str = "$expr$";
|
pub const MARKER_EXPR: &str = "$expr$";
|
||||||
pub const MARKER_STMT: &str = "$stmt$";
|
#[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$";
|
||||||
|
|
||||||
/// A type specifying the method of chaining.
|
/// A type specifying the method of chaining.
|
||||||
@ -279,6 +284,7 @@ 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.
|
||||||
@ -329,6 +335,8 @@ 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
|
||||||
@ -562,6 +570,8 @@ 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(|_| {}),
|
||||||
@ -1667,14 +1677,14 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluate an expression inside an AST.
|
/// Evaluate an expression tree.
|
||||||
///
|
///
|
||||||
/// ## 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")]
|
#[cfg(feature = "internals")]
|
||||||
#[deprecated(note = "this method is volatile and may change")]
|
#[deprecated(note = "this method is volatile and may change")]
|
||||||
pub fn eval_expr_from_ast(
|
pub fn eval_expression_tree(
|
||||||
&self,
|
&self,
|
||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
mods: &mut Imports,
|
mods: &mut Imports,
|
||||||
@ -2118,6 +2128,7 @@ 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 exprs = (x.0).0.as_ref();
|
let exprs = (x.0).0.as_ref();
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
//! 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::Module;
|
||||||
|
@ -91,6 +91,7 @@ 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;
|
||||||
|
@ -2,10 +2,13 @@ use crate::any::Dynamic;
|
|||||||
use crate::calc_fn_hash;
|
use crate::calc_fn_hash;
|
||||||
use crate::engine::{Engine, Imports, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_PRINT, KEYWORD_TYPE_OF};
|
use crate::engine::{Engine, Imports, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_PRINT, KEYWORD_TYPE_OF};
|
||||||
use crate::module::Module;
|
use crate::module::Module;
|
||||||
use crate::parser::{map_dynamic_to_expr, CustomExpr, Expr, ReturnType, ScriptFnDef, Stmt, AST};
|
use crate::parser::{map_dynamic_to_expr, Expr, ReturnType, ScriptFnDef, Stmt, AST};
|
||||||
use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope};
|
use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope};
|
||||||
use crate::utils::StaticVec;
|
use crate::utils::StaticVec;
|
||||||
|
|
||||||
|
#[cfg(feature = "internals")]
|
||||||
|
use crate::parser::CustomExpr;
|
||||||
|
|
||||||
use crate::stdlib::{
|
use crate::stdlib::{
|
||||||
boxed::Box,
|
boxed::Box,
|
||||||
iter::empty,
|
iter::empty,
|
||||||
@ -599,6 +602,7 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Custom syntax
|
// Custom syntax
|
||||||
|
#[cfg(feature = "internals")]
|
||||||
Expr::Custom(x) => Expr::Custom(Box::new((
|
Expr::Custom(x) => Expr::Custom(Box::new((
|
||||||
CustomExpr(
|
CustomExpr(
|
||||||
(x.0).0.into_iter().map(|expr| optimize_expr(expr, state)).collect(),
|
(x.0).0.into_iter().map(|expr| optimize_expr(expr, state)).collect(),
|
||||||
|
@ -2,19 +2,23 @@
|
|||||||
|
|
||||||
use crate::any::{Dynamic, Union};
|
use crate::any::{Dynamic, Union};
|
||||||
use crate::calc_fn_hash;
|
use crate::calc_fn_hash;
|
||||||
use crate::engine::{
|
use crate::engine::{make_getter, make_setter, Engine, KEYWORD_THIS};
|
||||||
make_getter, make_setter, Engine, KEYWORD_THIS, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT,
|
|
||||||
MARKER_STMT,
|
|
||||||
};
|
|
||||||
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::{Position, Token, TokenStream};
|
use crate::token::{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,
|
||||||
@ -574,8 +578,10 @@ 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)
|
||||||
@ -647,6 +653,7 @@ pub enum Expr {
|
|||||||
/// ()
|
/// ()
|
||||||
Unit(Position),
|
Unit(Position),
|
||||||
/// Custom syntax
|
/// Custom syntax
|
||||||
|
#[cfg(feature = "internals")]
|
||||||
Custom(Box<(CustomExpr, Position)>),
|
Custom(Box<(CustomExpr, Position)>),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -743,6 +750,7 @@ 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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -776,6 +784,8 @@ 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,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -881,6 +891,7 @@ impl Expr {
|
|||||||
_ => false,
|
_ => false,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
#[cfg(feature = "internals")]
|
||||||
Self::Custom(_) => false,
|
Self::Custom(_) => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -897,6 +908,14 @@ impl Expr {
|
|||||||
_ => self,
|
_ => self,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "internals")]
|
||||||
|
pub fn get_variable_name(&self) -> Option<&str> {
|
||||||
|
match self {
|
||||||
|
Self::Variable(x) => Some((x.0).0.as_str()),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Consume a particular token, checking that it is the expected one.
|
/// Consume a particular token, checking that it is the expected one.
|
||||||
@ -2046,6 +2065,7 @@ 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;
|
||||||
@ -2085,12 +2105,6 @@ fn parse_expr(
|
|||||||
(_, pos) => return Err(PERR::VariableExpected.into_err(pos)),
|
(_, pos) => return Err(PERR::VariableExpected.into_err(pos)),
|
||||||
},
|
},
|
||||||
MARKER_EXPR => exprs.push(parse_expr(input, state, lib, settings)?),
|
MARKER_EXPR => exprs.push(parse_expr(input, state, lib, settings)?),
|
||||||
MARKER_STMT => {
|
|
||||||
let stmt = parse_stmt(input, state, lib, settings)?
|
|
||||||
.unwrap_or_else(|| Stmt::Noop(settings.pos));
|
|
||||||
let pos = stmt.position();
|
|
||||||
exprs.push(Expr::Stmt(Box::new((stmt, pos))))
|
|
||||||
}
|
|
||||||
MARKER_BLOCK => {
|
MARKER_BLOCK => {
|
||||||
let stmt = parse_block(input, state, lib, settings)?;
|
let stmt = parse_block(input, state, lib, settings)?;
|
||||||
let pos = stmt.position();
|
let pos = stmt.position();
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
|
//! Module containing implementation for custom syntax.
|
||||||
|
#![cfg(feature = "internals")]
|
||||||
|
|
||||||
use crate::any::Dynamic;
|
use crate::any::Dynamic;
|
||||||
use crate::engine::{Engine, Imports, State, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT, MARKER_STMT};
|
use crate::engine::{Engine, Imports, State, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT};
|
||||||
use crate::error::LexError;
|
use crate::error::LexError;
|
||||||
use crate::fn_native::{SendSync, Shared};
|
use crate::fn_native::{SendSync, Shared};
|
||||||
use crate::module::Module;
|
use crate::module::Module;
|
||||||
@ -57,7 +60,7 @@ impl fmt::Debug for CustomSyntax {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Engine {
|
impl Engine {
|
||||||
pub fn add_custom_syntax<S: AsRef<str> + ToString>(
|
pub fn register_custom_syntax<S: AsRef<str> + ToString>(
|
||||||
&mut self,
|
&mut self,
|
||||||
value: &[S],
|
value: &[S],
|
||||||
scope_delta: isize,
|
scope_delta: isize,
|
||||||
@ -83,12 +86,28 @@ impl Engine {
|
|||||||
for s in value {
|
for s in value {
|
||||||
let seg = match s.as_ref() {
|
let seg = match s.as_ref() {
|
||||||
// Markers not in first position
|
// Markers not in first position
|
||||||
MARKER_EXPR | MARKER_STMT | MARKER_BLOCK | MARKER_IDENT if !segments.is_empty() => {
|
MARKER_EXPR | MARKER_BLOCK | MARKER_IDENT if !segments.is_empty() => s.to_string(),
|
||||||
s.to_string()
|
|
||||||
}
|
|
||||||
// Standard symbols not in first position
|
// Standard symbols not in first position
|
||||||
s if !segments.is_empty() && Token::lookup_from_syntax(s).is_some() => s.into(),
|
s if !segments.is_empty() && Token::lookup_from_syntax(s).is_some() => {
|
||||||
// Custom keyword
|
if self
|
||||||
|
.disabled_symbols
|
||||||
|
.as_ref()
|
||||||
|
.map(|d| d.contains(s))
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
|
// If symbol is disabled, make it a custom keyword
|
||||||
|
if self.custom_keywords.is_none() {
|
||||||
|
self.custom_keywords = Some(Default::default());
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.custom_keywords.as_ref().unwrap().contains_key(s) {
|
||||||
|
self.custom_keywords.as_mut().unwrap().insert(s.into(), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s.into()
|
||||||
|
}
|
||||||
|
// Identifier
|
||||||
s if is_valid_identifier(s.chars()) => {
|
s if is_valid_identifier(s.chars()) => {
|
||||||
if self.custom_keywords.is_none() {
|
if self.custom_keywords.is_none() {
|
||||||
self.custom_keywords = Some(Default::default());
|
self.custom_keywords = Some(Default::default());
|
||||||
|
@ -7,9 +7,16 @@ use rhai::{
|
|||||||
fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
|
fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
|
// Disable 'while' and make sure it still works with custom syntax
|
||||||
|
engine.disable_symbol("while");
|
||||||
|
engine.consume("while false {}").expect_err("should error");
|
||||||
|
engine.consume("let while = 0")?;
|
||||||
|
|
||||||
engine
|
engine
|
||||||
.add_custom_syntax(
|
.register_custom_syntax(
|
||||||
&["do", "$ident$", "$block$", "while", "$expr$"],
|
&[
|
||||||
|
"do", "|", "$ident$", "|", "->", "$block$", "while", "$expr$",
|
||||||
|
],
|
||||||
1,
|
1,
|
||||||
|engine: &Engine,
|
|engine: &Engine,
|
||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
@ -17,22 +24,19 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
|
|||||||
state: &mut EvalState,
|
state: &mut EvalState,
|
||||||
lib: &Module,
|
lib: &Module,
|
||||||
this_ptr: &mut Option<&mut Dynamic>,
|
this_ptr: &mut Option<&mut Dynamic>,
|
||||||
exprs: &[Expr],
|
inputs: &[Expr],
|
||||||
level: usize| {
|
level: usize| {
|
||||||
let var_name = match exprs.get(0).unwrap() {
|
let var_name = inputs[0].get_variable_name().unwrap().to_string();
|
||||||
Expr::Variable(s) => (s.0).0.clone(),
|
let stmt = inputs.get(1).unwrap();
|
||||||
_ => unreachable!(),
|
let expr = inputs.get(2).unwrap();
|
||||||
};
|
|
||||||
let stmt = exprs.get(1).unwrap();
|
|
||||||
let expr = exprs.get(2).unwrap();
|
|
||||||
|
|
||||||
scope.push(var_name, 0 as INT);
|
scope.push(var_name, 0 as INT);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
engine.eval_expr_from_ast(scope, mods, state, lib, this_ptr, stmt, level)?;
|
engine.eval_expression_tree(scope, mods, state, lib, this_ptr, stmt, level)?;
|
||||||
|
|
||||||
if !engine
|
if !engine
|
||||||
.eval_expr_from_ast(scope, mods, state, lib, this_ptr, expr, level)?
|
.eval_expression_tree(scope, mods, state, lib, this_ptr, expr, level)?
|
||||||
.as_bool()
|
.as_bool()
|
||||||
.map_err(|_| {
|
.map_err(|_| {
|
||||||
EvalAltResult::ErrorBooleanArgMismatch(
|
EvalAltResult::ErrorBooleanArgMismatch(
|
||||||
@ -50,20 +54,24 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert!(matches!(
|
// 'while' is now a custom keyword so this it can no longer be a variable
|
||||||
*engine.add_custom_syntax(&["!"], 0, |_, _, _, _, _, _, _, _| Ok(().into())).expect_err("should error"),
|
engine.consume("let while = 0").expect_err("should error");
|
||||||
LexError::ImproperSymbol(s) if s == "!"
|
|
||||||
));
|
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<INT>(
|
engine.eval::<INT>(
|
||||||
r"
|
r"
|
||||||
do x { x += 1 } while x < 42;
|
do |x| -> { x += 1 } while x < 42;
|
||||||
x
|
x
|
||||||
"
|
"
|
||||||
)?,
|
)?,
|
||||||
42
|
42
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// The first symbol must be an identifier
|
||||||
|
assert!(matches!(
|
||||||
|
*engine.register_custom_syntax(&["!"], 0, |_, _, _, _, _, _, _, _| Ok(().into())).expect_err("should error"),
|
||||||
|
LexError::ImproperSymbol(s) if s == "!"
|
||||||
|
));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user