Enable custom syntax without internals.

This commit is contained in:
Stephen Chung
2020-07-22 17:05:13 +08:00
parent 35374f5b3b
commit abf66850f6
8 changed files with 98 additions and 131 deletions

View File

@@ -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.
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
Step One - Design The Syntax
---------------------------
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.
@@ -164,8 +153,6 @@ let expr = inputs.get(0).unwrap();
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
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 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.
@@ -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.
@@ -254,13 +241,13 @@ custom syntax (plus possibly expressions). But again, Don't Do It™ - unless y
of what you're doing.
Step Six - Document
-------------------
Step Five - Document
--------------------
For custom syntax, documentation is crucial.
Make sure there are _lots_ of examples for users to follow.
Step Seven - Profit!
--------------------
Step Six - Profit!
------------------

View File

@@ -11,7 +11,7 @@ Expressions Only
In many DSL scenarios, only evaluation of expressions is needed.
The `Engine::eval_expression_XXX`[`eval_expression`] API can be used to restrict
The [`Engine::eval_expression_XXX`][`eval_expression`] API can be used to restrict
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
language features that are not necessary or dangerous to the application.
For example, a DSL may disable the `while` loop altogether while keeping all other statement
types intact.
For example, a DSL may disable the `while` loop while keeping all other statement types intact.
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] -
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
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;
// 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$
// 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$
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
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
`exprs[1] = "price"` - field name
`exprs[2] = Expression(table)` - data source
`exprs[3] = "row"` - loop variable name
`exprs[4] = Expression(row.wright > 50)` - expression
* `inputs[0] = "sum"` - math operator
* `inputs[1] = "price"` - field name
* `inputs[2] = Expression(table)` - data source
* `inputs[3] = "row"` - loop variable name
* `inputs[4] = Expression(row.wright > 50)` - filter predicate
The other identified, such as `"select"`, `"from"`, as as as symbols `->` and `:` are
parsed in the order defined within the custom syntax.
Other identifiers, such as `"calculate"`, `"from"`, as well as symbols such as `->` and `:`,
are parsed in the order defined within the custom syntax.