Merge pull request #19 from Eliah-Lakhin/closures

Autocurry feature
This commit is contained in:
Stephen Chung 2020-07-29 21:24:43 +08:00 committed by GitHub
commit a5a3bbd399
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 119 additions and 19 deletions

View File

@ -33,6 +33,7 @@ only_i64 = [] # set INT=i64 (default) and disable support for all other in
no_index = [] # no arrays and indexing no_index = [] # no arrays and indexing
no_object = [] # no custom objects no_object = [] # no custom objects
no_function = [] # no script-defined functions no_function = [] # no script-defined functions
no_capture = [] # no automatic read/write binding of anonymous function's local variables to it's external context
no_module = [] # no modules no_module = [] # no modules
internals = [] # expose internal data structures internals = [] # expose internal data structures
unicode-xid-ident = ["unicode-xid"] # allow Unicode Standard Annex #31 for identifiers. unicode-xid-ident = ["unicode-xid"] # allow Unicode Standard Annex #31 for identifiers.

View File

@ -15,6 +15,9 @@ use crate::utils::{StaticVec, StraightHasherBuilder};
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
use crate::engine::FN_ANONYMOUS; use crate::engine::FN_ANONYMOUS;
#[cfg(not(feature = "no_capture"))]
use crate::engine::KEYWORD_FN_PTR_CURRY;
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
use crate::engine::{make_getter, make_setter}; use crate::engine::{make_getter, make_setter};
@ -405,6 +408,10 @@ struct ParseState<'e> {
engine: &'e Engine, engine: &'e Engine,
/// Encapsulates a local stack with variable names to simulate an actual runtime scope. /// Encapsulates a local stack with variable names to simulate an actual runtime scope.
stack: Vec<(String, ScopeEntryType)>, stack: Vec<(String, ScopeEntryType)>,
/// Tracks a list of external variables(variables that are not explicitly
/// declared in the scope during AST evaluation).
#[cfg(not(feature = "no_capture"))]
externals: Vec<String>,
/// Encapsulates a local stack with variable names to simulate an actual runtime scope. /// Encapsulates a local stack with variable names to simulate an actual runtime scope.
modules: Vec<String>, modules: Vec<String>,
/// Maximum levels of expression nesting. /// Maximum levels of expression nesting.
@ -424,30 +431,78 @@ impl<'e> ParseState<'e> {
) -> Self { ) -> Self {
Self { Self {
engine, engine,
#[cfg(not(feature = "unchecked"))] max_expr_depth,
#[cfg(not(feature = "unchecked"))] max_function_expr_depth,
#[cfg(not(feature = "no_capture"))] externals: Default::default(),
stack: Default::default(), stack: Default::default(),
modules: Default::default(), modules: Default::default(),
#[cfg(not(feature = "unchecked"))]
max_expr_depth,
#[cfg(not(feature = "unchecked"))]
max_function_expr_depth,
} }
} }
/// Find a variable by name in the `ParseState`, searching in reverse.
/// Find explicitly declared variable by name in the `ParseState`,
/// searching in reverse order.
///
/// If the variable is not present in the scope adds it to the list of
/// external variables
///
/// The return value is the offset to be deducted from `Stack::len`, /// The return value is the offset to be deducted from `Stack::len`,
/// i.e. the top element of the `ParseState` is offset 1. /// i.e. the top element of the `ParseState` is offset 1.
/// Return zero when the variable name is not found in the `ParseState`. /// Return `None` when the variable name is not found in the `stack`.
pub fn find_var(&self, name: &str) -> Option<NonZeroUsize> { fn access_var(&mut self, name: &str) -> Option<NonZeroUsize> {
self.stack let index = self.stack
.iter() .iter()
.rev() .rev()
.enumerate() .enumerate()
.find(|(_, (n, _))| *n == name) .find(|(_, (n, _))| *n == name)
.and_then(|(i, _)| NonZeroUsize::new(i + 1)) .and_then(|(i, _)| NonZeroUsize::new(i + 1));
if index.is_some() {
return index
}
#[cfg(not(feature = "no_capture"))]
if self.externals.iter().find(|n| *n == name).is_none() {
self.externals.push(name.to_string());
}
None
} }
/// Creates a curry expression from a list of external variables
#[cfg(not(feature = "no_capture"))]
pub fn make_curry_from_externals(&self, fn_expr: Expr, settings: &ParseSettings) -> Expr {
if self.externals.is_empty() {
return fn_expr
}
let mut args = StaticVec::new();
for var in self.externals.iter() {
args.push(Expr::Variable(Box::new((
(var.clone(), settings.pos),
None,
0,
None,
))));
}
Expr::Dot(Box::new((
fn_expr,
Expr::FnCall(Box::new((
(KEYWORD_FN_PTR_CURRY.into(), false, settings.pos),
None,
calc_fn_hash(empty(), KEYWORD_FN_PTR_CURRY, self.externals.len(), empty()),
args,
None,
))),
settings.pos,
)))
}
/// Find a module by name in the `ParseState`, searching in reverse. /// Find a module by name in the `ParseState`, searching in reverse.
/// The return value is the offset to be deducted from `Stack::len`, /// The return value is the offset to be deducted from `Stack::len`,
/// i.e. the top element of the `ParseState` is offset 1. /// i.e. the top element of the `ParseState` is offset 1.
/// Return zero when the variable name is not found in the `ParseState`. /// Return `None` when the variable name is not found in the `ParseState`.
pub fn find_module(&self, name: &str) -> Option<NonZeroUsize> { pub fn find_module(&self, name: &str) -> Option<NonZeroUsize> {
self.modules self.modules
.iter() .iter()
@ -1577,7 +1632,7 @@ fn parse_primary(
Token::CharConstant(c) => Expr::CharConstant(Box::new((c, settings.pos))), Token::CharConstant(c) => Expr::CharConstant(Box::new((c, settings.pos))),
Token::StringConstant(s) => Expr::StringConstant(Box::new((s.into(), settings.pos))), Token::StringConstant(s) => Expr::StringConstant(Box::new((s.into(), settings.pos))),
Token::Identifier(s) => { Token::Identifier(s) => {
let index = state.find_var(&s); let index = state.access_var(&s);
Expr::Variable(Box::new(((s, settings.pos), None, 0, index))) Expr::Variable(Box::new(((s, settings.pos), None, 0, index)))
} }
// Function call is allowed to have reserved keyword // Function call is allowed to have reserved keyword
@ -1778,7 +1833,7 @@ fn parse_unary(
// | ... // | ...
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
Token::Pipe | Token::Or => { Token::Pipe | Token::Or => {
let mut state = ParseState::new( let mut new_state = ParseState::new(
state.engine, state.engine,
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
state.max_function_expr_depth, state.max_function_expr_depth,
@ -1797,7 +1852,12 @@ fn parse_unary(
pos: *token_pos, pos: *token_pos,
}; };
let (expr, func) = parse_anon_fn(input, &mut state, lib, settings)?; let (expr, func) = parse_anon_fn(input, &mut new_state, lib, settings)?;
#[cfg(not(feature = "no_capture"))]
for closure in new_state.externals {
state.access_var(&closure);
}
// Qualifiers (none) + function name + number of arguments. // Qualifiers (none) + function name + number of arguments.
let hash = calc_fn_hash(empty(), &func.name, func.params.len(), empty()); let hash = calc_fn_hash(empty(), &func.name, func.params.len(), empty());
@ -3091,7 +3151,16 @@ fn parse_anon_fn(
let body = parse_stmt(input, state, lib, settings.level_up()) let body = parse_stmt(input, state, lib, settings.level_up())
.map(|stmt| stmt.unwrap_or_else(|| Stmt::Noop(pos)))?; .map(|stmt| stmt.unwrap_or_else(|| Stmt::Noop(pos)))?;
let params: StaticVec<_> = params.into_iter().map(|(p, _)| p).collect(); let mut static_params = StaticVec::<String>::new();
#[cfg(not(feature = "no_capture"))]
for closure in state.externals.iter() {
static_params.push(closure.clone());
}
for param in params.into_iter() {
static_params.push(param.0);
}
// Calculate hash // Calculate hash
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
@ -3099,8 +3168,8 @@ fn parse_anon_fn(
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
let mut s = DefaultHasher::new(); let mut s = DefaultHasher::new();
s.write_usize(params.len()); s.write_usize(static_params.len());
params.iter().for_each(|a| a.hash(&mut s)); static_params.iter().for_each(|a| a.hash(&mut s));
body.hash(&mut s); body.hash(&mut s);
let hash = s.finish(); let hash = s.finish();
@ -3110,11 +3179,18 @@ fn parse_anon_fn(
let script = ScriptFnDef { let script = ScriptFnDef {
name: fn_name.clone(), name: fn_name.clone(),
access: FnAccess::Public, access: FnAccess::Public,
params, params: static_params,
body, body,
pos: settings.pos, pos: settings.pos,
}; };
#[cfg(not(feature = "no_capture"))]
let expr = state.make_curry_from_externals(
Expr::FnPointer(Box::new((fn_name, settings.pos))),
&settings,
);
#[cfg(feature = "no_capture")]
let expr = Expr::FnPointer(Box::new((fn_name, settings.pos))); let expr = Expr::FnPointer(Box::new((fn_name, settings.pos)));
Ok((expr, script)) Ok((expr, script))
@ -3128,7 +3204,6 @@ impl Engine {
optimization_level: OptimizationLevel, optimization_level: OptimizationLevel,
) -> Result<AST, ParseError> { ) -> Result<AST, ParseError> {
let mut functions = Default::default(); let mut functions = Default::default();
let mut state = ParseState::new( let mut state = ParseState::new(
self, self,
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
@ -3174,7 +3249,6 @@ impl Engine {
) -> Result<(Vec<Stmt>, Vec<ScriptFnDef>), ParseError> { ) -> Result<(Vec<Stmt>, Vec<ScriptFnDef>), ParseError> {
let mut statements: Vec<Stmt> = Default::default(); let mut statements: Vec<Stmt> = Default::default();
let mut functions = Default::default(); let mut functions = Default::default();
let mut state = ParseState::new( let mut state = ParseState::new(
self, self,
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]

View File

@ -209,3 +209,28 @@ fn test_fn_ptr_curry_call() -> Result<(), Box<EvalAltResult>> {
Ok(()) Ok(())
} }
#[test]
#[cfg(not(feature = "no_capture"))]
fn test_fn_closures() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new();
assert_eq!(
engine.eval::<INT>(
r#"
let x = 8;
let res = |y, z| {
let w = 12;
return (|| x + y + z + w).call();
}.curry(15).call(2);
res + (|| x - 3).call()
"#
)?,
42
);
Ok(())
}