Merge branch 'master' into plugins

This commit is contained in:
Stephen Chung 2020-07-26 22:38:35 +08:00
commit 4bda306815
11 changed files with 196 additions and 250 deletions

View File

@ -6,24 +6,32 @@ Version 0.18.0
This version adds: This version adds:
* Anonymous functions (in closure syntax). Simplifies creation of ad hoc functions. * Binding the `this` pointer in a function pointer `call`.
* Anonymous functions (in Rust closure syntax). Simplifies creation of single-use ad-hoc functions.
* Currying of function pointers. * Currying of function pointers.
New features New features
------------ ------------
* `call` can now be called function-call style for function pointers - this is to handle builds with `no_object`. * `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. * Reserve language keywords, such as `print`, `eval`, `call`, `this` etc.
* `x.call(f, ...)` allows binding `x` to `this` for the function referenced by the function pointer `f`. * `x.call(f, ...)` allows binding `x` to `this` for the function referenced by the function pointer `f`.
* Anonymous functions in the syntax of a closure, e.g. `|x, y, z| x + y - z`. * Anonymous functions are supported in the syntax of a Rust closure, e.g. `|x, y, z| x + y - z`.
* Custom syntax now works even without the `internals` feature. * Custom syntax now works even without the `internals` feature.
* Currying of function pointers is supported via the `curry` keyword. * Currying of function pointers is supported via the new `curry` keyword.
* `Module::set_indexer_get_set_fn` is added as a shorthand of both `Module::set_indexer_get_fn` and `Module::set_indexer_set_fn`. * `Module::set_indexer_get_set_fn` is added as a shorthand of both `Module::set_indexer_get_fn` and `Module::set_indexer_set_fn`.
Breaking changes Breaking changes
---------------- ----------------
* Language keywords are now _reserved_ (even when disabled) and they can no longer be used as variable names.
* Function signature for defining custom syntax is simplified. * Function signature for defining custom syntax is simplified.
* `Engine::register_raw_fn_XXX` API shortcuts are removed.
Housekeeping
------------
* Most compilation warnings are eliminated via feature gates.
Version 0.17.0 Version 0.17.0
@ -57,7 +65,7 @@ New features
* `Engine::disable_symbol` to surgically disable keywords and/or operators. * `Engine::disable_symbol` to surgically disable keywords and/or operators.
* `Engine::register_custom_operator` to define a custom operator. * `Engine::register_custom_operator` to define a custom operator.
* `Engine::register_custom_syntax` to define a custom syntax. * `Engine::register_custom_syntax` to define a custom syntax.
* New low-level API `Engine::register_raw_fn` and `Engine::register_raw_fn_XXX`. * New low-level API `Engine::register_raw_fn`.
* New low-level API `Module::set_raw_fn` mirroring `Engine::register_raw_fn`. * New low-level API `Module::set_raw_fn` mirroring `Engine::register_raw_fn`.
* `AST::clone_functions_only`, `AST::clone_functions_only_filtered` and `AST::clone_statements_only` to clone only part of an `AST`. * `AST::clone_functions_only`, `AST::clone_functions_only_filtered` and `AST::clone_statements_only` to clone only part of an `AST`.
* The boolean `^` (XOR) operator is added. * The boolean `^` (XOR) operator is added.

View File

@ -3,32 +3,66 @@ Keywords List
{{#include ../links.md}} {{#include ../links.md}}
| Keyword | Description | Not available under | | Keyword | Description | Inactive under |
| :-------------------: | ---------------------------------------- | :-----------------: | | :-------------------: | ---------------------------------------- | :-------------: |
| `true` | Boolean true literal | | | `true` | Boolean true literal | |
| `false` | Boolean false literal | | | `false` | Boolean false literal | |
| `let` | Variable declaration | | | `let` | Variable declaration | |
| `const` | Constant declaration | | | `const` | Constant declaration | |
| `if` | If statement | | | `if` | If statement | |
| `else` | else block of if statement | | | `else` | else block of if statement | |
| `while` | While loop | | | `while` | While loop | |
| `loop` | Infinite loop | | | `loop` | Infinite loop | |
| `for` | For loop | | | `for` | For loop | |
| `in` | Containment test, part of for loop | | | `in` | Containment test, part of for loop | |
| `continue` | Continue a loop at the next iteration | | | `continue` | Continue a loop at the next iteration | |
| `break` | Loop breaking | | | `break` | Loop breaking | |
| `return` | Return value | | | `return` | Return value | |
| `throw` | Throw exception | | | `throw` | Throw exception | |
| `import` | Import module | [`no_module`] | | `import` | Import module | [`no_module`] |
| `export` | Export variable | [`no_module`] | | `export` | Export variable | [`no_module`] |
| `as` | Alias for variable export | [`no_module`] | | `as` | Alias for variable export | [`no_module`] |
| `private` | Mark function private | [`no_function`] | | `private` | Mark function private | [`no_function`] |
| `fn` (lower-case `f`) | Function definition | [`no_function`] | | `fn` (lower-case `f`) | Function definition | [`no_function`] |
| `Fn` (capital `F`) | Function to create a [function pointer] | | | `Fn` (capital `F`) | Function to create a [function pointer] | |
| `call` | Call a [function pointer] | | | `call` | Call a [function pointer] | |
| `curry` | Curry a [function pointer] | | | `curry` | Curry a [function pointer] | |
| `this` | Reference to base object for method call | [`no_function`] | | `this` | Reference to base object for method call | [`no_function`] |
| `type_of` | Get type name of value | | | `type_of` | Get type name of value | |
| `print` | Print value | | | `print` | Print value | |
| `debug` | Print value in debug format | | | `debug` | Print value in debug format | |
| `eval` | Evaluate script | | | `eval` | Evaluate script | |
Reserved Keywords
-----------------
| Keyword | Potential usage |
| --------- | --------------------- |
| `var` | Variable declaration |
| `static` | Variable declaration |
| `do` | Looping |
| `each` | Looping |
| `then` | Control flow |
| `goto` | Control flow |
| `exit` | Control flow |
| `switch` | Matching |
| `match` | Matching |
| `case` | Matching |
| `public` | Function/field access |
| `new` | Constructor |
| `try` | Trap exception |
| `catch` | Catch exception |
| `use` | Import namespace |
| `with` | Scope |
| `module` | Module |
| `package` | Package |
| `spawn` | Threading |
| `go` | Threading |
| `await` | Async |
| `async` | Async |
| `yield` | Async |
| `default` | Special value |
| `void` | Special value |
| `null` | Special value |
| `nil` | Special value |

View File

@ -114,10 +114,7 @@ Any custom syntax must include an _implementation_ of it.
The function signature of an implementation is: The function signature of an implementation is:
```rust > `Fn(engine: &Engine, context: &mut EvalContext, scope: &mut Scope, inputs: &[Expression]) -> Result<Dynamic, Box<EvalAltResult>>`
Fn(engine: &Engine, context: &mut EvalContext, scope: &mut Scope, inputs: &[Expression])
-> Result<Dynamic, Box<EvalAltResult>>
```
where: where:

View File

@ -5,18 +5,21 @@ Keywords
The following are reserved keywords in Rhai: The following are reserved keywords in Rhai:
| Keywords | Usage | Not available under feature | | Active keywords | Reserved keywords | Usage | Inactive under feature |
| ------------------------------------------------- | --------------------- | :-------------------------: | | ------------------------------------------------- | ---------------------------------------- | --------------------- | :--------------------: |
| `true`, `false` | Boolean constants | | | `true`, `false` | | Boolean constants | |
| `let`, `const` | Variable declarations | | | `let`, `const` | `var`, `static` | Variable declarations | |
| `if`, `else` | Control flow | | | `if`, `else` | `then`, `goto`, `exit` | Control flow | |
| `while`, `loop`, `for`, `in`, `continue`, `break` | Looping | | | | `switch`, `match`, `case` | Matching | |
| `fn`, `private` | Functions | [`no_function`] | | `while`, `loop`, `for`, `in`, `continue`, `break` | `do`, `each` | Looping | |
| `return` | Return values | | | `fn`, `private` | `public`, `new` | Functions | [`no_function`] |
| `throw` | throw exceptions | | | `return` | | Return values | |
| `import`, `export`, `as` | Modules | [`no_module`] | | `throw` | `try`, `catch` | Throw exceptions | |
| `Fn`, `call` | Function pointers | | | `import`, `export`, `as` | `use`, `with`, `module`, `package` | Modules/packages | [`no_module`] |
| `type_of`, `print`, `debug`, `eval` | Special functions | | | `Fn`, `call`, `curry` | | Function pointers | |
| | `spawn`, `go`, `async`, `await`, `yield` | Threading/async | |
| `type_of`, `print`, `debug`, `eval` | | Special functions | |
| | `default`, `void`, `null`, `nil` | Special values | |
Keywords cannot become the name of a [function] or [variable], even when they are disabled.
Keywords cannot be the name of a [function] or [variable], unless the relevant feature is enabled.
For example, `fn` is a valid variable name under [`no_function`].

View File

@ -55,27 +55,12 @@ engine.register_fn("increment_by", |x: &mut i64, y: i64| x += y);
``` ```
Shortcuts Function Signature
--------- ------------------
As usual with Rhai, there are shortcuts. For functions of zero to four parameters, which should be The function signature passed to `Engine::register_raw_fn` takes the following form:
the majority, use one of the `Engine::register_raw_fn_n` (where `n = 0..4`) methods:
```rust > `Fn(engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]) -> Result<T, Box<EvalAltResult>> + 'static`
// Specify parameter types as generics
engine.register_raw_fn_2::<i64, i64>(
"increment_by",
|engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| { ... }
);
```
Closure Signature
-----------------
The closure passed to `Engine::register_raw_fn` takes the following form:
`Fn(engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]) -> Result<T, Box<EvalAltResult>> + 'static`
where: where:
@ -113,6 +98,12 @@ there can be no other immutable references to `args`, otherwise the Rust borrow
Example - Passing a Function Pointer to a Rust Function Example - Passing a Function Pointer to a Rust Function
------------------------------------------------------ ------------------------------------------------------
The low-level API is useful when there is a need to interact with the scripting [`Engine`] within a function.
The following example registers a function that takes a [function pointer] as an argument,
then calls it within the same [`Engine`]. This way, a _callback_ function can be provided
to a native Rust function.
```rust ```rust
use rhai::{Engine, Module, Dynamic, FnPtr}; use rhai::{Engine, Module, Dynamic, FnPtr};
@ -133,11 +124,10 @@ engine.register_raw_fn(
let value = args[2].clone(); // 3rd argument - function argument let value = args[2].clone(); // 3rd argument - function argument
let this_ptr = args.get_mut(0).unwrap(); // 1st argument - this pointer let this_ptr = args.get_mut(0).unwrap(); // 1st argument - this pointer
// Use 'call_fn_dynamic' to call the function name. // Use 'FnPtr::call_dynamic' to call the function pointer.
// Pass 'lib' as the current global library of functions. // Beware, only script-defined functions are supported by 'FnPtr::call_dynamic'.
engine.call_fn_dynamic(&mut Scope::new(), lib, fp.fn_name(), Some(this_ptr), [value])?; // If it is a native Rust function, directly call it here in Rust instead!
fp.call_dynamic(engine, lib, Some(this_ptr), [value])
Ok(())
}, },
); );

