commit
b46420778d
@ -6,7 +6,7 @@ members = [
|
||||
|
||||
[package]
|
||||
name = "rhai"
|
||||
version = "0.19.3"
|
||||
version = "0.19.4"
|
||||
edition = "2018"
|
||||
authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung", "jhwgh1968"]
|
||||
description = "Embedded scripting for Rust"
|
||||
|
@ -30,7 +30,7 @@ Standard features
|
||||
* Fairly low compile-time overhead.
|
||||
* Fairly efficient evaluation (1 million iterations in 0.3 sec on a single core, 2.3 GHz Linux VM).
|
||||
* Tight integration with native Rust [functions](https://schungx.github.io/rhai/rust/functions.html) and [types]([#custom-types-and-methods](https://schungx.github.io/rhai/rust/custom.html)), including [getters/setters](https://schungx.github.io/rhai/rust/getters-setters.html), [methods](https://schungx.github.io/rhai/rust/custom.html) and [indexers](https://schungx.github.io/rhai/rust/indexers.html).
|
||||
* Freely pass Rust variables/constants into a script via an external [`Scope`](https://schungx.github.io/rhai/rust/scope.html).
|
||||
* Freely pass Rust variables/constants into a script via an external [`Scope`](https://schungx.github.io/rhai/rust/scope.html) - all clonable Rust types are supported; no need to implement any special trait.
|
||||
* Easily [call a script-defined function](https://schungx.github.io/rhai/engine/call-fn.html) from Rust.
|
||||
* Relatively little `unsafe` code (yes there are some for performance reasons).
|
||||
* Few dependencies (currently only [`smallvec`](https://crates.io/crates/smallvec)).
|
||||
|
@ -19,6 +19,10 @@ Breaking changes
|
||||
----------------
|
||||
|
||||
* Custom syntax can no longer start with a keyword (even a _reserved_ one), even if it has been disabled. That is to avoid breaking scripts later when the keyword is no longer disabled.
|
||||
|
||||
Changes to Error Handling
|
||||
------------------------
|
||||
|
||||
* `EvalAltResult::ErrorAssignmentToUnknownLHS` is moved to `ParseError::AssignmentToInvalidLHS`. `ParseError::AssignmentToCopy` is removed.
|
||||
* `EvalAltResult::ErrorDataTooLarge` is simplified.
|
||||
* `Engine::on_progress` closure signature now returns `Option<Dynamic>` with the termination value passed on to `EvalAltResult::ErrorTerminated`.
|
||||
@ -30,11 +34,13 @@ New features
|
||||
* `f32_float` feature to set `FLOAT` to `f32`.
|
||||
* Low-level API for custom syntax allowing more flexibility in designing the syntax.
|
||||
* `Module::fill_with` to poly-fill a module with another.
|
||||
* Scripts terminated via `Engine::on_progress` can now pass on a value as a termination token.
|
||||
|
||||
Enhancements
|
||||
------------
|
||||
|
||||
* Essential AST structures like `Expr` and `Stmt` are packed into smaller sizes (16 bytes and 32 bytes on 64-bit), stored inline for more cache friendliness, and de-`Box`ed as much as possible.
|
||||
* `Scope` is optimized for cache friendliness.
|
||||
|
||||
|
||||
Version 0.19.3
|
||||
|
@ -8,9 +8,11 @@ Easy
|
||||
|
||||
* Easy-to-use language similar to JavaScript+Rust with dynamic typing.
|
||||
|
||||
* Tight integration with native Rust [functions] and [types][custom types], including [getters/setters], [methods][custom type] and [indexers].
|
||||
* Tight integration with native Rust [functions] and [types][custom types] including [getters/setters],
|
||||
[methods][custom type] and [indexers].
|
||||
|
||||
* Freely pass Rust variables/constants into a script via an external [`Scope`].
|
||||
* Freely pass Rust variables/constants into a script via an external [`Scope`] - all clonable Rust types are supported seamlessly
|
||||
without the need to implement any special trait.
|
||||
|
||||
* Easily [call a script-defined function]({{rootUrl}}/engine/call-fn.md) from Rust.
|
||||
|
||||
@ -51,12 +53,14 @@ Safe
|
||||
|
||||
* Relatively little `unsafe` code (yes there are some for performance reasons).
|
||||
|
||||
* Sand-boxed - the scripting [`Engine`], if declared immutable, cannot mutate the containing environment unless [explicitly permitted]({{rootUrl}}/patterns/control.md).
|
||||
* Sand-boxed - the scripting [`Engine`], if declared immutable, cannot mutate the containing environment unless
|
||||
[explicitly permitted]({{rootUrl}}/patterns/control.md).
|
||||
|
||||
Rugged
|
||||
------
|
||||
|
||||
* Protected against malicious attacks (such as [stack-overflow][maximum call stack depth], [over-sized data][maximum length of strings], and [runaway scripts][maximum number of operations] etc.) that may come from untrusted third-party user-land scripts.
|
||||
* Protected against malicious attacks (such as [stack-overflow][maximum call stack depth], [over-sized data][maximum length of strings],
|
||||
and [runaway scripts][maximum number of operations] etc.) that may come from untrusted third-party user-land scripts.
|
||||
|
||||
* Track script evaluation [progress] and manually terminate a script run.
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "0.19.3",
|
||||
"version": "0.19.4",
|
||||
"repoHome": "https://github.com/jonathandturner/rhai/blob/master",
|
||||
"repoTree": "https://github.com/jonathandturner/rhai/tree/master",
|
||||
"rootUrl": "",
|
||||
|
@ -11,7 +11,7 @@ Rhai works seamlessly with _any_ Rust type. The type can be _anything_; it does
|
||||
have any prerequisites other than being `Clone`. It does not need to implement
|
||||
any other trait or use any custom `#[derive]`.
|
||||
|
||||
This allows Rhai to be integrated into an existing code base with as little plumbing
|
||||
This allows Rhai to be integrated into an existing Rust code base with as little plumbing
|
||||
as possible, usually silently and seamlessly. External types that are not defined
|
||||
within the same crate (and thus cannot implement special Rhai traits or
|
||||
use special `#[derive]`) can also be used easily with Rhai.
|
||||
|
@ -24,7 +24,21 @@ engine.on_progress(|&count| { // parameter is '&u64' - number of operations al
|
||||
|
||||
The closure passed to `Engine::on_progress` will be called once for every operation.
|
||||
Return `Some(token)` to terminate the script immediately, with the provided value
|
||||
(any [`Dynamic`] value) passed to `EvalAltResult::ErrorTerminated` as a termination token.
|
||||
(any [`Dynamic`]) acting as a termination token.
|
||||
|
||||
|
||||
Termination Token
|
||||
-----------------
|
||||
|
||||
The [`Dynamic`] value returned by the closure for `Engine::on_progress` is a _termination token_.
|
||||
A script that is manually terminated returns with `Err(EvalAltResult::ErrorTerminated)`
|
||||
wrapping this value.
|
||||
|
||||
The termination token is commonly used to provide information on the _reason_ or _source_
|
||||
behind the termination decision.
|
||||
|
||||
If the termination token is not needed, simply return `Some(().into())` to terminate the script
|
||||
run with [`()`] as the token.
|
||||
|
||||
|
||||
Operations Count vs. Progress Percentage
|
||||
|
@ -29,8 +29,9 @@ due to 64-bit arithmetic requiring more CPU cycles to complete.
|
||||
Minimize Size of `Dynamic`
|
||||
-------------------------
|
||||
|
||||
Turning on [`no_float`] and [`only_i32`] on 32-bit targets makes the critical [`Dynamic`] data type only 8 bytes long.
|
||||
Normally [`Dynamic`] can be up to 16 bytes (e.g. on x86/x64 CPU's) in order to hold an `i64` or `f64`.
|
||||
Turning on [`no_float`] or [`f32_float`] and [`only_i32`] on 32-bit targets makes the critical [`Dynamic`]
|
||||
data type only 8 bytes long.
|
||||
Normally [`Dynamic`] can be up to 12-16 bytes in order to hold an `i64` or `f64`.
|
||||
|
||||
A small [`Dynamic`] helps performance due to better cache efficiency.
|
||||
|
||||
|
@ -42,13 +42,14 @@ are typically used for a WASM build:
|
||||
| Feature | Description |
|
||||
| :-----------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| [`unchecked`] | When a WASM module panics, it doesn't crash the entire web app; however this also disables [maximum number of operations] and [progress] tracking so a script can still run indefinitely - the web app must terminate it itself. |
|
||||
| [`only_i32`] | JavaScript has only one `number` type and we're only supporting `wasm32` here (so far). |
|
||||
| [`only_i32`] | WASM supports 32-bit and 64-bit integers, but most scripts will only need 32-bit. |
|
||||
| [`f32_float`] | WASM supports 32-bit single-precision and 64-bit double-precision floating-point numbers, but single-precision is usually fine for most uses. |
|
||||
| [`no_module`] | A WASM module cannot load modules from the file system, so usually this is not needed, but the savings are minimal; alternatively, a custom [module resolver] can be provided that loads other Rhai scripts. |
|
||||
|
||||
The following features are typically _not_ used because they don't make sense in a WASM build:
|
||||
|
||||
| Feature | Why unnecessary |
|
||||
| :-----------: | ------------------------------------------------------------------ |
|
||||
| :-----------: | ------------------------------------------------------------------------------------------------------ |
|
||||
| [`sync`] | WASM is single-threaded. |
|
||||
| [`no_std`] | `std` lib works fine with WASM. |
|
||||
| [`internals`] | WASM usually doesn't need to access Rhai internal data structures. |
|
||||
| [`internals`] | WASM usually doesn't need to access Rhai internal data structures, unless you are walking the [`AST`]. |
|
||||
|
@ -17,7 +17,7 @@ more control over what a script can (or cannot) do.
|
||||
| `sync` | no | restricts all values types to those that are `Send + Sync`. Under this feature, all Rhai types, including [`Engine`], [`Scope`] and [`AST`], are all `Send + Sync` |
|
||||
| `no_optimize` | no | disables [script optimization] |
|
||||
| `no_float` | no | disables floating-point numbers and math |
|
||||
| `f32_float` | no | sets the system floating-point type to `f32` instead of `f64` |
|
||||
| `f32_float` | no | sets the system floating-point type to `f32` instead of `f64`. `FLOAT` is set to `f32` |
|
||||
| `only_i32` | no | sets the system integer type to `i32` and disable all other integer types. `INT` is set to `i32` |
|
||||
| `only_i64` | no | sets the system integer type to `i64` and disable all other integer types. `INT` is set to `i64` |
|
||||
| `no_index` | no | disables [arrays] and indexing features |
|
||||
|
@ -12,7 +12,7 @@ homepage = "https://github.com/jonathandturner/rhai/tree/no_std/no_std_test"
|
||||
repository = "https://github.com/jonathandturner/rhai"
|
||||
|
||||
[dependencies]
|
||||
rhai = { path = "../../", features = [ "no_std", "unchecked", "only_i32", "no_module" ], default_features = false }
|
||||
rhai = { path = "../../", features = [ "no_std", "unchecked", "only_i32", "f32_float", "no_module" ], default_features = false }
|
||||
wee_alloc = { version = "0.4.5", default_features = false }
|
||||
|
||||
[profile.dev]
|
||||
|
30
src/ast.rs
30
src/ast.rs
@ -83,6 +83,10 @@ impl FnAccess {
|
||||
/// This type is volatile and may change.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ScriptFnDef {
|
||||
/// Function body.
|
||||
pub body: Stmt,
|
||||
/// Encapsulated running environment, if any.
|
||||
pub lib: Option<Shared<Module>>,
|
||||
/// Function name.
|
||||
pub name: ImmutableString,
|
||||
/// Function access mode.
|
||||
@ -92,12 +96,6 @@ pub struct ScriptFnDef {
|
||||
/// Access to external variables.
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
pub externals: HashSet<String>,
|
||||
/// Function body.
|
||||
pub body: Stmt,
|
||||
/// Position of the function definition.
|
||||
pub pos: Position,
|
||||
/// Encapsulated running environment, if any.
|
||||
pub lib: Option<Shared<Module>>,
|
||||
}
|
||||
|
||||
impl fmt::Display for ScriptFnDef {
|
||||
@ -908,7 +906,7 @@ pub enum Expr {
|
||||
/// Property access - (getter, setter), prop
|
||||
Property(Box<((String, String), IdentX)>),
|
||||
/// { stmt }
|
||||
Stmt(Box<Stmt>, Position),
|
||||
Stmt(Box<StaticVec<Stmt>>, Position),
|
||||
/// Wrapped expression - should not be optimized away.
|
||||
Expr(Box<Expr>),
|
||||
/// func(expr, ... )
|
||||
@ -1092,7 +1090,7 @@ impl Expr {
|
||||
x.lhs.is_pure() && x.rhs.is_pure()
|
||||
}
|
||||
|
||||
Self::Stmt(x, _) => x.is_pure(),
|
||||
Self::Stmt(x, _) => x.iter().all(Stmt::is_pure),
|
||||
|
||||
Self::Variable(_) => true,
|
||||
|
||||
@ -1240,3 +1238,19 @@ impl Expr {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// This test is to make sure no code changes increase the sizes of critical data structures.
|
||||
#[test]
|
||||
fn check_struct_sizes() {
|
||||
use std::mem::size_of;
|
||||
|
||||
assert_eq!(size_of::<Dynamic>(), 16);
|
||||
assert_eq!(size_of::<Option<Dynamic>>(), 16);
|
||||
assert_eq!(size_of::<Expr>(), 16);
|
||||
assert_eq!(size_of::<Stmt>(), 32);
|
||||
}
|
||||
}
|
||||
|
@ -1579,7 +1579,9 @@ impl Engine {
|
||||
Expr::Property(_) => unreachable!(),
|
||||
|
||||
// Statement block
|
||||
Expr::Stmt(x, _) => self.eval_stmt(scope, mods, state, lib, this_ptr, x, level),
|
||||
Expr::Stmt(x, _) => {
|
||||
self.eval_statements(scope, mods, state, lib, this_ptr, x.as_ref(), level)
|
||||
}
|
||||
|
||||
// lhs[idx_expr]
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
@ -1706,6 +1708,37 @@ impl Engine {
|
||||
.map_err(|err| err.fill_position(expr.position()))
|
||||
}
|
||||
|
||||
pub(crate) fn eval_statements<'a>(
|
||||
&self,
|
||||
scope: &mut Scope,
|
||||
mods: &mut Imports,
|
||||
state: &mut State,
|
||||
lib: &[&Module],
|
||||
this_ptr: &mut Option<&mut Dynamic>,
|
||||
statements: impl IntoIterator<Item = &'a Stmt>,
|
||||
level: usize,
|
||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
let prev_scope_len = scope.len();
|
||||
let prev_mods_len = mods.len();
|
||||
state.scope_level += 1;
|
||||
|
||||
let result = statements
|
||||
.into_iter()
|
||||
.try_fold(Default::default(), |_, stmt| {
|
||||
self.eval_stmt(scope, mods, state, lib, this_ptr, stmt, level)
|
||||
});
|
||||
|
||||
scope.rewind(prev_scope_len);
|
||||
mods.truncate(prev_mods_len);
|
||||
state.scope_level -= 1;
|
||||
|
||||
// The impact of an eval statement goes away at the end of a block
|
||||
// because any new variables introduced will go out of scope
|
||||
state.always_search = false;
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Evaluate a statement
|
||||
///
|
||||
///
|
||||
@ -1886,23 +1919,7 @@ impl Engine {
|
||||
|
||||
// Block scope
|
||||
Stmt::Block(statements, _) => {
|
||||
let prev_scope_len = scope.len();
|
||||
let prev_mods_len = mods.len();
|
||||
state.scope_level += 1;
|
||||
|
||||
let result = statements.iter().try_fold(Default::default(), |_, stmt| {
|
||||
self.eval_stmt(scope, mods, state, lib, this_ptr, stmt, level)
|
||||
});
|
||||
|
||||
scope.rewind(prev_scope_len);
|
||||
mods.truncate(prev_mods_len);
|
||||
state.scope_level -= 1;
|
||||
|
||||
// The impact of an eval statement goes away at the end of a block
|
||||
// because any new variables introduced will go out of scope
|
||||
state.always_search = false;
|
||||
|
||||
result
|
||||
self.eval_statements(scope, mods, state, lib, this_ptr, statements, level)
|
||||
}
|
||||
|
||||
// If-else statement
|
||||
|
@ -1405,7 +1405,7 @@ impl Engine {
|
||||
mods: &mut Imports,
|
||||
ast: &'a AST,
|
||||
) -> Result<(Dynamic, u64), Box<EvalAltResult>> {
|
||||
self.eval_statements(scope, mods, ast.statements(), &[ast.lib()])
|
||||
self.eval_statements_raw(scope, mods, ast.statements(), &[ast.lib()])
|
||||
}
|
||||
|
||||
/// Evaluate a file, but throw away the result and only return error (if any).
|
||||
@ -1467,7 +1467,7 @@ impl Engine {
|
||||
ast: &AST,
|
||||
) -> Result<(), Box<EvalAltResult>> {
|
||||
let mut mods = Default::default();
|
||||
self.eval_statements(scope, &mut mods, ast.statements(), &[ast.lib()])
|
||||
self.eval_statements_raw(scope, &mut mods, ast.statements(), &[ast.lib()])
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
@ -1661,8 +1661,8 @@ impl Engine {
|
||||
let lib = if cfg!(not(feature = "no_function")) {
|
||||
ast.lib()
|
||||
.iter_fn()
|
||||
.filter(|(_, _, _, _, f)| f.is_script())
|
||||
.map(|(_, _, _, _, f)| f.get_fn_def().clone())
|
||||
.filter(|f| f.func.is_script())
|
||||
.map(|f| f.func.get_fn_def().clone())
|
||||
.collect()
|
||||
} else {
|
||||
Default::default()
|
||||
|
@ -604,9 +604,10 @@ impl Engine {
|
||||
}
|
||||
}
|
||||
|
||||
/// Evaluate a list of statements.
|
||||
/// Evaluate a list of statements with an empty state and no `this` pointer.
|
||||
/// This is commonly used to evaluate a list of statements in an `AST` or a script function body.
|
||||
#[inline]
|
||||
pub(crate) fn eval_statements<'a>(
|
||||
pub(crate) fn eval_statements_raw<'a>(
|
||||
&self,
|
||||
scope: &mut Scope,
|
||||
mods: &mut Imports,
|
||||
@ -667,7 +668,7 @@ impl Engine {
|
||||
}
|
||||
|
||||
// Evaluate the AST
|
||||
let (result, operations) = self.eval_statements(scope, mods, ast.statements(), lib)?;
|
||||
let (result, operations) = self.eval_statements_raw(scope, mods, ast.statements(), lib)?;
|
||||
|
||||
state.operations += operations;
|
||||
self.inc_operations(state)?;
|
||||
|
@ -115,8 +115,8 @@ pub struct FnPtr(ImmutableString, StaticVec<Dynamic>);
|
||||
impl FnPtr {
|
||||
/// Create a new function pointer.
|
||||
#[inline(always)]
|
||||
pub(crate) fn new_unchecked<S: Into<ImmutableString>>(
|
||||
name: S,
|
||||
pub(crate) fn new_unchecked(
|
||||
name: impl Into<ImmutableString>,
|
||||
curry: StaticVec<Dynamic>,
|
||||
) -> Self {
|
||||
Self(name.into(), curry)
|
||||
@ -147,7 +147,6 @@ impl FnPtr {
|
||||
pub fn is_anonymous(&self) -> bool {
|
||||
self.0.starts_with(FN_ANONYMOUS)
|
||||
}
|
||||
|
||||
/// Call the function pointer with curried arguments (if any).
|
||||
///
|
||||
/// If this function is a script-defined function, it must not be marked private.
|
||||
|
@ -144,7 +144,7 @@ macro_rules! make_func {
|
||||
|
||||
/// To Dynamic mapping function.
|
||||
#[inline(always)]
|
||||
pub fn map_dynamic<T: Variant + Clone>(data: T) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
pub fn map_dynamic(data: impl Variant + Clone) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
Ok(data.into_dynamic())
|
||||
}
|
||||
|
||||
|
@ -37,13 +37,20 @@ use crate::stdlib::{
|
||||
vec::Vec,
|
||||
};
|
||||
|
||||
pub type FuncInfo = (
|
||||
String,
|
||||
FnAccess,
|
||||
usize,
|
||||
Option<StaticVec<TypeId>>,
|
||||
CallableFunction,
|
||||
);
|
||||
/// Data structure containing a single registered function.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FuncInfo {
|
||||
/// Function instance.
|
||||
pub func: CallableFunction,
|
||||
/// Function access mode.
|
||||
pub access: FnAccess,
|
||||
/// Function name.
|
||||
pub name: String,
|
||||
/// Number of parameters.
|
||||
pub params: usize,
|
||||
/// Parameter types (if applicable).
|
||||
pub types: Option<StaticVec<TypeId>>,
|
||||
}
|
||||
|
||||
/// An imported module, which may contain variables, sub-modules,
|
||||
/// external Rust functions, and script-defined functions.
|
||||
@ -91,7 +98,7 @@ impl fmt::Debug for Module {
|
||||
.join(", "),
|
||||
self.functions
|
||||
.values()
|
||||
.map(|(_, _, _, _, f)| f.to_string())
|
||||
.map(|FuncInfo { func, .. }| func.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", "),
|
||||
)
|
||||
@ -290,13 +297,13 @@ impl Module {
|
||||
let hash_script = calc_script_fn_hash(empty(), &fn_def.name, num_params);
|
||||
self.functions.insert(
|
||||
hash_script,
|
||||
(
|
||||
fn_def.name.to_string(),
|
||||
fn_def.access,
|
||||
num_params,
|
||||
None,
|
||||
fn_def.into(),
|
||||
),
|
||||
FuncInfo {
|
||||
name: fn_def.name.to_string(),
|
||||
access: fn_def.access,
|
||||
params: num_params,
|
||||
types: None,
|
||||
func: fn_def.into(),
|
||||
},
|
||||
);
|
||||
self.indexed = false;
|
||||
hash_script
|
||||
@ -313,12 +320,19 @@ impl Module {
|
||||
) -> Option<&Shared<ScriptFnDef>> {
|
||||
self.functions
|
||||
.values()
|
||||
.find(|(fn_name, access, num, _, _)| {
|
||||
.find(
|
||||
|FuncInfo {
|
||||
name: fn_name,
|
||||
access,
|
||||
params,
|
||||
..
|
||||
}| {
|
||||
(!public_only || *access == FnAccess::Public)
|
||||
&& *num == num_params
|
||||
&& *params == num_params
|
||||
&& fn_name == name
|
||||
})
|
||||
.map(|(_, _, _, _, f)| f.get_shared_fn_def())
|
||||
},
|
||||
)
|
||||
.map(|FuncInfo { func, .. }| func.get_shared_fn_def())
|
||||
}
|
||||
|
||||
/// Does a sub-module exist in the module?
|
||||
@ -414,7 +428,7 @@ impl Module {
|
||||
} else if public_only {
|
||||
self.functions
|
||||
.get(&hash_fn)
|
||||
.map(|(_, access, _, _, _)| access.is_public())
|
||||
.map(|FuncInfo { access, .. }| access.is_public())
|
||||
.unwrap_or(false)
|
||||
} else {
|
||||
self.functions.contains_key(&hash_fn)
|
||||
@ -453,7 +467,13 @@ impl Module {
|
||||
|
||||
self.functions.insert(
|
||||
hash_fn,
|
||||
(name, access, params.len(), Some(params), func.into()),
|
||||
FuncInfo {
|
||||
name,
|
||||
access,
|
||||
params: params.len(),
|
||||
types: Some(params),
|
||||
func: func.into(),
|
||||
},
|
||||
);
|
||||
|
||||
self.indexed = false;
|
||||
@ -1091,9 +1111,9 @@ impl Module {
|
||||
} else {
|
||||
self.functions
|
||||
.get(&hash_fn)
|
||||
.and_then(|(_, access, _, _, f)| match access {
|
||||
_ if !public_only => Some(f),
|
||||
FnAccess::Public => Some(f),
|
||||
.and_then(|FuncInfo { access, func, .. }| match access {
|
||||
_ if !public_only => Some(func),
|
||||
FnAccess::Public => Some(func),
|
||||
FnAccess::Private => None,
|
||||
})
|
||||
}
|
||||
@ -1194,7 +1214,7 @@ impl Module {
|
||||
other
|
||||
.functions
|
||||
.iter()
|
||||
.filter(|(_, (_, _, _, _, v))| match v {
|
||||
.filter(|(_, FuncInfo { func, .. })| match func {
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
CallableFunction::Script(f) => {
|
||||
_filter(f.access, f.name.as_str(), f.params.len())
|
||||
@ -1218,7 +1238,8 @@ impl Module {
|
||||
&mut self,
|
||||
mut filter: impl FnMut(FnAccess, &str, usize) -> bool,
|
||||
) -> &mut Self {
|
||||
self.functions.retain(|_, (_, _, _, _, v)| match v {
|
||||
self.functions
|
||||
.retain(|_, FuncInfo { func, .. }| match func {
|
||||
CallableFunction::Script(f) => filter(f.access, f.name.as_str(), f.params.len()),
|
||||
_ => true,
|
||||
});
|
||||
@ -1266,7 +1287,7 @@ impl Module {
|
||||
) -> impl Iterator<Item = (FnAccess, &str, usize, Shared<ScriptFnDef>)> + 'a {
|
||||
self.functions
|
||||
.values()
|
||||
.map(|(_, _, _, _, f)| f)
|
||||
.map(|f| &f.func)
|
||||
.filter(|f| f.is_script())
|
||||
.map(CallableFunction::get_shared_fn_def)
|
||||
.map(|f| {
|
||||
@ -1285,10 +1306,14 @@ impl Module {
|
||||
#[cfg(not(feature = "internals"))]
|
||||
#[inline(always)]
|
||||
pub fn iter_script_fn_info(&self) -> impl Iterator<Item = (FnAccess, &str, usize)> {
|
||||
self.functions
|
||||
.values()
|
||||
.filter(|(_, _, _, _, f)| f.is_script())
|
||||
.map(|(name, access, num_params, _, _)| (*access, name.as_str(), *num_params))
|
||||
self.functions.values().filter(|f| f.func.is_script()).map(
|
||||
|FuncInfo {
|
||||
name,
|
||||
access,
|
||||
params,
|
||||
..
|
||||
}| (*access, name.as_str(), *params),
|
||||
)
|
||||
}
|
||||
|
||||
/// Get an iterator over all script-defined functions in the module.
|
||||
@ -1401,17 +1426,30 @@ impl Module {
|
||||
module
|
||||
.functions
|
||||
.iter()
|
||||
.filter(|(_, (_, access, _, _, _))| access.is_public())
|
||||
.for_each(|(&_hash, (name, _, _num_params, params, func))| {
|
||||
if let Some(params) = params {
|
||||
.filter(|(_, FuncInfo { access, .. })| access.is_public())
|
||||
.for_each(
|
||||
|(
|
||||
&_hash,
|
||||
FuncInfo {
|
||||
name,
|
||||
params,
|
||||
types,
|
||||
func,
|
||||
..
|
||||
},
|
||||
)| {
|
||||
if let Some(param_types) = types {
|
||||
assert_eq!(*params, param_types.len());
|
||||
|
||||
// Qualified Rust functions are indexed in two steps:
|
||||
// 1) Calculate a hash in a similar manner to script-defined functions,
|
||||
// i.e. qualifiers + function name + number of arguments.
|
||||
let hash_qualified_script =
|
||||
calc_script_fn_hash(qualifiers.iter().cloned(), name, params.len());
|
||||
calc_script_fn_hash(qualifiers.iter().cloned(), name, *params);
|
||||
// 2) Calculate a second hash with no qualifiers, empty function name,
|
||||
// and the actual list of argument `TypeId`'.s
|
||||
let hash_fn_args = calc_native_fn_hash(empty(), "", params.iter().cloned());
|
||||
let hash_fn_args =
|
||||
calc_native_fn_hash(empty(), "", param_types.iter().cloned());
|
||||
// 3) The final hash is the XOR of the two hashes.
|
||||
let hash_qualified_fn = hash_qualified_script ^ hash_fn_args;
|
||||
|
||||
@ -1421,11 +1459,12 @@ impl Module {
|
||||
_hash
|
||||
} else {
|
||||
// Qualifiers + function name + number of arguments.
|
||||
calc_script_fn_hash(qualifiers.iter().map(|&v| v), &name, *_num_params)
|
||||
calc_script_fn_hash(qualifiers.iter().map(|&v| v), &name, *params)
|
||||
};
|
||||
functions.push((hash_qualified_script, func.clone()));
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if !self.indexed {
|
||||
|
@ -48,7 +48,7 @@ impl StaticModuleResolver {
|
||||
}
|
||||
/// Add a module keyed by its path.
|
||||
#[inline(always)]
|
||||
pub fn insert<S: Into<String>>(&mut self, path: S, module: Module) {
|
||||
pub fn insert(&mut self, path: impl Into<String>, module: Module) {
|
||||
self.0.insert(path.into(), module);
|
||||
}
|
||||
/// Remove a module given its path.
|
||||
|
@ -408,10 +408,15 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt {
|
||||
pos,
|
||||
)))
|
||||
}
|
||||
// expr;
|
||||
Stmt::Expr(Expr::Stmt(x, _)) if matches!(*x, Stmt::Expr(_)) => {
|
||||
// {}
|
||||
Stmt::Expr(Expr::Stmt(x, pos)) if x.is_empty() => {
|
||||
state.set_dirty();
|
||||
optimize_stmt(*x, state, preserve_result)
|
||||
Stmt::Noop(pos)
|
||||
}
|
||||
// {...};
|
||||
Stmt::Expr(Expr::Stmt(x, pos)) => {
|
||||
state.set_dirty();
|
||||
Stmt::Block(x.into_vec(), pos)
|
||||
}
|
||||
// expr;
|
||||
Stmt::Expr(expr) => Stmt::Expr(optimize_expr(expr, state)),
|
||||
@ -438,8 +443,13 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
|
||||
match expr {
|
||||
// expr - do not promote because there is a reason it is wrapped in an `Expr::Expr`
|
||||
Expr::Expr(x) => Expr::Expr(Box::new(optimize_expr(*x, state))),
|
||||
// {}
|
||||
Expr::Stmt(x, pos) if x.is_empty() => {
|
||||
state.set_dirty();
|
||||
Expr::Unit(pos)
|
||||
}
|
||||
// { stmt }
|
||||
Expr::Stmt(x, pos) => match *x {
|
||||
Expr::Stmt(mut x, pos) if x.len() == 1 => match x.pop().unwrap() {
|
||||
// {} -> ()
|
||||
Stmt::Noop(_) => {
|
||||
state.set_dirty();
|
||||
@ -451,8 +461,12 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
|
||||
optimize_expr(expr, state)
|
||||
}
|
||||
// { stmt }
|
||||
stmt => Expr::Stmt(Box::new(optimize_stmt(stmt, state, true)), pos),
|
||||
},
|
||||
stmt => Expr::Stmt(Box::new(vec![optimize_stmt(stmt, state, true)].into()), pos)
|
||||
}
|
||||
// { stmt; ... }
|
||||
Expr::Stmt(x, pos) => Expr::Stmt(Box::new(
|
||||
x.into_iter().map(|stmt| optimize_stmt(stmt, state, true)).collect(),
|
||||
), pos),
|
||||
|
||||
// lhs.rhs
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
@ -852,7 +866,6 @@ pub fn optimize_into_ast(
|
||||
params: fn_def.params.clone(),
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
externals: fn_def.externals.clone(),
|
||||
pos: fn_def.pos,
|
||||
lib: None,
|
||||
}
|
||||
.into()
|
||||
|
@ -1,6 +1,10 @@
|
||||
use crate::def_package;
|
||||
use crate::plugin::*;
|
||||
|
||||
#[cfg(any(
|
||||
not(feature = "no_float"),
|
||||
all(not(feature = "only_i32"), not(feature = "only_i64"))
|
||||
))]
|
||||
macro_rules! gen_cmp_functions {
|
||||
($root:ident => $($arg_type:ident),+) => {
|
||||
mod $root { $(pub mod $arg_type {
|
||||
@ -37,6 +41,10 @@ macro_rules! gen_cmp_functions {
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(any(
|
||||
not(feature = "no_float"),
|
||||
all(not(feature = "only_i32"), not(feature = "only_i64"))
|
||||
))]
|
||||
macro_rules! reg_functions {
|
||||
($mod_name:ident += $root:ident ; $($arg_type:ident),+) => { $(
|
||||
combine_with_exported_module!($mod_name, "logic", $root::$arg_type::functions);
|
||||
@ -54,8 +62,14 @@ def_package!(crate:LogicPackage:"Logical operators.", lib, {
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
{
|
||||
#[cfg(not(feature = "f32_float"))]
|
||||
reg_functions!(lib += float; f32);
|
||||
|
||||
#[cfg(feature = "f32_float")]
|
||||
reg_functions!(lib += float; f64);
|
||||
}
|
||||
|
||||
set_exported_fn!(lib, "!", not);
|
||||
});
|
||||
|
||||
@ -75,4 +89,9 @@ gen_cmp_functions!(numbers => i8, u8, i16, u16, i32, u32, u64);
|
||||
gen_cmp_functions!(num_128 => i128, u128);
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
#[cfg(not(feature = "f32_float"))]
|
||||
gen_cmp_functions!(float => f32);
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
#[cfg(feature = "f32_float")]
|
||||
gen_cmp_functions!(float => f64);
|
||||
|
@ -765,8 +765,10 @@ fn parse_primary(
|
||||
let (token, _) = match token {
|
||||
// { - block statement as expression
|
||||
Token::LeftBrace if settings.allow_stmt_expr => {
|
||||
return parse_block(input, state, lib, settings.level_up())
|
||||
.map(|block| Expr::Stmt(Box::new(block), settings.pos))
|
||||
return parse_block(input, state, lib, settings.level_up()).map(|block| match block {
|
||||
Stmt::Block(statements, pos) => Expr::Stmt(Box::new(statements.into()), pos),
|
||||
_ => unreachable!(),
|
||||
})
|
||||
}
|
||||
Token::EOF => return Err(PERR::UnexpectedEOF.into_err(settings.pos)),
|
||||
_ => input.next().unwrap(),
|
||||
@ -962,10 +964,11 @@ fn parse_unary(
|
||||
|
||||
match token {
|
||||
// If statement is allowed to act as expressions
|
||||
Token::If if settings.allow_if_expr => Ok(Expr::Stmt(
|
||||
Box::new(parse_if(input, state, lib, settings.level_up())?),
|
||||
settings.pos,
|
||||
)),
|
||||
Token::If if settings.allow_if_expr => {
|
||||
let mut block: StaticVec<_> = Default::default();
|
||||
block.push(parse_if(input, state, lib, settings.level_up())?);
|
||||
Ok(Expr::Stmt(Box::new(block), settings.pos))
|
||||
}
|
||||
// -expr
|
||||
Token::UnaryMinus => {
|
||||
let pos = eat_token(input, Token::UnaryMinus);
|
||||
@ -1657,12 +1660,13 @@ fn parse_custom_syntax(
|
||||
exprs.push(parse_expr(input, state, lib, settings)?);
|
||||
segments.push(MARKER_EXPR.into());
|
||||
}
|
||||
MARKER_BLOCK => {
|
||||
let stmt = parse_block(input, state, lib, settings)?;
|
||||
let pos = stmt.position();
|
||||
exprs.push(Expr::Stmt(Box::new(stmt), pos));
|
||||
MARKER_BLOCK => match parse_block(input, state, lib, settings)? {
|
||||
Stmt::Block(statements, pos) => {
|
||||
exprs.push(Expr::Stmt(Box::new(statements.into()), pos));
|
||||
segments.push(MARKER_BLOCK.into());
|
||||
}
|
||||
_ => unreachable!(),
|
||||
},
|
||||
s => match input.next().unwrap() {
|
||||
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
|
||||
(t, _) if t.syntax().as_ref() == s => {
|
||||
@ -2481,7 +2485,6 @@ fn parse_fn(
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
externals,
|
||||
body,
|
||||
pos: settings.pos,
|
||||
lib: None,
|
||||
})
|
||||
}
|
||||
@ -2525,12 +2528,12 @@ fn make_curry_from_externals(fn_expr: Expr, externals: StaticVec<Ident>, pos: Po
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
{
|
||||
// Statement block
|
||||
let mut statements: Vec<_> = Default::default();
|
||||
let mut statements: StaticVec<_> = Default::default();
|
||||
// Insert `Share` statements
|
||||
statements.extend(externals.into_iter().map(|x| Stmt::Share(Box::new(x))));
|
||||
// Final expression
|
||||
statements.push(Stmt::Expr(expr));
|
||||
Expr::Stmt(Box::new(Stmt::Block(statements, pos)), pos)
|
||||
Expr::Stmt(Box::new(statements), pos)
|
||||
}
|
||||
|
||||
#[cfg(feature = "no_closure")]
|
||||
@ -2651,7 +2654,6 @@ fn parse_anon_fn(
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
externals: Default::default(),
|
||||
body,
|
||||
pos: settings.pos,
|
||||
lib: None,
|
||||
};
|
||||
|
||||
|
24
src/scope.rs
24
src/scope.rs
@ -168,10 +168,10 @@ impl<'a> Scope<'a> {
|
||||
/// assert_eq!(my_scope.get_value::<i64>("x").unwrap(), 42);
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn push<K: Into<Cow<'a, str>>, T: Variant + Clone>(
|
||||
pub fn push(
|
||||
&mut self,
|
||||
name: K,
|
||||
value: T,
|
||||
name: impl Into<Cow<'a, str>>,
|
||||
value: impl Variant + Clone,
|
||||
) -> &mut Self {
|
||||
self.push_dynamic_value(name, EntryType::Normal, Dynamic::from(value))
|
||||
}
|
||||
@ -189,7 +189,7 @@ impl<'a> Scope<'a> {
|
||||
/// assert_eq!(my_scope.get_value::<i64>("x").unwrap(), 42);
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn push_dynamic<K: Into<Cow<'a, str>>>(&mut self, name: K, value: Dynamic) -> &mut Self {
|
||||
pub fn push_dynamic(&mut self, name: impl Into<Cow<'a, str>>, value: Dynamic) -> &mut Self {
|
||||
self.push_dynamic_value(name, EntryType::Normal, value)
|
||||
}
|
||||
|
||||
@ -212,10 +212,10 @@ impl<'a> Scope<'a> {
|
||||
/// assert_eq!(my_scope.get_value::<i64>("x").unwrap(), 42);
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn push_constant<K: Into<Cow<'a, str>>, T: Variant + Clone>(
|
||||
pub fn push_constant(
|
||||
&mut self,
|
||||
name: K,
|
||||
value: T,
|
||||
name: impl Into<Cow<'a, str>>,
|
||||
value: impl Variant + Clone,
|
||||
) -> &mut Self {
|
||||
self.push_dynamic_value(name, EntryType::Constant, Dynamic::from(value))
|
||||
}
|
||||
@ -240,9 +240,9 @@ impl<'a> Scope<'a> {
|
||||
/// assert_eq!(my_scope.get_value::<i64>("x").unwrap(), 42);
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn push_constant_dynamic<K: Into<Cow<'a, str>>>(
|
||||
pub fn push_constant_dynamic(
|
||||
&mut self,
|
||||
name: K,
|
||||
name: impl Into<Cow<'a, str>>,
|
||||
value: Dynamic,
|
||||
) -> &mut Self {
|
||||
self.push_dynamic_value(name, EntryType::Constant, value)
|
||||
@ -250,9 +250,9 @@ impl<'a> Scope<'a> {
|
||||
|
||||
/// Add (push) a new entry with a `Dynamic` value to the Scope.
|
||||
#[inline]
|
||||
pub(crate) fn push_dynamic_value<K: Into<Cow<'a, str>>>(
|
||||
pub(crate) fn push_dynamic_value(
|
||||
&mut self,
|
||||
name: K,
|
||||
name: impl Into<Cow<'a, str>>,
|
||||
entry_type: EntryType,
|
||||
value: Dynamic,
|
||||
) -> &mut Self {
|
||||
@ -377,7 +377,7 @@ impl<'a> Scope<'a> {
|
||||
/// assert_eq!(my_scope.get_value::<i64>("x").unwrap(), 0);
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn set_value<T: Variant + Clone>(&mut self, name: &'a str, value: T) -> &mut Self {
|
||||
pub fn set_value(&mut self, name: &'a str, value: impl Variant + Clone) -> &mut Self {
|
||||
match self.get_index(name) {
|
||||
None => {
|
||||
self.push(name, value);
|
||||
|
@ -452,28 +452,19 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> {
|
||||
}
|
||||
|
||||
/// `SeqAccess` implementation for arrays.
|
||||
struct IterateArray<'a, ITER>
|
||||
where
|
||||
ITER: Iterator<Item = &'a Dynamic>,
|
||||
{
|
||||
struct IterateArray<'a, ITER: Iterator<Item = &'a Dynamic>> {
|
||||
/// Iterator for a stream of `Dynamic` values.
|
||||
iter: ITER,
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
impl<'a, ITER> IterateArray<'a, ITER>
|
||||
where
|
||||
ITER: Iterator<Item = &'a Dynamic>,
|
||||
{
|
||||
impl<'a, ITER: Iterator<Item = &'a Dynamic>> IterateArray<'a, ITER> {
|
||||
pub fn new(iter: ITER) -> Self {
|
||||
Self { iter }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a: 'de, 'de, ITER> SeqAccess<'de> for IterateArray<'a, ITER>
|
||||
where
|
||||
ITER: Iterator<Item = &'a Dynamic>,
|
||||
{
|
||||
impl<'a: 'de, 'de, ITER: Iterator<Item = &'a Dynamic>> SeqAccess<'de> for IterateArray<'a, ITER> {
|
||||
type Error = Box<EvalAltResult>;
|
||||
|
||||
fn next_element_seed<T: DeserializeSeed<'de>>(
|
||||
@ -555,10 +546,10 @@ impl<'t, 'de> EnumAccess<'de> for EnumDeserializer<'t, 'de> {
|
||||
type Error = Box<EvalAltResult>;
|
||||
type Variant = Self;
|
||||
|
||||
fn variant_seed<V>(self, seed: V) -> Result<(V::Value, Self::Variant), Self::Error>
|
||||
where
|
||||
V: DeserializeSeed<'de>,
|
||||
{
|
||||
fn variant_seed<V: DeserializeSeed<'de>>(
|
||||
self,
|
||||
seed: V,
|
||||
) -> Result<(V::Value, Self::Variant), Self::Error> {
|
||||
seed.deserialize(self.tag.into_deserializer())
|
||||
.map(|v| (v, self))
|
||||
}
|
||||
@ -572,28 +563,26 @@ impl<'t, 'de> VariantAccess<'de> for EnumDeserializer<'t, 'de> {
|
||||
Deserialize::deserialize(&mut self.content)
|
||||
}
|
||||
|
||||
fn newtype_variant_seed<T>(mut self, seed: T) -> Result<T::Value, Self::Error>
|
||||
where
|
||||
T: DeserializeSeed<'de>,
|
||||
{
|
||||
fn newtype_variant_seed<T: DeserializeSeed<'de>>(
|
||||
mut self,
|
||||
seed: T,
|
||||
) -> Result<T::Value, Self::Error> {
|
||||
seed.deserialize(&mut self.content)
|
||||
}
|
||||
|
||||
fn tuple_variant<V>(mut self, len: usize, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
fn tuple_variant<V: Visitor<'de>>(
|
||||
mut self,
|
||||
len: usize,
|
||||
visitor: V,
|
||||
) -> Result<V::Value, Self::Error> {
|
||||
self.content.deserialize_tuple(len, visitor)
|
||||
}
|
||||
|
||||
fn struct_variant<V>(
|
||||
fn struct_variant<V: Visitor<'de>>(
|
||||
mut self,
|
||||
fields: &'static [&'static str],
|
||||
visitor: V,
|
||||
) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
) -> Result<V::Value, Self::Error> {
|
||||
self.content.deserialize_struct("", fields, visitor)
|
||||
}
|
||||
}
|
||||
|
@ -66,7 +66,7 @@ macro_rules! reg_functions {
|
||||
}
|
||||
}
|
||||
|
||||
fn make_greeting<T: std::fmt::Display>(n: T) -> String {
|
||||
fn make_greeting(n: impl std::fmt::Display) -> String {
|
||||
format!("{} kitties", n)
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user