Add function pointer short-hand.
This commit is contained in:
parent
ca65e17610
commit
d8532b48b6
@ -30,6 +30,10 @@ New features
|
||||
|
||||
* An API is added to automatically generate definition files from a fully-configured `Engine`, for use with the Rhai Language Server.
|
||||
|
||||
### Short-hand to function pointers
|
||||
|
||||
* Using a script-defined function's name (in place of a variable) implicitly creates a function pointer to the function.
|
||||
|
||||
Enhancements
|
||||
------------
|
||||
|
||||
|
@ -57,7 +57,6 @@ impl Engine {
|
||||
}
|
||||
/// Set whether `if`-expression is allowed.
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn set_allow_if_expression(&mut self, enable: bool) {
|
||||
self.options.set(LangOptions::IF_EXPR, enable);
|
||||
}
|
||||
|
@ -149,6 +149,18 @@ impl Engine {
|
||||
}
|
||||
_ if global.always_search_scope => (0, expr.start_position()),
|
||||
Expr::Variable(.., Some(i), pos) => (i.get() as usize, *pos),
|
||||
// Scripted function with the same name
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
Expr::Variable(v, None, pos)
|
||||
if lib
|
||||
.iter()
|
||||
.flat_map(|&m| m.iter_script_fn())
|
||||
.any(|(_, _, f, ..)| f == v.3) =>
|
||||
{
|
||||
let val: Dynamic =
|
||||
crate::FnPtr::new_unchecked(v.3.as_str(), Default::default()).into();
|
||||
return Ok((val.into(), *pos));
|
||||
}
|
||||
Expr::Variable(v, None, pos) => (v.0.map_or(0, NonZeroUsize::get), *pos),
|
||||
_ => unreachable!("Expr::Variable expected but gets {:?}", expr),
|
||||
};
|
||||
|
@ -244,12 +244,12 @@ impl Engine {
|
||||
// We shouldn't do this for too many variants because, soon or later, the added comparisons
|
||||
// will cost more than the mis-predicted `match` branch.
|
||||
if let Stmt::Assignment(x, ..) = stmt {
|
||||
let (op_info, BinaryExpr { lhs, rhs }) = &**x;
|
||||
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
self.inc_operations(&mut global.num_operations, stmt.position())?;
|
||||
|
||||
let result = if x.1.lhs.is_variable_access(false) {
|
||||
let (op_info, BinaryExpr { lhs, rhs }) = &**x;
|
||||
|
||||
let result = if let Expr::Variable(x, ..) = lhs {
|
||||
let rhs_result = self
|
||||
.eval_expr(scope, global, caches, lib, this_ptr, rhs, level)
|
||||
.map(Dynamic::flatten);
|
||||
@ -261,7 +261,7 @@ impl Engine {
|
||||
if let Ok(search_val) = search_result {
|
||||
let (mut lhs_ptr, pos) = search_val;
|
||||
|
||||
let var_name = lhs.get_variable_name(false).expect("`Expr::Variable`");
|
||||
let var_name = x.3.as_str();
|
||||
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
// Also handle case where target is a `Dynamic` shared value
|
||||
|
@ -159,17 +159,34 @@ impl<'e> ParseState<'e> {
|
||||
/// The return value is the offset to be deducted from `ParseState::stack::len()`,
|
||||
/// i.e. the top element of [`ParseState`]'s variables stack is offset 1.
|
||||
///
|
||||
/// Return `None` when the variable name is not found in the `stack`.
|
||||
/// # Return value: `(index, is_func)`
|
||||
///
|
||||
/// * `index`: `None` when the variable name is not found in the `stack`,
|
||||
/// otherwise the index value.
|
||||
///
|
||||
/// * `is_func`: `true` if the variable is actually the name of a function
|
||||
/// (in which case it will be converted into a function pointer).
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn access_var(&mut self, name: &str, pos: Position) -> Option<NonZeroUsize> {
|
||||
pub fn access_var(
|
||||
&mut self,
|
||||
name: &str,
|
||||
lib: &FnLib,
|
||||
pos: Position,
|
||||
) -> (Option<NonZeroUsize>, bool) {
|
||||
let _pos = pos;
|
||||
|
||||
let (index, hit_barrier) = self.find_var(name);
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
let is_func = lib.values().any(|f| f.name == name);
|
||||
|
||||
#[cfg(feature = "no_function")]
|
||||
let is_func = false;
|
||||
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
if self.allow_capture {
|
||||
if index == 0 && !self.external_vars.iter().any(|v| v.as_str() == name) {
|
||||
if !is_func && index == 0 && !self.external_vars.iter().any(|v| v.as_str() == name) {
|
||||
self.external_vars.push(crate::ast::Ident {
|
||||
name: name.into(),
|
||||
pos: _pos,
|
||||
@ -179,11 +196,13 @@ impl<'e> ParseState<'e> {
|
||||
self.allow_capture = true;
|
||||
}
|
||||
|
||||
if hit_barrier {
|
||||
let index = if hit_barrier {
|
||||
None
|
||||
} else {
|
||||
NonZeroUsize::new(index)
|
||||
}
|
||||
};
|
||||
|
||||
(index, is_func)
|
||||
}
|
||||
|
||||
/// Find a module by name in the [`ParseState`], searching in reverse.
|
||||
@ -1366,12 +1385,13 @@ impl Engine {
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
new_state.external_vars.iter().try_for_each(
|
||||
|crate::ast::Ident { name, pos }| {
|
||||
let index = state.access_var(name, *pos);
|
||||
let (index, is_func) = state.access_var(name, lib, *pos);
|
||||
|
||||
if settings.options.contains(LangOptions::STRICT_VAR)
|
||||
&& !settings.is_closure_scope
|
||||
&& index.is_none()
|
||||
&& !state.scope.contains(name)
|
||||
&& !is_func
|
||||
{
|
||||
// If the parent scope is not inside another capturing closure
|
||||
// then we can conclude that the captured variable doesn't exist.
|
||||
@ -1512,11 +1532,12 @@ impl Engine {
|
||||
}
|
||||
// Normal variable access
|
||||
_ => {
|
||||
let index = state.access_var(&s, settings.pos);
|
||||
let (index, is_func) = state.access_var(&s, lib, settings.pos);
|
||||
|
||||
if settings.options.contains(LangOptions::STRICT_VAR)
|
||||
&& index.is_none()
|
||||
&& !state.scope.contains(&s)
|
||||
&& !is_func
|
||||
{
|
||||
return Err(
|
||||
PERR::VariableUndefined(s.to_string()).into_err(settings.pos)
|
||||
|
@ -186,7 +186,7 @@ fn test_fn_ptr_raw() -> Result<(), Box<EvalAltResult>> {
|
||||
fn foo(x) { this += x; }
|
||||
|
||||
let x = 41;
|
||||
x.bar(Fn("foo"), 1);
|
||||
x.bar(foo, 1);
|
||||
x
|
||||
"#
|
||||
)?,
|
||||
|
@ -77,6 +77,20 @@ fn test_fn_ptr() -> Result<(), Box<EvalAltResult>> {
|
||||
if fn_name == "foo" && matches!(*err, EvalAltResult::ErrorUnboundThis(..))
|
||||
));
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(
|
||||
r#"
|
||||
fn foo(x) { x + 1 }
|
||||
let f = foo;
|
||||
let g = 42;
|
||||
g = foo;
|
||||
call(f, 39) + call(g, 1)
|
||||
"#
|
||||
)?,
|
||||
42
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -104,6 +104,11 @@ fn test_options_strict_var() -> Result<(), Box<EvalAltResult>> {
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
{
|
||||
assert_eq!(
|
||||
engine
|
||||
.eval_with_scope::<INT>(&mut scope, "fn foo(z) { y + z } let f = foo; f.call(x)")?,
|
||||
42
|
||||
);
|
||||
assert!(engine.compile("let f = |y| x * y;").is_err());
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user