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