Skip to content

Commit

Permalink
Merge pull request #163 from schungx/master
Browse files Browse the repository at this point in the history
Minor refinements and enhancements, WASM.
  • Loading branch information
schungx committed Jun 18, 2020
2 parents b2f7c50 + 2f815e2 commit c7df158
Show file tree
Hide file tree
Showing 48 changed files with 3,341 additions and 1,831 deletions.
6 changes: 5 additions & 1 deletion Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "rhai"
version = "0.15.0"
version = "0.15.1"
edition = "2018"
authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung"]
description = "Embedded scripting for Rust"
Expand All @@ -22,6 +22,7 @@ num-traits = { version = "0.2.11", default-features = false }
[features]
#default = ["unchecked", "sync", "no_optimize", "no_float", "only_i32", "no_index", "no_object", "no_function", "no_module"]
default = []
plugins = []
unchecked = [] # unchecked arithmetic
sync = [] # restrict to only types that implement Send + Sync
no_optimize = [] # no script optimizer
Expand Down Expand Up @@ -62,3 +63,6 @@ version = "0.3.2"
default-features = false
features = ["compile-time-rng"]
optional = true

[target.'cfg(target_arch = "wasm32")'.dependencies]
instant= { version = "0.1.4", features = ["wasm-bindgen"] } # WASM implementation of std::time::Instant
395 changes: 301 additions & 94 deletions README.md

Large diffs are not rendered by default.

79 changes: 45 additions & 34 deletions RELEASES.md
@@ -1,9 +1,40 @@
Rhai Release Notes
==================

Version 0.14.2
Version 0.15.1
==============

This is a minor release which enables updating indexers (via registered indexer setters) and supports functions
with `&str` parameters (maps transparently to `ImmutableString`). WASM is also a tested target.

Buf fix
-------

* `let s="abc"; s[1].change_to('X');` now correctly sets the character '`X`' into '`s`' yielding `"aXc"`.

Breaking changes
----------------

* Callback closure passed to `Engine::on_progress` now takes `&u64` instead of `u64` to be consistent with other callback signatures.
* `Engine::register_indexer` is renamed to `Engine::register_indexer_get`.
* `Module::set_indexer_fn` is renamed to `Module::set_indexer_get_fn`.
* The tuple `ParseError` now exposes the internal fields and the `ParseError::error_type` and `ParseError::position` methods are removed. The first tuple field is the `ParseErrorType` and the second tuple field is the `Position`.
* `Engine::call_fn_dynamic` now takes any type that implements `IntoIterator<Item = Dynamic>`.

New features
------------

* Indexers are now split into getters and setters (which now support updates). The API is split into `Engine::register_indexer_get` and `Engine::register_indexer_set` with `Engine::register_indexer_get_set` being a shorthand. Similarly, `Module::set_indexer_get_fn` and `Module::set_indexer_set_fn` are added.
* `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`.
* Supports trailing commas on array literals, object map literals, function definitions and function calls.
* Enhances support for compiling to WASM.

Version 0.15.0
==============

This version uses immutable strings (`ImmutableString` type) and built-in operator functions (e.g. `+`, `>`, `+=`) to improve speed, plus some bug fixes.

Regression fix
--------------

Expand All @@ -14,26 +45,18 @@ Bug fixes

* Indexing with an index or dot expression now works property (it compiled wrongly before).
For example, `let s = "hello"; s[s.len-1] = 'x';` now works property instead of causing a runtime error.
* `if` expressions are not supposed to be allowed when compiling for expressions only. This is fixed.

Breaking changes
----------------

