Gate WASM target.

This commit is contained in:
Stephen Chung 2020-06-17 09:54:17 +08:00
parent b6e1f652b6
commit 7f4f737ff2
9 changed files with 75 additions and 14 deletions

View File

@ -11,6 +11,11 @@ Rhai - Embedded Scripting for Rust
Rhai is an embedded scripting language and evaluation engine for Rust that gives a safe and easy way Rhai is an embedded scripting language and evaluation engine for Rust that gives a safe and easy way
to add scripting to any application. to add scripting to any application.
Supported targets
-----------------
* All common targets, including [WASM] and `no-std`.
Features Features
-------- --------
@ -27,13 +32,11 @@ Features
* Sand-boxed - the scripting [`Engine`], if declared immutable, cannot mutate the containing environment unless explicitly permitted (e.g. via a `RefCell`). * Sand-boxed - the scripting [`Engine`], if declared immutable, cannot mutate the containing environment unless explicitly permitted (e.g. via a `RefCell`).
* Rugged - protection 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. * Rugged - protection 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](#tracking-progress-and-force-terminate-script-run) and manually terminate a script run. * Track script evaluation [progress](#tracking-progress-and-force-terminate-script-run) and manually terminate a script run.
* [`no-std`](#optional-features) support.
* Supports compiling to `WASM`, optionally with [minimal builds](#minimal-builds).
* [Function overloading](#function-overloading). * [Function overloading](#function-overloading).
* [Operator overloading](#operator-overloading). * [Operator overloading](#operator-overloading).
* Organize code base with dynamically-loadable [modules]. * Organize code base with dynamically-loadable [modules].
* Scripts are [optimized](#script-optimization) (useful for template-based machine-generated scripts) for repeated evaluations. * Scripts are [optimized](#script-optimization) (useful for template-based machine-generated scripts) for repeated evaluations.
* Support for [minimal builds](#minimal-builds) by excluding unneeded language [features](#optional-features). * Support for [minimal builds] by excluding unneeded language [features](#optional-features).
* Very few additional dependencies (right now only [`num-traits`](https://crates.io/crates/num-traits/) * Very few additional dependencies (right now only [`num-traits`](https://crates.io/crates/num-traits/)
to do checked arithmetic operations); for [`no-std`](#optional-features) builds, a number of additional dependencies are to do checked arithmetic operations); for [`no-std`](#optional-features) builds, a number of additional dependencies are
pulled in to provide for functionalities that used to be in `std`. pulled in to provide for functionalities that used to be in `std`.
@ -142,8 +145,10 @@ Making [`Dynamic`] small helps performance due to better cache efficiency.
### Minimal builds ### Minimal builds
[minimal builds]: #minimal-builds
In order to compile a _minimal_build - i.e. a build optimized for size - perhaps for `no-std` embedded targets or for In order to compile a _minimal_build - i.e. a build optimized for size - perhaps for `no-std` embedded targets or for
compiling to `WASM`, it is essential that the correct linker flags are used in `cargo.toml`: compiling to [WASM], it is essential that the correct linker flags are used in `cargo.toml`:
```toml ```toml
[profile.release] [profile.release]
@ -167,8 +172,19 @@ A _raw_ engine supports, out of the box, only a very [restricted set](#built-in-
Selectively include other necessary functionalities by loading specific [packages] to minimize the footprint. Selectively include other necessary functionalities by loading specific [packages] to minimize the footprint.
Packages are sharable (even across threads via the [`sync`] feature), so they only have to be created once. Packages are sharable (even across threads via the [`sync`] feature), so they only have to be created once.
Related ### Compiling to WebAssembly (WASM)
-------
[WASM]: #compiling-to-WebAssembly-wasm
It is possible to use Rhai when compiling to WebAssembly (WASM), but certain features will not be available,
such as the script file API's and loading modules from external script files.
Also look into [minimal builds] to reduce generated WASM size. As of this version, a typical, full-featured
Rhai scripting engine compiles to a single WASM file around 200KB gzipped. When excluding features that are
marginal in WASM environment, the gzipped payload can be further shrunk to 160KB.
Related Resources
-----------------
Other cool projects to check out: Other cool projects to check out:
@ -2453,10 +2469,10 @@ which simply loads a script file based on the path (with `.rhai` extension attac
Built-in module resolvers are grouped under the `rhai::module_resolvers` module namespace. Built-in module resolvers are grouped under the `rhai::module_resolvers` module namespace.
| Module Resolver | Description | | Module Resolver | Description |
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `FileModuleResolver` | The default module resolution service, not available under [`no_std`]. Loads a script file (based off the current directory) with `.rhai` extension.<br/>The base directory can be changed via the `FileModuleResolver::new_with_path()` constructor function.<br/>`FileModuleResolver::create_module()` loads a script file and returns a module. | | `FileModuleResolver` | The default module resolution service, not available under [`no_std`] or [WASM] builds. Loads a script file (based off the current directory) with `.rhai` extension.<br/>The base directory can be changed via the `FileModuleResolver::new_with_path()` constructor function.<br/>`FileModuleResolver::create_module()` loads a script file and returns a module. |
| `StaticModuleResolver` | Loads modules that are statically added. This can be used under [`no_std`]. | | `StaticModuleResolver` | Loads modules that are statically added. This can be used under [`no_std`]. |
An [`Engine`]'s module resolver is set via a call to `Engine::set_module_resolver`: An [`Engine`]'s module resolver is set via a call to `Engine::set_module_resolver`:

View File

@ -5,7 +5,7 @@ Version 0.15.1
============== ==============
This is a minor release which enables updating indexers (via registered indexer setters) and supports functions This is a minor release which enables updating indexers (via registered indexer setters) and supports functions
with `&str` parameters (maps transparently to `ImmutableString`). with `&str` parameters (maps transparently to `ImmutableString`). WASM is also a tested target.
Buf fix Buf fix
------- -------
@ -28,7 +28,7 @@ New features
* `Engine:register_fn` and `Engine:register_result_fn` accepts functions that take parameters of type `&str` (immutable string slice), which maps directly to `ImmutableString`. This is to avoid needing wrappers for functions taking string parameters. * `Engine:register_fn` and `Engine:register_result_fn` accepts functions that take parameters of type `&str` (immutable string slice), which maps directly to `ImmutableString`. This is to avoid needing wrappers for functions taking string parameters.
* Set maximum limit on data sizes: `Engine::set_max_string_size`, `Engine::set_max_array_size` and `Engine::set_max_map_size`. * Set maximum limit on data sizes: `Engine::set_max_string_size`, `Engine::set_max_array_size` and `Engine::set_max_map_size`.
* Supports trailing commas on array literals, object map literals, function definitions and function calls. * Supports trailing commas on array literals, object map literals, function definitions and function calls.
* Supports compiling to `WASM`. * Enhances support for compiling to WASM.
Version 0.15.0 Version 0.15.0
============== ==============

View File

@ -555,6 +555,8 @@ impl Engine {
/// Read the contents of a file into a string. /// Read the contents of a file into a string.
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))]
#[cfg(not(target_arch = "wasm64"))]
fn read_file(path: PathBuf) -> Result<String, Box<EvalAltResult>> { fn read_file(path: PathBuf) -> Result<String, Box<EvalAltResult>> {
let mut f = File::open(path.clone()).map_err(|err| { let mut f = File::open(path.clone()).map_err(|err| {
Box::new(EvalAltResult::ErrorReadingScriptFile( Box::new(EvalAltResult::ErrorReadingScriptFile(
@ -598,6 +600,8 @@ impl Engine {
/// # } /// # }
/// ``` /// ```
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))]
#[cfg(not(target_arch = "wasm64"))]
pub fn compile_file(&self, path: PathBuf) -> Result<AST, Box<EvalAltResult>> { pub fn compile_file(&self, path: PathBuf) -> Result<AST, Box<EvalAltResult>> {
self.compile_file_with_scope(&Scope::new(), path) self.compile_file_with_scope(&Scope::new(), path)
} }
@ -634,6 +638,8 @@ impl Engine {
/// # } /// # }
/// ``` /// ```
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))]
#[cfg(not(target_arch = "wasm64"))]
pub fn compile_file_with_scope( pub fn compile_file_with_scope(
&self, &self,
scope: &Scope, scope: &Scope,
@ -775,6 +781,8 @@ impl Engine {
/// # } /// # }
/// ``` /// ```
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))]
#[cfg(not(target_arch = "wasm64"))]
pub fn eval_file<T: Variant + Clone>(&self, path: PathBuf) -> Result<T, Box<EvalAltResult>> { pub fn eval_file<T: Variant + Clone>(&self, path: PathBuf) -> Result<T, Box<EvalAltResult>> {
Self::read_file(path).and_then(|contents| self.eval::<T>(&contents)) Self::read_file(path).and_then(|contents| self.eval::<T>(&contents))
} }
@ -799,6 +807,8 @@ impl Engine {
/// # } /// # }
/// ``` /// ```
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))]
#[cfg(not(target_arch = "wasm64"))]
pub fn eval_file_with_scope<T: Variant + Clone>( pub fn eval_file_with_scope<T: Variant + Clone>(
&self, &self,
scope: &mut Scope, scope: &mut Scope,
@ -1004,6 +1014,8 @@ impl Engine {
/// 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).
/// Useful for when you don't need the result, but still need to keep track of possible errors. /// Useful for when you don't need the result, but still need to keep track of possible errors.
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))]
#[cfg(not(target_arch = "wasm64"))]
pub fn consume_file(&self, path: PathBuf) -> Result<(), Box<EvalAltResult>> { pub fn consume_file(&self, path: PathBuf) -> Result<(), Box<EvalAltResult>> {
Self::read_file(path).and_then(|contents| self.consume(&contents)) Self::read_file(path).and_then(|contents| self.consume(&contents))
} }
@ -1011,6 +1023,8 @@ impl Engine {
/// Evaluate a file with own scope, but throw away the result and only return error (if any). /// Evaluate a file with own scope, but throw away the result and only return error (if any).
/// Useful for when you don't need the result, but still need to keep track of possible errors. /// Useful for when you don't need the result, but still need to keep track of possible errors.
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))]
#[cfg(not(target_arch = "wasm64"))]
pub fn consume_file_with_scope( pub fn consume_file_with_scope(
&self, &self,
scope: &mut Scope, scope: &mut Scope,

View File

@ -292,8 +292,15 @@ impl Default for Engine {
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))]
#[cfg(not(target_arch = "wasm64"))]
module_resolver: Some(Box::new(resolvers::FileModuleResolver::new())), module_resolver: Some(Box::new(resolvers::FileModuleResolver::new())),
#[cfg(any(feature = "no_module", feature = "no_std"))] #[cfg(any(
feature = "no_module",
feature = "no_std",
target_arch = "wasm32",
target_arch = "wasm64"
))]
module_resolver: None, module_resolver: None,
type_names: HashMap::new(), type_names: HashMap::new(),
@ -373,6 +380,8 @@ fn extract_prop_from_setter(fn_name: &str) -> Option<&str> {
/// Print/debug to stdout /// Print/debug to stdout
fn default_print(s: &str) { fn default_print(s: &str) {
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))]
#[cfg(not(target_arch = "wasm64"))]
println!("{}", s); println!("{}", s);
} }

View File

@ -37,6 +37,8 @@
//! engine.register_fn("compute", compute_something); //! engine.register_fn("compute", compute_something);
//! //!
//! # #[cfg(not(feature = "no_std"))] //! # #[cfg(not(feature = "no_std"))]
//! # #[cfg(not(target_arch = "wasm32"))]
//! # #[cfg(not(target_arch = "wasm64"))]
//! assert_eq!( //! assert_eq!(
//! // Evaluate the script, expects a 'bool' return //! // Evaluate the script, expects a 'bool' return
//! engine.eval_file::<bool>("my_script.rhai".into())?, //! engine.eval_file::<bool>("my_script.rhai".into())?,

View File

@ -1054,6 +1054,8 @@ pub trait ModuleResolver: SendSync {
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
pub mod resolvers { pub mod resolvers {
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))]
#[cfg(not(target_arch = "wasm64"))]
pub use super::file::FileModuleResolver; pub use super::file::FileModuleResolver;
pub use super::stat::StaticModuleResolver; pub use super::stat::StaticModuleResolver;
} }
@ -1063,6 +1065,8 @@ pub mod resolvers {}
/// Script file-based module resolver. /// Script file-based module resolver.
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))]
#[cfg(not(target_arch = "wasm64"))]
mod file { mod file {
use super::*; use super::*;
use crate::stdlib::path::PathBuf; use crate::stdlib::path::PathBuf;

View File

@ -33,6 +33,8 @@ pub use pkg_std::StandardPackage;
pub use string_basic::BasicStringPackage; pub use string_basic::BasicStringPackage;
pub use string_more::MoreStringPackage; pub use string_more::MoreStringPackage;
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))]
#[cfg(not(target_arch = "wasm64"))]
pub use time_basic::BasicTimePackage; pub use time_basic::BasicTimePackage;
/// Trait that all packages must implement. /// Trait that all packages must implement.

