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]
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"

View File

@ -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)).

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.
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

View File

@ -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.

View File

@ -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": "",

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
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.

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.
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

View File

@ -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.

View File

@ -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. |
| 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, 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` |
| `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 |

View File

@ -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]

View File

@ -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);
}
}

View File

@ -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

View File

@ -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()

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]
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)?;

View File

@ -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.

View File

@ -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())
}

View File

@ -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, _, _)| {
(!public_only || *access == FnAccess::Public)
&& *num == num_params
&& fn_name == name
})
.map(|(_, _, _, _, f)| f.get_shared_fn_def())
.find(
|FuncInfo {
name: fn_name,
access,
params,
..
}| {
(!public_only || *access == FnAccess::Public)
&& *params == num_params
&& fn_name == name
},
)
.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,10 +1238,11 @@ impl Module {
&mut self,
mut filter: impl FnMut(FnAccess, &str, usize) -> bool,
) -> &mut Self {
self.functions.retain(|_, (_, _, _, _, v)| match v {
CallableFunction::Script(f) => filter(f.access, f.name.as_str(), f.params.len()),
_ => true,
});
self.functions
.retain(|_, FuncInfo { func, .. }| match func {
CallableFunction::Script(f) => filter(f.access, f.name.as_str(), f.params.len()),
_ => true,
});
self.all_functions.clear();
self.all_variables.clear();
@ -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,31 +1426,45 @@ impl Module {
module
.functions
.iter()
.filter(|(_, (_, access, _, _, _))| access.is_public())
.for_each(|(&_hash, (name, _, _num_params, params, func))| {
if let Some(params) = params {
// 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());
// 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());
// 3) The final hash is the XOR of the two hashes.
let hash_qualified_fn = hash_qualified_script ^ hash_fn_args;
.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());
functions.push((hash_qualified_fn, func.clone()));
} else if cfg!(not(feature = "no_function")) {
let hash_qualified_script = if qualifiers.is_empty() {
_hash
} else {
// Qualifiers + function name + number of arguments.
calc_script_fn_hash(qualifiers.iter().map(|&v| v), &name, *_num_params)
};
functions.push((hash_qualified_script, func.clone()));
}
});
// 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);
// 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(), "", 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;
functions.push((hash_qualified_fn, func.clone()));
} else if cfg!(not(feature = "no_function")) {
let hash_qualified_script = if qualifiers.is_empty() {
_hash
} else {
// Qualifiers + function name + number of arguments.
calc_script_fn_hash(qualifiers.iter().map(|&v| v), &name, *params)
};
functions.push((hash_qualified_script, func.clone()));
}
},
);
}
if !self.indexed {

View File

@ -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.

View File

@ -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()

View File

@ -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,7 +62,13 @@ def_package!(crate:LogicPackage:"Logical operators.", lib, {
}
#[cfg(not(feature = "no_float"))]
reg_functions!(lib += float; f32);
{
#[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);

View File

@ -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));
segments.push(MARKER_BLOCK.into());
}
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,
};

View File

@ -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);

View File

@ -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)
}
}

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)
}