Refine what can be called in method style.

This commit is contained in:
Stephen Chung 2022-12-27 22:06:51 +08:00
parent 7c00b74916
commit bb1136e8ad
6 changed files with 81 additions and 77 deletions

View File

@ -11,6 +11,7 @@ Bug fixes
* Parsing deeply-nested closures (e.g. `||{||{||{||{||{||{||{...}}}}}}}`) no longer panics but will be confined to the nesting limit.
* Closures containing a single expression are now allowed in `Engine::eval_expression` etc.
* Strings interpolation now works under `Engine::new_raw` without any standard package.
* `Fn` now throws an error if the name is a reserved keyword as it cannot possibly map to such a function. This also disallows creating function pointers to custom operators which are defined as disabled keywords (a mouthful), but such custom operators are designed primarily to be used as operators.
Breaking API changes
--------------------
@ -60,6 +61,7 @@ Net features
* Array methods that take a function pointer, usually a closure (e.g. `map`, `filter`, `index_of`, `reduce` etc.), can now bind the array element to `this` when calling a closure.
* This vastly improves performance when working with arrays of large types (e.g. object maps) by avoiding unnecessary cloning.
* `find` and `find_map` are added for arrays.
* `for_each` is also added for arrays, allowing a closure to mutate array elements (bound to `this`) in turn.
Enhancements
@ -79,7 +81,6 @@ Enhancements
* `FnPtr::iter_curry` and `FnPtr::iter_curry_mut` are added.
* `Dynamic::deep_scan` is added to recursively scan for `Dynamic` values.
* `>>` and `<<` operators on integers no longer throw errors when the number of bits to shift is out of bounds. Shifting by a negative number of bits simply reverses the shift direction.
* `find` and `find_map` are added for arrays.
Version 1.11.0

View File

