Merge pull request #280 from schungx/master

Wrap up 0.19.4.
This commit is contained in:
Stephen Chung 2020-11-04 17:27:30 +08:00 committed by GitHub
commit b46420778d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 302 additions and 183 deletions

View File

@ -6,7 +6,7 @@ members = [
[package] [package]
name = "rhai" name = "rhai"
version = "0.19.3" version = "0.19.4"
edition = "2018" edition = "2018"
authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung", "jhwgh1968"] authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung", "jhwgh1968"]
description = "Embedded scripting for Rust" description = "Embedded scripting for Rust"

View File

@ -30,7 +30,7 @@ Standard features
* Fairly low compile-time overhead. * Fairly low compile-time overhead.
* Fairly efficient evaluation (1 million iterations in 0.3 sec on a single core, 2.3 GHz Linux VM). * 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). * 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. * 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). * Relatively little `unsafe` code (yes there are some for performance reasons).
* Few dependencies (currently only [`smallvec`](https://crates.io/crates/smallvec)). * Few dependencies (currently only [`smallvec`](https://crates.io/crates/smallvec)).

View File

@ -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. * 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::ErrorAssignmentToUnknownLHS` is moved to `ParseError::AssignmentToInvalidLHS`. `ParseError::AssignmentToCopy` is removed.
* `EvalAltResult::ErrorDataTooLarge` is simplified. * `EvalAltResult::ErrorDataTooLarge` is simplified.
* `Engine::on_progress` closure signature now returns `Option<Dynamic>` with the termination value passed on to `EvalAltResult::ErrorTerminated`. * `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`. * `f32_float` feature to set `FLOAT` to `f32`.
* Low-level API for custom syntax allowing more flexibility in designing the syntax. * Low-level API for custom syntax allowing more flexibility in designing the syntax.
* `Module::fill_with` to poly-fill a module with another. * `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 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. * 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 Version 0.19.3

View File

@ -8,9 +8,11 @@ Easy
* Easy-to-use language similar to JavaScript+Rust with dynamic typing. * 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. * 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). * 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 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. * Track script evaluation [progress] and manually terminate a script run.

View File

@ -1,5 +1,5 @@
{ {
"version": "0.19.3", "version": "0.19.4",
"repoHome": "https://github.com/jonathandturner/rhai/blob/master", "repoHome": "https://github.com/jonathandturner/rhai/blob/master",
"repoTree": "https://github.com/jonathandturner/rhai/tree/master", "repoTree": "https://github.com/jonathandturner/rhai/tree/master",
"rootUrl": "", "rootUrl": "",

View File

@ -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 have any prerequisites other than being `Clone`. It does not need to implement
any other trait or use any custom `#[derive]`. 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 as possible, usually silently and seamlessly. External types that are not defined
within the same crate (and thus cannot implement special Rhai traits or within the same crate (and thus cannot implement special Rhai traits or
use special `#[derive]`) can also be used easily with Rhai. use special `#[derive]`) can also be used easily with Rhai.

View File

@ -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. 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 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 Operations Count vs. Progress Percentage

View File

@ -29,8 +29,9 @@ due to 64-bit arithmetic requiring more CPU cycles to complete.
Minimize Size of `Dynamic` 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. Turning on [`no_float`] or [`f32_float`] and [`only_i32`] on 32-bit targets makes the critical [`Dynamic`]
Normally [`Dynamic`] can be up to 16 bytes (e.g. on x86/x64 CPU's) in order to hold an `i64` or `f64`. 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. A small [`Dynamic`] helps performance due to better cache efficiency.

View File

@ -42,13 +42,14 @@ are typically used for a WASM build:
| Feature | Description | | 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. | | [`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. | | [`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: The following features are typically _not_ used because they don't make sense in a WASM build:
| Feature | Why unnecessary | | Feature | Why unnecessary |
| :-----------: | ------------------------------------------------------------------ | | :-----------: | ------------------------------------------------------------------------------------------------------ |
| [`sync`] | WASM is single-threaded. | | [`sync`] | WASM is single-threaded. |
| [`no_std`] | `std` lib works fine with WASM. | | [`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`]. |

View File

@ -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` | | `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_optimize` | no | disables [script optimization] |
| `no_float` | no | disables floating-point numbers and math | | `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_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` | | `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 | | `no_index` | no | disables [arrays] and indexing features |

View File

@ -12,7 +12,7 @@ homepage = "https://github.com/jonathandturner/rhai/tree/no_std/no_std_test"
repository = "https://github.com/jonathandturner/rhai" repository = "https://github.com/jonathandturner/rhai"
[dependencies] [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 } wee_alloc = { version = "0.4.5", default_features = false }
[profile.dev] [profile.dev]

View File

@ -83,6 +83,10 @@ impl FnAccess {
/// This type is volatile and may change. /// This type is volatile and may change.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ScriptFnDef { pub struct ScriptFnDef {
/// Function body.
pub body: Stmt,
/// Encapsulated running environment, if any.
pub lib: Option<Shared<Module>>,
/// Function name. /// Function name.
pub name: ImmutableString, pub name: ImmutableString,
/// Function access mode. /// Function access mode.
@ -92,12 +96,6 @@ pub struct ScriptFnDef {
/// Access to external variables. /// Access to external variables.
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
pub externals: HashSet<String>, 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 { impl fmt::Display for ScriptFnDef {
@ -908,7 +906,7 @@ pub enum Expr {
/// Property access - (getter, setter), prop /// Property access - (getter, setter), prop
Property(Box<((String, String), IdentX)>), Property(Box<((String, String), IdentX)>),
/// { stmt } /// { stmt }
Stmt(Box<Stmt>, Position), Stmt(Box<StaticVec<Stmt>>, Position),
/// Wrapped expression - should not be optimized away. /// Wrapped expression - should not be optimized away.
Expr(Box<Expr>), Expr(Box<Expr>),
/// func(expr, ... ) /// func(expr, ... )
@ -1092,7 +1090,7 @@ impl Expr {
x.lhs.is_pure() && x.rhs.is_pure() 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, 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);
}
}

View File

@ -1579,7 +1579,9 @@ impl Engine {
Expr::Property(_) => unreachable!(), Expr::Property(_) => unreachable!(),
// Statement block // 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] // lhs[idx_expr]
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
@ -1706,6 +1708,37 @@ impl Engine {
.map_err(|err| err.fill_position(expr.position())) .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 /// Evaluate a statement
/// ///
/// ///
@ -1886,23 +1919,7 @@ impl Engine {
// Block scope // Block scope
Stmt::Block(statements, _) => { Stmt::Block(statements, _) => {
let prev_scope_len = scope.len(); self.eval_statements(scope, mods, state, lib, this_ptr, statements, level)
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
} }
// If-else statement // If-else statement

View File

@ -1405,7 +1405,7 @@ impl Engine {
mods: &mut Imports, mods: &mut Imports,
ast: &'a AST, ast: &'a AST,
) -> Result<(Dynamic, u64), Box<EvalAltResult>> { ) -> 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). /// Evaluate a file, but throw away the result and only return error (if any).
@ -1467,7 +1467,7 @@ impl Engine {
ast: &AST, ast: &AST,
) -> Result<(), Box<EvalAltResult>> { ) -> Result<(), Box<EvalAltResult>> {
let mut mods = Default::default(); 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(|_| ()) .map(|_| ())
} }
@ -1661,8 +1661,8 @@ impl Engine {
let lib = if cfg!(not(feature = "no_function")) { let lib = if cfg!(not(feature = "no_function")) {
ast.lib() ast.lib()
.iter_fn() .iter_fn()
.filter(|(_, _, _, _, f)| f.is_script()) .filter(|f| f.func.is_script())
.map(|(_, _, _, _, f)| f.get_fn_def().clone()) .map(|f| f.func.get_fn_def().clone())
.collect() .collect()
} else { } else {
Default::default() Default::default()

View File

@ -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] #[inline]
pub(crate) fn eval_statements<'a>( pub(crate) fn eval_statements_raw<'a>(
&self, &self,
scope: &mut Scope, scope: &mut Scope,
mods: &mut Imports, mods: &mut Imports,
@ -667,7 +668,7 @@ impl Engine {
} }
// Evaluate the AST // 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; state.operations += operations;
self.inc_operations(state)?; self.inc_operations(state)?;

View File

@ -115,8 +115,8 @@ pub struct FnPtr(ImmutableString, StaticVec<Dynamic>);
impl FnPtr { impl FnPtr {
/// Create a new function pointer. /// Create a new function pointer.
#[inline(always)] #[inline(always)]
pub(crate) fn new_unchecked<S: Into<ImmutableString>>( pub(crate) fn new_unchecked(
name: S, name: impl Into<ImmutableString>,
curry: StaticVec<Dynamic>, curry: StaticVec<Dynamic>,
) -> Self { ) -> Self {
Self(name.into(), curry) Self(name.into(), curry)
@ -147,7 +147,6 @@ impl FnPtr {
pub fn is_anonymous(&self) -> bool { pub fn is_anonymous(&self) -> bool {
self.0.starts_with(FN_ANONYMOUS) self.0.starts_with(FN_ANONYMOUS)
} }
/// Call the function pointer with curried arguments (if any). /// Call the function pointer with curried arguments (if any).
/// ///
/// If this function is a script-defined function, it must not be marked private. /// If this function is a script-defined function, it must not be marked private.

View File

@ -144,7 +144,7 @@ macro_rules! make_func {
/// To Dynamic mapping function. /// To Dynamic mapping function.
#[inline(always)] #[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()) Ok(data.into_dynamic())
} }

View File

@ -37,13 +37,20 @@ use crate::stdlib::{
vec::Vec, vec::Vec,
}; };
pub type FuncInfo = ( /// Data structure containing a single registered function.
String, #[derive(Debug, Clone)]
FnAccess, pub struct FuncInfo {
usize, /// Function instance.
Option<StaticVec<TypeId>>, pub func: CallableFunction,
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, /// An imported module, which may contain variables, sub-modules,
/// external Rust functions, and script-defined functions. /// external Rust functions, and script-defined functions.
@ -91,7 +98,7 @@ impl fmt::Debug for Module {
.join(", "), .join(", "),
self.functions self.functions
.values() .values()
.map(|(_, _, _, _, f)| f.to_string()) .map(|FuncInfo { func, .. }| func.to_string())
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join(", "), .join(", "),
) )
@ -290,13 +297,13 @@ impl Module {
let hash_script = calc_script_fn_hash(empty(), &fn_def.name, num_params); let hash_script = calc_script_fn_hash(empty(), &fn_def.name, num_params);
self.functions.insert( self.functions.insert(
hash_script, hash_script,
( FuncInfo {
fn_def.name.to_string(), name: fn_def.name.to_string(),
fn_def.access, access: fn_def.access,
num_params, params: num_params,
None, types: None,
fn_def.into(), func: fn_def.into(),
), },
); );
self.indexed = false; self.indexed = false;
hash_script hash_script
@ -313,12 +320,19 @@ impl Module {
) -> Option<&Shared<ScriptFnDef>> { ) -> Option<&Shared<ScriptFnDef>> {
self.functions self.functions
.values() .values()
.find(|(fn_name, access, num, _, _)| { .find(
|FuncInfo {
name: fn_name,
access,
params,
..
}| {
(!public_only || *access == FnAccess::Public) (!public_only || *access == FnAccess::Public)
&& *num == num_params && *params == num_params
&& fn_name == name && 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? /// Does a sub-module exist in the module?
@ -414,7 +428,7 @@ impl Module {
} else if public_only { } else if public_only {
self.functions self.functions
.get(&hash_fn) .get(&hash_fn)
.map(|(_, access, _, _, _)| access.is_public()) .map(|FuncInfo { access, .. }| access.is_public())
.unwrap_or(false) .unwrap_or(false)
} else { } else {
self.functions.contains_key(&hash_fn) self.functions.contains_key(&hash_fn)
@ -453,7 +467,13 @@ impl Module {
self.functions.insert( self.functions.insert(
hash_fn, 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; self.indexed = false;
@ -1091,9 +1111,9 @@ impl Module {
} else { } else {
self.functions self.functions
.get(&hash_fn) .get(&hash_fn)
.and_then(|(_, access, _, _, f)| match access { .and_then(|FuncInfo { access, func, .. }| match access {
_ if !public_only => Some(f), _ if !public_only => Some(func),
FnAccess::Public => Some(f), FnAccess::Public => Some(func),
FnAccess::Private => None, FnAccess::Private => None,
}) })
} }
@ -1194,7 +1214,7 @@ impl Module {
other other
.functions .functions
.iter() .iter()
.filter(|(_, (_, _, _, _, v))| match v { .filter(|(_, FuncInfo { func, .. })| match func {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
CallableFunction::Script(f) => { CallableFunction::Script(f) => {
_filter(f.access, f.name.as_str(), f.params.len()) _filter(f.access, f.name.as_str(), f.params.len())
@ -1218,7 +1238,8 @@ impl Module {
&mut self, &mut self,
mut filter: impl FnMut(FnAccess, &str, usize) -> bool, mut filter: impl FnMut(FnAccess, &str, usize) -> bool,
) -> &mut Self { ) -> &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()), CallableFunction::Script(f) => filter(f.access, f.name.as_str(), f.params.len()),
_ => true, _ => true,
}); });
@ -1266,7 +1287,7 @@ impl Module {
) -> impl Iterator<Item = (FnAccess, &str, usize, Shared<ScriptFnDef>)> + 'a { ) -> impl Iterator<Item = (FnAccess, &str, usize, Shared<ScriptFnDef>)> + 'a {
self.functions self.functions
.values() .values()
.map(|(_, _, _, _, f)| f) .map(|f| &f.func)
.filter(|f| f.is_script()) .filter(|f| f.is_script())
.map(CallableFunction::get_shared_fn_def) .map(CallableFunction::get_shared_fn_def)
.map(|f| { .map(|f| {
@ -1285,10 +1306,14 @@ impl Module {
#[cfg(not(feature = "internals"))] #[cfg(not(feature = "internals"))]
#[inline(always)] #[inline(always)]
pub fn iter_script_fn_info(&self) -> impl Iterator<Item = (FnAccess, &str, usize)> { pub fn iter_script_fn_info(&self) -> impl Iterator<Item = (FnAccess, &str, usize)> {
self.functions self.functions.values().filter(|f| f.func.is_script()).map(
.values() |FuncInfo {
.filter(|(_, _, _, _, f)| f.is_script()) name,
.map(|(name, access, num_params, _, _)| (*access, name.as_str(), *num_params)) access,
params,
..
}| (*access, name.as_str(), *params),
)
} }
/// Get an iterator over all script-defined functions in the module. /// Get an iterator over all script-defined functions in the module.
@ -1401,17 +1426,30 @@ impl Module {
module module
.functions .functions
.iter() .iter()
.filter(|(_, (_, access, _, _, _))| access.is_public()) .filter(|(_, FuncInfo { access, .. })| access.is_public())
.for_each(|(&_hash, (name, _, _num_params, params, func))| { .for_each(
if let Some(params) = params { |(
&_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: // Qualified Rust functions are indexed in two steps:
// 1) Calculate a hash in a similar manner to script-defined functions, // 1) Calculate a hash in a similar manner to script-defined functions,
// i.e. qualifiers + function name + number of arguments. // i.e. qualifiers + function name + number of arguments.
let hash_qualified_script = 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, // 2) Calculate a second hash with no qualifiers, empty function name,
// and the actual list of argument `TypeId`'.s // 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. // 3) The final hash is the XOR of the two hashes.
let hash_qualified_fn = hash_qualified_script ^ hash_fn_args; let hash_qualified_fn = hash_qualified_script ^ hash_fn_args;
@ -1421,11 +1459,12 @@ impl Module {
_hash _hash
} else { } else {
// Qualifiers + function name + number of arguments. // 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())); functions.push((hash_qualified_script, func.clone()));
} }
}); },
);
} }
if !self.indexed { if !self.indexed {

View File

@ -48,7 +48,7 @@ impl StaticModuleResolver {
} }
/// Add a module keyed by its path. /// Add a module keyed by its path.
#[inline(always)] #[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); self.0.insert(path.into(), module);
} }
/// Remove a module given its path. /// Remove a module given its path.

View File

@ -408,10 +408,15 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt {
pos, 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(); 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; // expr;
Stmt::Expr(expr) => Stmt::Expr(optimize_expr(expr, state)), Stmt::Expr(expr) => Stmt::Expr(optimize_expr(expr, state)),
@ -438,8 +443,13 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
match expr { match expr {
// expr - do not promote because there is a reason it is wrapped in an `Expr::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::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 } // { stmt }
Expr::Stmt(x, pos) => match *x { Expr::Stmt(mut x, pos) if x.len() == 1 => match x.pop().unwrap() {
// {} -> () // {} -> ()
Stmt::Noop(_) => { Stmt::Noop(_) => {
state.set_dirty(); state.set_dirty();
@ -451,8 +461,12 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
optimize_expr(expr, state) optimize_expr(expr, state)
} }
// { stmt } // { 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 // lhs.rhs
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
@ -852,7 +866,6 @@ pub fn optimize_into_ast(
params: fn_def.params.clone(), params: fn_def.params.clone(),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
externals: fn_def.externals.clone(), externals: fn_def.externals.clone(),
pos: fn_def.pos,
lib: None, lib: None,
} }
.into() .into()

View File

@ -1,6 +1,10 @@
use crate::def_package; use crate::def_package;
use crate::plugin::*; use crate::plugin::*;
#[cfg(any(
not(feature = "no_float"),
all(not(feature = "only_i32"), not(feature = "only_i64"))
))]
macro_rules! gen_cmp_functions { macro_rules! gen_cmp_functions {
($root:ident => $($arg_type:ident),+) => { ($root:ident => $($arg_type:ident),+) => {
mod $root { $(pub mod $arg_type { 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 { macro_rules! reg_functions {
($mod_name:ident += $root:ident ; $($arg_type:ident),+) => { $( ($mod_name:ident += $root:ident ; $($arg_type:ident),+) => { $(
combine_with_exported_module!($mod_name, "logic", $root::$arg_type::functions); 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 = "no_float"))]
{
#[cfg(not(feature = "f32_float"))]
reg_functions!(lib += float; f32); reg_functions!(lib += float; f32);
#[cfg(feature = "f32_float")]
reg_functions!(lib += float; f64);
}
set_exported_fn!(lib, "!", not); 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); gen_cmp_functions!(num_128 => i128, u128);
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
#[cfg(not(feature = "f32_float"))]
gen_cmp_functions!(float => f32); gen_cmp_functions!(float => f32);
#[cfg(not(feature = "no_float"))]
#[cfg(feature = "f32_float")]
gen_cmp_functions!(float => f64);

View File

@ -765,8 +765,10 @@ fn parse_primary(
let (token, _) = match token { let (token, _) = match token {
// { - block statement as expression // { - block statement as expression
Token::LeftBrace if settings.allow_stmt_expr => { Token::LeftBrace if settings.allow_stmt_expr => {
return parse_block(input, state, lib, settings.level_up()) return parse_block(input, state, lib, settings.level_up()).map(|block| match block {
.map(|block| Expr::Stmt(Box::new(block), settings.pos)) Stmt::Block(statements, pos) => Expr::Stmt(Box::new(statements.into()), pos),
_ => unreachable!(),
})
} }
Token::EOF => return Err(PERR::UnexpectedEOF.into_err(settings.pos)), Token::EOF => return Err(PERR::UnexpectedEOF.into_err(settings.pos)),
_ => input.next().unwrap(), _ => input.next().unwrap(),
@ -962,10 +964,11 @@ fn parse_unary(
match token { match token {
// If statement is allowed to act as expressions // If statement is allowed to act as expressions
Token::If if settings.allow_if_expr => Ok(Expr::Stmt( Token::If if settings.allow_if_expr => {
Box::new(parse_if(input, state, lib, settings.level_up())?), let mut block: StaticVec<_> = Default::default();
settings.pos, block.push(parse_if(input, state, lib, settings.level_up())?);
)), Ok(Expr::Stmt(Box::new(block), settings.pos))
}
// -expr // -expr
Token::UnaryMinus => { Token::UnaryMinus => {
let pos = eat_token(input, 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)?); exprs.push(parse_expr(input, state, lib, settings)?);
segments.push(MARKER_EXPR.into()); segments.push(MARKER_EXPR.into());
} }
MARKER_BLOCK => { MARKER_BLOCK => match parse_block(input, state, lib, settings)? {
let stmt = parse_block(input, state, lib, settings)?; Stmt::Block(statements, pos) => {
let pos = stmt.position(); exprs.push(Expr::Stmt(Box::new(statements.into()), pos));
exprs.push(Expr::Stmt(Box::new(stmt), pos));
segments.push(MARKER_BLOCK.into()); segments.push(MARKER_BLOCK.into());
} }
_ => unreachable!(),
},
s => match input.next().unwrap() { s => match input.next().unwrap() {
(Token::LexError(err), pos) => return Err(err.into_err(pos)), (Token::LexError(err), pos) => return Err(err.into_err(pos)),
(t, _) if t.syntax().as_ref() == s => { (t, _) if t.syntax().as_ref() == s => {
@ -2481,7 +2485,6 @@ fn parse_fn(
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
externals, externals,
body, body,
pos: settings.pos,
lib: None, lib: None,
}) })
} }
@ -2525,12 +2528,12 @@ fn make_curry_from_externals(fn_expr: Expr, externals: StaticVec<Ident>, pos: Po
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
{ {
// Statement block // Statement block
let mut statements: Vec<_> = Default::default(); let mut statements: StaticVec<_> = Default::default();
// Insert `Share` statements // Insert `Share` statements
statements.extend(externals.into_iter().map(|x| Stmt::Share(Box::new(x)))); statements.extend(externals.into_iter().map(|x| Stmt::Share(Box::new(x))));
// Final expression // Final expression
statements.push(Stmt::Expr(expr)); statements.push(Stmt::Expr(expr));
Expr::Stmt(Box::new(Stmt::Block(statements, pos)), pos) Expr::Stmt(Box::new(statements), pos)
} }
#[cfg(feature = "no_closure")] #[cfg(feature = "no_closure")]
@ -2651,7 +2654,6 @@ fn parse_anon_fn(
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
externals: Default::default(), externals: Default::default(),
body, body,
pos: settings.pos,
lib: None, lib: None,
}; };

View File

@ -168,10 +168,10 @@ impl<'a> Scope<'a> {
/// assert_eq!(my_scope.get_value::<i64>("x").unwrap(), 42); /// assert_eq!(my_scope.get_value::<i64>("x").unwrap(), 42);
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn push<K: Into<Cow<'a, str>>, T: Variant + Clone>( pub fn push(
&mut self, &mut self,
name: K, name: impl Into<Cow<'a, str>>,
value: T, value: impl Variant + Clone,
) -> &mut Self { ) -> &mut Self {
self.push_dynamic_value(name, EntryType::Normal, Dynamic::from(value)) 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); /// assert_eq!(my_scope.get_value::<i64>("x").unwrap(), 42);
/// ``` /// ```
#[inline(always)] #[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) 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); /// assert_eq!(my_scope.get_value::<i64>("x").unwrap(), 42);
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn push_constant<K: Into<Cow<'a, str>>, T: Variant + Clone>( pub fn push_constant(
&mut self, &mut self,
name: K, name: impl Into<Cow<'a, str>>,
value: T, value: impl Variant + Clone,
) -> &mut Self { ) -> &mut Self {
self.push_dynamic_value(name, EntryType::Constant, Dynamic::from(value)) 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); /// assert_eq!(my_scope.get_value::<i64>("x").unwrap(), 42);
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn push_constant_dynamic<K: Into<Cow<'a, str>>>( pub fn push_constant_dynamic(
&mut self, &mut self,
name: K, name: impl Into<Cow<'a, str>>,
value: Dynamic, value: Dynamic,
) -> &mut Self { ) -> &mut Self {
self.push_dynamic_value(name, EntryType::Constant, value) 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. /// Add (push) a new entry with a `Dynamic` value to the Scope.
#[inline] #[inline]
pub(crate) fn push_dynamic_value<K: Into<Cow<'a, str>>>( pub(crate) fn push_dynamic_value(
&mut self, &mut self,
name: K, name: impl Into<Cow<'a, str>>,
entry_type: EntryType, entry_type: EntryType,
value: Dynamic, value: Dynamic,
) -> &mut Self { ) -> &mut Self {
@ -377,7 +377,7 @@ impl<'a> Scope<'a> {
/// assert_eq!(my_scope.get_value::<i64>("x").unwrap(), 0); /// assert_eq!(my_scope.get_value::<i64>("x").unwrap(), 0);
/// ``` /// ```
#[inline(always)] #[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) { match self.get_index(name) {
None => { None => {
self.push(name, value); self.push(name, value);

View File

@ -452,28 +452,19 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> {
} }
/// `SeqAccess` implementation for arrays. /// `SeqAccess` implementation for arrays.
struct IterateArray<'a, ITER> struct IterateArray<'a, ITER: Iterator<Item = &'a Dynamic>> {
where
ITER: Iterator<Item = &'a Dynamic>,
{
/// Iterator for a stream of `Dynamic` values. /// Iterator for a stream of `Dynamic` values.
iter: ITER, iter: ITER,
} }
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
impl<'a, ITER> IterateArray<'a, ITER> impl<'a, ITER: Iterator<Item = &'a Dynamic>> IterateArray<'a, ITER> {
where
ITER: Iterator<Item = &'a Dynamic>,
{
pub fn new(iter: ITER) -> Self { pub fn new(iter: ITER) -> Self {
Self { iter } Self { iter }
} }
} }
impl<'a: 'de, 'de, ITER> SeqAccess<'de> for IterateArray<'a, ITER> impl<'a: 'de, 'de, ITER: Iterator<Item = &'a Dynamic>> SeqAccess<'de> for IterateArray<'a, ITER> {
where
ITER: Iterator<Item = &'a Dynamic>,
{
type Error = Box<EvalAltResult>; type Error = Box<EvalAltResult>;
fn next_element_seed<T: DeserializeSeed<'de>>( 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 Error = Box<EvalAltResult>;
type Variant = Self; type Variant = Self;
fn variant_seed<V>(self, seed: V) -> Result<(V::Value, Self::Variant), Self::Error> fn variant_seed<V: DeserializeSeed<'de>>(
where self,
V: DeserializeSeed<'de>, seed: V,
{ ) -> Result<(V::Value, Self::Variant), Self::Error> {
seed.deserialize(self.tag.into_deserializer()) seed.deserialize(self.tag.into_deserializer())
.map(|v| (v, self)) .map(|v| (v, self))
} }
@ -572,28 +563,26 @@ impl<'t, 'de> VariantAccess<'de> for EnumDeserializer<'t, 'de> {
Deserialize::deserialize(&mut self.content) Deserialize::deserialize(&mut self.content)
} }
fn newtype_variant_seed<T>(mut self, seed: T) -> Result<T::Value, Self::Error> fn newtype_variant_seed<T: DeserializeSeed<'de>>(
where mut self,
T: DeserializeSeed<'de>, seed: T,
{ ) -> Result<T::Value, Self::Error> {
seed.deserialize(&mut self.content) seed.deserialize(&mut self.content)
} }
fn tuple_variant<V>(mut self, len: usize, visitor: V) -> Result<V::Value, Self::Error> fn tuple_variant<V: Visitor<'de>>(
where mut self,
V: Visitor<'de>, len: usize,
{ visitor: V,
) -> Result<V::Value, Self::Error> {
self.content.deserialize_tuple(len, visitor) self.content.deserialize_tuple(len, visitor)
} }
fn struct_variant<V>( fn struct_variant<V: Visitor<'de>>(
mut self, mut self,
fields: &'static [&'static str], fields: &'static [&'static str],
visitor: V, visitor: V,
) -> Result<V::Value, Self::Error> ) -> Result<V::Value, Self::Error> {
where
V: Visitor<'de>,
{
self.content.deserialize_struct("", fields, visitor) self.content.deserialize_struct("", fields, visitor)
} }
} }

View File

@ -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) format!("{} kitties", n)
} }