* `Engine::compile_XXX` functions now return `ParseError` instead of `Box<ParseError>`.
* The `RegisterDynamicFn` trait is merged into the `RegisterResultFn` trait which now always returns
`Result<Dynamic, Box<EvalAltResult>>`.
* The `RegisterDynamicFn` trait is merged into the `RegisterResultFn` trait which now always returns `Result<Dynamic, Box<EvalAltResult>>`.
* Default maximum limit on levels of nested function calls is fine-tuned and set to a different value.
* Some operator functions are now built in (see _Speed enhancements_ below), so they are available even
under `Engine::new_raw`.
* Strings are now immutable. The type `rhai::ImmutableString` is used instead of `std::string::String`.
This is to avoid excessive cloning of strings. All native-Rust functions taking string parameters
should switch to `rhai::ImmutableString` (which is either `Rc<String>` or `Arc<String>` depending on
whether the `sync` feature is used).
* Native Rust functions registered with the `Engine` also mutates the first argument when called in
normal function-call style (previously the first argument will be passed by _value_ if not called
in method-call style). Of course, if the first argument is a calculated value (e.g. result of an
expression), then mutating it has no effect, but at least it is not cloned.
* Some built-in methods (e.g. `len` for string, `floor` for `FLOAT`) now have _property_ versions in
addition to methods to simplify coding.
* Some operator functions are now built in (see _Speed enhancements_ below), so they are available even under `Engine::new_raw`.
* Strings are now immutable. The type `rhai::ImmutableString` is used instead of `std::string::String`. This is to avoid excessive cloning of strings. All native-Rust functions taking string parameters should switch to `rhai::ImmutableString` (which is either `Rc<String>` or `Arc<String>` depending on whether the `sync` feature is used).
* Native Rust functions registered with the `Engine` also mutates the first argument when called in normal function-call style (previously the first argument will be passed by _value_ if not called in method-call style). Of course, if the first argument is a calculated value (e.g. result of an expression), then mutating it has no effect, but at least it is not cloned.
* Some built-in methods (e.g. `len` for string, `floor` for `FLOAT`) now have _property_ versions in addition to methods to simplify coding.

New features
------------
Expand All @@ -46,23 +69,13 @@ New features
Speed enhancements
------------------

* Common operators (e.g. `+`, `>`, `==`) now call into highly efficient built-in implementations for standard types
(i.e. `INT`, `FLOAT`, `bool`, `char`, `()` and `ImmutableString`) if not overridden by a registered function.
This yields a 5-10% speed benefit depending on script operator usage. Scripts running tight loops will see
significant speed-up.
* Common assignment operators (e.g. `+=`, `%=`) now call into highly efficient built-in implementations for
standard types (i.e. `INT`, `FLOAT`, `bool`, `char`, `()` and `ImmutableString`) if not overridden by a registered function.
* Implementations of common operators for standard types are removed from the `ArithmeticPackage` and `LogicPackage`
(and therefore the `CorePackage`) because they are now always available, even under `Engine::new_raw`.
* Common operators (e.g. `+`, `>`, `==`) now call into highly efficient built-in implementations for standard types (i.e. `INT`, `FLOAT`, `bool`, `char`, `()` and `ImmutableString`) if not overridden by a registered function. This yields a 5-10% speed benefit depending on script operator usage. Scripts running tight loops will see significant speed-up.
* Common assignment operators (e.g. `+=`, `%=`) now call into highly efficient built-in implementations for standard types (i.e. `INT`, `FLOAT`, `bool`, `char`, `()` and `ImmutableString`) if not overridden by a registered function.
* Implementations of common operators for standard types are removed from the `ArithmeticPackage` and `LogicPackage` (and therefore the `CorePackage`) because they are now always available, even under `Engine::new_raw`.
* Operator-assignment statements (e.g. `+=`) are now handled directly and much faster.
* Strings are now _immutable_ and use the `rhai::ImmutableString` type, eliminating large amounts of cloning.
* For Native Rust functions taking a first `&mut` parameter, the first argument is passed by reference instead of
by value, even if not called in method-call style. This allows many functions declared with `&mut` parameter to avoid
excessive cloning. For example, if `a` is a large array, getting its length in this manner: `len(a)` used to result
in a full clone of `a` before taking the length and throwing the copy away. Now, `a` is simply passed by reference,
avoiding the cloning altogether.
* A custom hasher simply passes through `u64` keys without hashing to avoid function call hash keys
(which are by themselves `u64`) being hashed twice.
* For Native Rust functions taking a first `&mut` parameter, the first argument is passed by reference instead of by value, even if not called in method-call style. This allows many functions declared with `&mut` parameter to avoid excessive cloning. For example, if `a` is a large array, getting its length in this manner: `len(a)` used to result in a full clone of `a` before taking the length and throwing the copy away. Now, `a` is simply passed by reference, avoiding the cloning altogether.
* A custom hasher simply passes through `u64` keys without hashing to avoid function call hash keys (which are by themselves `u64`) being hashed twice.


