Add raw API for custom syntax.

This commit is contained in:
Stephen Chung
2020-10-25 21:57:18 +08:00
parent f670d55871
commit b607a3a9ba
11 changed files with 298 additions and 176 deletions

View File

@@ -54,16 +54,14 @@ These symbol types can be used:
* `$ident$` - any [variable] name.
### The First Symbol Must be a Keyword
### The First Symbol Must be an Identifier
There is no specific limit on the combination and sequencing of each symbol type,
except the _first_ symbol which must be a custom keyword that follows the naming rules
of [variables].
The first symbol also cannot be a reserved [keyword], unless that keyword
has been [disabled][disable keywords and operators].
In other words, any valid identifier that is not an active [keyword] will work fine.
The first symbol also cannot be a normal or reserved [keyword].
In other words, any valid identifier that is not a [keyword] will work fine.
### The First Symbol Must be Unique
@@ -127,6 +125,8 @@ where:
* `inputs: &[Expression]` - a list of input expression trees.
* Return value - result of evaluating the custom syntax expression.
### Access Arguments
The most important argument is `inputs` where the matched identifiers (`$ident$`), expressions/statements (`$expr$`)
@@ -215,9 +215,9 @@ fn implementation_func(
Ok(().into())
}
// Register the custom syntax (sample): do |x| -> { x += 1 } while x < 0;
// Register the custom syntax (sample): exec |x| -> { x += 1 } while x < 0;
engine.register_custom_syntax(
&[ "do", "|", "$ident$", "|", "->", "$block$", "while", "$expr$" ], // the custom syntax
&[ "exec", "|", "$ident$", "|", "->", "$block$", "while", "$expr$" ], // the custom syntax
1, // the number of new variables declared within this custom syntax
implementation_func
)?;
@@ -252,3 +252,70 @@ Make sure there are _lots_ of examples for users to follow.
Step Six - Profit!
------------------
Really Advanced - Low Level Custom Syntax API
--------------------------------------------
Sometimes it is desirable to have multiple custom syntax starting with the
same symbol. This is especially common for _command-style_ syntax where the
second symbol calls a particular command:
```rust
// The following simulates a command-style syntax, all starting with 'perform'.
perform action 42; // Perform a system action with a parameter
perform update system; // Update the system
perform check all; // Check all system settings
perform cleanup; // Clean up the system
perform add something; // Add something to the system
perform remove something; // Delete something from the system
```
For even more flexibility, there is a _low level_ API for custom syntax that
allows the registration of an entire mini-parser.
Use `Engine::register_custom_syntax_raw` to register a custom syntax _parser_
together with the implementation function:
```rust
// Register the custom syntax (sample): exec |x| -> { x += 1 } while x < 0;
engine.register_custom_syntax_raw(
"exec",
|stream| match stream.len() {
1 => Ok(Some("|".into())),
2 => Ok(Some("$ident$".into())),
3 => Ok(Some("|".into())),
4 => Ok(Some("->".into())),
5 => Ok(Some("$block$".into())),
6 => Ok(Some("while".into())),
7 => Ok(Some("$expr$".into())),
8 => Ok(None)
_ => unreachable!(),
}
1, // the number of new variables declared within this custom syntax
implementation_func // implementation function as above
)?;
```
### Function Signature
The custom syntax parser has the following signature:
> `Fn(stream: &[&String]) -> Result<Option<ImmutableString>, ParseError>`
where:
* `stream: &[&String]` - a slice of symbols that have been parsed so far, perhaps containing the following:
* `$expr$` - an expression
* `$block$` - a statement block
### Return Value
The return value is `Result<Option<ImmutableString>, ParseError>` where:
* `Ok(None)` - parsing complete and there are no more symbols to match.
* `Ok(Some(symbol))` - next symbol to match.
* `Err(ParseError)` - error is reflected back to the [`Engine`].
Normally this is `ParseErrorType::ImproperSymbol` to indicate that there is a syntax error, but it can be any error.

View File

@@ -16,7 +16,7 @@ To do so, provide a closure to the [`Engine`] via the `Engine::on_var` method:
let mut engine = Engine::new();
// Register a variable resolver.
engine.on_var(|name, index, scope, context| {
engine.on_var(|name, index, context| {
match name {
"MYSTIC_NUMBER" => Ok(Some((42 as INT).into())),
// Override a variable - make it not found even if it exists!
@@ -24,7 +24,7 @@ engine.on_var(|name, index, scope, context| {
EvalAltResult::ErrorVariableNotFound(name.to_string(), Position::none())
)),
// Silently maps 'chameleon' into 'innocent'.
"chameleon" => scope.get_value("innocent").map(Some).ok_or_else(|| Box::new(
"chameleon" => context.scope.get_value("innocent").map(Some).ok_or_else(|| Box::new(
EvalAltResult::ErrorVariableNotFound(name.to_string(), Position::none())
)),
// Return Ok(None) to continue with the normal variable resolution process.