A prototype of a new feature to automatically curry of external scope variables
This commit is contained in:
parent
e2daba55d7
commit
8e51988b66
@ -32,6 +32,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_closures = [] # 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.
|
||||||
|
135
src/parser.rs
135
src/parser.rs
@ -2,7 +2,10 @@
|
|||||||
|
|
||||||
use crate::any::{Dynamic, Union};
|
use crate::any::{Dynamic, Union};
|
||||||
use crate::calc_fn_hash;
|
use crate::calc_fn_hash;
|
||||||
use crate::engine::{Engine, KEYWORD_THIS, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT};
|
use crate::engine::{
|
||||||
|
Engine, KEYWORD_THIS, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT,
|
||||||
|
KEYWORD_FN_PTR_CURRY,
|
||||||
|
};
|
||||||
use crate::error::{LexError, ParseError, ParseErrorType};
|
use crate::error::{LexError, ParseError, ParseErrorType};
|
||||||
use crate::fn_native::Shared;
|
use crate::fn_native::Shared;
|
||||||
use crate::module::{Module, ModuleRef};
|
use crate::module::{Module, ModuleRef};
|
||||||
@ -399,12 +402,14 @@ pub enum ReturnType {
|
|||||||
Exception,
|
Exception,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
struct ParseState<'e, 's> {
|
||||||
struct ParseState<'e> {
|
|
||||||
/// Reference to the scripting `Engine`.
|
/// Reference to the scripting `Engine`.
|
||||||
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).
|
||||||
|
externals: &'s mut 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.
|
||||||
@ -415,40 +420,103 @@ struct ParseState<'e> {
|
|||||||
max_function_expr_depth: usize,
|
max_function_expr_depth: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'e> ParseState<'e> {
|
impl<'e, 's> ParseState<'e, 's> {
|
||||||
/// Create a new `ParseState`.
|
/// Create a new `ParseState`.
|
||||||
pub fn new(
|
fn new(
|
||||||
engine: &'e Engine,
|
engine: &'e Engine,
|
||||||
|
externals: &'s mut Vec<String>,
|
||||||
#[cfg(not(feature = "unchecked"))] max_expr_depth: usize,
|
#[cfg(not(feature = "unchecked"))] max_expr_depth: usize,
|
||||||
#[cfg(not(feature = "unchecked"))] max_function_expr_depth: usize,
|
#[cfg(not(feature = "unchecked"))] max_function_expr_depth: usize,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
engine,
|
engine,
|
||||||
|
#[cfg(not(feature = "unchecked"))] max_expr_depth,
|
||||||
|
#[cfg(not(feature = "unchecked"))] max_function_expr_depth,
|
||||||
|
externals,
|
||||||
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.
|
|
||||||
|
/// Creates a new `ParseState` with empty `stack` and `modules` lists, but
|
||||||
|
/// deriving other settings from the passed `ParseState` instance.
|
||||||
|
fn derive(&'s mut self) -> Self {
|
||||||
|
Self {
|
||||||
|
engine: self.engine,
|
||||||
|
#[cfg(not(feature = "unchecked"))]
|
||||||
|
max_expr_depth: self.max_expr_depth,
|
||||||
|
#[cfg(not(feature = "unchecked"))]
|
||||||
|
max_function_expr_depth: self.max_function_expr_depth,
|
||||||
|
externals: self.externals,
|
||||||
|
stack: Default::default(),
|
||||||
|
modules: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 mut 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_closures"))]
|
||||||
|
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
|
||||||
|
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> {
|
fn find_module(&self, name: &str) -> Option<NonZeroUsize> {
|
||||||
self.modules
|
self.modules
|
||||||
.iter()
|
.iter()
|
||||||
.rev()
|
.rev()
|
||||||
@ -1577,7 +1645,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,9 +1846,10 @@ fn parse_unary(
|
|||||||
// | ...
|
// | ...
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
Token::Pipe | Token::Or => {
|
Token::Pipe | Token::Or => {
|
||||||
|
let mut _externals = Default::default();
|
||||||
let mut state = ParseState::new(
|
let mut state = ParseState::new(
|
||||||
state.engine,
|
state.engine,
|
||||||
#[cfg(not(feature = "unchecked"))]
|
&mut _externals,
|
||||||
state.max_function_expr_depth,
|
state.max_function_expr_depth,
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
state.max_function_expr_depth,
|
state.max_function_expr_depth,
|
||||||
@ -2822,8 +2891,10 @@ fn parse_stmt(
|
|||||||
|
|
||||||
match input.next().unwrap() {
|
match input.next().unwrap() {
|
||||||
(Token::Fn, pos) => {
|
(Token::Fn, pos) => {
|
||||||
|
let mut _externals = Default::default();
|
||||||
let mut state = ParseState::new(
|
let mut state = ParseState::new(
|
||||||
state.engine,
|
state.engine,
|
||||||
|
&mut _externals,
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
state.max_function_expr_depth,
|
state.max_function_expr_depth,
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
@ -3091,7 +3162,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_closures"))]
|
||||||
|
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 +3179,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,12 +3190,15 @@ 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,
|
||||||
};
|
};
|
||||||
|
|
||||||
let expr = Expr::FnPointer(Box::new((fn_name, settings.pos)));
|
let mut expr = state.make_curry_from_externals(
|
||||||
|
Expr::FnPointer(Box::new((fn_name, settings.pos))),
|
||||||
|
&settings,
|
||||||
|
);
|
||||||
|
|
||||||
Ok((expr, script))
|
Ok((expr, script))
|
||||||
}
|
}
|
||||||
@ -3128,9 +3211,10 @@ 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 _externals = Default::default();
|
||||||
let mut state = ParseState::new(
|
let mut state = ParseState::new(
|
||||||
self,
|
self,
|
||||||
|
&mut _externals,
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
self.limits.max_expr_depth,
|
self.limits.max_expr_depth,
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
@ -3174,9 +3258,10 @@ 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 _externals = Default::default();
|
||||||
let mut state = ParseState::new(
|
let mut state = ParseState::new(
|
||||||
self,
|
self,
|
||||||
|
&mut _externals,
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
self.limits.max_expr_depth,
|
self.limits.max_expr_depth,
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
|
@ -209,3 +209,24 @@ fn test_fn_ptr_curry_call() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_fn_closures() -> Result<(), Box<EvalAltResult>> {
|
||||||
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
|
let res = engine.eval::<INT>(
|
||||||
|
r#"
|
||||||
|
let x = 100;
|
||||||
|
|
||||||
|
let f = || x;
|
||||||
|
|
||||||
|
let x = 200;
|
||||||
|
|
||||||
|
f.call()
|
||||||
|
"#
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
|
panic!("{:#?}", res);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user