Add is_def_var and is_def_fn.
This commit is contained in:
parent
eec3f4e1bf
commit
fbfb7677c1
@ -12,6 +12,11 @@ Breaking changes
|
||||
* The `merge_namespaces` parameter to `Module::eval_ast_as_new` is removed and now defaults to `true`.
|
||||
* `GlobalFileModuleResolver` is removed because its performance gain over the `FileModuleResolver` is no longer very significant.
|
||||
|
||||
New features
|
||||
------------
|
||||
|
||||
* `is_def_var()` to detect if variable is defined and `is_def_fn()` to detect if script function is defined.
|
||||
|
||||
|
||||
Version 0.19.0
|
||||
==============
|
||||
|
@ -14,7 +14,9 @@ Versions
|
||||
|
||||
This Book is for version **{{version}}** of Rhai.
|
||||
|
||||
{% if rootUrl != "" and not rootUrl is ending_with("vnext") %}
|
||||
For the latest development version, see [here]({{rootUrl}}/vnext/).
|
||||
{% endif %}
|
||||
|
||||
|
||||
Etymology of the name "Rhai"
|
||||
|
@ -3,36 +3,38 @@ Keywords List
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
| Keyword | Description | Inactive under | Overloadable |
|
||||
| :-------------------: | ------------------------------------------- | :-------------: | :----------: |
|
||||
| `true` | boolean true literal | | no |
|
||||
| `false` | boolean false literal | | no |
|
||||
| `let` | variable declaration | | no |
|
||||
| `const` | constant declaration | | no |
|
||||
| `is_shared` | is a value shared? | | no |
|
||||
| `if` | if statement | | no |
|
||||
| `else` | else block of if statement | | no |
|
||||
| `while` | while loop | | no |
|
||||
| `loop` | infinite loop | | no |
|
||||
| `for` | for loop | | no |
|
||||
| `in` | 1) containment test<br/>2) part of for loop | | no |
|
||||
| `continue` | continue a loop at the next iteration | | no |
|
||||
| `break` | break out of loop iteration | | no |
|
||||
| `return` | return value | | no |
|
||||
| `throw` | throw exception | | no |
|
||||
| `import` | import module | [`no_module`] | no |
|
||||
| `export` | export variable | [`no_module`] | no |
|
||||
| `as` | alias for variable export | [`no_module`] | no |
|
||||
| `private` | mark function private | [`no_function`] | no |
|
||||
| `fn` (lower-case `f`) | function definition | [`no_function`] | no |
|
||||
| `Fn` (capital `F`) | create a [function pointer] | | yes |
|
||||
| `call` | call a [function pointer] | | no |
|
||||
| `curry` | curry a [function pointer] | | no |
|
||||
| `this` | reference to base object for method call | [`no_function`] | no |
|
||||
| `type_of` | get type name of value | | yes |
|
||||
| `print` | print value | | yes |
|
||||
| `debug` | print value in debug format | | yes |
|
||||
| `eval` | evaluate script | | yes |
|
||||
| Keyword | Description | Inactive under | Is function? | Overloadable |
|
||||
| :-------------------: | ------------------------------------------- | :-------------: | :----------: | :----------: |
|
||||
| `true` | boolean true literal | | no | |
|
||||
| `false` | boolean false literal | | no | |
|
||||
| `let` | variable declaration | | no | |
|
||||
| `const` | constant declaration | | no | |
|
||||
| `is_def_var` | is a variable declared? | | yes | yes |
|
||||
| `is_shared` | is a value shared? | [`no_closure`] | yes | no |
|
||||
| `if` | if statement | | no | |
|
||||
| `else` | else block of if statement | | no | |
|
||||
| `while` | while loop | | no | |
|
||||
| `loop` | infinite loop | | no | |
|
||||
| `for` | for loop | | no | |
|
||||
| `in` | 1) containment test<br/>2) part of for loop | | no | |
|
||||
| `continue` | continue a loop at the next iteration | | no | |
|
||||
| `break` | break out of loop iteration | | no | |
|
||||
| `return` | return value | | no | |
|
||||
| `throw` | throw exception | | no | |
|
||||
| `import` | import module | [`no_module`] | no | |
|
||||
| `export` | export variable | [`no_module`] | no | |
|
||||
| `as` | alias for variable export | [`no_module`] | no | |
|
||||
| `private` | mark function private | [`no_function`] | no | |
|
||||
| `fn` (lower-case `f`) | function definition | [`no_function`] | no | |
|
||||
| `Fn` (capital `F`) | create a [function pointer] | | yes | yes |
|
||||
| `call` | call a [function pointer] | | yes | no |
|
||||
| `curry` | curry a [function pointer] | | yes | no |
|
||||
| `this` | reference to base object for method call | [`no_function`] | no | |
|
||||
| `is_def_fn` | is a scripted function defined? | [`no_function`] | yes | yes |
|
||||
| `type_of` | get type name of value | | yes | yes |
|
||||
| `print` | print value | | yes | yes |
|
||||
| `debug` | print value in debug format | | yes | yes |
|
||||
| `eval` | evaluate script | | yes | yes |
|
||||
|
||||
|
||||
Reserved Keywords
|
||||
|
@ -101,6 +101,21 @@ a statement in the script can freely call a function defined afterwards.
|
||||
This is similar to Rust and many other modern languages, such as JavaScript's `function` keyword.
|
||||
|
||||
|
||||
`is_def_fn`
|
||||
-----------
|
||||
|
||||
Use `is_def_fn` to detect if a function is defined (and therefore callable), based on its name
|
||||
and the number of parameters.
|
||||
|
||||
```rust
|
||||
fn foo(x) { x + 1 }
|
||||
|
||||
is_def_fn("foo", 1) == true;
|
||||
|
||||
is_def_fn("bar", 1) == false;
|
||||
```
|
||||
|
||||
|
||||
Arguments are Passed by Value
|
||||
----------------------------
|
||||
|
||||
|
@ -37,6 +37,8 @@ If none is provided, it defaults to [`()`].
|
||||
|
||||
A variable defined within a statement block is _local_ to that block.
|
||||
|
||||
Use `is_def_var` to detect if a variable is defined.
|
||||
|
||||
```rust
|
||||
let x; // ok - value is '()'
|
||||
let x = 3; // ok
|
||||
@ -57,4 +59,10 @@ X == 123;
|
||||
x == 999; // access to local 'x'
|
||||
}
|
||||
x == 42; // the parent block's 'x' is not changed
|
||||
|
||||
is_def_var("x") == true;
|
||||
|
||||
is_def_var("_x") == true;
|
||||
|
||||
is_def_var("y") == false;
|
||||
```
|
||||
|
@ -32,28 +32,97 @@ are _merged_ into a _unified_ namespace.
|
||||
| my_module.rhai |
|
||||
------------------
|
||||
|
||||
// This function overrides any in the main script.
|
||||
private fn inner_message() { "hello! from module!" }
|
||||
|
||||
fn greet(callback) { print(callback.call()); }
|
||||
fn greet() {
|
||||
print(inner_message()); // call function in module script
|
||||
}
|
||||
|
||||
fn greet_main() {
|
||||
print(main_message()); // call function not in module script
|
||||
}
|
||||
|
||||
-------------
|
||||
| main.rhai |
|
||||
-------------
|
||||
|
||||
fn main_message() { "hi! from main!" }
|
||||
// This function is overridden by the module script.
|
||||
fn inner_message() { "hi! from main!" }
|
||||
|
||||
// This function is found by the module script.
|
||||
fn main_message() { "main here!" }
|
||||
|
||||
import "my_module" as m;
|
||||
|
||||
m::greet(|| "hello, " + "world!"); // works - anonymous function in global
|
||||
m::greet(); // prints "hello! from module!"
|
||||
|
||||
m::greet(|| inner_message()); // works - function in module
|
||||
|
||||
m::greet(|| main_message()); // works - function in global
|
||||
m::greet_main(); // prints "main here!"
|
||||
```
|
||||
|
||||
### Simulating virtual functions
|
||||
|
||||
When calling a namespace-qualified function defined within a module, other functions defined within
|
||||
the same module script override any similar-named functions (with the same number of parameters)
|
||||
defined in the global namespace. This is to ensure that a module acts as a self-contained unit and
|
||||
functions defined in the calling script do not override module code.
|
||||
|
||||
In some situations, however, it is actually beneficial to do it in reverse: have module code call functions
|
||||
defined in the calling script (i.e. in the global namespace) if they exist, and only call those defined
|
||||
in the module script if none are found.
|
||||
|
||||
One such situation is the need to provide a _default implementation_ to a simulated _virtual_ function:
|
||||
|
||||
```rust
|
||||
------------------
|
||||
| my_module.rhai |
|
||||
------------------
|
||||
|
||||
// Do not do this (it will override the main script):
|
||||
// fn message() { "hello! from module!" }
|
||||
|
||||
// This function acts as the default implementation.
|
||||
private fn default_message() { "hello! from module!" }
|
||||
|
||||
// This function depends on a 'virtual' function 'message'
|
||||
// which is not defined in the module script.
|
||||
fn greet() {
|
||||
if is_def_fn("message", 0) { // 'is_def_fn' detects if 'message' is defined.
|
||||
print(message());
|
||||
} else {
|
||||
print(default_message());
|
||||
}
|
||||
}
|
||||
|
||||
-------------
|
||||
| main.rhai |
|
||||
-------------
|
||||
|
||||
// The main script defines 'message' which is needed by the module script.
|
||||
fn message() { "hi! from main!" }
|
||||
|
||||
import "my_module" as m;
|
||||
|
||||
m::greet(); // prints "hi! from main!"
|
||||
|
||||
--------------
|
||||
| main2.rhai |
|
||||
--------------
|
||||
|
||||
// The main script does not define 'message' which is needed by the module script.
|
||||
|
||||
import "my_module" as m;
|
||||
|
||||
m::greet(); // prints "hello! from module!"
|
||||
```
|
||||
|
||||
### Changing the base directory
|
||||
|
||||
The base directory can be changed via the `FileModuleResolver::new_with_path` constructor function.
|
||||
|
||||
`FileModuleResolver::create_module` loads a script file and returns a module.
|
||||
### Returning a module instead
|
||||
|
||||
`FileModuleResolver::create_module` loads a script file and returns a module with the standard behavior.
|
||||
|
||||
|
||||
`StaticModuleResolver`
|
||||
|
@ -99,7 +99,10 @@ pub const KEYWORD_EVAL: &str = "eval";
|
||||
pub const KEYWORD_FN_PTR: &str = "Fn";
|
||||
pub const KEYWORD_FN_PTR_CALL: &str = "call";
|
||||
pub const KEYWORD_FN_PTR_CURRY: &str = "curry";
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
pub const KEYWORD_IS_SHARED: &str = "is_shared";
|
||||
pub const KEYWORD_IS_DEF_VAR: &str = "is_def_var";
|
||||
pub const KEYWORD_IS_DEF_FN: &str = "is_def_fn";
|
||||
pub const KEYWORD_THIS: &str = "this";
|
||||
pub const FN_TO_STRING: &str = "to_string";
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
|
@ -4,8 +4,8 @@ use crate::any::Dynamic;
|
||||
use crate::calc_fn_hash;
|
||||
use crate::engine::{
|
||||
search_imports, search_namespace, search_scope_only, Engine, Imports, State, KEYWORD_DEBUG,
|
||||
KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY, KEYWORD_IS_SHARED,
|
||||
KEYWORD_PRINT, KEYWORD_TYPE_OF,
|
||||
KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY, KEYWORD_IS_DEF_FN,
|
||||
KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_TYPE_OF,
|
||||
};
|
||||
use crate::error::ParseErrorType;
|
||||
use crate::fn_native::{FnCallArgs, FnPtr};
|
||||
@ -33,6 +33,9 @@ use crate::engine::{FN_IDX_GET, FN_IDX_SET};
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
use crate::engine::{Map, Target, FN_GET, FN_SET};
|
||||
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
use crate::engine::KEYWORD_IS_SHARED;
|
||||
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
use crate::scope::Entry as ScopeEntry;
|
||||
@ -744,15 +747,18 @@ impl Engine {
|
||||
.into(),
|
||||
false,
|
||||
))
|
||||
} else if cfg!(not(feature = "no_closure"))
|
||||
&& _fn_name == KEYWORD_IS_SHARED
|
||||
&& idx.is_empty()
|
||||
{
|
||||
} else if {
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
{
|
||||
_fn_name == KEYWORD_IS_SHARED && idx.is_empty()
|
||||
}
|
||||
#[cfg(feature = "no_closure")]
|
||||
false
|
||||
} {
|
||||
// is_shared call
|
||||
Ok((target.is_shared().into(), false))
|
||||
} else {
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
let redirected;
|
||||
let _redirected;
|
||||
let mut hash = hash_script;
|
||||
|
||||
// Check if it is a map method call in OOP style
|
||||
@ -761,8 +767,8 @@ impl Engine {
|
||||
if let Some(val) = map.get(_fn_name) {
|
||||
if let Some(fn_ptr) = val.read_lock::<FnPtr>() {
|
||||
// Remap the function name
|
||||
redirected = fn_ptr.get_fn_name().clone();
|
||||
_fn_name = &redirected;
|
||||
_redirected = fn_ptr.get_fn_name().clone();
|
||||
_fn_name = &_redirected;
|
||||
// Add curried arguments
|
||||
if !fn_ptr.curry().is_empty() {
|
||||
fn_ptr
|
||||
@ -877,7 +883,8 @@ impl Engine {
|
||||
}
|
||||
|
||||
// Handle is_shared()
|
||||
if cfg!(not(feature = "no_closure")) && name == KEYWORD_IS_SHARED && args_expr.len() == 1 {
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
if name == KEYWORD_IS_SHARED && args_expr.len() == 1 {
|
||||
let expr = args_expr.get(0).unwrap();
|
||||
let value = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
|
||||
|
||||
@ -917,6 +924,48 @@ impl Engine {
|
||||
}
|
||||
}
|
||||
|
||||
// Handle is_def_var()
|
||||
if name == KEYWORD_IS_DEF_VAR && args_expr.len() == 1 {
|
||||
let hash_fn = calc_fn_hash(empty(), name, 1, once(TypeId::of::<ImmutableString>()));
|
||||
|
||||
if !self.has_override(lib, hash_fn, hash_script, pub_only) {
|
||||
let expr = args_expr.get(0).unwrap();
|
||||
if let Ok(var_name) = self
|
||||
.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
|
||||
.as_str()
|
||||
{
|
||||
return Ok(scope.contains(var_name).into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle is_def_fn()
|
||||
if name == KEYWORD_IS_DEF_FN && args_expr.len() == 2 {
|
||||
let hash_fn = calc_fn_hash(
|
||||
empty(),
|
||||
name,
|
||||
2,
|
||||
[TypeId::of::<ImmutableString>(), TypeId::of::<INT>()]
|
||||
.iter()
|
||||
.cloned(),
|
||||
);
|
||||
|
||||
if !self.has_override(lib, hash_fn, hash_script, pub_only) {
|
||||
let fn_name_expr = args_expr.get(0).unwrap();
|
||||
let num_params_expr = args_expr.get(1).unwrap();
|
||||
|
||||
if let (Ok(fn_name), Ok(num_params)) = (
|
||||
self.eval_expr(scope, mods, state, lib, this_ptr, fn_name_expr, level)?
|
||||
.as_str(),
|
||||
self.eval_expr(scope, mods, state, lib, this_ptr, num_params_expr, level)?
|
||||
.as_int(),
|
||||
) {
|
||||
let hash = calc_fn_hash(empty(), fn_name, num_params as usize, empty());
|
||||
return Ok(lib.contains_fn(hash, false).into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle eval()
|
||||
if name == KEYWORD_EVAL && args_expr.len() == 1 {
|
||||
let hash_fn = calc_fn_hash(empty(), name, 1, once(TypeId::of::<ImmutableString>()));
|
||||
|
@ -454,6 +454,9 @@ impl Module {
|
||||
/// Arguments are simply passed in as a mutable array of `&mut Dynamic`,
|
||||
/// which is guaranteed to contain enough arguments of the correct types.
|
||||
///
|
||||
/// The function is assumed to be a _method_, meaning that the first argument should not be consumed.
|
||||
/// All other arguments can be consumed.
|
||||
///
|
||||
/// 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>()`.
|
||||
@ -1299,8 +1302,8 @@ impl Module {
|
||||
.into_iter()
|
||||
.for_each(|ScopeEntry { value, alias, .. }| {
|
||||
// Variables with an alias left in the scope become module variables
|
||||
if alias.is_some() {
|
||||
module.variables.insert(*alias.unwrap(), value);
|
||||
if let Some(alias) = alias {
|
||||
module.variables.insert(*alias, value);
|
||||
}
|
||||
});
|
||||
|
||||
|
20
src/token.rs
20
src/token.rs
@ -2,9 +2,12 @@
|
||||
|
||||
use crate::engine::{
|
||||
Engine, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY,
|
||||
KEYWORD_IS_SHARED, KEYWORD_PRINT, KEYWORD_THIS, KEYWORD_TYPE_OF,
|
||||
KEYWORD_IS_DEF_FN, KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_THIS, KEYWORD_TYPE_OF,
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
use crate::engine::KEYWORD_IS_SHARED;
|
||||
|
||||
use crate::error::LexError;
|
||||
use crate::parser::INT;
|
||||
use crate::utils::StaticVec;
|
||||
@ -507,9 +510,11 @@ impl Token {
|
||||
| "await" | "yield" => Reserved(syntax.into()),
|
||||
|
||||
KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR
|
||||
| KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_IS_SHARED | KEYWORD_THIS => {
|
||||
Reserved(syntax.into())
|
||||
}
|
||||
| KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_IS_DEF_VAR
|
||||
| KEYWORD_IS_DEF_FN | KEYWORD_THIS => Reserved(syntax.into()),
|
||||
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
KEYWORD_IS_SHARED => Reserved(syntax.into()),
|
||||
|
||||
_ => return None,
|
||||
})
|
||||
@ -1455,7 +1460,9 @@ pub fn is_keyword_function(name: &str) -> bool {
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
KEYWORD_IS_SHARED => true,
|
||||
KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR
|
||||
| KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY => true,
|
||||
| KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_IS_DEF_VAR | KEYWORD_IS_DEF_FN => {
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
@ -1465,7 +1472,8 @@ pub fn is_keyword_function(name: &str) -> bool {
|
||||
#[inline(always)]
|
||||
pub fn can_override_keyword(name: &str) -> bool {
|
||||
match name {
|
||||
KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR => true,
|
||||
KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR
|
||||
| KEYWORD_IS_DEF_VAR | KEYWORD_IS_DEF_FN => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user