Enable call in function-call style.

This commit is contained in:
Stephen Chung
2020-07-16 12:09:31 +08:00
parent a97ffc536c
commit 16fbfbb606
6 changed files with 102 additions and 29 deletions

View File

@@ -944,7 +944,7 @@ impl Engine {
}
// 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
// First check script-defined functions
@@ -986,13 +986,15 @@ impl Engine {
match fn_name {
// type_of
KEYWORD_TYPE_OF if args.len() == 1 && !self.has_override(lib, hashes) => Ok((
self.map_type_name(args[0].type_name()).to_string().into(),
false,
)),
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(),
false,
))
}
// 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(
"'Fn' should not be called in method style. Try Fn(...);".into(),
Position::none(),
@@ -1000,7 +1002,7 @@ impl Engine {
}
// 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(
"'eval' should not be called in method style. Try eval(...);".into(),
Position::none(),
@@ -1926,7 +1928,7 @@ impl Engine {
let hash_fn =
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
let expr = args_expr.get(0);
let arg_value =
@@ -1952,7 +1954,7 @@ impl Engine {
let hash_fn =
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
let prev_len = scope.len();
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)
let mut arg_values: StaticVec<Dynamic>;
let mut args: StaticVec<_>;
@@ -1983,7 +2015,7 @@ impl Engine {
} else {
// 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
match args_expr.get(0) {
match args_expr.get(0).unwrap() {
// func(x, ...) -> x.func(...)
lhs @ Expr::Variable(_) => {
arg_values = args_expr
@@ -2020,7 +2052,7 @@ impl Engine {
let args = args.as_mut();
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_err(|err| err.new_position(*pos))

View File

@@ -7,7 +7,7 @@ use crate::error::{LexError, ParseError, ParseErrorType};
use crate::module::{Module, ModuleRef};
use crate::optimize::{optimize_into_ast, OptimizationLevel};
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};
#[cfg(feature = "internals")]
@@ -440,6 +440,8 @@ struct ParseSettings {
pos: Position,
/// Is the construct being parsed located at global level?
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_breakable: bool,
/// Is anonymous function allowed?
@@ -1460,6 +1462,21 @@ fn parse_primary(
let index = state.find_var(&s);
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())?,
#[cfg(not(feature = "no_index"))]
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)
}
@@ -2315,16 +2333,6 @@ fn parse_let(
(_, 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 = ...
if match_token(input, Token::Equals)? {
// let name = expr
@@ -2598,6 +2606,7 @@ fn parse_stmt(
allow_stmt_expr: true,
allow_anonymous_fn: true,
is_global: false,
is_function_scope: true,
is_breakable: false,
level: 0,
pos: pos,
@@ -2695,7 +2704,11 @@ fn parse_fn(
settings.ensure_level_within_max_limit(state.max_expr_depth)?;
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)),
};
@@ -2790,6 +2803,7 @@ impl Engine {
allow_stmt_expr: false,
allow_anonymous_fn: false,
is_global: true,
is_function_scope: false,
is_breakable: false,
level: 0,
pos: Position::none(),
@@ -2829,6 +2843,7 @@ impl Engine {
allow_stmt_expr: true,
allow_anonymous_fn: true,
is_global: true,
is_function_scope: false,
is_breakable: false,
level: 0,
pos: Position::none(),

View File

@@ -1,6 +1,10 @@
//! 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::parser::INT;
use crate::utils::StaticVec;
@@ -388,6 +392,8 @@ impl Token {
"===" | "!==" | "->" | "<-" | "=>" | ":=" | "::<" | "(*" | "*)" | "#" => {
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,
})
@@ -1353,9 +1359,10 @@ impl<'a> Iterator for TokenIterator<'a, '_> {
"'#' is not a valid symbol. Should it be '#{'?"
.to_string(),
))),
token => Token::LexError(Box::new(LERR::ImproperSymbol(
format!("'{}' is not a valid symbol.", token)
token if !is_valid_identifier(token.chars()) => Token::LexError(Box::new(LERR::ImproperSymbol(
format!("'{}' is a reserved symbol.", token)
))),
_ => Token::Reserved(s)
}, pos)),
(r @ Some(_), None, None) => r,
(Some((Token::Identifier(s), pos)), _, Some(custom)) if custom.contains_key(&s) => {