Allow self-terminating custom syntax.

This commit is contained in:
Stephen Chung 2021-08-02 10:24:03 +08:00
parent 3127f9a8af
commit e0125a1033
4 changed files with 43 additions and 4 deletions

View File

@ -8,6 +8,7 @@ Enhancements
------------
* `$symbol$` is supported in custom syntax to match any symbol.
* Custom syntax with `$block$`, `}` or `;` as the last symbol are now self-terminating (i.e. no need to attach a terminating `;`).
* `Dynamic::as_string` and `Dynamic::as_immutable_string` are deprecated and replaced by `into_string` and `into_immutable_string` respectively.
* Added a number of constants to `Dynamic`.
* Added a number of constants and `fromXXX` constant methods to `Dynamic`.

View File

@ -1263,10 +1263,12 @@ impl Stmt {
// A No-op requires a semicolon in order to know it is an empty statement!
Self::Noop(_) => false,
Self::Expr(Expr::Custom(x, _)) if x.is_self_terminated() => true,
Self::Var(_, _, _, _)
| Self::Assignment(_, _)
| Self::FnCall(_, _)
| Self::Expr(_)
| Self::FnCall(_, _)
| Self::Do(_, _, _, _)
| Self::Continue(_)
| Self::Break(_)
@ -1509,6 +1511,19 @@ pub struct CustomExpr {
pub scope_may_be_changed: bool,
/// List of tokens actually parsed.
pub tokens: StaticVec<Identifier>,
/// Is this custom syntax self-terminated?
pub self_terminated: bool,
}
impl CustomExpr {
/// Is this custom syntax self-terminated (i.e. no need for a semicolon terminator)?
///
/// A self-terminated custom syntax always ends in `$block$`, `}` or `;`
#[must_use]
#[inline(always)]
pub const fn is_self_terminated(&self) -> bool {
self.self_terminated
}
}
/// _(internals)_ A binary expression.

View File

@ -2004,16 +2004,17 @@ fn parse_custom_syntax(
}
let parse_func = syntax.parse.as_ref();
let mut required_token: ImmutableString = key.into();
segments.push(key.into());
tokens.push(key.into());
tokens.push(required_token.clone().into());
segments.push(required_token.clone());
loop {
let (fwd_token, fwd_pos) = input.peek().expect(NEVER_ENDS);
settings.pos = *fwd_pos;
let settings = settings.level_up();
let required_token = match parse_func(&segments, fwd_token.syntax().as_ref()) {
required_token = match parse_func(&segments, fwd_token.syntax().as_ref()) {
Ok(Some(seg)) => seg,
Ok(None) => break,
Err(err) => return Err(err.0.into_err(settings.pos)),
@ -2120,11 +2121,23 @@ fn parse_custom_syntax(
keywords.shrink_to_fit();
tokens.shrink_to_fit();
const KEYWORD_SEMICOLON: &str = Token::SemiColon.literal_syntax();
const KEYWORD_CLOSE_BRACE: &str = Token::RightBrace.literal_syntax();
let self_terminated = match required_token.as_str() {
// It is self-terminating if the last symbol is a block
CUSTOM_SYNTAX_MARKER_BLOCK => true,
// If the last symbol is `;` or `}`, it is self-terminating
KEYWORD_SEMICOLON | KEYWORD_CLOSE_BRACE => true,
_ => false,
};
Ok(Expr::Custom(
CustomExpr {
keywords,
tokens,
scope_may_be_changed: syntax.scope_may_be_changed,
self_terminated,
}
.into(),
pos,

View File

@ -136,6 +136,16 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
))
);
// Check self-termination
engine
.register_custom_syntax(&["test1", "$block$"], true, |_, _| Ok(Dynamic::UNIT))?
.register_custom_syntax(&["test2", "}"], true, |_, _| Ok(Dynamic::UNIT))?
.register_custom_syntax(&["test3", ";"], true, |_, _| Ok(Dynamic::UNIT))?;
assert_eq!(engine.eval::<INT>("test1 { x = y + z; } 42")?, 42);
assert_eq!(engine.eval::<INT>("test2 } 42")?, 42);
assert_eq!(engine.eval::<INT>("test3; 42")?, 42);
Ok(())
}