diff --git a/src/optimize.rs b/src/optimize.rs index b46b7488..e26d1908 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -5,10 +5,10 @@ use crate::calc_fn_hash; use crate::engine::{ Engine, Imports, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT, KEYWORD_TYPE_OF, }; +use crate::fn_native::FnPtr; use crate::module::Module; use crate::parser::{map_dynamic_to_expr, Expr, ScriptFnDef, Stmt, AST}; use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope}; -use crate::token::is_valid_identifier; use crate::utils::StaticVec; #[cfg(not(feature = "no_function"))] @@ -19,6 +19,7 @@ use crate::parser::CustomExpr; use crate::stdlib::{ boxed::Box, + convert::TryFrom, iter::empty, string::{String, ToString}, vec, @@ -418,7 +419,7 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { // All other items can be thrown away. state.set_dirty(); let pos = m.1; - m.0.into_iter().find(|((name, _), _)| name.as_str() == prop.as_str()) + m.0.into_iter().find(|((name, _), _)| name == prop) .map(|(_, mut expr)| { expr.set_position(pos); expr }) .unwrap_or_else(|| Expr::Unit(pos)) } @@ -495,7 +496,7 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { state.set_dirty(); let ch = a.0.to_string(); - if b.0.iter().find(|((name, _), _)| name.as_str() == ch.as_str()).is_some() { + if b.0.iter().find(|((name, _), _)| name == &ch).is_some() { Expr::True(a.1) } else { Expr::False(a.1) @@ -553,14 +554,19 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { // Fn("...") Expr::FnCall(x) - if x.1.is_none() - && (x.0).0 == KEYWORD_FN_PTR - && x.3.len() == 1 - && matches!(x.3[0], Expr::StringConstant(_)) + if x.1.is_none() + && (x.0).0 == KEYWORD_FN_PTR + && x.3.len() == 1 + && matches!(x.3[0], Expr::StringConstant(_)) => { - match &x.3[0] { - Expr::StringConstant(s) if is_valid_identifier(s.0.chars()) => Expr::FnPointer(s.clone()), - _ => Expr::FnCall(x) + if let Expr::StringConstant(s) = &x.3[0] { + if let Ok(fn_ptr) = FnPtr::try_from(s.0.as_str()) { + Expr::FnPointer(Box::new((fn_ptr.take_data().0, s.1))) + } else { + Expr::FnCall(x) + } + } else { + unreachable!() } } @@ -578,7 +584,7 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { let _has_script_fn = state.lib.iter_fn().find(|(_, _, _, f)| { if !f.is_script() { return false; } let fn_def = f.get_fn_def(); - fn_def.name.as_str() == name && (args.len()..=args.len() + 1).contains(&fn_def.params.len()) + fn_def.name == name && (args.len()..=args.len() + 1).contains(&fn_def.params.len()) }).is_some(); #[cfg(feature = "no_function")] @@ -765,6 +771,8 @@ pub fn optimize_into_ast( access: fn_def.access, body: Default::default(), params: fn_def.params.clone(), + #[cfg(not(feature = "no_capture"))] + externals: fn_def.externals.clone(), pos: fn_def.pos, } .into() diff --git a/src/parser.rs b/src/parser.rs index 3068bad0..abe3c7d8 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -363,6 +363,9 @@ pub struct ScriptFnDef { pub access: FnAccess, /// Names of function parameters. pub params: StaticVec, + /// Access to external variables. + #[cfg(not(feature = "no_capture"))] + pub externals: StaticVec, /// Function body. pub body: Stmt, /// Position of the function definition. @@ -448,7 +451,7 @@ impl<'e> ParseState<'e> { /// The return value is the offset to be deducted from `Stack::len`, /// i.e. the top element of the `ParseState` is offset 1. /// Return `None` when the variable name is not found in the `stack`. - fn access_var(&mut self, name: &str, pos: Position) -> Option { + fn access_var(&mut self, name: &str, _pos: Position) -> Option { let index = self .stack .iter() @@ -459,7 +462,7 @@ impl<'e> ParseState<'e> { #[cfg(not(feature = "no_capture"))] if index.is_none() && !self.externals.contains_key(name) { - self.externals.insert(name.to_string(), pos); + self.externals.insert(name.to_string(), _pos); } index @@ -2848,7 +2851,7 @@ fn parse_stmt( match input.next().unwrap() { (Token::Fn, pos) => { - let mut state = ParseState::new( + let mut new_state = ParseState::new( state.engine, #[cfg(not(feature = "unchecked"))] state.max_function_expr_depth, @@ -2867,7 +2870,7 @@ fn parse_stmt( pos: pos, }; - let func = parse_fn(input, &mut state, lib, access, settings)?; + let func = parse_fn(input, &mut new_state, lib, access, settings)?; // Qualifiers (none) + function name + number of arguments. let hash = calc_fn_hash(empty(), &func.name, func.params.len(), empty()); @@ -3039,12 +3042,23 @@ fn parse_fn( (_, pos) => return Err(PERR::FnMissingBody(name).into_err(*pos)), }; - let params = params.into_iter().map(|(p, _)| p).collect(); + let params: StaticVec<_> = params.into_iter().map(|(p, _)| p).collect(); + + #[cfg(not(feature = "no_capture"))] + let externals = state + .externals + .iter() + .map(|(name, _)| name) + .filter(|name| !params.contains(name)) + .cloned() + .collect(); Ok(ScriptFnDef { name: name.into(), access, params, + #[cfg(not(feature = "no_capture"))] + externals, body, pos: settings.pos, }) @@ -3054,40 +3068,31 @@ fn parse_fn( #[cfg(not(feature = "no_capture"))] fn make_curry_from_externals( fn_expr: Expr, - state: &mut ParseState, - settings: &ParseSettings, + externals: StaticVec<(String, Position)>, + pos: Position, ) -> Expr { - if state.externals.is_empty() { + if externals.is_empty() { return fn_expr; } + let num_externals = externals.len(); let mut args: StaticVec<_> = Default::default(); - state.externals.iter().for_each(|(var_name, pos)| { - args.push(Expr::Variable(Box::new(( - (var_name.clone(), *pos), - None, - 0, - None, - )))); + externals.into_iter().for_each(|(var_name, pos)| { + args.push(Expr::Variable(Box::new(((var_name, pos), None, 0, None)))); }); - let hash = calc_fn_hash( - empty(), - KEYWORD_FN_PTR_CURRY, - state.externals.len(), - empty(), - ); + let hash = calc_fn_hash(empty(), KEYWORD_FN_PTR_CURRY, num_externals, empty()); let fn_call = Expr::FnCall(Box::new(( - (KEYWORD_FN_PTR_CURRY.into(), false, settings.pos), + (KEYWORD_FN_PTR_CURRY.into(), false, pos), None, hash, args, None, ))); - Expr::Dot(Box::new((fn_expr, fn_call, settings.pos))) + Expr::Dot(Box::new((fn_expr, fn_call, pos))) } /// Parse an anonymous function definition. @@ -3160,11 +3165,20 @@ fn parse_anon_fn( #[cfg(feature = "no_capture")] let params: StaticVec<_> = params.into_iter().map(|(v, _)| v).collect(); + // External variables may need to be processed in a consistent order, + // so extract them into a list. + #[cfg(not(feature = "no_capture"))] + let externals: StaticVec<_> = state + .externals + .iter() + .map(|(k, &v)| (k.clone(), v)) + .collect(); + // Add parameters that are auto-curried #[cfg(not(feature = "no_capture"))] - let params: StaticVec<_> = state - .externals - .keys() + let params: StaticVec<_> = externals + .iter() + .map(|(k, _)| k) .cloned() .chain(params.into_iter().map(|(v, _)| v)) .collect(); @@ -3183,10 +3197,13 @@ fn parse_anon_fn( // Create unique function name let fn_name: ImmutableString = format!("{}{:16x}", FN_ANONYMOUS, hash).into(); + // Define the function let script = ScriptFnDef { name: fn_name.clone(), access: FnAccess::Public, params, + #[cfg(not(feature = "no_capture"))] + externals: Default::default(), body, pos: settings.pos, }; @@ -3194,7 +3211,7 @@ fn parse_anon_fn( let expr = Expr::FnPointer(Box::new((fn_name, settings.pos))); #[cfg(not(feature = "no_capture"))] - let expr = make_curry_from_externals(expr, state, &settings); + let expr = make_curry_from_externals(expr, externals, settings.pos); Ok((expr, script)) }