View File

@ -13,6 +13,8 @@ use crate::stdlib::{
}; };
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))]
#[cfg(not(target_arch = "wasm64"))]
use crate::stdlib::path::PathBuf; use crate::stdlib::path::PathBuf;
/// Evaluation result. /// Evaluation result.
@ -29,6 +31,8 @@ pub enum EvalAltResult {
/// ///
/// Never appears under the `no_std` feature. /// Never appears under the `no_std` feature.
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))]
#[cfg(not(target_arch = "wasm64"))]
ErrorReadingScriptFile(PathBuf, Position, std::io::Error), ErrorReadingScriptFile(PathBuf, Position, std::io::Error),
/// Call to an unknown function. Wrapped value is the name of the function. /// Call to an unknown function. Wrapped value is the name of the function.
@ -101,7 +105,9 @@ impl EvalAltResult {
pub(crate) fn desc(&self) -> &str { pub(crate) fn desc(&self) -> &str {
match self { match self {
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
Self::ErrorReadingScriptFile(_, _, _) => "Cannot read from script file", #[cfg(not(target_arch = "wasm32"))]
#[cfg(not(target_arch = "wasm64"))]
Self::ErrorReadingScriptFile(_, _, _) => "Cannot read from script file",
Self::ErrorParsing(p, _) => p.desc(), Self::ErrorParsing(p, _) => p.desc(),
Self::ErrorInFunctionCall(_, _, _) => "Error in called function", Self::ErrorInFunctionCall(_, _, _) => "Error in called function",
@ -160,6 +166,8 @@ impl fmt::Display for EvalAltResult {
match self { match self {
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))]
#[cfg(not(target_arch = "wasm64"))]
Self::ErrorReadingScriptFile(path, _, err) => { Self::ErrorReadingScriptFile(path, _, err) => {
write!(f, "{} '{}': {}", desc, path.display(), err)? write!(f, "{} '{}': {}", desc, path.display(), err)?
} }
@ -259,6 +267,8 @@ impl EvalAltResult {
pub fn position(&self) -> Position { pub fn position(&self) -> Position {
match self { match self {
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))]
#[cfg(not(target_arch = "wasm64"))]
Self::ErrorReadingScriptFile(_, pos, _) => *pos, Self::ErrorReadingScriptFile(_, pos, _) => *pos,
Self::ErrorParsing(_, pos) Self::ErrorParsing(_, pos)
@ -297,6 +307,8 @@ impl EvalAltResult {
pub fn set_position(&mut self, new_position: Position) { pub fn set_position(&mut self, new_position: Position) {
match self { match self {
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))]
#[cfg(not(target_arch = "wasm64"))]
Self::ErrorReadingScriptFile(_, pos, _) => *pos = new_position, Self::ErrorReadingScriptFile(_, pos, _) => *pos = new_position,
Self::ErrorParsing(_, pos) Self::ErrorParsing(_, pos)

View File

@ -1,4 +1,6 @@
#![cfg(not(feature = "no_std"))] #![cfg(not(feature = "no_std"))]
#![cfg(not(target_arch = "wasm32"))]
#![cfg(not(target_arch = "wasm64"))]
use rhai::{Engine, EvalAltResult, INT}; use rhai::{Engine, EvalAltResult, INT};