Enable call in function-call style.
This commit is contained in:
parent
a97ffc536c
commit
16fbfbb606
@ -4,6 +4,11 @@ Rhai Release Notes
|
|||||||
Version 0.18.0
|
Version 0.18.0
|
||||||
==============
|
==============
|
||||||
|
|
||||||
|
New features
|
||||||
|
------------
|
||||||
|
|
||||||
|
* `call` can now be called function-call style for function pointers - this is to handle builds with `no_object`.
|
||||||
|
* Disallow many keywords as variables, such as `print`, `eval`, `call`, `this` etc.
|
||||||
|
|
||||||
|
|
||||||
Version 0.17.0
|
Version 0.17.0
|
||||||
|
@ -6,7 +6,7 @@ Function Pointers
|
|||||||
It is possible to store a _function pointer_ in a variable just like a normal value.
|
It is possible to store a _function pointer_ in a variable just like a normal value.
|
||||||
In fact, internally a function pointer simply stores the _name_ of the function as a string.
|
In fact, internally a function pointer simply stores the _name_ of the function as a string.
|
||||||
|
|
||||||
Call a function pointer using the `call` method, which needs to be called in method-call style.
|
Call a function pointer using the `call` method.
|
||||||
|
|
||||||
|
|
||||||
Built-in methods
|
Built-in methods
|
||||||
@ -40,7 +40,7 @@ func.call(1) == 42; // call a function pointer with the 'call' method
|
|||||||
|
|
||||||
foo(1) == 42; // <- the above de-sugars to this
|
foo(1) == 42; // <- the above de-sugars to this
|
||||||
|
|
||||||
call(func, 1); //<- error: 'call (Fn, i64)' is not a registered function
|
call(func, 1); // normal function call style also works for 'call'
|
||||||
|
|
||||||
let len = Fn("len"); // 'Fn' also works with registered native Rust functions
|
let len = Fn("len"); // 'Fn' also works with registered native Rust functions
|
||||||
|
|
||||||
|
@ -944,7 +944,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Has a system function an override?
|
// Has a system function an override?
|
||||||
fn has_override(&self, lib: &Module, (hash_fn, hash_script): (u64, u64)) -> bool {
|
fn has_override(&self, lib: &Module, hash_fn: u64, hash_script: u64) -> bool {
|
||||||
// NOTE: We skip script functions for global_module and packages, and native functions for lib
|
// NOTE: We skip script functions for global_module and packages, and native functions for lib
|
||||||
|
|
||||||
// First check script-defined functions
|
// First check script-defined functions
|
||||||
@ -986,13 +986,15 @@ impl Engine {
|
|||||||
|
|
||||||
match fn_name {
|
match fn_name {
|
||||||
// type_of
|
// type_of
|
||||||
KEYWORD_TYPE_OF if args.len() == 1 && !self.has_override(lib, hashes) => Ok((
|
KEYWORD_TYPE_OF if args.len() == 1 && !self.has_override(lib, hashes.0, hashes.1) => {
|
||||||
|
Ok((
|
||||||
self.map_type_name(args[0].type_name()).to_string().into(),
|
self.map_type_name(args[0].type_name()).to_string().into(),
|
||||||
false,
|
false,
|
||||||
)),
|
))
|
||||||
|
}
|
||||||
|
|
||||||
// Fn
|
// Fn
|
||||||
KEYWORD_FN_PTR if args.len() == 1 && !self.has_override(lib, hashes) => {
|
KEYWORD_FN_PTR if args.len() == 1 && !self.has_override(lib, hashes.0, hashes.1) => {
|
||||||
Err(Box::new(EvalAltResult::ErrorRuntime(
|
Err(Box::new(EvalAltResult::ErrorRuntime(
|
||||||
"'Fn' should not be called in method style. Try Fn(...);".into(),
|
"'Fn' should not be called in method style. Try Fn(...);".into(),
|
||||||
Position::none(),
|
Position::none(),
|
||||||
@ -1000,7 +1002,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// eval - reaching this point it must be a method-style call
|
// eval - reaching this point it must be a method-style call
|
||||||
KEYWORD_EVAL if args.len() == 1 && !self.has_override(lib, hashes) => {
|
KEYWORD_EVAL if args.len() == 1 && !self.has_override(lib, hashes.0, hashes.1) => {
|
||||||
Err(Box::new(EvalAltResult::ErrorRuntime(
|
Err(Box::new(EvalAltResult::ErrorRuntime(
|
||||||
"'eval' should not be called in method style. Try eval(...);".into(),
|
"'eval' should not be called in method style. Try eval(...);".into(),
|
||||||
Position::none(),
|
Position::none(),
|
||||||
@ -1926,7 +1928,7 @@ impl Engine {
|
|||||||
let hash_fn =
|
let hash_fn =
|
||||||
calc_fn_hash(empty(), name, 1, once(TypeId::of::<ImmutableString>()));
|
calc_fn_hash(empty(), name, 1, once(TypeId::of::<ImmutableString>()));
|
||||||
|
|
||||||
if !self.has_override(lib, (hash_fn, *hash)) {
|
if !self.has_override(lib, hash_fn, *hash) {
|
||||||
// Fn - only in function call style
|
// Fn - only in function call style
|
||||||
let expr = args_expr.get(0);
|
let expr = args_expr.get(0);
|
||||||
let arg_value =
|
let arg_value =
|
||||||
@ -1952,7 +1954,7 @@ impl Engine {
|
|||||||
let hash_fn =
|
let hash_fn =
|
||||||
calc_fn_hash(empty(), name, 1, once(TypeId::of::<ImmutableString>()));
|
calc_fn_hash(empty(), name, 1, once(TypeId::of::<ImmutableString>()));
|
||||||
|
|
||||||
if !self.has_override(lib, (hash_fn, *hash)) {
|
if !self.has_override(lib, hash_fn, *hash) {
|
||||||
// eval - only in function call style
|
// eval - only in function call style
|
||||||
let prev_len = scope.len();
|
let prev_len = scope.len();
|
||||||
let expr = args_expr.get(0);
|
let expr = args_expr.get(0);
|
||||||
@ -1972,6 +1974,36 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle call() - Redirect function call
|
||||||
|
let redirected;
|
||||||
|
let mut name = name.as_ref();
|
||||||
|
let mut args_expr = args_expr.as_ref();
|
||||||
|
let mut hash = *hash;
|
||||||
|
|
||||||
|
if name == KEYWORD_FN_PTR_CALL
|
||||||
|
&& args_expr.len() >= 1
|
||||||
|
&& !self.has_override(lib, 0, hash)
|
||||||
|
{
|
||||||
|
let expr = args_expr.get(0).unwrap();
|
||||||
|
let fn_ptr = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
|
||||||
|
|
||||||
|
if fn_ptr.is::<FnPtr>() {
|
||||||
|
// Redirect function name
|
||||||
|
redirected = Some(fn_ptr.cast::<FnPtr>().take_fn_name());
|
||||||
|
name = redirected.as_ref().unwrap();
|
||||||
|
// Skip the first argument
|
||||||
|
args_expr = &args_expr.as_ref()[1..];
|
||||||
|
// Recalculate hash
|
||||||
|
hash = calc_fn_hash(empty(), name, args_expr.len(), empty());
|
||||||
|
} else {
|
||||||
|
return Err(Box::new(EvalAltResult::ErrorMismatchOutputType(
|
||||||
|
self.map_type_name(type_name::<FnPtr>()).into(),
|
||||||
|
fn_ptr.type_name().into(),
|
||||||
|
expr.position(),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Normal function call - except for Fn and eval (handled above)
|
// Normal function call - except for Fn and eval (handled above)
|
||||||
let mut arg_values: StaticVec<Dynamic>;
|
let mut arg_values: StaticVec<Dynamic>;
|
||||||
let mut args: StaticVec<_>;
|
let mut args: StaticVec<_>;
|
||||||
@ -1983,7 +2015,7 @@ impl Engine {
|
|||||||
} else {
|
} else {
|
||||||
// See if the first argument is a variable, if so, convert to method-call style
|
// See if the first argument is a variable, if so, convert to method-call style
|
||||||
// in order to leverage potential &mut first argument and avoid cloning the value
|
// in order to leverage potential &mut first argument and avoid cloning the value
|
||||||
match args_expr.get(0) {
|
match args_expr.get(0).unwrap() {
|
||||||
// func(x, ...) -> x.func(...)
|
// func(x, ...) -> x.func(...)
|
||||||
lhs @ Expr::Variable(_) => {
|
lhs @ Expr::Variable(_) => {
|
||||||
arg_values = args_expr
|
arg_values = args_expr
|
||||||
@ -2020,7 +2052,7 @@ impl Engine {
|
|||||||
|
|
||||||
let args = args.as_mut();
|
let args = args.as_mut();
|
||||||
self.exec_fn_call(
|
self.exec_fn_call(
|
||||||
state, lib, name, *native, *hash, args, is_ref, false, def_val, level,
|
state, lib, name, *native, hash, args, is_ref, false, def_val, level,
|
||||||
)
|
)
|
||||||
.map(|(v, _)| v)
|
.map(|(v, _)| v)
|
||||||
.map_err(|err| err.new_position(*pos))
|
.map_err(|err| err.new_position(*pos))
|
||||||
|
@ -7,7 +7,7 @@ use crate::error::{LexError, ParseError, ParseErrorType};
|
|||||||
use crate::module::{Module, ModuleRef};
|
use crate::module::{Module, ModuleRef};
|
||||||
use crate::optimize::{optimize_into_ast, OptimizationLevel};
|
use crate::optimize::{optimize_into_ast, OptimizationLevel};
|
||||||
use crate::scope::{EntryType as ScopeEntryType, Scope};
|
use crate::scope::{EntryType as ScopeEntryType, Scope};
|
||||||
use crate::token::{Position, Token, TokenStream};
|
use crate::token::{is_valid_identifier, Position, Token, TokenStream};
|
||||||
use crate::utils::{StaticVec, StraightHasherBuilder};
|
use crate::utils::{StaticVec, StraightHasherBuilder};
|
||||||
|
|
||||||
#[cfg(feature = "internals")]
|
#[cfg(feature = "internals")]
|
||||||
@ -440,6 +440,8 @@ struct ParseSettings {
|
|||||||
pos: Position,
|
pos: Position,
|
||||||
/// Is the construct being parsed located at global level?
|
/// Is the construct being parsed located at global level?
|
||||||
is_global: bool,
|
is_global: bool,
|
||||||
|
/// Is the construct being parsed located at function definition level?
|
||||||
|
is_function_scope: bool,
|
||||||
/// Is the current position inside a loop?
|
/// Is the current position inside a loop?
|
||||||
is_breakable: bool,
|
is_breakable: bool,
|
||||||
/// Is anonymous function allowed?
|
/// Is anonymous function allowed?
|
||||||
@ -1460,6 +1462,21 @@ fn parse_primary(
|
|||||||
let index = state.find_var(&s);
|
let index = state.find_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
|
||||||
|
Token::Reserved(s) if s != KEYWORD_THIS && input.peek().unwrap().0 == Token::LeftParen => {
|
||||||
|
Expr::Variable(Box::new(((s, settings.pos), None, 0, None)))
|
||||||
|
}
|
||||||
|
// Access to `this` as a variable is OK
|
||||||
|
Token::Reserved(s) if s == KEYWORD_THIS && input.peek().unwrap().0 != Token::LeftParen => {
|
||||||
|
if !settings.is_function_scope {
|
||||||
|
return Err(
|
||||||
|
PERR::BadInput(format!("'{}' can only be used in functions", s))
|
||||||
|
.into_err(settings.pos),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
Expr::Variable(Box::new(((s, settings.pos), None, 0, None)))
|
||||||
|
}
|
||||||
|
}
|
||||||
Token::LeftParen => parse_paren_expr(input, state, lib, settings.level_up())?,
|
Token::LeftParen => parse_paren_expr(input, state, lib, settings.level_up())?,
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Token::LeftBracket => parse_array_literal(input, state, lib, settings.level_up())?,
|
Token::LeftBracket => parse_array_literal(input, state, lib, settings.level_up())?,
|
||||||
@ -1538,6 +1555,7 @@ fn parse_primary(
|
|||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make sure identifiers are valid
|
||||||
Ok(root_expr)
|
Ok(root_expr)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2315,16 +2333,6 @@ fn parse_let(
|
|||||||
(_, pos) => return Err(PERR::VariableExpected.into_err(pos)),
|
(_, pos) => return Err(PERR::VariableExpected.into_err(pos)),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check if the name is allowed
|
|
||||||
match name.as_str() {
|
|
||||||
KEYWORD_THIS => {
|
|
||||||
return Err(
|
|
||||||
PERR::BadInput(LexError::MalformedIdentifier(name).to_string()).into_err(pos),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
|
|
||||||
// let name = ...
|
// let name = ...
|
||||||
if match_token(input, Token::Equals)? {
|
if match_token(input, Token::Equals)? {
|
||||||
// let name = expr
|
// let name = expr
|
||||||
@ -2598,6 +2606,7 @@ fn parse_stmt(
|
|||||||
allow_stmt_expr: true,
|
allow_stmt_expr: true,
|
||||||
allow_anonymous_fn: true,
|
allow_anonymous_fn: true,
|
||||||
is_global: false,
|
is_global: false,
|
||||||
|
is_function_scope: true,
|
||||||
is_breakable: false,
|
is_breakable: false,
|
||||||
level: 0,
|
level: 0,
|
||||||
pos: pos,
|
pos: pos,
|
||||||
@ -2695,7 +2704,11 @@ fn parse_fn(
|
|||||||
settings.ensure_level_within_max_limit(state.max_expr_depth)?;
|
settings.ensure_level_within_max_limit(state.max_expr_depth)?;
|
||||||
|
|
||||||
let name = match input.next().unwrap() {
|
let name = match input.next().unwrap() {
|
||||||
(Token::Identifier(s), _) | (Token::Custom(s), _) => s,
|
(Token::Identifier(s), _) | (Token::Custom(s), _) | (Token::Reserved(s), _)
|
||||||
|
if s != KEYWORD_THIS && is_valid_identifier(s.chars()) =>
|
||||||
|
{
|
||||||
|
s
|
||||||
|
}
|
||||||
(_, pos) => return Err(PERR::FnMissingName.into_err(pos)),
|
(_, pos) => return Err(PERR::FnMissingName.into_err(pos)),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -2790,6 +2803,7 @@ impl Engine {
|
|||||||
allow_stmt_expr: false,
|
allow_stmt_expr: false,
|
||||||
allow_anonymous_fn: false,
|
allow_anonymous_fn: false,
|
||||||
is_global: true,
|
is_global: true,
|
||||||
|
is_function_scope: false,
|
||||||
is_breakable: false,
|
is_breakable: false,
|
||||||
level: 0,
|
level: 0,
|
||||||
pos: Position::none(),
|
pos: Position::none(),
|
||||||
@ -2829,6 +2843,7 @@ impl Engine {
|
|||||||
allow_stmt_expr: true,
|
allow_stmt_expr: true,
|
||||||
allow_anonymous_fn: true,
|
allow_anonymous_fn: true,
|
||||||
is_global: true,
|
is_global: true,
|
||||||
|
is_function_scope: false,
|
||||||
is_breakable: false,
|
is_breakable: false,
|
||||||
level: 0,
|
level: 0,
|
||||||
pos: Position::none(),
|
pos: Position::none(),
|
||||||
|
13
src/token.rs
13
src/token.rs
@ -1,6 +1,10 @@
|
|||||||
//! Main module defining the lexer and parser.
|
//! Main module defining the lexer and parser.
|
||||||
|
|
||||||
use crate::engine::Engine;
|
use crate::engine::{
|
||||||
|
Engine, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_PRINT,
|
||||||
|
KEYWORD_THIS, KEYWORD_TYPE_OF,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::error::LexError;
|
use crate::error::LexError;
|
||||||
use crate::parser::INT;
|
use crate::parser::INT;
|
||||||
use crate::utils::StaticVec;
|
use crate::utils::StaticVec;
|
||||||
@ -388,6 +392,8 @@ impl Token {
|
|||||||
"===" | "!==" | "->" | "<-" | "=>" | ":=" | "::<" | "(*" | "*)" | "#" => {
|
"===" | "!==" | "->" | "<-" | "=>" | ":=" | "::<" | "(*" | "*)" | "#" => {
|
||||||
Reserved(syntax.into())
|
Reserved(syntax.into())
|
||||||
}
|
}
|
||||||
|
KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR
|
||||||
|
| KEYWORD_FN_PTR_CALL | KEYWORD_THIS => Reserved(syntax.into()),
|
||||||
|
|
||||||
_ => return None,
|
_ => return None,
|
||||||
})
|
})
|
||||||
@ -1353,9 +1359,10 @@ impl<'a> Iterator for TokenIterator<'a, '_> {
|
|||||||
"'#' is not a valid symbol. Should it be '#{'?"
|
"'#' is not a valid symbol. Should it be '#{'?"
|
||||||
.to_string(),
|
.to_string(),
|
||||||
))),
|
))),
|
||||||
token => Token::LexError(Box::new(LERR::ImproperSymbol(
|
token if !is_valid_identifier(token.chars()) => Token::LexError(Box::new(LERR::ImproperSymbol(
|
||||||
format!("'{}' is not a valid symbol.", token)
|
format!("'{}' is a reserved symbol.", token)
|
||||||
))),
|
))),
|
||||||
|
_ => Token::Reserved(s)
|
||||||
}, pos)),
|
}, pos)),
|
||||||
(r @ Some(_), None, None) => r,
|
(r @ Some(_), None, None) => r,
|
||||||
(Some((Token::Identifier(s), pos)), _, Some(custom)) if custom.contains_key(&s) => {
|
(Some((Token::Identifier(s), pos)), _, Some(custom)) if custom.contains_key(&s) => {
|
||||||
|
@ -52,12 +52,24 @@ fn test_functions() -> Result<(), Box<EvalAltResult>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(not(feature = "no_object"))]
|
|
||||||
fn test_function_pointers() -> Result<(), Box<EvalAltResult>> {
|
fn test_function_pointers() -> Result<(), Box<EvalAltResult>> {
|
||||||
let engine = Engine::new();
|
let engine = Engine::new();
|
||||||
|
|
||||||
assert_eq!(engine.eval::<String>(r#"type_of(Fn("abc"))"#)?, "Fn");
|
assert_eq!(engine.eval::<String>(r#"type_of(Fn("abc"))"#)?, "Fn");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<INT>(
|
||||||
|
r#"
|
||||||
|
fn foo(x) { 40 + x }
|
||||||
|
|
||||||
|
let f = Fn("foo");
|
||||||
|
call(f, 2)
|
||||||
|
"#
|
||||||
|
)?,
|
||||||
|
42
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<INT>(
|
engine.eval::<INT>(
|
||||||
r#"
|
r#"
|
||||||
@ -73,11 +85,13 @@ fn test_function_pointers() -> Result<(), Box<EvalAltResult>> {
|
|||||||
42
|
42
|
||||||
);
|
);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
*engine.eval::<INT>(r#"let f = Fn("abc"); f.call(0)"#).expect_err("should error"),
|
*engine.eval::<INT>(r#"let f = Fn("abc"); f.call(0)"#).expect_err("should error"),
|
||||||
EvalAltResult::ErrorFunctionNotFound(f, _) if f.starts_with("abc (")
|
EvalAltResult::ErrorFunctionNotFound(f, _) if f.starts_with("abc (")
|
||||||
));
|
));
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<INT>(
|
engine.eval::<INT>(
|
||||||
r#"
|
r#"
|
||||||
|
Loading…
Reference in New Issue
Block a user