diff --git a/CHANGELOG.md b/CHANGELOG.md index 22e95b089..ac48a220a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ Rhai Release Notes ================== -Version 1.18.1 +Version 1.19.0 ============== Bug fixes @@ -10,6 +10,11 @@ Bug fixes * Variable resolver now correctly resolves variables that are captured in a closure. * `NativeCallContext<'_>` (with a lifetime parameter) now parses correctly in the `#[export_module]` macro. This is to allow for `rust_2018_idioms` lints. +New features +------------ + +* A new symbol, `$func$`, is added to custom syntax to allow parsing of anonymous functions. + Version 1.18.0 ============== diff --git a/src/api/custom_syntax.rs b/src/api/custom_syntax.rs index c18e1c634..f1c101585 100644 --- a/src/api/custom_syntax.rs +++ b/src/api/custom_syntax.rs @@ -19,6 +19,9 @@ pub mod markers { pub const CUSTOM_SYNTAX_MARKER_EXPR: &str = "$expr$"; /// Special marker for matching a statements block. pub const CUSTOM_SYNTAX_MARKER_BLOCK: &str = "$block$"; + /// Special marker for matching a function body. + #[cfg(not(feature = "no_function"))] + pub const CUSTOM_SYNTAX_MARKER_FUNC: &str = "$func$"; /// Special marker for matching an identifier. pub const CUSTOM_SYNTAX_MARKER_IDENT: &str = "$ident$"; /// Special marker for matching a single symbol. @@ -251,6 +254,11 @@ impl Engine { { s.into() } + + // Markers not in first position + #[cfg(not(feature = "no_function"))] + CUSTOM_SYNTAX_MARKER_FUNC if !segments.is_empty() => s.into(), + // Markers not in first position #[cfg(not(feature = "no_float"))] CUSTOM_SYNTAX_MARKER_FLOAT if !segments.is_empty() => s.into(), diff --git a/src/eval/stmt.rs b/src/eval/stmt.rs index 22e42d277..c7886cc00 100644 --- a/src/eval/stmt.rs +++ b/src/eval/stmt.rs @@ -8,7 +8,6 @@ use crate::func::{get_builtin_op_assignment_fn, get_hasher}; use crate::tokenizer::Token; use crate::types::dynamic::{AccessMode, Union}; use crate::{Dynamic, Engine, RhaiResult, RhaiResultOf, Scope, VarDefInfo, ERR, INT}; -use core::num::NonZeroUsize; use std::hash::{Hash, Hasher}; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -978,8 +977,11 @@ impl Engine { let context = EvalContext::new(self, global, caches, scope, this_ptr.as_deref_mut()); - let resolved_var = - resolve_var(&var.name, index.map_or(0, NonZeroUsize::get), context); + let resolved_var = resolve_var( + &var.name, + index.map_or(0, core::num::NonZeroUsize::get), + context, + ); if orig_scope_len != scope.len() { // The scope is changed, always search from now on diff --git a/src/parser.rs b/src/parser.rs index 3ccd5531f..1a83f44e1 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1424,47 +1424,13 @@ impl Engine { } #[cfg(not(feature = "no_function"))] Token::Pipe | Token::Or if settings.has_option(LangOptions::ANON_FN) => { - // Build new parse state - let new_state = &mut ParseState::new( - state.external_constants, - state.input, - state.tokenizer_control.clone(), - state.lib, - ); - - #[cfg(not(feature = "no_module"))] - { - // Do not allow storing an index to a globally-imported module - // just in case the function is separated from this `AST`. - // - // Keep them in `global_imports` instead so that strict variables - // mode will not complain. - new_state.global_imports.clone_from(&state.global_imports); - new_state.global_imports.extend(state.imports.clone()); - } - - // Brand new options - #[cfg(not(feature = "no_closure"))] - let options = self.options & !LangOptions::STRICT_VAR; // a capturing closure can access variables not defined locally, so turn off Strict Variables mode - #[cfg(feature = "no_closure")] - let options = self.options | (settings.options & LangOptions::STRICT_VAR); - - // Brand new flags, turn on function scope and closure scope - let flags = ParseSettingFlags::FN_SCOPE - | ParseSettingFlags::CLOSURE_SCOPE - | (settings.flags - & (ParseSettingFlags::DISALLOW_UNQUOTED_MAP_PROPERTIES - | ParseSettingFlags::DISALLOW_STATEMENTS_IN_BLOCKS)); - - let new_settings = ParseSettings { - flags, - options, - ..settings - }; - - let result = self.parse_anon_fn(new_state, new_settings.level_up()?); - - let (expr, fn_def, _externals) = result?; + let (expr, fn_def, _externals) = self.parse_anon_fn( + state, + settings.level_up()?, + false, + #[cfg(not(feature = "no_closure"))] + true, + )?; #[cfg(not(feature = "no_closure"))] for Ident { name, pos } in &_externals { @@ -2556,6 +2522,33 @@ impl Engine { } stmt => unreachable!("Stmt::Block expected but gets {:?}", stmt), }, + #[cfg(not(feature = "no_function"))] + CUSTOM_SYNTAX_MARKER_FUNC => { + let skip = match fwd_token { + Token::Or | Token::Pipe => false, + Token::LeftBrace => true, + _ => { + return Err(PERR::MissingSymbol("Expecting '{' or '|'".into()) + .into_err(*fwd_pos)) + } + }; + + let (expr, fn_def, _) = self.parse_anon_fn( + state, + settings.level_up()?, + skip, + #[cfg(not(feature = "no_closure"))] + false, + )?; + + let hash_script = calc_fn_hash(None, &fn_def.name, fn_def.params.len()); + state.lib.insert(hash_script, fn_def); + + inputs.push(expr); + let keyword = self.get_interned_string(CUSTOM_SYNTAX_MARKER_FUNC); + segments.push(keyword.clone()); + tokens.push(keyword); + } CUSTOM_SYNTAX_MARKER_BOOL => match state.input.next().unwrap() { (b @ (Token::True | Token::False), pos) => { inputs.push(Expr::BoolConstant(b == Token::True, pos)); @@ -3717,11 +3710,54 @@ impl Engine { &self, state: &mut ParseState, settings: ParseSettings, + skip_parameters: bool, + #[cfg(not(feature = "no_closure"))] allow_capture: bool, ) -> ParseResult<(Expr, Shared, ThinVec)> { - let settings = settings.level_up()?; + // Build new parse state + let new_state = &mut ParseState::new( + state.external_constants, + state.input, + state.tokenizer_control.clone(), + state.lib, + ); + + #[cfg(not(feature = "no_module"))] + { + // Do not allow storing an index to a globally-imported module + // just in case the function is separated from this `AST`. + // + // Keep them in `global_imports` instead so that strict variables + // mode will not complain. + new_state.global_imports.clone_from(&state.global_imports); + new_state.global_imports.extend(state.imports.clone()); + } + + // Brand new options + #[cfg(not(feature = "no_closure"))] + let options = self.options & !LangOptions::STRICT_VAR; // a capturing closure can access variables not defined locally, so turn off Strict Variables mode + #[cfg(feature = "no_closure")] + let options = self.options | (settings.options & LangOptions::STRICT_VAR); + + // Brand new flags, turn on function scope and closure scope + let flags = ParseSettingFlags::FN_SCOPE + | ParseSettingFlags::CLOSURE_SCOPE + | (settings.flags + & (ParseSettingFlags::DISALLOW_UNQUOTED_MAP_PROPERTIES + | ParseSettingFlags::DISALLOW_STATEMENTS_IN_BLOCKS)); + + let new_settings = ParseSettings { + flags, + options, + ..settings + }; + let mut params_list = StaticVec::::new_const(); - if state.input.next().unwrap().0 != Token::Or && !match_token(state.input, &Token::Pipe).0 { + // Parse parameters + if !skip_parameters + && state.input.next().unwrap().0 != Token::Or + && !match_token(state.input, &Token::Pipe).0 + { loop { match state.input.next().unwrap() { (Token::Pipe, ..) => break, @@ -3762,18 +3798,20 @@ impl Engine { } // Parse function body - let body = self.parse_stmt(state, settings)?; + let body = self.parse_stmt(state, new_settings)?; // External variables may need to be processed in a consistent order, // so extract them into a list. #[cfg(not(feature = "no_closure"))] - let (mut params, externals) = { + let (mut params, externals) = if allow_capture { let externals = std::mem::take(&mut state.external_vars); let mut params = FnArgsVec::with_capacity(params_list.len() + externals.len()); params.extend(externals.iter().map(|Ident { name, .. }| name.clone())); (params, externals) + } else { + (FnArgsVec::with_capacity(params_list.len()), ThinVec::new()) }; #[cfg(feature = "no_closure")] let (mut params, externals) = (FnArgsVec::with_capacity(params_list.len()), ThinVec::new()); @@ -3807,7 +3845,7 @@ impl Engine { #[cfg(not(feature = "no_function"))] fn_def: Some(script.clone()), }; - let expr = Expr::DynamicConstant(Box::new(fn_ptr.into()), settings.pos); + let expr = Expr::DynamicConstant(Box::new(fn_ptr.into()), new_settings.pos); Ok((expr, script, externals)) } diff --git a/tests/custom_syntax.rs b/tests/custom_syntax.rs index 42fc637ca..dba3fe7e6 100644 --- a/tests/custom_syntax.rs +++ b/tests/custom_syntax.rs @@ -272,6 +272,19 @@ fn test_custom_syntax_scope() { ); } +#[cfg(not(feature = "no_function"))] +#[test] +fn test_custom_syntax_func() { + let mut engine = Engine::new(); + + engine + .register_custom_syntax(["hello", "$func$"], false, |context, inputs| context.eval_expression_tree(&inputs[0])) + .unwrap(); + + assert_eq!(engine.eval::("(hello |x| { x + 1 }).call(41)").unwrap(), 42); + assert_eq!(engine.eval::("(hello { 42 }).call()").unwrap(), 42); +} + #[test] fn test_custom_syntax_matrix() { let mut engine = Engine::new();