diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml
new file mode 100644
index 00000000..df310705
--- /dev/null
+++ b/.github/workflows/benchmark.yml
@@ -0,0 +1,29 @@
+name: Benchmark
+on:
+ push:
+ branches:
+ - master
+
+jobs:
+ benchmark:
+ name: Run Rust benchmark
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - run: rustup toolchain update nightly && rustup default nightly
+ - name: Run benchmark
+ run: cargo +nightly bench | tee output.txt
+ - name: Store benchmark result
+ uses: rhysd/github-action-benchmark@v1
+ with:
+ name: Rust Benchmark
+ tool: 'cargo'
+ output-file-path: output.txt
+ # Use personal access token instead of GITHUB_TOKEN due to https://github.community/t5/GitHub-Actions/Github-action-not-triggering-gh-pages-upon-push/td-p/26869/highlight/false
+ github-token: ${{ secrets.RHAI }}
+ auto-push: true
+ # Show alert with commit comment on detecting possible performance regression
+ alert-threshold: '200%'
+ comment-on-alert: true
+ fail-on-alert: true
+ alert-comment-cc-users: '@schungx'
diff --git a/Cargo.toml b/Cargo.toml
index bf4e0dc2..2f2ab525 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -75,3 +75,6 @@ optional = true
[target.'cfg(target_arch = "wasm32")'.dependencies]
instant= { version = "0.1.4", features = ["wasm-bindgen"] } # WASM implementation of std::time::Instant
+
+[package.metadata.docs.rs]
+features = [ "serde", "internals" ]
diff --git a/README.md b/README.md
index b40d97a8..88d26eae 100644
--- a/README.md
+++ b/README.md
@@ -68,9 +68,13 @@ Scripts can be evaluated directly from the editor.
License
-------
-Licensed under either of Apache License, Version
-2.0 or MIT license at your option.
+Licensed under either:
-Unless you explicitly state otherwise, any contribution intentionally submitted
-for inclusion in this crate by you, as defined in the Apache-2.0 license, shall
-be dual licensed as above, without any additional terms or conditions.
+* [Apache License, Version 2.0](https://github.com/jonathandturner/rhai/blob/master/LICENSE-APACHE.txt), or
+* [MIT license](https://github.com/jonathandturner/rhai/blob/master/LICENSE-MIT.txt)
+
+at your option.
+
+Unless explicitly stated otherwise, any contribution intentionally submitted
+for inclusion in this crate, as defined in the Apache-2.0 license, shall
+be dual-licensed as above, without any additional terms or conditions.
diff --git a/RELEASES.md b/RELEASES.md
index 8ba7d09c..22aae65d 100644
--- a/RELEASES.md
+++ b/RELEASES.md
@@ -18,6 +18,7 @@ New features
* Anonymous functions in the syntax of a closure, e.g. `|x, y, z| x + y - z`.
* Custom syntax now works even without the `internals` feature.
* Currying of function pointers is supported via the `curry` keyword.
+* `Module::set_indexer_get_set_fn` is added as a shorthand of both `Module::set_indexer_get_fn` and `Module::set_indexer_set_fn`.
Breaking changes
----------------
diff --git a/src/any.rs b/src/any.rs
index 202afa24..1d478e62 100644
--- a/src/any.rs
+++ b/src/any.rs
@@ -34,9 +34,9 @@ mod private {
use crate::fn_native::SendSync;
use crate::stdlib::any::Any;
- /// A sealed trait that prevents other crates from implementing [Variant].
+ /// A sealed trait that prevents other crates from implementing [`Variant`].
///
- /// [Variant]: super::Variant
+ /// [`Variant`]: super::Variant
pub trait Sealed {}
impl Sealed for T {}
@@ -810,8 +810,3 @@ impl From> for Dynamic {
Self(Union::FnPtr(value))
}
}
-
-/// Private type which ensures that `rhai::Any` and `rhai::AnyExt` can only
-/// be implemented by this crate.
-#[doc(hidden)]
-pub struct _Private;
diff --git a/src/engine.rs b/src/engine.rs
index daa24844..a852b3fa 100644
--- a/src/engine.rs
+++ b/src/engine.rs
@@ -11,7 +11,7 @@ use crate::parser::{Expr, FnAccess, ImmutableString, ReturnType, ScriptFnDef, St
use crate::r#unsafe::unsafe_cast_var_name_to_lifetime;
use crate::result::EvalAltResult;
use crate::scope::{EntryType as ScopeEntryType, Scope};
-use crate::syntax::{CustomSyntax, EvalContext, Expression};
+use crate::syntax::{CustomSyntax, EvalContext};
use crate::token::Position;
use crate::utils::StaticVec;
@@ -38,7 +38,12 @@ pub type Array = Vec;
#[cfg(not(feature = "no_object"))]
pub type Map = HashMap;
-/// A stack of imported modules.
+/// [INTERNALS] A stack of imported modules.
+/// Exported under the `internals` feature only.
+///
+/// ## WARNING
+///
+/// This type is volatile and may change.
pub type Imports<'a> = Vec<(Cow<'a, str>, Module)>;
#[cfg(not(feature = "unchecked"))]
@@ -189,12 +194,17 @@ impl> From for Target<'_> {
}
}
-/// A type that holds all the current states of the Engine.
+/// [INTERNALS] A type that holds all the current states of the Engine.
+/// Exported under the `internals` feature only.
///
/// # Safety
///
/// This type uses some unsafe code, mainly for avoiding cloning of local variable names via
/// direct lifetime casting.
+///
+/// ## WARNING
+///
+/// This type is volatile and may change.
#[derive(Debug, Clone, Eq, PartialEq, Hash, Default)]
pub struct State {
/// Normally, access to variables are parsed with a relative offset into the scope to avoid a lookup.
@@ -1020,7 +1030,7 @@ impl Engine {
map.entry(index).or_insert(Default::default()).into()
} else {
let index = idx
- .downcast_ref::()
+ .downcast_ref::()
.ok_or_else(|| EvalAltResult::ErrorStringIndexExpr(idx_pos))?;
map.get_mut(index.as_str())
@@ -1050,22 +1060,20 @@ impl Engine {
}
}
+ #[cfg(not(feature = "no_object"))]
#[cfg(not(feature = "no_index"))]
_ => {
- let val_type_name = val.type_name();
+ let type_name = val.type_name();
let args = &mut [val, &mut idx];
self.exec_fn_call(
state, lib, FN_IDX_GET, true, 0, args, is_ref, true, None, level,
)
.map(|(v, _)| v.into())
- .map_err(|e| match *e {
- EvalAltResult::ErrorFunctionNotFound(..) => {
- Box::new(EvalAltResult::ErrorIndexingType(
- self.map_type_name(val_type_name).into(),
- Position::none(),
- ))
- }
- _ => e,
+ .map_err(|err| match *err {
+ EvalAltResult::ErrorFunctionNotFound(_, _) => Box::new(
+ EvalAltResult::ErrorIndexingType(type_name.into(), Position::none()),
+ ),
+ _ => err,
})
}
diff --git a/src/error.rs b/src/error.rs
index 86c49091..b6901c68 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -10,7 +10,12 @@ use crate::stdlib::{
string::{String, ToString},
};
-/// Error when tokenizing the script text.
+/// [INTERNALS] Error encountered when tokenizing the script text.
+/// Exported under the `internals` feature only.
+///
+/// ## WARNING
+///
+/// This type is volatile and may change.
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
#[non_exhaustive]
pub enum LexError {
diff --git a/src/fn_native.rs b/src/fn_native.rs
index a113db4b..2833729a 100644
--- a/src/fn_native.rs
+++ b/src/fn_native.rs
@@ -5,12 +5,13 @@ use crate::engine::Engine;
use crate::module::{FuncReturn, Module};
use crate::parser::ScriptFnDef;
use crate::result::EvalAltResult;
-use crate::stdlib::vec::Vec;
use crate::token::{is_valid_identifier, Position};
use crate::utils::{ImmutableString, StaticVec};
use crate::Scope;
-use crate::stdlib::{boxed::Box, convert::TryFrom, fmt, mem, rc::Rc, string::String, sync::Arc};
+use crate::stdlib::{
+ boxed::Box, convert::TryFrom, fmt, mem, rc::Rc, string::String, sync::Arc, vec::Vec,
+};
/// Trait that maps to `Send + Sync` only under the `sync` feature.
#[cfg(feature = "sync")]
diff --git a/src/lib.rs b/src/lib.rs
index 87f60cf3..240d87d1 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -166,7 +166,7 @@ pub use token::{get_next_token, parse_string_literal, InputStream, Token, Tokeni
#[cfg(feature = "internals")]
#[deprecated(note = "this type is volatile and may change")]
-pub use parser::{CustomExpr, Expr, ReturnType, ScriptFnDef, Stmt};
+pub use parser::{CustomExpr, Expr, FloatWrapper, ReturnType, ScriptFnDef, Stmt};
#[cfg(feature = "internals")]
#[deprecated(note = "this type is volatile and may change")]
diff --git a/src/module.rs b/src/module.rs
index d4cec91e..22632c7d 100644
--- a/src/module.rs
+++ b/src/module.rs
@@ -758,6 +758,40 @@ impl Module {
)
}
+ /// Set a pair of Rust index getter and setter functions, returning both hash keys.
+ /// This is a shorthand for `set_indexer_get_fn` and `set_indexer_set_fn`.
+ ///
+ /// If there are similar existing Rust functions, they are replaced.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use rhai::{Module, ImmutableString};
+ ///
+ /// let mut module = Module::new();
+ /// let (hash_get, hash_set) = module.set_indexer_get_set_fn(
+ /// |x: &mut i64, y: ImmutableString| {
+ /// Ok(*x + y.len() as i64)
+ /// },
+ /// |x: &mut i64, y: ImmutableString, value: i64| {
+ /// *x = y.len() as i64 + value;
+ /// Ok(())
+ /// }
+ /// );
+ /// assert!(module.contains_fn(hash_get));
+ /// assert!(module.contains_fn(hash_set));
+ /// ```
+ pub fn set_indexer_get_set_fn(
+ &mut self,
+ getter: impl Fn(&mut A, B) -> FuncReturn + SendSync + 'static,
+ setter: impl Fn(&mut A, B, T) -> FuncReturn<()> + SendSync + 'static,
+ ) -> (u64, u64) {
+ (
+ self.set_indexer_get_fn(getter),
+ self.set_indexer_set_fn(setter),
+ )
+ }
+
/// Set a Rust function taking four parameters into the module, returning a hash key.
///
/// If there is a similar existing Rust function, it is replaced.
@@ -1094,11 +1128,17 @@ impl Module {
}
}
-/// A chain of module names to qualify a variable or function call.
-/// A `u64` hash key is kept for quick search purposes.
+/// [INTERNALS] A chain of module names to qualify a variable or function call.
+/// Exported under the `internals` feature only.
+///
+/// A `u64` hash key is cached for quick search purposes.
///
/// A `StaticVec` is used because most module-level access contains only one level,
/// and it is wasteful to always allocate a `Vec` with one element.
+///
+/// ## WARNING
+///
+/// This type is volatile and may change.
#[derive(Clone, Eq, PartialEq, Default, Hash)]
pub struct ModuleRef(StaticVec<(String, Position)>, Option);
diff --git a/src/optimize.rs b/src/optimize.rs
index eaa8aeb3..738ea300 100644
--- a/src/optimize.rs
+++ b/src/optimize.rs
@@ -456,7 +456,7 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
// "xxx" in "xxxxx"
(Expr::StringConstant(a), Expr::StringConstant(b)) => {
state.set_dirty();
- if b.0.contains(a.0.as_ref()) { Expr::True(a.1) } else { Expr::False(a.1) }
+ if b.0.contains(a.0.as_str()) { Expr::True(a.1) } else { Expr::False(a.1) }
}
// 'x' in "xxxxx"
(Expr::CharConstant(a), Expr::StringConstant(b)) => {
@@ -560,7 +560,7 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
let has_script_fn = state.lib.iter_fn().find(|(_, _, _, f)| {
if !f.is_script() { return false; }
let fn_def = f.get_fn_def();
- &fn_def.name == name && (args.len()..=args.len() + 1).contains(&fn_def.params.len())
+ fn_def.name.as_str() == name && (args.len()..=args.len() + 1).contains(&fn_def.params.len())
}).is_some();
#[cfg(feature = "no_function")]
diff --git a/src/parser.rs b/src/parser.rs
index 643c7aa9..da0409db 100644
--- a/src/parser.rs
+++ b/src/parser.rs
@@ -342,11 +342,16 @@ impl fmt::Display for FnAccess {
}
}
-/// A scripted function definition.
+/// [INTERNALS] A type containing information on a scripted function.
+/// Exported under the `internals` feature only.
+///
+/// ## WARNING
+///
+/// This type is volatile and may change.
#[derive(Debug, Clone, Hash)]
pub struct ScriptFnDef {
/// Function name.
- pub name: String,
+ pub name: ImmutableString,
/// Function access mode.
pub access: FnAccess,
/// Names of function parameters.
@@ -376,7 +381,12 @@ impl fmt::Display for ScriptFnDef {
}
}
-/// `return`/`throw` statement.
+/// [INTERNALS] A type encapsulating the mode of a `return`/`throw` statement.
+/// Exported under the `internals` feature only.
+///
+/// ## WARNING
+///
+/// This type is volatile and may change.
#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash)]
pub enum ReturnType {
/// `return` statement.
@@ -477,7 +487,8 @@ impl ParseSettings {
}
}
-/// A statement.
+/// [INTERNALS] A Rhai statement.
+/// Exported under the `internals` feature only.
///
/// Each variant is at most one pointer in size (for speed),
/// with everything being allocated together in one single tuple.
@@ -582,6 +593,12 @@ impl Stmt {
}
}
+/// [INTERNALS] A type wrapping a custom syntax definition.
+/// Exported under the `internals` feature only.
+///
+/// ## WARNING
+///
+/// This type is volatile and may change.
#[derive(Clone)]
pub struct CustomExpr(pub StaticVec, pub Shared);
@@ -597,6 +614,15 @@ impl Hash for CustomExpr {
}
}
+/// [INTERNALS] A type wrapping a floating-point number.
+/// Exported under the `internals` feature only.
+///
+/// This type is mainly used to provide a standard `Hash` implementation
+/// to floating-point numbers, allowing `Expr` to derive `Hash` automatically.
+///
+/// ## WARNING
+///
+/// This type is volatile and may change.
#[cfg(not(feature = "no_float"))]
#[derive(Debug, PartialEq, PartialOrd, Clone)]
pub struct FloatWrapper(pub FLOAT, pub Position);
@@ -609,10 +635,15 @@ impl Hash for FloatWrapper {
}
}
-/// An expression.
+/// [INTERNALS] An expression sub-tree.
+/// Exported under the `internals` feature only.
///
/// Each variant is at most one pointer in size (for speed),
/// with everything being allocated together in one single tuple.
+///
+/// ## WARNING
+///
+/// This type is volatile and may change.
#[derive(Debug, Clone, Hash)]
pub enum Expr {
/// Integer constant.
@@ -2852,7 +2883,7 @@ fn parse_fn(
let params = params.into_iter().map(|(p, _)| p).collect();
Ok(ScriptFnDef {
- name,
+ name: name.into(),
access,
params,
body,
@@ -2940,7 +2971,7 @@ fn parse_anon_fn(
let hash = s.finish();
// Create unique function name
- let fn_name = format!("{}{}", FN_ANONYMOUS, hash);
+ let fn_name: ImmutableString = format!("{}{:16x}", FN_ANONYMOUS, hash).into();
let script = ScriptFnDef {
name: fn_name.clone(),
@@ -2950,7 +2981,7 @@ fn parse_anon_fn(
pos: settings.pos,
};
- let expr = Expr::FnPointer(Box::new((fn_name.into(), settings.pos)));
+ let expr = Expr::FnPointer(Box::new((fn_name, settings.pos)));
Ok((expr, script))
}
diff --git a/src/token.rs b/src/token.rs
index 1248d742..72e733d3 100644
--- a/src/token.rs
+++ b/src/token.rs
@@ -136,89 +136,181 @@ impl fmt::Debug for Position {
}
}
-/// Tokens.
+/// [INTERNALS] A Rhai language token.
+/// Exported under the `internals` feature only.
+///
+/// ## WARNING
+///
+/// This type is volatile and may change.
#[derive(Debug, PartialEq, Clone)]
pub enum Token {
+ /// An `INT` constant.
IntegerConstant(INT),
+ /// A `FLOAT` constaint.
+ ///
+ /// Never appears under the `no_float` feature.
#[cfg(not(feature = "no_float"))]
FloatConstant(FLOAT),
+ /// An identifier.
Identifier(String),
+ /// A character constant.
CharConstant(char),
+ /// A string constant.
StringConstant(String),
+ /// `{`
LeftBrace,
+ /// `}`
RightBrace,
+ /// `(`
LeftParen,
+ /// `)`
RightParen,
+ /// `[`
LeftBracket,
+ /// `]`
RightBracket,
+ /// `+`
Plus,
+ /// `+` (unary)
UnaryPlus,
+ /// `-`
Minus,
+ /// `-` (unary)
UnaryMinus,
+ /// `*`
Multiply,
+ /// `/`
Divide,
+ /// `%`
Modulo,
+ /// `~`
PowerOf,
+ /// `<<`
LeftShift,
+ /// `>>`
RightShift,
+ /// `;`
SemiColon,
+ /// `:`
Colon,
+ /// `::`
DoubleColon,
+ /// `,`
Comma,
+ /// `.`
Period,
+ /// `#{`
MapStart,
+ /// `=`
Equals,
+ /// `true`
True,
+ /// `false`
False,
+ /// `let`
Let,
+ /// `const`
Const,
+ /// `if`
If,
+ /// `else`
Else,
+ /// `while`
While,
+ /// `loop`
Loop,
+ /// `for`
For,
+ /// `in`
In,
+ /// `<`
LessThan,
+ /// `>`
GreaterThan,
+ /// `<=`
LessThanEqualsTo,
+ /// `>=`
GreaterThanEqualsTo,
+ /// `==`
EqualsTo,
+ /// `!=`
NotEqualsTo,
+ /// `!`
Bang,
+ /// `|`
Pipe,
+ /// `||`
Or,
+ /// `^`
XOr,
+ /// `&`
Ampersand,
+ /// `&&`
And,
+ /// `fn`
+ ///
+ /// Never appears under the `no_function` feature.
#[cfg(not(feature = "no_function"))]
Fn,
+ /// `continue`
Continue,
+ /// `break`
Break,
+ /// `return`
Return,
+ /// `throw`
Throw,
+ /// `+=`
PlusAssign,
+ /// `-=`
MinusAssign,
+ /// `*=`
MultiplyAssign,
+ /// `/=`
DivideAssign,
+ /// `<<=`
LeftShiftAssign,
+ /// `>>=`
RightShiftAssign,
+ /// `&=`
AndAssign,
+ /// `|=`
OrAssign,
+ /// `^=`
XOrAssign,
+ /// `%=`
ModuloAssign,
+ /// `~=`
PowerOfAssign,
+ /// `private`
+ ///
+ /// Never appears under the `no_function` feature.
#[cfg(not(feature = "no_function"))]
Private,
+ /// `import`
+ ///
+ /// Never appears under the `no_module` feature.
#[cfg(not(feature = "no_module"))]
Import,
+ /// `export`
+ ///
+ /// Never appears under the `no_module` feature.
#[cfg(not(feature = "no_module"))]
Export,
+ /// `as`
+ ///
+ /// Never appears under the `no_module` feature.
#[cfg(not(feature = "no_module"))]
As,
+ /// A lexer error.
LexError(Box),
+ /// A comment block.
Comment(String),
+ /// A reserved symbol.
Reserved(String),
+ /// A custom keyword.
Custom(String),
+ /// End of the input stream.
EOF,
}
@@ -566,7 +658,7 @@ impl Token {
}
}
- /// Is this token a reserved keyword?
+ /// Is this token a reserved symbol?
pub fn is_reserved(&self) -> bool {
match self {
Self::Reserved(_) => true,
@@ -589,7 +681,12 @@ impl From for String {
}
}
-/// State of the tokenizer.
+/// [INTERNALS] State of the tokenizer.
+/// Exported under the `internals` feature only.
+///
+/// ## WARNING
+///
+/// This type is volatile and may change.
#[derive(Debug, Clone, Eq, PartialEq, Default)]
pub struct TokenizeState {
/// Maximum length of a string (0 = unlimited).
@@ -604,7 +701,12 @@ pub struct TokenizeState {
pub include_comments: bool,
}
-/// Trait that encapsulates a peekable character input stream.
+/// [INTERNALS] Trait that encapsulates a peekable character input stream.
+/// Exported under the `internals` feature only.
+///
+/// ## WARNING
+///
+/// This trait is volatile and may change.
pub trait InputStream {
/// Get the next character
fn get_next(&mut self) -> Option;
@@ -628,7 +730,12 @@ pub fn is_valid_identifier(name: impl Iterator- ) -> bool {
first_alphabetic
}
-/// Parse a string literal wrapped by `enclosing_char`.
+/// [INTERNALS] Parse a string literal wrapped by `enclosing_char`.
+/// Exported under the `internals` feature only.
+///
+/// ## WARNING
+///
+/// This type is volatile and may change.
pub fn parse_string_literal(
stream: &mut impl InputStream,
state: &mut TokenizeState,
@@ -794,7 +901,12 @@ fn scan_comment(
}
}
-/// Get the next token.
+/// [INTERNALS] Get the next token from the `InputStream`.
+/// Exported under the `internals` feature only.
+///
+/// ## WARNING
+///
+/// This type is volatile and may change.
pub fn get_next_token(
stream: &mut impl InputStream,
state: &mut TokenizeState,
diff --git a/src/utils.rs b/src/utils.rs
index a6eff859..760c809e 100644
--- a/src/utils.rs
+++ b/src/utils.rs
@@ -92,9 +92,12 @@ pub fn calc_fn_spec<'a>(
s.finish()
}
-/// A type to hold a number of values in static storage for no-allocation, quick access.
+/// [INTERNALS] An array-like type that holds a number of values in static storage for no-allocation, quick access.
+/// Exported under the `internals` feature only.
+///
/// If too many items are stored, it converts into using a `Vec`.
///
+///
/// This is essentially a knock-off of the [`staticvec`](https://crates.io/crates/staticvec) crate.
/// This simplified implementation here is to avoid pulling in another crate.
///
@@ -130,6 +133,10 @@ pub fn calc_fn_spec<'a>(
/// # Safety
///
/// This type uses some unsafe code (mainly for uninitialized/unused array slots) for efficiency.
+///
+/// ## WARNING
+///
+/// This type is volatile and may change.
//
// TODO - remove unsafe code
pub struct StaticVec {
diff --git a/tests/functions.rs b/tests/functions.rs
index 3c0a080b..78d20fa3 100644
--- a/tests/functions.rs
+++ b/tests/functions.rs
@@ -104,5 +104,19 @@ fn test_function_pointers() -> Result<(), Box> {
42
);
+ #[cfg(not(feature = "no_object"))]
+ assert_eq!(
+ engine.eval::(
+ r#"
+ fn foo(x) { this.data += x; }
+
+ let x = #{ data: 40, action: Fn("foo") };
+ x.action(2);
+ x.data
+ "#
+ )?,
+ 42
+ );
+
Ok(())
}