Version 0.14.1
Expand All @@ -74,15 +87,13 @@ The major features for this release is modules, script resource limits, and spee
New features
------------

* Modules and _module resolvers_ allow loading external scripts under a module namespace.
A module can contain constant variables, Rust functions and Rhai functions.
* Modules and _module resolvers_ allow loading external scripts under a module namespace. A module can contain constant variables, Rust functions and Rhai functions.
* `export` variables and `private` functions.
* _Indexers_ for Rust types.
* Track script evaluation progress and terminate script run.
* Set limit on maximum number of operations allowed per script run.
* Set limit on maximum number of modules loaded per script run.
* A new API, `Engine::compile_scripts_with_scope`, can compile a list of script segments without needing to
first concatenate them together into one large string.
* A new API, `Engine::compile_scripts_with_scope`, can compile a list of script segments without needing to first concatenate them together into one large string.
* Stepped `range` function with a custom step.

Speed improvements
Expand Down
1 change: 0 additions & 1 deletion _config.yml

This file was deleted.

52 changes: 25 additions & 27 deletions examples/repl.rs
Expand Up @@ -7,43 +7,41 @@ use std::io::{stdin, stdout, Write};

fn print_error(input: &str, err: EvalAltResult) {
let lines: Vec<_> = input.trim().split('\n').collect();
let pos = err.position();

let line_no = if lines.len() > 1 {
match err.position() {
p if p.is_none() => "".to_string(),
p => format!("{}: ", p.line().unwrap()),
if pos.is_none() {
"".to_string()
} else {
format!("{}: ", pos.line().unwrap())
}
} else {
"".to_string()
};

// Print error
let pos = err.position();
let pos_text = format!(" ({})", pos);

match pos {
p if p.is_none() => {
// No position
println!("{}", err);
}
p => {
// Specific position
println!("{}{}", line_no, lines[p.line().unwrap() - 1]);
if pos.is_none() {
// No position
println!("{}", err);
} else {
// Specific position
println!("{}{}", line_no, lines[pos.line().unwrap() - 1]);

let err_text = match err {
EvalAltResult::ErrorRuntime(err, _) if !err.is_empty() => {
format!("Runtime error: {}", err)
}
err => err.to_string(),
};

println!(
"{0:>1$} {2}",
"^",
line_no.len() + p.position().unwrap(),
err_text.replace(&pos_text, "")
);
}
let err_text = match err {
EvalAltResult::ErrorRuntime(err, _) if !err.is_empty() => {
format!("Runtime error: {}", err)
}
err => err.to_string(),
};

println!(
"{0:>1$} {2}",
"^",
line_no.len() + pos.position().unwrap(),
err_text.replace(&pos_text, "")
);
}
}

Expand Down Expand Up @@ -127,7 +125,7 @@ fn main() {

match engine
.compile_with_scope(&scope, &script)
.map_err(|err| err.into())
.map_err(Into::into)
.and_then(|r| {
ast_u = r.clone();

Expand Down
39 changes: 19 additions & 20 deletions examples/rhai_runner.rs
@@ -1,20 +1,22 @@
use rhai::{Engine, EvalAltResult};
use rhai::{Engine, EvalAltResult, Position};

#[cfg(not(feature = "no_optimize"))]
use rhai::OptimizationLevel;

use std::{env, fs::File, io::Read, process::exit};

fn eprint_error(input: &str, err: EvalAltResult) {
fn eprint_line(lines: &[&str], line: usize, pos: usize, err: &str) {
fn eprint_line(lines: &[&str], pos: Position, err: &str) {
let line = pos.line().unwrap();

let line_no = format!("{}: ", line);
let pos_text = format!(" (line {}, position {})", line, pos);
let pos_text = format!(" ({})", pos);

eprintln!("{}{}", line_no, lines[line - 1]);
eprintln!(
"{:>1$} {2}",
"^",
line_no.len() + pos,
line_no.len() + pos.position().unwrap(),
err.replace(&pos_text, "")
);
eprintln!("");
Expand All @@ -25,22 +27,19 @@ fn eprint_error(input: &str, err: EvalAltResult) {
// Print error
let pos = err.position();

match pos {
p if p.is_none() => {
// No position
eprintln!("{}", err);
}
p => {
// Specific position
let err_text = match err {
EvalAltResult::ErrorRuntime(err, _) if !err.is_empty() => {
format!("Runtime error: {}", err)
}
err => err.to_string(),
};

eprint_line(&lines, p.line().unwrap(), p.position().unwrap(), &err_text)
}
if pos.is_none() {
// No position
eprintln!("{}", err);
} else {
// Specific position
let err_text = match err {
EvalAltResult::ErrorRuntime(err, _) if !err.is_empty() => {
format!("Runtime error: {}", err)
}
err => err.to_string(),
};

eprint_line(&lines, pos, &err_text)
}
}

Expand Down
77 changes: 77 additions & 0 deletions examples/strings.rs
@@ -0,0 +1,77 @@
///! This example registers a variety of functions that operate on strings.
///! Remember to use `ImmutableString` or `&str` instead of `String` as parameters.
use rhai::{Engine, EvalAltResult, ImmutableString, RegisterFn, Scope, INT};
use std::io::{stdin, stdout, Write};

/// Trim whitespace from a string. The original string argument is changed.
///
/// This version uses `&mut ImmutableString`
fn trim_string(s: &mut ImmutableString) {
*s = s.trim().into();
}

/// Notice this is different from the built-in Rhai 'len' function for strings
/// which counts the actual number of Unicode _characters_ in a string.
/// This version simply counts the number of _bytes_ in the UTF-8 representation.
///
/// This version uses `&str`.
fn count_string_bytes(s: &str) -> INT {
s.len() as INT
}

/// This version uses `ImmutableString` and `&str`.
fn find_substring(s: ImmutableString, sub: &str) -> INT {
s.as_str().find(sub).map(|x| x as INT).unwrap_or(-1)
}

fn main() -> Result<(), Box<EvalAltResult>> {
// Create a `raw` Engine with no built-in string functions.
let mut engine = Engine::new_raw();

// Register string functions
engine.register_fn("trim", trim_string);
engine.register_fn("len", count_string_bytes);
engine.register_fn("index_of", find_substring);

// Register string functions using closures
engine.register_fn("display", |label: &str, x: INT| {
println!("{}: {}", label, x)
});
engine.register_fn("display", |label: ImmutableString, x: &str| {
println!(r#"{}: "{}""#, label, x) // Quote the input string
});

let mut scope = Scope::new();
let mut input = String::new();

loop {
scope.clear();

println!("Type something. Press Ctrl-C to exit.");
print!("strings> ");
stdout().flush().expect("couldn't flush stdout");

input.clear();

if let Err(err) = stdin().read_line(&mut input) {
panic!("input error: {}", err);
}

scope.push("x", input.clone());

println!("Line: {}", input.replace('\r', "\\r").replace('\n', "\\n"));

engine.consume_with_scope(
&mut scope,
r#"
display("Length", x.len());
x.trim();
display("Trimmed", x);
display("Trimmed Length", x.len());
display("Index of \"!!!\"", x.index_of("!!!"));
"#,
)?;

println!();
}
}

0 comments on commit c7df158

Please sign in to comment.