Skip to content

Commit

Permalink
Merge pull request #280 from schungx/master
Browse files Browse the repository at this point in the history
Wrap up 0.19.4.
  • Loading branch information
schungx committed Nov 4, 2020
2 parents 6cba637 + 99669b5 commit b464207
Show file tree
Hide file tree
Showing 25 changed files with 303 additions and 184 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -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)).
Expand Down
6 changes: 6 additions & 0 deletions RELEASES.md
Expand Up @@ -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`.
Expand All @@ -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
Expand Down
12 changes: 8 additions & 4 deletions doc/src/about/features.md
Expand Up @@ -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.

Expand Down Expand Up @@ -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.

Expand Down
2 changes: 1 addition & 1 deletion doc/src/context.json
@@ -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": "",
Expand Down
2 changes: 1 addition & 1 deletion doc/src/rust/custom.md
Expand Up @@ -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.
Expand Down
16 changes: 15 additions & 1 deletion doc/src/safety/progress.md
Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions doc/src/start/builds/performance.md
Expand Up @@ -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.

Expand Down
13 changes: 7 additions & 6 deletions doc/src/start/builds/wasm.md
Expand Up @@ -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`]. |
2 changes: 1 addition & 1 deletion doc/src/start/features.md
Expand Up @@ -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 |
Expand Down
2 changes: 1 addition & 1 deletion no_std/no_std_test/Cargo.toml
Expand Up @@ -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]
Expand Down
30 changes: 22 additions & 8 deletions src/ast.rs
Expand Up @@ -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.
Expand All @@ -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 {
Expand Down Expand Up @@ -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, ... )
Expand Down Expand Up @@ -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,

Expand Down Expand Up @@ -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);
}
}
53 changes: 35 additions & 18 deletions src/engine.rs
Expand Up @@ -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"))]
Expand Down Expand Up @@ -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
///
///
Expand Down Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions src/engine_api.rs
Expand Up @@ -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).
Expand Down Expand Up @@ -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(|_| ())
}

Expand Down Expand Up @@ -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()
Expand Down
7 changes: 4 additions & 3 deletions src/fn_call.rs
Expand Up @@ -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,
Expand Down Expand Up @@ -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)?;
Expand Down
5 changes: 2 additions & 3 deletions src/fn_native.rs
Expand Up @@ -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)
Expand Down Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion src/fn_register.rs
Expand Up @@ -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())
}

Expand Down

0 comments on commit b464207

Please sign in to comment.