View File

@ -19,6 +19,7 @@ use crate::engine::{FN_IDX_GET, FN_IDX_SET};
use crate::{ use crate::{
engine::{make_getter, make_setter, Map}, engine::{make_getter, make_setter, Map},
fn_register::RegisterFn, fn_register::RegisterFn,
token::Token,
}; };
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
@ -62,147 +63,6 @@ impl Engine {
self self
} }
/// Register a function of no parameters with the `Engine`.
///
/// ## WARNING - Low Level API
///
/// This function is very low level.
#[deprecated(note = "this function is volatile and may change")]
pub fn register_raw_fn_0<T: Variant + Clone>(
&mut self,
name: &str,
func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> FuncReturn<T> + SendSync + 'static,
) -> &mut Self {
self.global_module.set_raw_fn(name, &[], func);
self
}
/// Register a function of one parameter with the `Engine`.
///
/// ## WARNING - Low Level API
///
/// This function is very low level.
///
/// Arguments are simply passed in as a mutable array of `&mut Dynamic`,
/// which is guaranteed to contain enough arguments of the correct types.
///
/// To access a primary parameter value (i.e. cloning is cheap), use: `args[n].clone().cast::<T>()`
///
/// To access a parameter value and avoid cloning, use `std::mem::take(args[n]).cast::<T>()`.
/// Notice that this will _consume_ the argument, replacing it with `()`.
///
/// To access the first mutable parameter, use `args.get_mut(0).unwrap()`
#[deprecated(note = "this function is volatile and may change")]
pub fn register_raw_fn_1<A: Variant + Clone, T: Variant + Clone>(
&mut self,
name: &str,
func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> FuncReturn<T> + SendSync + 'static,
) -> &mut Self {
self.global_module
.set_raw_fn(name, &[TypeId::of::<A>()], func);
self
}
/// Register a function of two parameters with the `Engine`.
///
/// ## WARNING - Low Level API
///
/// This function is very low level.
///
/// Arguments are simply passed in as a mutable array of `&mut Dynamic`,
/// which is guaranteed to contain enough arguments of the correct types.
///
/// To access a primary parameter value (i.e. cloning is cheap), use: `args[n].clone().cast::<T>()`
///
/// To access a parameter value and avoid cloning, use `std::mem::take(args[n]).cast::<T>()`.
/// Notice that this will _consume_ the argument, replacing it with `()`.
///
/// To access the first mutable parameter, use `args.get_mut(0).unwrap()`
#[deprecated(note = "this function is volatile and may change")]
pub fn register_raw_fn_2<A: Variant + Clone, B: Variant + Clone, T: Variant + Clone>(
&mut self,
name: &str,
func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> FuncReturn<T> + SendSync + 'static,
) -> &mut Self {
self.global_module
.set_raw_fn(name, &[TypeId::of::<A>(), TypeId::of::<B>()], func);
self
}
/// Register a function of three parameters with the `Engine`.
///
/// ## WARNING - Low Level API
///
/// This function is very low level.
///
/// Arguments are simply passed in as a mutable array of `&mut Dynamic`,
/// which is guaranteed to contain enough arguments of the correct types.
///
/// To access a primary parameter value (i.e. cloning is cheap), use: `args[n].clone().cast::<T>()`
///
/// To access a parameter value and avoid cloning, use `std::mem::take(args[n]).cast::<T>()`.
/// Notice that this will _consume_ the argument, replacing it with `()`.
///
/// To access the first mutable parameter, use `args.get_mut(0).unwrap()`
#[deprecated(note = "this function is volatile and may change")]
pub fn register_raw_fn_3<
A: Variant + Clone,
B: Variant + Clone,
C: Variant + Clone,
T: Variant + Clone,
>(
&mut self,
name: &str,
func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> FuncReturn<T> + SendSync + 'static,
) -> &mut Self {
self.global_module.set_raw_fn(
name,
&[TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()],
func,
);
self
}
/// Register a function of four parameters with the `Engine`.
///
/// ## WARNING - Low Level API
///
/// This function is very low level.
///
/// Arguments are simply passed in as a mutable array of `&mut Dynamic`,
/// which is guaranteed to contain enough arguments of the correct types.
///
/// To access a primary parameter value (i.e. cloning is cheap), use: `args[n].clone().cast::<T>()`
///
/// To access a parameter value and avoid cloning, use `std::mem::take(args[n]).cast::<T>()`.
/// Notice that this will _consume_ the argument, replacing it with `()`.
///
/// To access the first mutable parameter, use `args.get_mut(0).unwrap()`
#[deprecated(note = "this function is volatile and may change")]
pub fn register_raw_fn_4<
A: Variant + Clone,
B: Variant + Clone,
C: Variant + Clone,
D: Variant + Clone,
T: Variant + Clone,
>(
&mut self,
name: &str,
func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> FuncReturn<T> + SendSync + 'static,
) -> &mut Self {
self.global_module.set_raw_fn(
name,
&[
TypeId::of::<A>(),
TypeId::of::<B>(),
TypeId::of::<C>(),
TypeId::of::<D>(),
],
func,
);
self
}
/// Register a custom type for use with the `Engine`. /// Register a custom type for use with the `Engine`.
/// The type must implement `Clone`. /// The type must implement `Clone`.
/// ///
@ -731,7 +591,7 @@ impl Engine {
scripts: &[&str], scripts: &[&str],
optimization_level: OptimizationLevel, optimization_level: OptimizationLevel,
) -> Result<AST, ParseError> { ) -> Result<AST, ParseError> {
let stream = lex(scripts, self); let stream = lex(scripts, None, self);
self.parse(&mut stream.peekable(), scope, optimization_level) self.parse(&mut stream.peekable(), scope, optimization_level)
} }
@ -856,7 +716,19 @@ impl Engine {
// Trims the JSON string and add a '#' in front // Trims the JSON string and add a '#' in front
let scripts = ["#", json.trim()]; let scripts = ["#", json.trim()];
let stream = lex(&scripts, self); let stream = lex(
&scripts,
if has_null {
Some(Box::new(|token| match token {
// If `null` is present, make sure `null` is treated as a variable
Token::Reserved(s) if s == "null" => Token::Identifier(s),
_ => token,
}))
} else {
None
},
self,
);
let ast = let ast =
self.parse_global_expr(&mut stream.peekable(), &scope, OptimizationLevel::None)?; self.parse_global_expr(&mut stream.peekable(), &scope, OptimizationLevel::None)?;
@ -937,7 +809,7 @@ impl Engine {
script: &str, script: &str,
) -> Result<AST, ParseError> { ) -> Result<AST, ParseError> {
let scripts = [script]; let scripts = [script];
let stream = lex(&scripts, self); let stream = lex(&scripts, None, self);
{ {
let mut peekable = stream.peekable(); let mut peekable = stream.peekable();
self.parse_global_expr(&mut peekable, scope, self.optimization_level) self.parse_global_expr(&mut peekable, scope, self.optimization_level)
@ -1092,7 +964,7 @@ impl Engine {
script: &str, script: &str,
) -> Result<T, Box<EvalAltResult>> { ) -> Result<T, Box<EvalAltResult>> {
let scripts = [script]; let scripts = [script];
let stream = lex(&scripts, self); let stream = lex(&scripts, None, self);
// No need to optimize a lone expression // No need to optimize a lone expression
let ast = self.parse_global_expr(&mut stream.peekable(), scope, OptimizationLevel::None)?; let ast = self.parse_global_expr(&mut stream.peekable(), scope, OptimizationLevel::None)?;
@ -1225,7 +1097,7 @@ impl Engine {
script: &str, script: &str,
) -> Result<(), Box<EvalAltResult>> { ) -> Result<(), Box<EvalAltResult>> {
let scripts = [script]; let scripts = [script];
let stream = lex(&scripts, self); let stream = lex(&scripts, None, self);
let ast = self.parse(&mut stream.peekable(), scope, self.optimization_level)?; let ast = self.parse(&mut stream.peekable(), scope, self.optimization_level)?;
self.consume_ast_with_scope(scope, &ast) self.consume_ast_with_scope(scope, &ast)
} }

View File

@ -172,7 +172,7 @@ impl ParseErrorType {
Self::VariableExpected => "Expecting name of a variable", Self::VariableExpected => "Expecting name of a variable",
Self::Reserved(_) => "Invalid use of reserved keyword", Self::Reserved(_) => "Invalid use of reserved keyword",
Self::ExprExpected(_) => "Expecting an expression", Self::ExprExpected(_) => "Expecting an expression",
Self::FnMissingName => "Expecting name in function declaration", Self::FnMissingName => "Expecting function name in function declaration",
Self::FnMissingParams(_) => "Expecting parameters in function declaration", Self::FnMissingParams(_) => "Expecting parameters in function declaration",
Self::FnDuplicatedParam(_,_) => "Duplicated parameters in function declaration", Self::FnDuplicatedParam(_,_) => "Duplicated parameters in function declaration",
Self::FnMissingBody(_) => "Expecting body statement block for function declaration", Self::FnMissingBody(_) => "Expecting body statement block for function declaration",

View File

@ -18,6 +18,8 @@ use num_traits::{
CheckedShr, CheckedSub, CheckedShr, CheckedSub,
}; };
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
use crate::stdlib::ops::{BitAnd, BitOr, BitXor}; use crate::stdlib::ops::{BitAnd, BitOr, BitXor};
#[cfg(any(feature = "unchecked", not(feature = "no_float")))] #[cfg(any(feature = "unchecked", not(feature = "no_float")))]
@ -145,12 +147,18 @@ where
} }
} }
// Bit operators // Bit operators
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
fn binary_and<T: BitAnd>(x: T, y: T) -> FuncReturn<<T as BitAnd>::Output> { fn binary_and<T: BitAnd>(x: T, y: T) -> FuncReturn<<T as BitAnd>::Output> {
Ok(x & y) Ok(x & y)
} }
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
fn binary_or<T: BitOr>(x: T, y: T) -> FuncReturn<<T as BitOr>::Output> { fn binary_or<T: BitOr>(x: T, y: T) -> FuncReturn<<T as BitOr>::Output> {
Ok(x | y) Ok(x | y)
} }
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
fn binary_xor<T: BitXor>(x: T, y: T) -> FuncReturn<<T as BitXor>::Output> { fn binary_xor<T: BitXor>(x: T, y: T) -> FuncReturn<<T as BitXor>::Output> {
Ok(x ^ y) Ok(x ^ y)
} }

