No need to specify number of variables added/removed for custom syntax.

This commit is contained in:
Stephen Chung 2021-05-11 10:58:28 +08:00
parent 3a47ed7c46
commit 97c8194d17
6 changed files with 56 additions and 41 deletions

View File

@ -8,6 +8,7 @@ Breaking changes
---------------- ----------------
* `Engine::disable_doc_comments` is removed because doc-comments are now placed under the `metadata` feature flag. * `Engine::disable_doc_comments` is removed because doc-comments are now placed under the `metadata` feature flag.
* Registering a custom syntax now only requires specifying whether the `Scope` is adjusted (i.e. whether variables are added or removed). There is no need to specify the number of variables added/removed.
New features New features
------------ ------------
@ -16,6 +17,11 @@ New features
* A new internal feature `no_smartstring` to turn off `SmartString` for those rare cases that it is needed. * A new internal feature `no_smartstring` to turn off `SmartString` for those rare cases that it is needed.
* `DynamicReadLock` and `DynamicWriteLoc` are exposed under `internals`. * `DynamicReadLock` and `DynamicWriteLoc` are exposed under `internals`.
Enhancements
------------
* Registering a custom syntax now only requires specifying whether the `Scope` is adjusted (i.e. whether variables are added or removed). This allows more flexibility for cases where the number of new variables declared depends on internal logic.
Version 0.20.1 Version 0.20.1
============== ==============

View File

