Custom syntax parser function takes &[String].

This commit is contained in:
Stephen Chung 2020-10-26 19:46:58 +08:00
parent b467b18722
commit 7496c77ac9
4 changed files with 62 additions and 37 deletions

View File

@ -5,14 +5,22 @@ Rhai Release Notes
Version 0.19.4 Version 0.19.4
============== ==============
This version adds a low-level API for more flexibility when defining custom syntax.
Bug fixes Bug fixes
--------- ---------
* Fixes `Send + Sync` for `EvalAltResult` under the `sync` feature. Bug introduced with `0.19.3`. * Fixes `Send + Sync` for `EvalAltResult` under the `sync` feature. Bug introduced with `0.19.3`.
Breaking changes
----------------
* Custom syntax can no longer start with a keyword (even a _reserved_ one), even if it has been disabled. That is to avoid breaking scripts later when the keyword is no longer disabled.
New features New features
------------ ------------
* Low-level API for custom syntax allowing more flexibility in designing the syntax.
* `Module::fill_with` to poly-fill a module with another. * `Module::fill_with` to poly-fill a module with another.

View File

@ -263,6 +263,7 @@ second symbol calls a particular command:
```rust ```rust
// The following simulates a command-style syntax, all starting with 'perform'. // The following simulates a command-style syntax, all starting with 'perform'.
perform hello world; // A fixed sequence of symbols
perform action 42; // Perform a system action with a parameter perform action 42; // Perform a system action with a parameter
perform update system; // Update the system perform update system; // Update the system
perform check all; // Check all system settings perform check all; // Check all system settings
@ -278,34 +279,52 @@ Use `Engine::register_custom_syntax_raw` to register a custom syntax _parser_
together with the implementation function: together with the implementation function:
```rust ```rust
// Register the custom syntax (sample): exec |x| -> { x += 1 } while x < 0;
engine.register_custom_syntax_raw( engine.register_custom_syntax_raw(
"exec", "perform",
|stream| match stream.len() { |stream| match stream.len() {
1 => Ok(Some("|".into())), // perform ...
2 => Ok(Some("$ident$".into())), 1 => Ok(Some("$ident$".into())),
3 => Ok(Some("|".into())), // perform command ...
4 => Ok(Some("->".into())), 2 => match stream[1].as_str() {
5 => Ok(Some("$block$".into())), "action" => Ok(Some("$expr$".into())),
6 => Ok(Some("while".into())), "hello" => Ok(Some("world".into())),
7 => Ok(Some("$expr$".into())), "update" | "check" | "add" | "remove" => Ok(Some("$ident$".into())),
8 => Ok(None) "cleanup" => Ok(None),
cmd => Err(ParseError(Box::new(ParseErrorType::BadInput(
format!("Improper command: {}", cmd))),
Position::none(),
)),
},
// perform command arg ...
3 => match (stream[1].as_str(), stream[2].as_str()) {
("action", _) => Ok(None),
("hello", "world") => Ok(None),
("update", arg) if arg == "system" => Ok(None),
("update", arg) if arg == "client" => Ok(None),
("check", arg) => Ok(None),
("add", arg) => Ok(None),
("remove", arg) => Ok(None),
(cmd, arg) => Err(ParseError(Box::new(ParseErrorType::BadInput(
format!("Invalid argument for command {}: {}", cmd, arg))),
Position::none(),
)),
},
_ => unreachable!(), _ => unreachable!(),
} },
1, // the number of new variables declared within this custom syntax 0, // the number of new variables declared within this custom syntax
implementation_func // implementation function as above implementation_func
)?; );
``` ```
### Function Signature ### Function Signature
The custom syntax parser has the following signature: The custom syntax parser has the following signature:
> `Fn(stream: &[&String]) -> Result<Option<ImmutableString>, ParseError>` > `Fn(stream: &[String]) -> Result<Option<ImmutableString>, ParseError>`
where: where:
* `stream: &[&String]` - a slice of symbols that have been parsed so far, perhaps containing the following: * `stream: &[String]` - a slice of symbols that have been parsed so far, perhaps containing the following:
* `$expr$` - an expression * `$expr$` - an expression
* `$block$` - a statement block * `$block$` - a statement block
@ -318,4 +337,4 @@ The return value is `Result<Option<ImmutableString>, ParseError>` where:
* `Ok(Some(symbol))` - next symbol to match. * `Ok(Some(symbol))` - next symbol to match.
* `Err(ParseError)` - error is reflected back to the [`Engine`]. * `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. Normally this is `ParseErrorType::BadInput` to indicate that there is a syntax error, but it can be any error.

View File

@ -2681,7 +2681,7 @@ fn parse_custom_syntax(
state: &mut ParseState, state: &mut ParseState,
lib: &mut FunctionsLib, lib: &mut FunctionsLib,
mut settings: ParseSettings, mut settings: ParseSettings,
key: String, key: &str,
syntax: &CustomSyntax, syntax: &CustomSyntax,
pos: Position, pos: Position,
) -> Result<Expr, ParseError> { ) -> Result<Expr, ParseError> {
@ -2703,25 +2703,24 @@ fn parse_custom_syntax(
} }
let mut segments: StaticVec<_> = Default::default(); let mut segments: StaticVec<_> = Default::default();
segments.push(key); segments.push(key.to_string());
loop { loop {
settings.pos = input.peek().unwrap().1; settings.pos = input.peek().unwrap().1;
let token = if let Some(seg) = (syntax.parse)(&segments.iter().collect::<StaticVec<_>>())
.map_err(|err| err.0.into_err(settings.pos))?
{
seg
} else {
break;
};
let settings = settings.level_up(); let settings = settings.level_up();
let parse_func = &syntax.parse;
let token =
if let Some(seg) = parse_func(&segments).map_err(|err| err.0.into_err(settings.pos))? {
seg
} else {
break;
};
match token.as_str() { match token.as_str() {
MARKER_IDENT => match input.next().unwrap() { MARKER_IDENT => match input.next().unwrap() {
(Token::Identifier(s), pos) => { (Token::Identifier(s), pos) => {
segments.push(s.to_string()); segments.push(s.clone());
exprs.push(Expr::Variable(Box::new(((s, pos), None, 0, None)))); exprs.push(Expr::Variable(Box::new(((s, pos), None, 0, None))));
} }
(Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => { (Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => {
@ -2731,18 +2730,18 @@ fn parse_custom_syntax(
}, },
MARKER_EXPR => { MARKER_EXPR => {
exprs.push(parse_expr(input, state, lib, settings)?); exprs.push(parse_expr(input, state, lib, settings)?);
segments.push(MARKER_EXPR.to_string()); segments.push(MARKER_EXPR.into());
} }
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();
exprs.push(Expr::Stmt(Box::new((stmt, pos)))); exprs.push(Expr::Stmt(Box::new((stmt, pos))));
segments.push(MARKER_BLOCK.to_string()); segments.push(MARKER_BLOCK.into());
} }
s => match input.peek().unwrap() { s => match input.peek().unwrap() {
(Token::LexError(err), pos) => return Err(err.into_err(*pos)), (Token::LexError(err), pos) => return Err(err.into_err(*pos)),
(t, _) if t.syntax().as_ref() == s => { (t, _) if t.syntax().as_ref() == s => {
segments.push(input.next().unwrap().0.syntax().into_owned()); segments.push(t.syntax().into_owned());
} }
(_, pos) => { (_, pos) => {
return Err(PERR::MissingToken( return Err(PERR::MissingToken(
@ -2782,7 +2781,6 @@ fn parse_expr(
Token::Custom(key) | Token::Reserved(key) | Token::Identifier(key) => { Token::Custom(key) | Token::Reserved(key) | Token::Identifier(key) => {
match state.engine.custom_syntax.get_key_value(key) { match state.engine.custom_syntax.get_key_value(key) {
Some((key, syntax)) => { Some((key, syntax)) => {
let key = key.to_string();
input.next().unwrap(); input.next().unwrap();
return parse_custom_syntax( return parse_custom_syntax(
input, state, lib, settings, key, syntax, token_pos, input, state, lib, settings, key, syntax, token_pos,

View File

@ -27,11 +27,11 @@ pub type FnCustomSyntaxEval =
/// A general expression parsing trait object. /// A general expression parsing trait object.
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
pub type FnCustomSyntaxParse = dyn Fn(&[&String]) -> Result<Option<ImmutableString>, ParseError>; pub type FnCustomSyntaxParse = dyn Fn(&[String]) -> Result<Option<ImmutableString>, ParseError>;
/// A general expression parsing trait object. /// A general expression parsing trait object.
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
pub type FnCustomSyntaxParse = pub type FnCustomSyntaxParse =
dyn Fn(&[&String]) -> Result<Option<ImmutableString>, ParseError> + Send + Sync; dyn Fn(&[String]) -> Result<Option<ImmutableString>, ParseError> + Send + Sync;
/// An expression sub-tree in an AST. /// An expression sub-tree in an AST.
#[derive(Debug, Clone, Hash)] #[derive(Debug, Clone, Hash)]
@ -204,7 +204,7 @@ impl Engine {
pub fn register_custom_syntax_raw( pub fn register_custom_syntax_raw(
&mut self, &mut self,
key: impl Into<ImmutableString>, key: impl Into<ImmutableString>,
parse: impl Fn(&[&String]) -> Result<Option<ImmutableString>, ParseError> + SendSync + 'static, parse: impl Fn(&[String]) -> Result<Option<ImmutableString>, ParseError> + SendSync + 'static,
new_vars: isize, new_vars: isize,
func: impl Fn(&mut EvalContext, &[Expression]) -> Result<Dynamic, Box<EvalAltResult>> func: impl Fn(&mut EvalContext, &[Expression]) -> Result<Dynamic, Box<EvalAltResult>>
+ SendSync + SendSync