From e0125a1033ac981cd3bbe0693d781f35425cb16e Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 2 Aug 2021 10:24:03 +0800 Subject: [PATCH] Allow self-terminating custom syntax. --- CHANGELOG.md | 1 + src/ast.rs | 17 ++++++++++++++++- src/parse.rs | 19 ++++++++++++++++--- tests/custom_syntax.rs | 10 ++++++++++ 4 files changed, 43 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f02a2c9a..9f75f681 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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`. diff --git a/src/ast.rs b/src/ast.rs index 4eb51428..968f7ac7 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -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, + /// 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. diff --git a/src/parse.rs b/src/parse.rs index b5bbe171..b0656b44 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -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, diff --git a/tests/custom_syntax.rs b/tests/custom_syntax.rs index f10422ed..0d130be4 100644 --- a/tests/custom_syntax.rs +++ b/tests/custom_syntax.rs @@ -136,6 +136,16 @@ fn test_custom_syntax() -> Result<(), Box> { )) ); + // 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::("test1 { x = y + z; } 42")?, 42); + assert_eq!(engine.eval::("test2 } 42")?, 42); + assert_eq!(engine.eval::("test3; 42")?, 42); + Ok(()) }