@ -1357,8 +1357,8 @@ impl Stmt {
pub struct CustomExpr { pub struct CustomExpr {
/// List of keywords. /// List of keywords.
pub keywords: StaticVec<Expr>, pub keywords: StaticVec<Expr>,
/// Delta number of variables in the scope. /// Is the current [`Scope`][crate::Scope] modified?
pub scope_delta: isize, pub scope_changed: bool,
/// List of tokens actually parsed. /// List of tokens actually parsed.
pub tokens: StaticVec<Identifier>, pub tokens: StaticVec<Identifier>,
} }

View File

@ -1024,7 +1024,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) {
// Custom syntax // Custom syntax
Expr::Custom(x, _) => { Expr::Custom(x, _) => {
if x.scope_delta != 0 { if x.scope_changed {
state.propagate_constants = false; state.propagate_constants = false;
} }
x.keywords.iter_mut().for_each(|expr| optimize_expr(expr, state)); x.keywords.iter_mut().for_each(|expr| optimize_expr(expr, state));

View File

@ -1848,22 +1848,12 @@ fn parse_custom_syntax(
let mut tokens: StaticVec<_> = Default::default(); let mut tokens: StaticVec<_> = Default::default();
// Adjust the variables stack // Adjust the variables stack
match syntax.scope_delta { if syntax.scope_changed {
delta if delta > 0 => { // Add an empty variable name to the stack.
// Add enough empty variable names to the stack.
// Empty variable names act as a barrier so earlier variables will not be matched. // Empty variable names act as a barrier so earlier variables will not be matched.
// Variable searches stop at the first empty variable name. // Variable searches stop at the first empty variable name.
let empty = state.get_identifier(""); let empty = state.get_identifier("");
state.stack.resize( state.stack.push((empty, AccessMode::ReadWrite));
state.stack.len() + delta as usize,
(empty, AccessMode::ReadWrite),
);
}
delta if delta < 0 && state.stack.len() <= delta.abs() as usize => state.stack.clear(),
delta if delta < 0 => state
.stack
.truncate(state.stack.len() - delta.abs() as usize),
_ => (),
} }
let parse_func = &syntax.parse; let parse_func = &syntax.parse;
@ -1936,7 +1926,7 @@ fn parse_custom_syntax(
Box::new(CustomExpr { Box::new(CustomExpr {
keywords, keywords,
tokens, tokens,
scope_delta: syntax.scope_delta, scope_changed: syntax.scope_changed,
}), }),
pos, pos,
)) ))

View File

@ -87,28 +87,33 @@ pub struct CustomSyntax {
pub parse: Box<FnCustomSyntaxParse>, pub parse: Box<FnCustomSyntaxParse>,
/// Custom syntax implementation function. /// Custom syntax implementation function.
pub func: Shared<FnCustomSyntaxEval>, pub func: Shared<FnCustomSyntaxEval>,
/// Delta number of variables in the scope. /// Any variables added/removed in the scope?
pub scope_delta: isize, pub scope_changed: bool,
} }
impl Engine { impl Engine {
/// Register a custom syntax with the [`Engine`]. /// Register a custom syntax with the [`Engine`].
/// ///
/// * `keywords` holds a slice of strings that define the custom syntax. /// * `keywords` holds a slice of strings that define the custom syntax.
/// * `new_vars` is the number of new variables declared by this custom syntax, or the number of variables removed (if negative). /// * `scope_changed` specifies variables have been added/removed by this custom syntax.
/// * `func` is the implementation function. /// * `func` is the implementation function.
/// ///
/// # Notes /// # Caveat - Do not change beyond block scope
/// ///
/// If `new_vars` is positive, then a number of new variables are expected to be pushed into the /// If `scope_changed` is `true`, then the current [`Scope`][crate::Scope] is assumed to be
/// current [`Scope`][crate::Scope]. /// modified by this custom syntax.
/// ///
/// If `new_vars` is negative, then it is expected that only the top `new_vars` variables in the /// Adding new variables and/or removing variables are allowed. Simply modifying the values of
/// current [`Scope`][crate::Scope] will be _popped_. Do not randomly remove variables. /// variables does NOT count, so `false` should be passed in this case.
///
/// However, only variables declared within the current _block scope_ should be touched,
/// since they all go away at the end of the block.
///
/// Variables in parent blocks should be left untouched as they persist beyond the current block.
pub fn register_custom_syntax<S: AsRef<str> + Into<Identifier>>( pub fn register_custom_syntax<S: AsRef<str> + Into<Identifier>>(
&mut self, &mut self,
keywords: &[S], keywords: &[S],
new_vars: isize, scope_changed: bool,
func: impl Fn(&mut EvalContext, &[Expression]) -> RhaiResult + SendSync + 'static, func: impl Fn(&mut EvalContext, &[Expression]) -> RhaiResult + SendSync + 'static,
) -> Result<&mut Self, ParseError> { ) -> Result<&mut Self, ParseError> {
let keywords = keywords.as_ref(); let keywords = keywords.as_ref();
@ -203,7 +208,7 @@ impl Engine {
Ok(Some(segments[stream.len()].clone())) Ok(Some(segments[stream.len()].clone()))
} }
}, },
new_vars, scope_changed,
func, func,
); );
@ -215,7 +220,7 @@ impl Engine {
/// ///
/// This function is very low level. /// This function is very low level.
/// ///
/// * `new_vars` is the number of new variables declared by this custom syntax, or the number of variables removed (if negative). /// * `scope_changed` specifies variables have been added/removed by this custom syntax.
/// * `parse` is the parsing function. /// * `parse` is the parsing function.
/// * `func` is the implementation function. /// * `func` is the implementation function.
/// ///
@ -227,7 +232,7 @@ impl Engine {
parse: impl Fn(&[ImmutableString], &str) -> Result<Option<ImmutableString>, ParseError> parse: impl Fn(&[ImmutableString], &str) -> Result<Option<ImmutableString>, ParseError>
+ SendSync + SendSync
+ 'static, + 'static,
new_vars: isize, scope_changed: bool,
func: impl Fn(&mut EvalContext, &[Expression]) -> RhaiResult + SendSync + 'static, func: impl Fn(&mut EvalContext, &[Expression]) -> RhaiResult + SendSync + 'static,
) -> &mut Self { ) -> &mut Self {
self.custom_syntax.insert( self.custom_syntax.insert(
@ -235,7 +240,7 @@ impl Engine {
Box::new(CustomSyntax { Box::new(CustomSyntax {
parse: Box::new(parse), parse: Box::new(parse),
func: (Box::new(func) as Box<FnCustomSyntaxEval>).into(), func: (Box::new(func) as Box<FnCustomSyntaxEval>).into(),
scope_delta: new_vars, scope_changed,
}), }),
); );
self self

View File

@ -19,15 +19,15 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
engine.register_custom_syntax( engine.register_custom_syntax(
&[ &[
"exec", "|", "$ident$", "|", "->", "$block$", "while", "$expr$", "exec", "[", "$ident$", "]", "->", "$block$", "while", "$expr$",
], ],
1, true,
|context, inputs| { |context, inputs| {
let var_name = inputs[0].get_variable_name().unwrap().to_string(); let var_name = inputs[0].get_variable_name().unwrap().to_string();
let stmt = inputs.get(1).unwrap(); let stmt = inputs.get(1).unwrap();
let condition = inputs.get(2).unwrap(); let condition = inputs.get(2).unwrap();
context.scope_mut().push(var_name, 0 as INT); context.scope_mut().push(var_name.clone(), 0 as INT);
let mut count: INT = 0; let mut count: INT = 0;
@ -35,6 +35,10 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
context.eval_expression_tree(stmt)?; context.eval_expression_tree(stmt)?;
count += 1; count += 1;
context
.scope_mut()
.push(format!("{}{}", var_name, count), count);
let stop = !context let stop = !context
.eval_expression_tree(condition)? .eval_expression_tree(condition)?
.as_bool() .as_bool()
@ -59,7 +63,7 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
engine.eval::<INT>( engine.eval::<INT>(
" "
let x = 0; let x = 0;
let foo = (exec |x| -> { x += 2 } while x < 42) * 10; let foo = (exec [x] -> { x += 2 } while x < 42) * 10;
foo foo
" "
)?, )?,
@ -69,7 +73,7 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
engine.eval::<INT>( engine.eval::<INT>(
" "
let x = 0; let x = 0;
exec |x| -> { x += 1 } while x < 42; exec [x] -> { x += 1 } while x < 42;
x x
" "
)?, )?,
@ -78,17 +82,27 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
assert_eq!( assert_eq!(
engine.eval::<INT>( engine.eval::<INT>(
" "
exec |x| -> { x += 1 } while x < 42; exec [x] -> { x += 1 } while x < 42;
x x
" "
)?, )?,
42 42
); );
assert_eq!(
engine.eval::<INT>(
"
let foo = 123;
exec [x] -> { x += 1 } while x < 42;
foo + x + x1 + x2 + x3
"
)?,
171
);
// The first symbol must be an identifier // The first symbol must be an identifier
assert_eq!( assert_eq!(
*engine *engine
.register_custom_syntax(&["!"], 0, |_, _| Ok(Dynamic::UNIT)) .register_custom_syntax(&["!"], false, |_, _| Ok(Dynamic::UNIT))
.expect_err("should error") .expect_err("should error")
.0, .0,
ParseErrorType::BadInput(LexError::ImproperSymbol( ParseErrorType::BadInput(LexError::ImproperSymbol(
@ -121,7 +135,7 @@ fn test_custom_syntax_raw() -> Result<(), Box<EvalAltResult>> {
}, },
_ => unreachable!(), _ => unreachable!(),
}, },
1, true,
|context, inputs| { |context, inputs| {
context.scope_mut().push("foo", 999 as INT); context.scope_mut().push("foo", 999 as INT);