@ -4,7 +4,7 @@
use crate::ast::Expr;
use crate::func::SendSync;
use crate::parser::ParseResult;
use crate::tokenizer::{is_valid_identifier, Token, NO_TOKEN};
use crate::tokenizer::{is_reserved_keyword_or_symbol, is_valid_identifier, Token, NO_TOKEN};
use crate::types::dynamic::Variant;
use crate::{
Dynamic, Engine, EvalContext, Identifier, ImmutableString, LexError, Position, RhaiResult,
@ -232,7 +232,7 @@ impl Engine {
}
let token = Token::lookup_symbol_from_syntax(s).unwrap_or_else(|| {
if Token::is_reserved_keyword(s) {
if is_reserved_keyword_or_symbol(s) {
Token::Reserved(Box::new(s.into()))
} else {
NO_TOKEN

View File

@ -575,14 +575,6 @@ impl Engine {
_is_method_call: bool,
pos: Position,
) -> RhaiResultOf<(Dynamic, bool)> {
fn no_method_err(name: &str, pos: Position) -> RhaiResultOf<(Dynamic, bool)> {
Err(ERR::ErrorRuntime(
format!("'{name}' should not be called this way. Try {name}(...);").into(),
pos,
)
.into())
}
// Check for data race.
#[cfg(not(feature = "no_closure"))]
ensure_no_data_race(fn_name, _args, is_ref_mut)?;
@ -622,16 +614,13 @@ impl Engine {
// Handle is_shared()
#[cfg(not(feature = "no_closure"))]
crate::engine::KEYWORD_IS_SHARED if _args.len() == 1 => {
return no_method_err(fn_name, pos)
crate::engine::KEYWORD_IS_SHARED => {
unreachable!("{} called as method", fn_name)
}
KEYWORD_FN_PTR | KEYWORD_EVAL | KEYWORD_IS_DEF_VAR if _args.len() == 1 => {
return no_method_err(fn_name, pos)
}
KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY if !_args.is_empty() => {
return no_method_err(fn_name, pos)
KEYWORD_FN_PTR | KEYWORD_EVAL | KEYWORD_IS_DEF_VAR | KEYWORD_FN_PTR_CALL
| KEYWORD_FN_PTR_CURRY => {
unreachable!("{} called as method", fn_name)
}
_ => (),

View File

@ -1657,7 +1657,7 @@ impl Engine {
match input.peek().expect(NEVER_ENDS).0 {
// Function call is allowed to have reserved keyword
Token::LeftParen | Token::Bang | Token::Unit if is_keyword_function(&s) => {
Token::LeftParen | Token::Bang | Token::Unit if is_keyword_function(&s).0 => {
Expr::Variable(
(None, ns, 0, state.get_interned_string(*s)).into(),
None,
@ -1800,7 +1800,10 @@ impl Engine {
state.allow_capture = false;
}
}
(Token::Reserved(s), ..) if is_keyword_function(s) => (),
(Token::Reserved(s), ..) if is_keyword_function(s).1 => (),
(Token::Reserved(s), pos) => {
return Err(PERR::Reserved(s.to_string()).into_err(*pos))
}
(.., pos) => return Err(PERR::PropertyExpected.into_err(*pos)),
}
@ -2109,9 +2112,7 @@ impl Engine {
}
// lhs.module::id - syntax error
#[cfg(not(feature = "no_module"))]
(.., Expr::Variable(x, ..)) if !x.1.is_empty() => {
Err(PERR::PropertyExpected.into_err(x.1.position()))
}
(.., Expr::Variable(x, ..)) if !x.1.is_empty() => unreachable!("lhs.ns::id"),
// lhs.id
(lhs, var_expr @ Expr::Variable(..)) => {
let rhs = var_expr.into_property(state);
@ -2125,9 +2126,7 @@ impl Engine {
)),
// lhs.nnn::func(...) - syntax error
#[cfg(not(feature = "no_module"))]
(.., Expr::FnCall(f, ..)) if f.is_qualified() => {
Err(PERR::PropertyExpected.into_err(f.namespace.position()))
}
(.., Expr::FnCall(f, ..)) if f.is_qualified() => unreachable!("lhs.ns::func()"),
// lhs.Fn() or lhs.eval()
(.., Expr::FnCall(f, func_pos))
if f.args.is_empty()
@ -2174,13 +2173,11 @@ impl Engine {
match x.lhs {
// lhs.module::id.dot_rhs or lhs.module::id[idx_rhs] - syntax error
#[cfg(not(feature = "no_module"))]
Expr::Variable(x, ..) if !x.1.is_empty() => {
Err(PERR::PropertyExpected.into_err(x.1.position()))
}
Expr::Variable(x, ..) if !x.1.is_empty() => unreachable!("lhs.ns::id..."),
// lhs.module::func().dot_rhs or lhs.module::func()[idx_rhs] - syntax error
#[cfg(not(feature = "no_module"))]
Expr::FnCall(f, ..) if f.is_qualified() => {
Err(PERR::PropertyExpected.into_err(f.namespace.position()))
unreachable!("lhs.ns::func()...")
}
// lhs.id.dot_rhs or lhs.id[idx_rhs]
Expr::Variable(..) | Expr::Property(..) => {

View File

@ -631,41 +631,6 @@ impl Token {
})
}
/// Is a piece of syntax a reserved keyword?
#[must_use]
pub fn is_reserved_keyword(syntax: &str) -> bool {
match syntax {
#[cfg(feature = "no_object")]
"?." => true,
#[cfg(feature = "no_index")]
"?[" => true,
#[cfg(feature = "no_function")]
"fn" | "private" => true,
#[cfg(feature = "no_module")]
"import" | "export" | "as" => true,
// List of reserved operators
"===" | "!==" | "->" | "<-" | "?" | ":=" | ":;" | "~" | "!." | "::<" | "(*" | "*)"
| "#" | "#!" | "@" | "$" | "++" | "--" | "..." | "<|" | "|>" => true,
// List of reserved keywords
"public" | "protected" | "super" | "new" | "use" | "module" | "package" | "var"
| "static" | "shared" | "with" | "is" | "goto" | "exit" | "match" | "case"
| "default" | "void" | "null" | "nil" | "spawn" | "thread" | "go" | "sync"
| "async" | "await" | "yield" => true,
KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR
| KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_THIS | KEYWORD_IS_DEF_VAR => {
true
}
#[cfg(not(feature = "no_function"))]
crate::engine::KEYWORD_IS_DEF_FN => true,
_ => false,
}
}
/// If another operator is after these, it's probably a unary operator
/// (not sure about `fn` name).
#[must_use]
@ -1994,7 +1959,8 @@ fn parse_identifier_token(
if let Some(token) = Token::lookup_symbol_from_syntax(&identifier) {
return (token, start_pos);
}
if Token::is_reserved_keyword(&identifier) {
if is_reserved_keyword_or_symbol(&identifier) {
return (Token::Reserved(Box::new(identifier)), start_pos);
}
@ -2008,18 +1974,27 @@ fn parse_identifier_token(
(Token::Identifier(identifier.into()), start_pos)
}
/// Is a keyword allowed as a function?
/// Can a keyword be called like a function?
///
/// # Return values
///
/// The first `bool` indicates whether the keyword can be called normally as a function.
///
/// The second `bool` indicates whether the keyword can be called in method-call style.
#[inline]
#[must_use]
pub fn is_keyword_function(name: &str) -> bool {
pub fn is_keyword_function(name: &str) -> (bool, bool) {
match name {
KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR
| KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_IS_DEF_VAR => true,
KEYWORD_TYPE_OF | KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY => (true, true),
KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_EVAL | KEYWORD_FN_PTR | KEYWORD_IS_DEF_VAR => {
(true, false)
}
#[cfg(not(feature = "no_function"))]
crate::engine::KEYWORD_IS_DEF_FN => true,
crate::engine::KEYWORD_IS_DEF_FN => (true, false),
_ => false,
_ => (false, false),
}
}
@ -2047,7 +2022,9 @@ pub fn is_valid_identifier(name: &str) -> bool {
#[inline(always)]
#[must_use]
pub fn is_valid_function_name(name: &str) -> bool {
is_valid_identifier(name) && !is_keyword_function(name)
is_valid_identifier(name)
&& !is_reserved_keyword_or_symbol(name)
&& Token::lookup_symbol_from_syntax(name).is_none()
}
/// Is a character valid to start an identifier?
@ -2082,6 +2059,39 @@ pub const fn is_id_continue(x: char) -> bool {
x.is_ascii_alphanumeric() || x == '_'
}
/// Is a piece of syntax a reserved keyword or symbol?
#[must_use]
pub fn is_reserved_keyword_or_symbol(syntax: &str) -> bool {
match syntax {
#[cfg(feature = "no_object")]
"?." => true,
#[cfg(feature = "no_index")]
"?[" => true,
#[cfg(feature = "no_function")]
"fn" | "private" => true,
#[cfg(feature = "no_module")]
"import" | "export" | "as" => true,
// List of reserved operators
"===" | "!==" | "->" | "<-" | "?" | ":=" | ":;" | "~" | "!." | "::<" | "(*" | "*)"
| "#" | "#!" | "@" | "$" | "++" | "--" | "..." | "<|" | "|>" => true,
// List of reserved keywords
"public" | "protected" | "super" | "new" | "use" | "module" | "package" | "var"
| "static" | "shared" | "with" | "is" | "goto" | "exit" | "match" | "case" | "default"
| "void" | "null" | "nil" | "spawn" | "thread" | "go" | "sync" | "async" | "await"
| "yield" => true,
KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR
| KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_THIS | KEYWORD_IS_DEF_VAR => true,
#[cfg(not(feature = "no_function"))]
crate::engine::KEYWORD_IS_DEF_FN => true,
_ => false,
}
}
/// _(internals)_ A type that implements the [`InputStream`] trait.
/// Exported under the `internals` feature only.
///

View File

@ -2,11 +2,11 @@
use crate::eval::GlobalRuntimeState;
use crate::func::EncapsulatedEnviron;
use crate::tokenizer::is_valid_function_name;
use crate::tokenizer::{is_reserved_keyword_or_symbol, is_valid_function_name, Token};
use crate::types::dynamic::Variant;
use crate::{
Dynamic, Engine, FnArgsVec, FuncArgs, ImmutableString, NativeCallContext, Position, RhaiError,
RhaiResult, RhaiResultOf, Shared, StaticVec, AST, ERR,
Dynamic, Engine, FnArgsVec, FuncArgs, ImmutableString, NativeCallContext, ParseErrorType,
Position, RhaiError, RhaiResult, RhaiResultOf, Shared, StaticVec, AST, ERR,
};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
@ -536,6 +536,13 @@ impl TryFrom<ImmutableString> for FnPtr {
#[cfg(not(feature = "no_function"))]
fn_def: None,
})
} else if is_reserved_keyword_or_symbol(&value)
|| Token::lookup_symbol_from_syntax(&value).is_some()
{
Err(
ERR::ErrorParsing(ParseErrorType::Reserved(value.to_string()), Position::NONE)
.into(),
)
} else {
Err(ERR::ErrorFunctionNotFound(value.to_string(), Position::NONE).into())
}