View File

@ -9,7 +9,7 @@ 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::syntax::FnCustomSyntaxEval; use crate::syntax::FnCustomSyntaxEval;
use crate::token::{is_valid_identifier, Position, Token, TokenStream}; use crate::token::{is_keyword_function, is_valid_identifier, Position, Token, TokenStream};
use crate::utils::{StaticVec, StraightHasherBuilder}; use crate::utils::{StaticVec, StraightHasherBuilder};
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
@ -1044,7 +1044,7 @@ fn parse_paren_expr(
} }
/// Parse a function call. /// Parse a function call.
fn parse_call_expr( fn parse_fn_call(
input: &mut TokenStream, input: &mut TokenStream,
state: &mut ParseState, state: &mut ParseState,
lib: &mut FunctionsLib, lib: &mut FunctionsLib,
@ -1553,8 +1553,12 @@ fn parse_primary(
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
Token::Reserved(s) if s != KEYWORD_THIS && input.peek().unwrap().0 == Token::LeftParen => { Token::Reserved(s) if input.peek().unwrap().0 == Token::LeftParen => {
Expr::Variable(Box::new(((s, settings.pos), None, 0, None))) if is_keyword_function(&s) {
Expr::Variable(Box::new(((s, settings.pos), None, 0, None)))
} else {
return Err(PERR::Reserved(s).into_err(settings.pos));
}
} }
// Access to `this` as a variable is OK // Access to `this` as a variable is OK
Token::Reserved(s) if s == KEYWORD_THIS && input.peek().unwrap().0 != Token::LeftParen => { Token::Reserved(s) if s == KEYWORD_THIS && input.peek().unwrap().0 != Token::LeftParen => {
@ -1601,7 +1605,7 @@ fn parse_primary(
(Expr::Variable(x), Token::LeftParen) => { (Expr::Variable(x), Token::LeftParen) => {
let ((name, pos), modules, _, _) = *x; let ((name, pos), modules, _, _) = *x;
settings.pos = pos; settings.pos = pos;
parse_call_expr(input, state, lib, name, modules, settings.level_up())? parse_fn_call(input, state, lib, name, modules, settings.level_up())?
} }
(Expr::Property(_), _) => unreachable!(), (Expr::Property(_), _) => unreachable!(),
// module access // module access
@ -2882,14 +2886,12 @@ fn parse_fn(
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
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 (token, pos) = input.next().unwrap();
(Token::Identifier(s), _) | (Token::Custom(s), _) | (Token::Reserved(s), _)
if s != KEYWORD_THIS && is_valid_identifier(s.chars()) => let name = token.into_function_name().map_err(|t| match t {
{ Token::Reserved(s) => PERR::Reserved(s).into_err(pos),
s _ => PERR::FnMissingName.into_err(pos),
} })?;
(_, pos) => return Err(PERR::FnMissingName.into_err(pos)),
};
match input.peek().unwrap() { match input.peek().unwrap() {
(Token::LeftParen, _) => eat_token(input, Token::LeftParen), (Token::LeftParen, _) => eat_token(input, Token::LeftParen),

View File

@ -492,9 +492,12 @@ impl Token {
#[cfg(feature = "no_module")] #[cfg(feature = "no_module")]
"import" | "export" | "as" => Reserved(syntax.into()), "import" | "export" | "as" => Reserved(syntax.into()),
"===" | "!==" | "->" | "<-" | "=>" | ":=" | "::<" | "(*" | "*)" | "#" => { "===" | "!==" | "->" | "<-" | "=>" | ":=" | "::<" | "(*" | "*)" | "#" | "public"
Reserved(syntax.into()) | "new" | "use" | "module" | "package" | "var" | "static" | "with" | "do" | "each"
} | "then" | "goto" | "exit" | "switch" | "match" | "case" | "try" | "catch"
| "default" | "void" | "null" | "nil" | "spawn" | "go" | "async" | "await"
| "yield" => Reserved(syntax.into()),
KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR
| KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_THIS => Reserved(syntax.into()), | KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_THIS => Reserved(syntax.into()),
@ -666,6 +669,15 @@ impl Token {
} }
} }
/// Convert a token into a function name, if possible.
pub fn into_function_name(self) -> Result<String, Self> {
match self {
Self::Reserved(s) if is_keyword_function(&s) => Ok(s),
Self::Custom(s) | Self::Identifier(s) if is_valid_identifier(s.chars()) => Ok(s),
_ => Err(self),
}
}
/// Is this token a custom keyword? /// Is this token a custom keyword?
pub fn is_custom(&self) -> bool { pub fn is_custom(&self) -> bool {
match self { match self {
@ -714,6 +726,16 @@ pub trait InputStream {
fn peek_next(&mut self) -> Option<char>; fn peek_next(&mut self) -> Option<char>;
} }
pub fn is_keyword_function(name: &str) -> bool {
name == KEYWORD_PRINT
|| name == KEYWORD_DEBUG
|| name == KEYWORD_TYPE_OF
|| name == KEYWORD_EVAL
|| name == KEYWORD_FN_PTR
|| name == KEYWORD_FN_PTR_CALL
|| name == KEYWORD_FN_PTR_CURRY
}
pub fn is_valid_identifier(name: impl Iterator<Item = char>) -> bool { pub fn is_valid_identifier(name: impl Iterator<Item = char>) -> bool {
let mut first_alphabetic = false; let mut first_alphabetic = false;
@ -1452,13 +1474,15 @@ pub struct TokenIterator<'a, 'e> {
pos: Position, pos: Position,
/// Input character stream. /// Input character stream.
stream: MultiInputsStream<'a>, stream: MultiInputsStream<'a>,
/// A processor function (if any) that maps a token to another.
map: Option<Box<dyn Fn(Token) -> Token>>,
} }
impl<'a> Iterator for TokenIterator<'a, '_> { impl<'a> Iterator for TokenIterator<'a, '_> {
type Item = (Token, Position); type Item = (Token, Position);
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
match ( let token = match (
get_next_token(&mut self.stream, &mut self.state, &mut self.pos), get_next_token(&mut self.stream, &mut self.state, &mut self.pos),
self.engine.disabled_symbols.as_ref(), self.engine.disabled_symbols.as_ref(),
self.engine.custom_keywords.as_ref(), self.engine.custom_keywords.as_ref(),
@ -1540,12 +1564,27 @@ impl<'a> Iterator for TokenIterator<'a, '_> {
Some((Token::Reserved(token.syntax().into()), pos)) Some((Token::Reserved(token.syntax().into()), pos))
} }
(r, _, _) => r, (r, _, _) => r,
};
match token {
None => None,
Some((token, pos)) => {
if let Some(ref map) = self.map {
Some((map(token), pos))
} else {
Some((token, pos))
}
}
} }
} }
} }
/// Tokenize an input text stream. /// Tokenize an input text stream.
pub fn lex<'a, 'e>(input: &'a [&'a str], engine: &'e Engine) -> TokenIterator<'a, 'e> { pub fn lex<'a, 'e>(
input: &'a [&'a str],
map: Option<Box<dyn Fn(Token) -> Token>>,
engine: &'e Engine,
) -> TokenIterator<'a, 'e> {
TokenIterator { TokenIterator {
engine, engine,
state: TokenizeState { state: TokenizeState {
@ -1563,5 +1602,6 @@ pub fn lex<'a, 'e>(input: &'a [&'a str], engine: &'e Engine) -> TokenIterator<'a
streams: input.iter().map(|s| s.chars().peekable()).collect(), streams: input.iter().map(|s| s.chars().peekable()).collect(),
index: 0, index: 0,
}, },
map,
} }
} }

View File

@ -131,15 +131,7 @@ fn test_fn_ptr_raw() -> Result<(), Box<EvalAltResult>> {
let value = args[2].clone(); let value = args[2].clone();
let this_ptr = args.get_mut(0).unwrap(); let this_ptr = args.get_mut(0).unwrap();
engine.call_fn_dynamic( fp.call_dynamic(engine, lib, Some(this_ptr), [value])
&mut Scope::new(),
lib,
fp.fn_name(),
Some(this_ptr),
[value],
)?;
Ok(())
}, },
); );