diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8f847ce5..996fc663 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -32,6 +32,7 @@ jobs: - uses: actions-rs/cargo@v1 with: command: check + # typical build with various feature combinations build: name: Build @@ -59,19 +60,19 @@ jobs: - "--features no_object,serde,metadata,internals,debugging" - "--features no_function,serde,metadata,internals,debugging" - "--features no_module,serde,metadata,internals,debugging" + - "--features no_time,serde,metadata,internals,debugging" - "--features no_closure,serde,metadata,internals,debugging" - "--features unicode-xid-ident,serde,metadata,internals,debugging" - - "--features sync,no_function,no_float,no_position,no_optimize,no_module,no_closure,no_custom_syntax,metadata,serde,unchecked,debugging" - - "--features no_function,no_float,no_position,no_index,no_object,no_optimize,no_module,no_closure,no_custom_syntax,unchecked" + - "--features sync,no_time,no_function,no_float,no_position,no_optimize,no_module,no_closure,no_custom_syntax,metadata,serde,unchecked,debugging" + - "--features no_time,no_function,no_float,no_position,no_index,no_object,no_optimize,no_module,no_closure,no_custom_syntax,unchecked" toolchain: [stable] experimental: [false] include: # smoketests for different toolchains - {toolchain: stable, os: windows-latest, experimental: false, flags: ""} - {toolchain: stable, os: macos-latest, experimental: false, flags: ""} - # data structure size changes - wait for beta to become stable and uncomment - #- {toolchain: beta, os: ubuntu-latest, experimental: false, flags: ""} - #- {toolchain: nightly, os: ubuntu-latest, experimental: true, flags: ""} + - {toolchain: beta, os: ubuntu-latest, experimental: false, flags: ""} + - {toolchain: nightly, os: ubuntu-latest, experimental: true, flags: ""} fail-fast: false steps: - name: Checkout @@ -86,6 +87,7 @@ jobs: with: command: test args: ${{matrix.flags}} + # no-std builds are a bit more extensive to test no_std_build: name: NoStdBuild @@ -110,6 +112,41 @@ jobs: with: command: build args: --manifest-path=no_std/no_std_test/Cargo.toml ${{matrix.flags}} + + wasm: + name: Check Wasm build + runs-on: ubuntu-latest + strategy: + matrix: + flags: + - "--target wasm32-wasi" +# These fail currently, future PR should fix them +# - "--target wasm32-unknown-unknown" +# - "--target wasm32-unknown-unknown --features wasm-bindgen" + - "--target wasm32-unknown-unknown --no-default-features" + - "--target wasm32-unknown-unknown --no-default-features --features wasm-bindgen" + fail-fast: false + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Setup Generic Wasm Toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + target: wasm32-unknown-unknown + - name: Setup Wasi Toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + target: wasm32-wasi + - name: Build + uses: actions-rs/cargo@v1 + with: + command: build + args: ${{matrix.flags}} + rustfmt: name: Check Formatting runs-on: windows-latest @@ -133,6 +170,7 @@ jobs: with: command: clippy args: --all -- -Aclippy::all -Dclippy::perf + codegen_build: name: Codegen Build runs-on: ${{matrix.os}} diff --git a/.gitignore b/.gitignore index fe87b992..10e86a86 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,6 @@ Rhai.toml **/*.bat doc/rhai-sync.json doc/rhai.json +.idea/ +.idea +.idea/* diff --git a/CHANGELOG.md b/CHANGELOG.md index adab88b9..4f785f8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,79 @@ Rhai Release Notes ================== +Version 1.11.0 +============== + +Speed Improvements +------------------ + +* Due to a code refactor, built-in operators for standard types now run even faster, in certain cases by 20-30%. + +Bug fixes +--------- + +* `Engine::parse_json` now returns an error on unquoted keys to be consistent with JSON specifications. +* `import` statements inside `eval` no longer cause errors in subsequent code. +* Functions marked `global` in `import`ed modules with no alias names now work properly. +* Incorrect loop optimizations that are too aggressive (e.g. unrolling a `do { ... } until true` with a `break` statement inside) and cause crashes are removed. + +Breaking changes +---------------- + +* `NativeCallContext::new` is completely deprecated and unimplemented (always panics) in favor of new API's. + +New features +------------ + +### Loop expressions + +* Loops (such as `loop`, `do`, `while` and `for`) can now act as _expressions_, with the `break` statement returning an optional value. +* Normal loops return `()` as the value. +* Loop expressions can be enabled/disabled via `Engine::set_allow_loop_expressions` + +### Static hashing + +* It is now possible to specify a fixed _seed_ for use with the `ahash` hasher, via a static function `rhai::config::hashing::set_ahash_seed` or an environment variable, in order to force static (i.e. deterministic) hashes for function signatures. +* This is necessary when using Rhai across shared-library boundaries. +* A build script is used to extract the environment variable (`RHAI_AHASH_SEED`, if any) and splice it into the source code before compilation. + +### No Timestamps + +* A new feature, `no_time`, is added to disable support timestamps. +* This may be necessary when building for architectures without time support, such as raw WASM. + +### Serializable `Scope` + +* `Scope` is now serializable and deserializable via `serde`. + +### Store and recreate `NativeCallContext` + +* A convenient API is added to store a `NativeCallContext` into a new `NativeCallContextStore` type. +* This allows a `NativeCallContext` to be stored and recreated later on. + +### Call native Rust functions in `NativeCallContext` + +* `NativeCallContext::call_native_fn` is added to call registered native Rust functions only. +* `NativeCallContext::call_native_fn_raw` is added as the advanced version. +* This is often desirable as Rust functions typically do not want a similar-named scripted function to hijack the process -- which will cause brittleness. + +### Custom syntax improvements + +* The look-ahead symbol for custom syntax now renders a string literal in quotes (instead of the generic term `string`). +* This facilitates more accurate parsing by separating strings and identifiers. + +### Limits API + +* Methods returning maximum limits (e.g. `Engine::max_string_len`) are now available even under `unchecked`. +* This helps avoid the proliferation of unnecessary feature flags in third-party library code. + +Enhancements +------------ + +* `parse_json` function is added to parse a JSON string into an object map. +* `Error::ErrorNonPureMethodCallOnConstant` is added which is raised when a non-pure method is called on a constant value. + + Version 1.10.1 ============== @@ -8,7 +81,7 @@ Bug fixes --------- * Compiling on 32-bit architectures no longer cause a compilation error. -* Fix type-size test fo 32-bit architectures without the `decimal` feature. +* Fix type-size test for 32-bit architectures without the `decimal` feature. Custom syntax with state ------------------------ diff --git a/Cargo.toml b/Cargo.toml index ff411847..4db19d3b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ members = [".", "codegen"] [package] name = "rhai" -version = "1.10.1" +version = "1.11.0" rust-version = "1.61.0" edition = "2018" resolver = "2" @@ -19,7 +19,8 @@ categories = ["no-std", "embedded", "wasm", "parser-implementations"] [dependencies] smallvec = { version = "1.7", default-features = false, features = ["union", "const_new", "const_generics"] } -ahash = { version = "0.8", default-features = false } +# 0.8.1 pulls in `getrandom/js` which breaks no-std +ahash = { version = "=0.8.0", default-features = false, features = ["compile-time-rng"] } num-traits = { version = "0.2", default-features = false } bitflags = { version = "1", default-features = false } smartstring = { version = "1", default-features = false } @@ -33,6 +34,7 @@ serde = { version = "1.0", default-features = false, features = ["derive", "allo serde_json = { version = "1.0", default-features = false, features = ["alloc"], optional = true } unicode-xid = { version = "0.2", default-features = false, optional = true } rust_decimal = { version = "1.16", default-features = false, features = ["maths"], optional = true } +getrandom = { version = "0.2", optional = true } rustyline = { version = "10", optional = true } [dev-dependencies] @@ -40,9 +42,8 @@ serde_bytes = "0.11" serde_json = { version = "1.0", default-features = false, features = ["alloc"] } [features] -default = ["std"] -std = ["ahash/std", "ahash/runtime-rng", "num-traits/std", "smartstring/std"] -base_std = ["ahash/std", "num-traits/std", "smartstring/std"] # Prevent ahash random seed, enabling dynamic libraries to be used. +default = ["std", "ahash/runtime-rng"] # ahash/runtime-rng trumps ahash/compile-time-rng +std = ["ahash/std", "num-traits/std", "smartstring/std"] unchecked = [] # unchecked arithmetic sync = [] # restrict to only types that implement Send + Sync no_position = [] # do not track position in the parser @@ -54,6 +55,7 @@ only_i64 = [] # set INT=i64 (default) and disable support for decimal = ["rust_decimal"] # add the Decimal number type no_index = [] # no arrays and indexing no_object = [] # no custom objects +no_time = [] # no timestamps no_function = ["no_closure"] # no script-defined functions (meaning no closures) no_closure = [] # no automatic sharing and capture of anonymous functions to external variables no_module = [] # no modules @@ -65,11 +67,11 @@ debugging = ["internals"] # enable debugging serde = ["dep:serde", "smartstring/serde", "smallvec/serde"] # implement serde for rhai types # compiling for no-std -no_std = ["no-std-compat", "num-traits/libm", "core-error", "libm", "ahash/compile-time-rng", "hashbrown/ahash-compile-time-rng"] +no_std = ["no-std-compat", "num-traits/libm", "core-error", "libm", "hashbrown", "no_time"] # compiling for WASM -wasm-bindgen = ["instant/wasm-bindgen"] -stdweb = ["instant/stdweb"] +wasm-bindgen = ["getrandom/js", "instant/wasm-bindgen"] +stdweb = ["getrandom/js", "instant/stdweb"] # compiling bin tools bin-features = ["decimal", "metadata", "serde", "debugging", "rustyline"] diff --git a/build.rs b/build.rs new file mode 100644 index 00000000..39c11238 --- /dev/null +++ b/build.rs @@ -0,0 +1,26 @@ +use std::{ + env, + fs::File, + io::{Read, Write}, +}; + +fn main() { + // Tell Cargo that if the given environment variable changes, to rerun this build script. + println!("cargo:rerun-if-changed=build.template"); + println!("cargo:rerun-if-env-changed=RHAI_AHASH_SEED"); + let mut contents = String::new(); + + File::open("build.template") + .expect("cannot open `build.template`") + .read_to_string(&mut contents) + .expect("cannot read from `build.template`"); + + let seed = env::var("RHAI_AHASH_SEED").map_or_else(|_| "None".into(), |s| format!("Some({s})")); + + contents = contents.replace("{{AHASH_SEED}}", &seed); + + File::create("src/config/hashing_env.rs") + .expect("cannot create `config.rs`") + .write_all(contents.as_bytes()) + .expect("cannot write to `config/hashing_env.rs`"); +} diff --git a/build.template b/build.template new file mode 100644 index 00000000..bce322e8 --- /dev/null +++ b/build.template @@ -0,0 +1,3 @@ +//! This file is automatically recreated during build time by `build.rs` from `build.template`. + +pub(crate) const AHASH_SEED: Option<[u64; 4]> = {{AHASH_SEED}}; diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml index d384c210..c1efde59 100644 --- a/codegen/Cargo.toml +++ b/codegen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rhai_codegen" -version = "1.4.2" +version = "1.4.3" edition = "2018" resolver = "2" authors = ["jhwgh1968", "Stephen Chung"] @@ -22,5 +22,5 @@ syn = { version = "1.0", features = ["full", "parsing", "printing", "proc-macro" quote = "1" [dev-dependencies] -rhai = { path = "..", version = "1.6", features = ["metadata"] } +rhai = { path = "..", version = "1.11", features = ["metadata"] } trybuild = "1" diff --git a/codegen/src/function.rs b/codegen/src/function.rs index d356645e..833e3d98 100644 --- a/codegen/src/function.rs +++ b/codegen/src/function.rs @@ -673,6 +673,7 @@ impl ExportedFn { let sig_name = self.name().clone(); let arg_count = self.arg_count(); let is_method_call = self.mutable_receiver(); + let is_pure = !self.mutable_receiver() || self.params().pure.is_some(); let mut unpack_statements = Vec::new(); let mut unpack_exprs = Vec::new(); @@ -713,18 +714,6 @@ impl ExportedFn { }) .unwrap(), ); - if self.params().pure.is_none() { - let arg_lit_str = - syn::LitStr::new(&pat.to_token_stream().to_string(), pat.span()); - unpack_statements.push( - syn::parse2::(quote! { - if args[0usize].is_read_only() { - return Err(EvalAltResult::ErrorAssignmentToConstant(#arg_lit_str.to_string(), Position::NONE).into()); - } - }) - .unwrap(), - ); - } #[cfg(feature = "metadata")] input_type_names.push(arg_name); input_type_exprs.push( @@ -877,6 +866,7 @@ impl ExportedFn { } #[inline(always)] fn is_method_call(&self) -> bool { #is_method_call } + #[inline(always)] fn is_pure(&self) -> bool { #is_pure } } } } diff --git a/codegen/src/test/function.rs b/codegen/src/test/function.rs index 8465cd61..05d99130 100644 --- a/codegen/src/test/function.rs +++ b/codegen/src/test/function.rs @@ -285,6 +285,7 @@ mod generate_tests { } #[inline(always)] fn is_method_call(&self) -> bool { false } + #[inline(always)] fn is_pure(&self) -> bool { true } } #[allow(unused)] #[doc(hidden)] @@ -323,6 +324,7 @@ mod generate_tests { } #[inline(always)] fn is_method_call(&self) -> bool { false } + #[inline(always)] fn is_pure(&self) -> bool { true } } #[allow(unused)] #[doc(hidden)] @@ -361,6 +363,7 @@ mod generate_tests { } #[inline(always)] fn is_method_call(&self) -> bool { false } + #[inline(always)] fn is_pure(&self) -> bool { true } } #[allow(unused)] #[doc(hidden)] @@ -401,6 +404,7 @@ mod generate_tests { } #[inline(always)] fn is_method_call(&self) -> bool { false } + #[inline(always)] fn is_pure(&self) -> bool { true } } #[allow(unused)] #[doc(hidden)] @@ -434,6 +438,7 @@ mod generate_tests { } #[inline(always)] fn is_method_call(&self) -> bool { false } + #[inline(always)] fn is_pure(&self) -> bool { true } } }; @@ -467,6 +472,7 @@ mod generate_tests { } #[inline(always)] fn is_method_call(&self) -> bool { false } + #[inline(always)] fn is_pure(&self) -> bool { true } } #[allow(unused)] #[doc(hidden)] @@ -500,15 +506,13 @@ mod generate_tests { impl PluginFunction for Token { #[inline(always)] fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { - if args[0usize].is_read_only() { - return Err(EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into()); - } let arg1 = mem::take(args[1usize]).cast::(); let arg0 = &mut args[0usize].write_lock::().unwrap(); Ok(Dynamic::from(increment(arg0, arg1))) } #[inline(always)] fn is_method_call(&self) -> bool { true } + #[inline(always)] fn is_pure(&self) -> bool { false } } #[allow(unused)] #[doc(hidden)] @@ -548,6 +552,7 @@ mod generate_tests { } #[inline(always)] fn is_method_call(&self) -> bool { false } + #[inline(always)] fn is_pure(&self) -> bool { true } } #[allow(unused)] #[doc(hidden)] diff --git a/codegen/src/test/module.rs b/codegen/src/test/module.rs index 881746b9..0a279452 100644 --- a/codegen/src/test/module.rs +++ b/codegen/src/test/module.rs @@ -390,6 +390,7 @@ mod generate_tests { } #[inline(always)] fn is_method_call(&self) -> bool { false } + #[inline(always)] fn is_pure(&self) -> bool { true } } } }; @@ -467,6 +468,7 @@ mod generate_tests { } #[inline(always)] fn is_method_call(&self) -> bool { false } + #[inline(always)] fn is_pure(&self) -> bool { true } } } }; @@ -525,6 +527,7 @@ mod generate_tests { } #[inline(always)] fn is_method_call(&self) -> bool { false } + #[inline(always)] fn is_pure(&self) -> bool { true } } } }; @@ -582,6 +585,7 @@ mod generate_tests { } #[inline(always)] fn is_method_call(&self) -> bool { false } + #[inline(always)] fn is_pure(&self) -> bool { true } } } }; @@ -653,6 +657,7 @@ mod generate_tests { } #[inline(always)] fn is_method_call(&self) -> bool { false } + #[inline(always)] fn is_pure(&self) -> bool { true } } #[allow(non_camel_case_types)] @@ -672,6 +677,7 @@ mod generate_tests { } #[inline(always)] fn is_method_call(&self) -> bool { false } + #[inline(always)] fn is_pure(&self) -> bool { true } } } }; @@ -730,6 +736,7 @@ mod generate_tests { } #[inline(always)] fn is_method_call(&self) -> bool { false } + #[inline(always)] fn is_pure(&self) -> bool { true } } } }; @@ -795,6 +802,7 @@ mod generate_tests { } #[inline(always)] fn is_method_call(&self) -> bool { false } + #[inline(always)] fn is_pure(&self) -> bool { true } } } }; @@ -864,14 +872,12 @@ mod generate_tests { impl PluginFunction for get_mystic_number_token { #[inline(always)] fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { - if args[0usize].is_read_only() { - return Err(EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into()); - } let arg0 = &mut args[0usize].write_lock::().unwrap(); Ok(Dynamic::from(get_mystic_number(arg0))) } #[inline(always)] fn is_method_call(&self) -> bool { true } + #[inline(always)] fn is_pure(&self) -> bool { false } } } }; @@ -1080,6 +1086,7 @@ mod generate_tests { } #[inline(always)] fn is_method_call(&self) -> bool { false } + #[inline(always)] fn is_pure(&self) -> bool { true } } } }; @@ -1170,6 +1177,7 @@ mod generate_tests { } #[inline(always)] fn is_method_call(&self) -> bool { false } + #[inline(always)] fn is_pure(&self) -> bool { true } } } }; @@ -1227,6 +1235,7 @@ mod generate_tests { } #[inline(always)] fn is_method_call(&self) -> bool { false } + #[inline(always)] fn is_pure(&self) -> bool { true } } } }; @@ -1286,6 +1295,7 @@ mod generate_tests { } #[inline(always)] fn is_method_call(&self) -> bool { true } + #[inline(always)] fn is_pure(&self) -> bool { true } } } }; @@ -1338,14 +1348,12 @@ mod generate_tests { impl PluginFunction for increment_token { #[inline(always)] fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { - if args[0usize].is_read_only() { - return Err(EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into()); - } let arg0 = &mut args[0usize].write_lock::().unwrap(); Ok(Dynamic::from(increment(arg0))) } #[inline(always)] fn is_method_call(&self) -> bool { true } + #[inline(always)] fn is_pure(&self) -> bool { false } } } }; @@ -1401,14 +1409,12 @@ mod generate_tests { impl PluginFunction for increment_token { #[inline(always)] fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { - if args[0usize].is_read_only() { - return Err(EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into()); - } let arg0 = &mut args[0usize].write_lock::().unwrap(); Ok(Dynamic::from(increment(arg0))) } #[inline(always)] fn is_method_call(&self) -> bool { true } + #[inline(always)] fn is_pure(&self) -> bool { false } } } #[allow(unused_imports)] @@ -1487,14 +1493,12 @@ mod generate_tests { impl PluginFunction for increment_token { #[inline(always)] fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { - if args[0usize].is_read_only() { - return Err(EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into()); - } let arg0 = &mut args[0usize].write_lock::().unwrap(); Ok(Dynamic::from(increment(arg0))) } #[inline(always)] fn is_method_call(&self) -> bool { true } + #[inline(always)] fn is_pure(&self) -> bool { false } } } #[allow(unused_imports)] @@ -1574,14 +1578,12 @@ mod generate_tests { impl PluginFunction for int_foo_token { #[inline(always)] fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { - if args[0usize].is_read_only() { - return Err(EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into()); - } let arg0 = &mut args[0usize].write_lock::().unwrap(); Ok(Dynamic::from(int_foo(arg0))) } #[inline(always)] fn is_method_call(&self) -> bool { true } + #[inline(always)] fn is_pure(&self) -> bool { false } } } }; @@ -1638,14 +1640,12 @@ mod generate_tests { impl PluginFunction for int_foo_token { #[inline(always)] fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { - if args[0usize].is_read_only() { - return Err(EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into()); - } let arg0 = &mut args[0usize].write_lock::().unwrap(); Ok(Dynamic::from(int_foo(arg0))) } #[inline(always)] fn is_method_call(&self) -> bool { true } + #[inline(always)] fn is_pure(&self) -> bool { false } } } }; @@ -1699,15 +1699,13 @@ mod generate_tests { impl PluginFunction for int_foo_token { #[inline(always)] fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { - if args[0usize].is_read_only() { - return Err(EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into()); - } let arg1 = mem::take(args[1usize]).cast::(); let arg0 = &mut args[0usize].write_lock::().unwrap(); Ok(Dynamic::from(int_foo(arg0, arg1))) } #[inline(always)] fn is_method_call(&self) -> bool { true } + #[inline(always)] fn is_pure(&self) -> bool { false } } } }; @@ -1764,15 +1762,13 @@ mod generate_tests { impl PluginFunction for int_foo_token { #[inline(always)] fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { - if args[0usize].is_read_only() { - return Err(EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into()); - } let arg1 = mem::take(args[1usize]).cast::(); let arg0 = &mut args[0usize].write_lock::().unwrap(); Ok(Dynamic::from(int_foo(arg0, arg1))) } #[inline(always)] fn is_method_call(&self) -> bool { true } + #[inline(always)] fn is_pure(&self) -> bool { false } } } }; @@ -1826,15 +1822,13 @@ mod generate_tests { impl PluginFunction for get_by_index_token { #[inline(always)] fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { - if args[0usize].is_read_only() { - return Err(EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into()); - } let arg1 = mem::take(args[1usize]).cast::(); let arg0 = &mut args[0usize].write_lock::().unwrap(); Ok(Dynamic::from(get_by_index(arg0, arg1))) } #[inline(always)] fn is_method_call(&self) -> bool { true } + #[inline(always)] fn is_pure(&self) -> bool { false } } } }; @@ -1896,15 +1890,13 @@ mod generate_tests { impl PluginFunction for get_by_index_token { #[inline(always)] fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { - if args[0usize].is_read_only() { - return Err(EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into()); - } let arg1 = mem::take(args[1usize]).cast::(); let arg0 = &mut args[0usize].write_lock::().unwrap(); Ok(Dynamic::from(get_by_index(arg0, arg1))) } #[inline(always)] fn is_method_call(&self) -> bool { true } + #[inline(always)] fn is_pure(&self) -> bool { false } } } }; @@ -1961,15 +1953,13 @@ mod generate_tests { impl PluginFunction for get_by_index_token { #[inline(always)] fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { - if args[0usize].is_read_only() { - return Err(EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into()); - } let arg1 = mem::take(args[1usize]).cast::(); let arg0 = &mut args[0usize].write_lock::().unwrap(); Ok(Dynamic::from(get_by_index(arg0, arg1))) } #[inline(always)] fn is_method_call(&self) -> bool { true } + #[inline(always)] fn is_pure(&self) -> bool { false } } } }; @@ -2023,9 +2013,6 @@ mod generate_tests { impl PluginFunction for set_by_index_token { #[inline(always)] fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { - if args[0usize].is_read_only() { - return Err(EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into()); - } let arg1 = mem::take(args[1usize]).cast::(); let arg2 = mem::take(args[2usize]).cast::(); let arg0 = &mut args[0usize].write_lock::().unwrap(); @@ -2033,6 +2020,7 @@ mod generate_tests { } #[inline(always)] fn is_method_call(&self) -> bool { true } + #[inline(always)] fn is_pure(&self) -> bool { false } } } }; @@ -2089,9 +2077,6 @@ mod generate_tests { impl PluginFunction for set_by_index_token { #[inline(always)] fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { - if args[0usize].is_read_only() { - return Err(EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into()); - } let arg1 = mem::take(args[1usize]).cast::(); let arg2 = mem::take(args[2usize]).cast::(); let arg0 = &mut args[0usize].write_lock::().unwrap(); @@ -2099,6 +2084,7 @@ mod generate_tests { } #[inline(always)] fn is_method_call(&self) -> bool { true } + #[inline(always)] fn is_pure(&self) -> bool { false } } } }; diff --git a/examples/arrays_and_structs.rs b/examples/arrays_and_structs.rs index fc937bcd..b5a7889a 100644 --- a/examples/arrays_and_structs.rs +++ b/examples/arrays_and_structs.rs @@ -38,7 +38,7 @@ fn main() -> Result<(), Box> { engine .gen_fn_signatures(false) .into_iter() - .for_each(|func| println!("{}", func)); + .for_each(|func| println!("{func}")); println!(); } @@ -51,7 +51,7 @@ fn main() -> Result<(), Box> { ", )?; - println!("{:?}", result); + println!("{result:?}"); let result = engine.eval::( " @@ -61,7 +61,7 @@ fn main() -> Result<(), Box> { ", )?; - println!("{:?}", result); + println!("{result:?}"); Ok(()) } diff --git a/examples/callback.rs b/examples/callback.rs index ce27f77c..99984691 100644 --- a/examples/callback.rs +++ b/examples/callback.rs @@ -36,7 +36,7 @@ fn main() -> Result<(), Box> { let r2 = func(1, 2); let r3 = func(1, 2); - println!("The Answers: {}, {}, {}", r1, r2, r3); // prints 40, 42, 44 + println!("The Answers: {r1}, {r2}, {r3}"); // prints 40, 42, 44 Ok(()) } diff --git a/examples/custom_types.rs b/examples/custom_types.rs index 3fa7c7ef..c4d7804f 100644 --- a/examples/custom_types.rs +++ b/examples/custom_types.rs @@ -36,6 +36,8 @@ fn main() -> Result<(), Box> { type Item = i64; type IntoIter = std::vec::IntoIter; + #[inline] + #[must_use] fn into_iter(self) -> Self::IntoIter { vec![self.x - 1, self.x, self.x + 1].into_iter() } @@ -65,7 +67,7 @@ fn main() -> Result<(), Box> { engine .gen_fn_signatures(false) .into_iter() - .for_each(|func| println!("{}", func)); + .for_each(|func| println!("{func}")); println!(); } @@ -87,7 +89,7 @@ fn main() -> Result<(), Box> { ", )?; - println!("result: {}", result); // prints 1085764 + println!("result: {result}"); // prints 1085764 Ok(()) } diff --git a/examples/custom_types_and_methods.rs b/examples/custom_types_and_methods.rs index a70265ff..fecbccfe 100644 --- a/examples/custom_types_and_methods.rs +++ b/examples/custom_types_and_methods.rs @@ -48,7 +48,7 @@ fn main() -> Result<(), Box> { engine .gen_fn_signatures(false) .into_iter() - .for_each(|func| println!("{}", func)); + .for_each(|func| println!("{func}")); println!(); } @@ -62,7 +62,7 @@ fn main() -> Result<(), Box> { ", )?; - println!("result: {}", result); // prints 1085764 + println!("result: {result}"); // prints 1085764 Ok(()) } diff --git a/examples/definitions/.rhai/all_in_one.d.rhai b/examples/definitions/.rhai/all_in_one.d.rhai index c1025aa8..4ec27f7f 100644 --- a/examples/definitions/.rhai/all_in_one.d.rhai +++ b/examples/definitions/.rhai/all_in_one.d.rhai @@ -677,8 +677,6 @@ op **(u64, int) -> u64; op **(u8, int) -> u8; -op +(Decimal) -> Decimal; - op +(int) -> int; op +(f32) -> f32; @@ -801,8 +799,6 @@ op +=(Instant, float) -> (); /// Add the specified number of `seconds` to the timestamp. op +=(Instant, int) -> (); -op -(Decimal) -> Decimal; - op -(int) -> int; op -(f32) -> f32; @@ -1131,9 +1127,6 @@ op ^(u64, u64) -> u64; op ^(u8, u8) -> u8; -/// Return the absolute value of the decimal number. -fn abs(x: Decimal) -> Decimal; - /// Return the absolute value of the number. fn abs(x: int) -> int; @@ -1306,13 +1299,6 @@ fn atan(x: float, y: float) -> float; /// Return the arc-hyperbolic-tangent of the floating-point number, in radians. fn atanh(x: float) -> float; -/// Get an array of object maps containing the function calls stack. -/// -/// If there is no debugging interface registered, an empty array is returned. -/// -/// An array of strings is returned under `no_object`. -fn back_trace() -> Array; - /// Return an iterator over all the bits in the number. /// /// # Example @@ -1426,9 +1412,6 @@ fn blob(len: int, value: int) -> Blob; /// ``` fn bytes(string: String) -> int; -/// Return the smallest whole number larger than or equals to the decimal number. -fn ceiling(x: Decimal) -> Decimal; - /// Return the smallest whole number larger than or equals to the floating-point number. fn ceiling(x: float) -> float; @@ -1570,8 +1553,63 @@ fn clear(string: String) -> (); /// ``` fn contains(array: Array, value: ?) -> bool; -/// Return the cosine of the decimal number in radians. -fn cos(x: Decimal) -> Decimal; +/// Return `true` if the BLOB contains a specified byte value. +/// +/// # Example +/// +/// ```rhai +/// let text = "hello, world!"; +/// +/// print(text.contains('h')); // prints true +/// +/// print(text.contains('x')); // prints false +/// ``` +fn contains(blob: Blob, value: int) -> bool; + +/// Returns `true` if the object map contains a specified property. +/// +/// # Example +/// +/// ```rhai +/// let m = #{a: 1, b: 2, c: 3}; +/// +/// print(m.contains("b")); // prints true +/// +/// print(m.contains("x")); // prints false +/// ``` +fn contains(map: Map, property: String) -> bool; + +/// Return `true` if the range contains a specified value. +fn contains(range: ExclusiveRange, value: int) -> bool; + +/// Return `true` if the range contains a specified value. +fn contains(range: InclusiveRange, value: int) -> bool; + +/// Return `true` if the string contains a specified character. +/// +/// # Example +/// +/// ```rhai +/// let text = "hello, world!"; +/// +/// print(text.contains('h')); // prints true +/// +/// print(text.contains('x')); // prints false +/// ``` +fn contains(string: String, character: char) -> bool; + +/// Return `true` if the string contains a specified string. +/// +/// # Example +/// +/// ```rhai +/// let text = "hello, world!"; +/// +/// print(text.contains("hello")); // prints true +/// +/// print(text.contains("hey")); // prints false +/// ``` +fn contains(string: String, match_string: String) -> bool; /// Return the cosine of the floating-point number in radians. fn cos(x: float) -> float; @@ -1650,37 +1688,37 @@ fn crop(string: String, start: int) -> (); fn crop(string: String, start: int, len: int) -> (); /// Return the empty string. -fn debug() -> String; +op debug() -> String; /// Convert the array into a string. -fn debug(array: Array) -> String; +op debug(Array) -> String; /// Convert the string into debug format. -fn debug(character: char) -> String; +op debug(char) -> String; /// Convert the function pointer into a string in debug format. -fn debug(f: FnPtr) -> String; +op debug(FnPtr) -> String; /// Convert the value of the `item` into a string in debug format. -fn debug(item: ?) -> String; +op debug(?) -> String; /// Convert the object map into a string. -fn debug(map: Map) -> String; +op debug(Map) -> String; /// Convert the value of `number` into a string. -fn debug(number: f32) -> String; +op debug(f32) -> String; /// Convert the value of `number` into a string. -fn debug(number: float) -> String; +op debug(float) -> String; /// Convert the string into debug format. -fn debug(string: String) -> String; +op debug(String) -> String; /// Convert the unit into a string in debug format. -fn debug(unit: ()) -> String; +op debug(()) -> String; /// Convert the boolean value into a string in debug format. -fn debug(value: bool) -> String; +op debug(bool) -> String; /// Remove duplicated _consecutive_ elements from the array. /// @@ -1986,9 +2024,6 @@ fn end(range: InclusiveRange) -> int; /// ``` fn ends_with(string: String, match_string: String) -> bool; -/// Return the exponential of the decimal number. -fn exp(x: Decimal) -> Decimal; - /// Return the exponential of the floating-point number. fn exp(x: float) -> float; @@ -2199,15 +2234,9 @@ fn filter(array: Array, filter: FnPtr) -> Array; /// ``` fn filter(array: Array, filter_func: String) -> Array; -/// Return the largest whole number less than or equals to the decimal number. -fn floor(x: Decimal) -> Decimal; - /// Return the largest whole number less than or equals to the floating-point number. fn floor(x: float) -> float; -/// Return the fractional part of the decimal number. -fn fraction(x: Decimal) -> Decimal; - /// Return the fractional part of the floating-point number. fn fraction(x: float) -> float; @@ -2309,9 +2338,6 @@ fn get bits(value: int) -> Iterator; /// ``` fn get bytes(string: String) -> int; -/// Return the smallest whole number larger than or equals to the decimal number. -fn get ceiling(x: Decimal) -> Decimal; - /// Return the smallest whole number larger than or equals to the floating-point number. fn get ceiling(x: float) -> float; @@ -2345,21 +2371,12 @@ fn get end(range: ExclusiveRange) -> int; /// Return the end of the inclusive range. fn get end(range: InclusiveRange) -> int; -/// Return the largest whole number less than or equals to the decimal number. -fn get floor(x: Decimal) -> Decimal; - /// Return the largest whole number less than or equals to the floating-point number. fn get floor(x: float) -> float; -/// Return the fractional part of the decimal number. -fn get fraction(x: Decimal) -> Decimal; - /// Return the fractional part of the floating-point number. fn get fraction(x: float) -> float; -/// Return the integral part of the decimal number. -fn get int(x: Decimal) -> Decimal; - /// Return the integral part of the floating-point number. fn get int(x: float) -> float; @@ -2470,9 +2487,6 @@ fn get is_odd(x: u64) -> bool; /// Return true if the number is odd. fn get is_odd(x: u8) -> bool; -/// Return true if the decimal number is zero. -fn get is_zero(x: Decimal) -> bool; - /// Return true if the number is zero. fn get is_zero(x: int) -> bool; @@ -2549,10 +2563,6 @@ fn get len(string: String) -> int; /// ``` fn get name(fn_ptr: FnPtr) -> String; -/// Return the nearest whole number closest to the decimal number. -/// Always round mid-point towards the closest even number. -fn get round(x: Decimal) -> Decimal; - /// Return the nearest whole number closest to the floating-point number. /// Rounds away from zero. fn get round(x: float) -> float; @@ -2917,9 +2927,6 @@ fn insert(array: Array, index: int, item: ?) -> (); /// ``` fn insert(blob: Blob, index: int, value: int) -> (); -/// Return the integral part of the decimal number. -fn int(x: Decimal) -> Decimal; - /// Return the integral part of the floating-point number. fn int(x: float) -> float; @@ -3033,9 +3040,6 @@ fn is_odd(x: u64) -> bool; /// Return true if the number is odd. fn is_odd(x: u8) -> bool; -/// Return true if the decimal number is zero. -fn is_zero(x: Decimal) -> bool; - /// Return true if the number is zero. fn is_zero(x: int) -> bool; @@ -3113,15 +3117,9 @@ fn len(map: Map) -> int; /// ``` fn len(string: String) -> int; -/// Return the natural log of the decimal number. -fn ln(x: Decimal) -> Decimal; - /// Return the natural log of the floating-point number. fn ln(x: float) -> float; -/// Return the log of the decimal number with base 10. -fn log(x: Decimal) -> Decimal; - /// Return the log of the floating-point number with base 10. fn log(x: float) -> float; @@ -3422,17 +3420,6 @@ fn parse_be_int(blob: Blob, range: RangeInclusive) -> int; /// ``` fn parse_be_int(blob: Blob, start: int, len: int) -> int; -/// Parse a string into a decimal number. -/// -/// # Example -/// -/// ```rhai -/// let x = parse_decimal("123.456"); -/// -/// print(x); // prints 123.456 -/// ``` -fn parse_decimal(string: String) -> Decimal; - /// Parse a string into a floating-point number. /// /// # Example @@ -3472,6 +3459,17 @@ fn parse_int(string: String) -> int; /// ``` fn parse_int(string: String, radix: int) -> int; +/// Parse a JSON string into a value. +/// +/// # Example +/// +/// ```rhai +/// let m = parse_json(`{"a":1, "b":2, "c":3}`); +/// +/// print(m); // prints #{"a":1, "b":2, "c":3} +/// ``` +fn parse_json(json: String) -> ?; + /// Parse the bytes within an exclusive `range` in the BLOB as a `FLOAT` /// in little-endian byte order. /// @@ -3621,34 +3619,34 @@ fn pop(string: String) -> ?; fn pop(string: String, len: int) -> String; /// Return the empty string. -fn print() -> String; +op print() -> String; /// Convert the array into a string. -fn print(array: Array) -> String; +op print(Array) -> String; /// Return the character into a string. -fn print(character: char) -> String; +op print(char) -> String; /// Convert the value of the `item` into a string. -fn print(item: ?) -> String; +op print(?) -> String; /// Convert the object map into a string. -fn print(map: Map) -> String; +op print(Map) -> String; /// Convert the value of `number` into a string. -fn print(number: f32) -> String; +op print(f32) -> String; /// Convert the value of `number` into a string. -fn print(number: float) -> String; +op print(float) -> String; /// Return the `string`. -fn print(string: String) -> String; +op print(String) -> String; /// Return the empty string. -fn print(unit: ()) -> String; +op print(()) -> String; /// Return the boolean value into a string. -fn print(value: bool) -> String; +op print(bool) -> String; /// Add a new element, which is not another array, to the end of the array. /// @@ -3810,27 +3808,6 @@ fn range(from: u64, to: u64) -> Iterator; /// ``` fn range(from: u8, to: u8) -> Iterator; -/// Return an iterator over an exclusive range, each iteration increasing by `step`. -/// -/// If `range` is reversed and `step` < 0, iteration goes backwards. -/// -/// Otherwise, if `range` is empty, an empty iterator is returned. -/// -/// # Example -/// -/// ```rhai -/// // prints all values from 8 to 17 in steps of 3 -/// for n in range(8..18, 3) { -/// print(n); -/// } -/// -/// // prints all values down from 18 to 9 in steps of -3 -/// for n in range(18..8, -3) { -/// print(n); -/// } -/// ``` -fn range(range: Range, step: Decimal) -> Iterator; - /// Return an iterator over an exclusive range, each iteration increasing by `step`. /// /// If `range` is reversed and `step` < 0, iteration goes backwards. @@ -4062,28 +4039,6 @@ fn range(range: Range, step: u64) -> Iterator; /// ``` fn range(range: Range, step: u8) -> Iterator; -/// Return an iterator over the exclusive range of `from..to`, each iteration increasing by `step`. -/// The value `to` is never included. -/// -/// If `from` > `to` and `step` < 0, iteration goes backwards. -/// -/// If `from` > `to` and `step` > 0 or `from` < `to` and `step` < 0, an empty iterator is returned. -/// -/// # Example -/// -/// ```rhai -/// // prints all values from 8 to 17 in steps of 3 -/// for n in range(8, 18, 3) { -/// print(n); -/// } -/// -/// // prints all values down from 18 to 9 in steps of -3 -/// for n in range(18, 8, -3) { -/// print(n); -/// } -/// ``` -fn range(from: Decimal, to: Decimal, step: Decimal) -> Iterator; - /// Return an iterator over the exclusive range of `from..to`, each iteration increasing by `step`. /// The value `to` is never included. /// @@ -4917,34 +4872,10 @@ fn reverse(array: Array) -> (); /// ``` fn reverse(blob: Blob) -> (); -/// Return the nearest whole number closest to the decimal number. -/// Always round mid-point towards the closest even number. -fn round(x: Decimal) -> Decimal; - /// Return the nearest whole number closest to the floating-point number. /// Rounds away from zero. fn round(x: float) -> float; -/// Round the decimal number to the specified number of `digits` after the decimal point and return it. -/// Always round mid-point towards the closest even number. -fn round(x: Decimal, digits: int) -> Decimal; - -/// Round the decimal number to the specified number of `digits` after the decimal point and return it. -/// Always round towards zero. -fn round_down(x: Decimal, digits: int) -> Decimal; - -/// Round the decimal number to the specified number of `digits` after the decimal point and return it. -/// Always round mid-points towards zero. -fn round_half_down(x: Decimal, digits: int) -> Decimal; - -/// Round the decimal number to the specified number of `digits` after the decimal point and return it. -/// Always round mid-points away from zero. -fn round_half_up(x: Decimal, digits: int) -> Decimal; - -/// Round the decimal number to the specified number of `digits` after the decimal point and return it. -/// Always round away from zero. -fn round_up(x: Decimal, digits: int) -> Decimal; - /// Set the element at the `index` position in the array to a new `value`. /// /// * If `index` < 0, position counts from the end of the array (`-1` is the last element). @@ -5170,13 +5101,6 @@ fn shift(array: Array) -> ?; /// ``` fn shift(blob: Blob) -> int; -/// Return the sign (as an integer) of the decimal number according to the following: -/// -/// * `0` if the number is zero -/// * `1` if the number is positive -/// * `-1` if the number is negative -fn sign(x: Decimal) -> int; - /// Return the sign (as an integer) of the number according to the following: /// /// * `0` if the number is zero @@ -5226,9 +5150,6 @@ fn sign(x: i32) -> int; /// * `-1` if the number is negative fn sign(x: i8) -> int; -/// Return the sine of the decimal number in radians. -fn sin(x: Decimal) -> Decimal; - /// Return the sine of the floating-point number in radians. fn sin(x: float) -> float; @@ -5658,9 +5579,6 @@ fn split_rev(string: String, delimiter: String, segments: int) -> Array; /// ``` fn split_rev(string: String, delimiter: char, segments: int) -> Array; -/// Return the square root of the decimal number. -fn sqrt(x: Decimal) -> Decimal; - /// Return the square root of the floating-point number. fn sqrt(x: float) -> float; @@ -5755,9 +5673,6 @@ fn sub_string(string: String, start: int, len: int) -> String; /// ``` fn tag(value: ?) -> int; -/// Return the tangent of the decimal number in radians. -fn tan(x: Decimal) -> Decimal; - /// Return the tangent of the floating-point number in radians. fn tan(x: float) -> float; @@ -5874,34 +5789,9 @@ fn to_debug(unit: ()) -> String; /// Convert the boolean value into a string in debug format. fn to_debug(value: bool) -> String; -/// Convert the floating-point number to decimal. -fn to_decimal(x: f32) -> Decimal; - -/// Convert the floating-point number to decimal. -fn to_decimal(x: float) -> Decimal; - -fn to_decimal(x: i16) -> Decimal; - -fn to_decimal(x: i32) -> Decimal; - -fn to_decimal(x: int) -> Decimal; - -fn to_decimal(x: i8) -> Decimal; - -fn to_decimal(x: u16) -> Decimal; - -fn to_decimal(x: u32) -> Decimal; - -fn to_decimal(x: u64) -> Decimal; - -fn to_decimal(x: u8) -> Decimal; - /// Convert radians to degrees. fn to_degrees(x: float) -> float; -/// Convert the decimal number to floating-point. -fn to_float(x: Decimal) -> float; - /// Convert the 32-bit floating-point number to 64-bit. fn to_float(x: f32) -> float; @@ -5953,9 +5843,6 @@ fn to_hex(value: u64) -> String; /// Convert the `value` into a string in hex format. fn to_hex(value: u8) -> String; -/// Convert the decimal number into an integer. -fn to_int(x: Decimal) -> int; - fn to_int(x: char) -> int; /// Convert the floating-point number into an integer. diff --git a/examples/definitions/.rhai/definitions/__static__.d.rhai b/examples/definitions/.rhai/definitions/__static__.d.rhai index a1876713..9a1e2f7a 100644 --- a/examples/definitions/.rhai/definitions/__static__.d.rhai +++ b/examples/definitions/.rhai/definitions/__static__.d.rhai @@ -159,8 +159,6 @@ op **(u64, int) -> u64; op **(u8, int) -> u8; -op +(Decimal) -> Decimal; - op +(int) -> int; op +(f32) -> f32; @@ -283,8 +281,6 @@ op +=(Instant, float) -> (); /// Add the specified number of `seconds` to the timestamp. op +=(Instant, int) -> (); -op -(Decimal) -> Decimal; - op -(int) -> int; op -(f32) -> f32; @@ -613,9 +609,6 @@ op ^(u64, u64) -> u64; op ^(u8, u8) -> u8; -/// Return the absolute value of the decimal number. -fn abs(x: Decimal) -> Decimal; - /// Return the absolute value of the number. fn abs(x: int) -> int; @@ -788,13 +781,6 @@ fn atan(x: float, y: float) -> float; /// Return the arc-hyperbolic-tangent of the floating-point number, in radians. fn atanh(x: float) -> float; -/// Get an array of object maps containing the function calls stack. -/// -/// If there is no debugging interface registered, an empty array is returned. -/// -/// An array of strings is returned under `no_object`. -fn back_trace() -> Array; - /// Return an iterator over all the bits in the number. /// /// # Example @@ -908,9 +894,6 @@ fn blob(len: int, value: int) -> Blob; /// ``` fn bytes(string: String) -> int; -/// Return the smallest whole number larger than or equals to the decimal number. -fn ceiling(x: Decimal) -> Decimal; - /// Return the smallest whole number larger than or equals to the floating-point number. fn ceiling(x: float) -> float; @@ -1052,8 +1035,63 @@ fn clear(string: String) -> (); /// ``` fn contains(array: Array, value: ?) -> bool; -/// Return the cosine of the decimal number in radians. -fn cos(x: Decimal) -> Decimal; +/// Return `true` if the BLOB contains a specified byte value. +/// +/// # Example +/// +/// ```rhai +/// let text = "hello, world!"; +/// +/// print(text.contains('h')); // prints true +/// +/// print(text.contains('x')); // prints false +/// ``` +fn contains(blob: Blob, value: int) -> bool; + +/// Returns `true` if the object map contains a specified property. +/// +/// # Example +/// +/// ```rhai +/// let m = #{a: 1, b: 2, c: 3}; +/// +/// print(m.contains("b")); // prints true +/// +/// print(m.contains("x")); // prints false +/// ``` +fn contains(map: Map, property: String) -> bool; + +/// Return `true` if the range contains a specified value. +fn contains(range: ExclusiveRange, value: int) -> bool; + +/// Return `true` if the range contains a specified value. +fn contains(range: InclusiveRange, value: int) -> bool; + +/// Return `true` if the string contains a specified character. +/// +/// # Example +/// +/// ```rhai +/// let text = "hello, world!"; +/// +/// print(text.contains('h')); // prints true +/// +/// print(text.contains('x')); // prints false +/// ``` +fn contains(string: String, character: char) -> bool; + +/// Return `true` if the string contains a specified string. +/// +/// # Example +/// +/// ```rhai +/// let text = "hello, world!"; +/// +/// print(text.contains("hello")); // prints true +/// +/// print(text.contains("hey")); // prints false +/// ``` +fn contains(string: String, match_string: String) -> bool; /// Return the cosine of the floating-point number in radians. fn cos(x: float) -> float; @@ -1132,37 +1170,37 @@ fn crop(string: String, start: int) -> (); fn crop(string: String, start: int, len: int) -> (); /// Return the empty string. -fn debug() -> String; +op debug() -> String; /// Convert the array into a string. -fn debug(array: Array) -> String; +op debug(Array) -> String; /// Convert the string into debug format. -fn debug(character: char) -> String; +op debug(char) -> String; /// Convert the function pointer into a string in debug format. -fn debug(f: FnPtr) -> String; +op debug(FnPtr) -> String; /// Convert the value of the `item` into a string in debug format. -fn debug(item: ?) -> String; +op debug(?) -> String; /// Convert the object map into a string. -fn debug(map: Map) -> String; +op debug(Map) -> String; /// Convert the value of `number` into a string. -fn debug(number: f32) -> String; +op debug(f32) -> String; /// Convert the value of `number` into a string. -fn debug(number: float) -> String; +op debug(float) -> String; /// Convert the string into debug format. -fn debug(string: String) -> String; +op debug(String) -> String; /// Convert the unit into a string in debug format. -fn debug(unit: ()) -> String; +op debug(()) -> String; /// Convert the boolean value into a string in debug format. -fn debug(value: bool) -> String; +op debug(bool) -> String; /// Remove duplicated _consecutive_ elements from the array. /// @@ -1468,9 +1506,6 @@ fn end(range: InclusiveRange) -> int; /// ``` fn ends_with(string: String, match_string: String) -> bool; -/// Return the exponential of the decimal number. -fn exp(x: Decimal) -> Decimal; - /// Return the exponential of the floating-point number. fn exp(x: float) -> float; @@ -1681,15 +1716,9 @@ fn filter(array: Array, filter: FnPtr) -> Array; /// ``` fn filter(array: Array, filter_func: String) -> Array; -/// Return the largest whole number less than or equals to the decimal number. -fn floor(x: Decimal) -> Decimal; - /// Return the largest whole number less than or equals to the floating-point number. fn floor(x: float) -> float; -/// Return the fractional part of the decimal number. -fn fraction(x: Decimal) -> Decimal; - /// Return the fractional part of the floating-point number. fn fraction(x: float) -> float; @@ -1791,9 +1820,6 @@ fn get bits(value: int) -> Iterator; /// ``` fn get bytes(string: String) -> int; -/// Return the smallest whole number larger than or equals to the decimal number. -fn get ceiling(x: Decimal) -> Decimal; - /// Return the smallest whole number larger than or equals to the floating-point number. fn get ceiling(x: float) -> float; @@ -1827,21 +1853,12 @@ fn get end(range: ExclusiveRange) -> int; /// Return the end of the inclusive range. fn get end(range: InclusiveRange) -> int; -/// Return the largest whole number less than or equals to the decimal number. -fn get floor(x: Decimal) -> Decimal; - /// Return the largest whole number less than or equals to the floating-point number. fn get floor(x: float) -> float; -/// Return the fractional part of the decimal number. -fn get fraction(x: Decimal) -> Decimal; - /// Return the fractional part of the floating-point number. fn get fraction(x: float) -> float; -/// Return the integral part of the decimal number. -fn get int(x: Decimal) -> Decimal; - /// Return the integral part of the floating-point number. fn get int(x: float) -> float; @@ -1952,9 +1969,6 @@ fn get is_odd(x: u64) -> bool; /// Return true if the number is odd. fn get is_odd(x: u8) -> bool; -/// Return true if the decimal number is zero. -fn get is_zero(x: Decimal) -> bool; - /// Return true if the number is zero. fn get is_zero(x: int) -> bool; @@ -2031,10 +2045,6 @@ fn get len(string: String) -> int; /// ``` fn get name(fn_ptr: FnPtr) -> String; -/// Return the nearest whole number closest to the decimal number. -/// Always round mid-point towards the closest even number. -fn get round(x: Decimal) -> Decimal; - /// Return the nearest whole number closest to the floating-point number. /// Rounds away from zero. fn get round(x: float) -> float; @@ -2399,9 +2409,6 @@ fn insert(array: Array, index: int, item: ?) -> (); /// ``` fn insert(blob: Blob, index: int, value: int) -> (); -/// Return the integral part of the decimal number. -fn int(x: Decimal) -> Decimal; - /// Return the integral part of the floating-point number. fn int(x: float) -> float; @@ -2515,9 +2522,6 @@ fn is_odd(x: u64) -> bool; /// Return true if the number is odd. fn is_odd(x: u8) -> bool; -/// Return true if the decimal number is zero. -fn is_zero(x: Decimal) -> bool; - /// Return true if the number is zero. fn is_zero(x: int) -> bool; @@ -2595,15 +2599,9 @@ fn len(map: Map) -> int; /// ``` fn len(string: String) -> int; -/// Return the natural log of the decimal number. -fn ln(x: Decimal) -> Decimal; - /// Return the natural log of the floating-point number. fn ln(x: float) -> float; -/// Return the log of the decimal number with base 10. -fn log(x: Decimal) -> Decimal; - /// Return the log of the floating-point number with base 10. fn log(x: float) -> float; @@ -2904,17 +2902,6 @@ fn parse_be_int(blob: Blob, range: RangeInclusive) -> int; /// ``` fn parse_be_int(blob: Blob, start: int, len: int) -> int; -/// Parse a string into a decimal number. -/// -/// # Example -/// -/// ```rhai -/// let x = parse_decimal("123.456"); -/// -/// print(x); // prints 123.456 -/// ``` -fn parse_decimal(string: String) -> Decimal; - /// Parse a string into a floating-point number. /// /// # Example @@ -2954,6 +2941,17 @@ fn parse_int(string: String) -> int; /// ``` fn parse_int(string: String, radix: int) -> int; +/// Parse a JSON string into a value. +/// +/// # Example +/// +/// ```rhai +/// let m = parse_json(`{"a":1, "b":2, "c":3}`); +/// +/// print(m); // prints #{"a":1, "b":2, "c":3} +/// ``` +fn parse_json(json: String) -> ?; + /// Parse the bytes within an exclusive `range` in the BLOB as a `FLOAT` /// in little-endian byte order. /// @@ -3103,34 +3101,34 @@ fn pop(string: String) -> ?; fn pop(string: String, len: int) -> String; /// Return the empty string. -fn print() -> String; +op print() -> String; /// Convert the array into a string. -fn print(array: Array) -> String; +op print(Array) -> String; /// Return the character into a string. -fn print(character: char) -> String; +op print(char) -> String; /// Convert the value of the `item` into a string. -fn print(item: ?) -> String; +op print(?) -> String; /// Convert the object map into a string. -fn print(map: Map) -> String; +op print(Map) -> String; /// Convert the value of `number` into a string. -fn print(number: f32) -> String; +op print(f32) -> String; /// Convert the value of `number` into a string. -fn print(number: float) -> String; +op print(float) -> String; /// Return the `string`. -fn print(string: String) -> String; +op print(String) -> String; /// Return the empty string. -fn print(unit: ()) -> String; +op print(()) -> String; /// Return the boolean value into a string. -fn print(value: bool) -> String; +op print(bool) -> String; /// Add a new element, which is not another array, to the end of the array. /// @@ -3292,27 +3290,6 @@ fn range(from: u64, to: u64) -> Iterator; /// ``` fn range(from: u8, to: u8) -> Iterator; -/// Return an iterator over an exclusive range, each iteration increasing by `step`. -/// -/// If `range` is reversed and `step` < 0, iteration goes backwards. -/// -/// Otherwise, if `range` is empty, an empty iterator is returned. -/// -/// # Example -/// -/// ```rhai -/// // prints all values from 8 to 17 in steps of 3 -/// for n in range(8..18, 3) { -/// print(n); -/// } -/// -/// // prints all values down from 18 to 9 in steps of -3 -/// for n in range(18..8, -3) { -/// print(n); -/// } -/// ``` -fn range(range: Range, step: Decimal) -> Iterator; - /// Return an iterator over an exclusive range, each iteration increasing by `step`. /// /// If `range` is reversed and `step` < 0, iteration goes backwards. @@ -3544,28 +3521,6 @@ fn range(range: Range, step: u64) -> Iterator; /// ``` fn range(range: Range, step: u8) -> Iterator; -/// Return an iterator over the exclusive range of `from..to`, each iteration increasing by `step`. -/// The value `to` is never included. -/// -/// If `from` > `to` and `step` < 0, iteration goes backwards. -/// -/// If `from` > `to` and `step` > 0 or `from` < `to` and `step` < 0, an empty iterator is returned. -/// -/// # Example -/// -/// ```rhai -/// // prints all values from 8 to 17 in steps of 3 -/// for n in range(8, 18, 3) { -/// print(n); -/// } -/// -/// // prints all values down from 18 to 9 in steps of -3 -/// for n in range(18, 8, -3) { -/// print(n); -/// } -/// ``` -fn range(from: Decimal, to: Decimal, step: Decimal) -> Iterator; - /// Return an iterator over the exclusive range of `from..to`, each iteration increasing by `step`. /// The value `to` is never included. /// @@ -4399,34 +4354,10 @@ fn reverse(array: Array) -> (); /// ``` fn reverse(blob: Blob) -> (); -/// Return the nearest whole number closest to the decimal number. -/// Always round mid-point towards the closest even number. -fn round(x: Decimal) -> Decimal; - /// Return the nearest whole number closest to the floating-point number. /// Rounds away from zero. fn round(x: float) -> float; -/// Round the decimal number to the specified number of `digits` after the decimal point and return it. -/// Always round mid-point towards the closest even number. -fn round(x: Decimal, digits: int) -> Decimal; - -/// Round the decimal number to the specified number of `digits` after the decimal point and return it. -/// Always round towards zero. -fn round_down(x: Decimal, digits: int) -> Decimal; - -/// Round the decimal number to the specified number of `digits` after the decimal point and return it. -/// Always round mid-points towards zero. -fn round_half_down(x: Decimal, digits: int) -> Decimal; - -/// Round the decimal number to the specified number of `digits` after the decimal point and return it. -/// Always round mid-points away from zero. -fn round_half_up(x: Decimal, digits: int) -> Decimal; - -/// Round the decimal number to the specified number of `digits` after the decimal point and return it. -/// Always round away from zero. -fn round_up(x: Decimal, digits: int) -> Decimal; - /// Set the element at the `index` position in the array to a new `value`. /// /// * If `index` < 0, position counts from the end of the array (`-1` is the last element). @@ -4652,13 +4583,6 @@ fn shift(array: Array) -> ?; /// ``` fn shift(blob: Blob) -> int; -/// Return the sign (as an integer) of the decimal number according to the following: -/// -/// * `0` if the number is zero -/// * `1` if the number is positive -/// * `-1` if the number is negative -fn sign(x: Decimal) -> int; - /// Return the sign (as an integer) of the number according to the following: /// /// * `0` if the number is zero @@ -4708,9 +4632,6 @@ fn sign(x: i32) -> int; /// * `-1` if the number is negative fn sign(x: i8) -> int; -/// Return the sine of the decimal number in radians. -fn sin(x: Decimal) -> Decimal; - /// Return the sine of the floating-point number in radians. fn sin(x: float) -> float; @@ -5140,9 +5061,6 @@ fn split_rev(string: String, delimiter: String, segments: int) -> Array; /// ``` fn split_rev(string: String, delimiter: char, segments: int) -> Array; -/// Return the square root of the decimal number. -fn sqrt(x: Decimal) -> Decimal; - /// Return the square root of the floating-point number. fn sqrt(x: float) -> float; @@ -5237,9 +5155,6 @@ fn sub_string(string: String, start: int, len: int) -> String; /// ``` fn tag(value: ?) -> int; -/// Return the tangent of the decimal number in radians. -fn tan(x: Decimal) -> Decimal; - /// Return the tangent of the floating-point number in radians. fn tan(x: float) -> float; @@ -5356,34 +5271,9 @@ fn to_debug(unit: ()) -> String; /// Convert the boolean value into a string in debug format. fn to_debug(value: bool) -> String; -/// Convert the floating-point number to decimal. -fn to_decimal(x: f32) -> Decimal; - -/// Convert the floating-point number to decimal. -fn to_decimal(x: float) -> Decimal; - -fn to_decimal(x: i16) -> Decimal; - -fn to_decimal(x: i32) -> Decimal; - -fn to_decimal(x: int) -> Decimal; - -fn to_decimal(x: i8) -> Decimal; - -fn to_decimal(x: u16) -> Decimal; - -fn to_decimal(x: u32) -> Decimal; - -fn to_decimal(x: u64) -> Decimal; - -fn to_decimal(x: u8) -> Decimal; - /// Convert radians to degrees. fn to_degrees(x: float) -> float; -/// Convert the decimal number to floating-point. -fn to_float(x: Decimal) -> float; - /// Convert the 32-bit floating-point number to 64-bit. fn to_float(x: f32) -> float; @@ -5435,9 +5325,6 @@ fn to_hex(value: u64) -> String; /// Convert the `value` into a string in hex format. fn to_hex(value: u8) -> String; -/// Convert the decimal number into an integer. -fn to_int(x: Decimal) -> int; - fn to_int(x: char) -> int; /// Convert the floating-point number into an integer. diff --git a/examples/definitions/.rhai/defs.json b/examples/definitions/.rhai/defs.json index bb25ab3e..467a5507 100644 --- a/examples/definitions/.rhai/defs.json +++ b/examples/definitions/.rhai/defs.json @@ -3,8 +3,8 @@ "general_kenobi": { "functions": [ { - "baseHash": 727795846011184342, - "fullHash": 5101524478338862216, + "baseHash": 3873007749982070651, + "fullHash": 5865213555928423624, "namespace": "internal", "access": "public", "name": "hello_there", @@ -27,8 +27,8 @@ }, "functions": [ { - "baseHash": 17133166385977770750, - "fullHash": 11299449021188202345, + "baseHash": 12461724250411739075, + "fullHash": 14530626537296006176, "namespace": "global", "access": "public", "name": "minus", diff --git a/examples/event_handler_js/main.rs b/examples/event_handler_js/main.rs index db5740f3..22c128c3 100644 --- a/examples/event_handler_js/main.rs +++ b/examples/event_handler_js/main.rs @@ -78,12 +78,12 @@ pub fn main() { scope.push_constant("MY_CONSTANT", 42_i64); // Compile the handler script. - println!("> Loading script file: {}", path); + println!("> Loading script file: {path}"); let ast = match engine.compile_file_with_scope(&mut scope, path.into()) { Ok(ast) => ast, Err(err) => { - eprintln!("! Error: {}", err); + eprintln!("! Error: {err}"); println!("Cannot continue. Bye!"); return; } @@ -101,7 +101,7 @@ pub fn main() { let result = engine.call_fn_raw(&mut scope, &ast, false, true, "init", Some(&mut states), []); if let Err(err) = result { - eprintln!("! {}", err) + eprintln!("! {err}") } // Create handler instance @@ -152,7 +152,7 @@ pub fn main() { engine.call_fn_raw(scope, ast, false, true, event, this_ptr, [arg.into()]); if let Err(err) = result { - eprintln!("! {}", err) + eprintln!("! {err}") } } } diff --git a/examples/event_handler_main/main.rs b/examples/event_handler_main/main.rs index c70c8740..b5b46577 100644 --- a/examples/event_handler_main/main.rs +++ b/examples/event_handler_main/main.rs @@ -67,7 +67,7 @@ pub fn main() { scope.push_constant("MY_CONSTANT", 42_i64); // Compile the handler script. - println!("> Loading script file: {}", path); + println!("> Loading script file: {path}"); let ast = match engine.compile_file_with_scope(&mut scope, path.into()) { Ok(ast) => ast, @@ -89,7 +89,7 @@ pub fn main() { let result = engine.call_fn_raw(&mut scope, &ast, false, false, "init", None, []); if let Err(err) = result { - eprintln!("! {}", err) + eprintln!("! {err}") } // Create handler instance @@ -127,7 +127,7 @@ pub fn main() { let result = engine.call_fn::<()>(scope, ast, event, (arg.to_string(),)); if let Err(err) = result { - eprintln!("! {}", err) + eprintln!("! {err}") } } } diff --git a/examples/event_handler_map/main.rs b/examples/event_handler_map/main.rs index e45688db..b41795c4 100644 --- a/examples/event_handler_map/main.rs +++ b/examples/event_handler_map/main.rs @@ -81,12 +81,12 @@ pub fn main() { scope.push("state", states); // Compile the handler script. - println!("> Loading script file: {}", path); + println!("> Loading script file: {path}"); let ast = match engine.compile_file_with_scope(&mut scope, path.into()) { Ok(ast) => ast, Err(err) => { - eprintln!("! Error: {}", err); + eprintln!("! Error: {err}"); println!("Cannot continue. Bye!"); return; } @@ -103,7 +103,7 @@ pub fn main() { let result = engine.call_fn::<()>(&mut scope, &ast, "init", ()); if let Err(err) = result { - eprintln!("! {}", err) + eprintln!("! {err}") } // Create handler instance @@ -141,7 +141,7 @@ pub fn main() { let result = engine.call_fn::<()>(scope, ast, event, (arg.to_string(),)); if let Err(err) = result { - eprintln!("! {}", err) + eprintln!("! {err}") } } } diff --git a/examples/hello.rs b/examples/hello.rs index b85b06da..dba66e5b 100644 --- a/examples/hello.rs +++ b/examples/hello.rs @@ -9,7 +9,7 @@ fn main() -> Result<(), Box> { let result = engine.eval::("40 + 2")?; - println!("The Answer: {}", result); // prints 42 + println!("The Answer: {result}"); // prints 42 Ok(()) } diff --git a/examples/reuse_scope.rs b/examples/reuse_scope.rs index 3bfa6bf3..486382a2 100644 --- a/examples/reuse_scope.rs +++ b/examples/reuse_scope.rs @@ -13,7 +13,7 @@ fn main() -> Result<(), Box> { for _ in 0..10 { let result = engine.eval_with_scope::(&mut scope, "x += 1; x")?; - println!("result: {}", result); + println!("result: {result}"); } println!("x = {}", scope.get_value::("x").unwrap()); diff --git a/examples/serde.rs b/examples/serde.rs index b145e247..cbdab2b2 100644 --- a/examples/serde.rs +++ b/examples/serde.rs @@ -36,13 +36,13 @@ fn main() { }, }; - println!("Source struct: {:#?}", x); + println!("Source struct: {x:#?}"); // Convert the 'MyStruct' into a 'Dynamic' let map: Dynamic = to_dynamic(x).unwrap(); assert!(map.is::()); - println!("Serialized to Dynamic: {:#?}", map); + println!("Serialized to Dynamic: {map:#?}"); } pub fn de() { @@ -60,7 +60,7 @@ fn main() { ) .unwrap(); - println!("Source Dynamic: {:#?}", result); + println!("Source Dynamic: {result:#?}"); // Convert the 'Dynamic' object map into 'MyStruct' let x: MyStruct = from_dynamic(&result).unwrap(); @@ -77,7 +77,7 @@ fn main() { }, } ); - println!("Deserialized to struct: {:#?}", x); + println!("Deserialized to struct: {x:#?}"); } ser(); diff --git a/examples/simple_fn.rs b/examples/simple_fn.rs index 06b28c53..887cad30 100644 --- a/examples/simple_fn.rs +++ b/examples/simple_fn.rs @@ -13,7 +13,7 @@ fn main() -> Result<(), Box> { let result = engine.eval::("add(40, 2)")?; - println!("Answer: {}", result); // prints 42 + println!("Answer: {result}"); // prints 42 Ok(()) } diff --git a/examples/strings.rs b/examples/strings.rs index e3fac382..b9c7285a 100644 --- a/examples/strings.rs +++ b/examples/strings.rs @@ -37,10 +37,10 @@ fn main() -> Result<(), Box> { .register_fn("index_of", find_substring) // Register string functions using closures .register_fn("display", |label: &str, value: i64| { - println!("{}: {}", label, value) + println!("{label}: {value}") }) .register_fn("display", |label: ImmutableString, value: &str| { - println!(r#"{}: "{}""#, label, value) // Quote the input string + println!(r#"{label}: "{value}""#) // Quote the input string }); let mut scope = Scope::new(); diff --git a/examples/threading.rs b/examples/threading.rs index 64cd04dc..adcf9bea 100644 --- a/examples/threading.rs +++ b/examples/threading.rs @@ -60,7 +60,7 @@ fn main() { let mut value: i64 = 0; while value < 10 { - println!("Value: {}", value); + println!("Value: {value}"); // Send value to script tx_master.send(value).unwrap(); // Receive value from script diff --git a/src/api/call_fn.rs b/src/api/call_fn.rs index ac1fb7f3..1a78cae1 100644 --- a/src/api/call_fn.rs +++ b/src/api/call_fn.rs @@ -4,8 +4,8 @@ use crate::eval::{Caches, GlobalRuntimeState}; use crate::types::dynamic::Variant; use crate::{ - reify, Dynamic, Engine, FuncArgs, Position, RhaiResult, RhaiResultOf, Scope, StaticVec, AST, - ERR, + reify, Dynamic, Engine, FuncArgs, Position, RhaiResult, RhaiResultOf, Scope, SharedModule, + StaticVec, AST, ERR, }; use std::any::{type_name, TypeId}; #[cfg(feature = "no_std")] @@ -233,6 +233,7 @@ impl Engine { arg_values, ) } + /// Call a script function defined in an [`AST`] with multiple [`Dynamic`] arguments. fn _call_fn( &self, @@ -247,8 +248,10 @@ impl Engine { arg_values: &mut [Dynamic], ) -> RhaiResult { let statements = ast.statements(); - let lib = &[ast.as_ref()]; - let mut this_ptr = this_ptr; + let lib = &[AsRef::::as_ref(ast).clone()]; + + let mut no_this_ptr = Dynamic::NULL; + let this_ptr = this_ptr.unwrap_or(&mut no_this_ptr); let orig_scope_len = scope.len(); @@ -257,54 +260,51 @@ impl Engine { &mut global.embedded_module_resolver, ast.resolver().cloned(), ); + #[cfg(not(feature = "no_module"))] + let global = &mut *crate::types::RestoreOnDrop::lock(global, move |g| { + g.embedded_module_resolver = orig_embedded_module_resolver + }); - let mut result = Ok(Dynamic::UNIT); - - if eval_ast && !statements.is_empty() { - result = self.eval_global_statements(scope, global, caches, statements, lib, 0); + let result = if eval_ast && !statements.is_empty() { + let r = self.eval_global_statements(global, caches, lib, scope, statements); if rewind_scope { scope.rewind(orig_scope_len); } - } - result = result.and_then(|_| { - let mut args: StaticVec<_> = arg_values.iter_mut().collect(); + r + } else { + Ok(Dynamic::UNIT) + } + .and_then(|_| { + let args = &mut arg_values.iter_mut().collect::>(); // Check for data race. #[cfg(not(feature = "no_closure"))] - crate::func::call::ensure_no_data_race(name, &args, false).map(|_| Dynamic::UNIT)?; + crate::func::ensure_no_data_race(name, args, false).map(|_| Dynamic::UNIT)?; if let Some(fn_def) = ast.shared_lib().get_script_fn(name, args.len()) { self.call_script_fn( - scope, global, caches, lib, - &mut this_ptr, + scope, + this_ptr, fn_def, - &mut args, + args, rewind_scope, Position::NONE, - 0, ) } else { Err(ERR::ErrorFunctionNotFound(name.into(), Position::NONE).into()) } - }); - - #[cfg(not(feature = "no_module"))] - { - global.embedded_module_resolver = orig_embedded_module_resolver; - } - - let result = result?; + })?; #[cfg(feature = "debugging")] if self.debugger.is_some() { global.debugger.status = crate::eval::DebuggerStatus::Terminate; let node = &crate::ast::Stmt::Noop(Position::NONE); - self.run_debugger(scope, global, lib, &mut this_ptr, node, 0)?; + self.run_debugger(global, caches, lib, scope, this_ptr, node)?; } Ok(result) diff --git a/src/api/compile.rs b/src/api/compile.rs index 5322dbeb..2d187160 100644 --- a/src/api/compile.rs +++ b/src/api/compile.rs @@ -295,6 +295,6 @@ impl Engine { let mut peekable = stream.peekable(); let mut state = ParseState::new(self, scope, Default::default(), tokenizer_control); - self.parse_global_expr(&mut peekable, &mut state, self.optimization_level) + self.parse_global_expr(&mut peekable, &mut state, |_| {}, self.optimization_level) } } diff --git a/src/api/custom_syntax.rs b/src/api/custom_syntax.rs index ad7e5b40..a12ea593 100644 --- a/src/api/custom_syntax.rs +++ b/src/api/custom_syntax.rs @@ -10,9 +10,9 @@ use crate::{ reify, Dynamic, Engine, EvalContext, Identifier, ImmutableString, LexError, Position, RhaiResult, StaticVec, }; -use std::ops::Deref; #[cfg(feature = "no_std")] use std::prelude::v1::*; +use std::{borrow::Borrow, ops::Deref}; /// Collection of special markers for custom syntax definition. pub mod markers { @@ -145,8 +145,17 @@ impl Expression<'_> { } } +impl Borrow for Expression<'_> { + #[inline(always)] + #[must_use] + fn borrow(&self) -> &Expr { + self.0 + } +} + impl AsRef for Expression<'_> { #[inline(always)] + #[must_use] fn as_ref(&self) -> &Expr { self.0 } @@ -219,7 +228,13 @@ impl Engine { continue; } - let token = Token::lookup_from_syntax(s); + let token = Token::lookup_symbol_from_syntax(s).or_else(|| { + if Token::is_reserved_keyword(s) { + Some(Token::Reserved(Box::new(s.into()))) + } else { + None + } + }); let seg = match s { // Markers not in first position @@ -264,7 +279,7 @@ impl Engine { .into_err(Position::NONE)); } // Identifier in first position - _ if segments.is_empty() && is_valid_identifier(s.chars()) => { + _ if segments.is_empty() && is_valid_identifier(s) => { // Make it a custom keyword/symbol if it is disabled or reserved if (!self.disabled_symbols.is_empty() && self.disabled_symbols.contains(s)) || token.map_or(false, |v| v.is_reserved()) diff --git a/src/api/definitions/mod.rs b/src/api/definitions/mod.rs index 73908d04..3ee43af0 100644 --- a/src/api/definitions/mod.rs +++ b/src/api/definitions/mod.rs @@ -77,6 +77,7 @@ pub struct DefinitionsConfig { impl Default for DefinitionsConfig { #[inline(always)] + #[must_use] fn default() -> Self { Self { write_headers: false, @@ -439,18 +440,16 @@ impl Module { } first = false; - if f.access == FnAccess::Private { - continue; + if f.access != FnAccess::Private { + #[cfg(not(feature = "no_custom_syntax"))] + let operator = def.engine.custom_keywords.contains_key(f.name.as_str()) + || (!f.name.contains('$') && !is_valid_function_name(f.name.as_str())); + + #[cfg(feature = "no_custom_syntax")] + let operator = !f.name.contains('$') && !is_valid_function_name(&f.name); + + f.write_definition(writer, def, operator)?; } - - #[cfg(not(feature = "no_custom_syntax"))] - let operator = def.engine.custom_keywords.contains_key(&f.name) - || (!f.name.contains('$') && !is_valid_function_name(&f.name)); - - #[cfg(feature = "no_custom_syntax")] - let operator = !f.name.contains('$') && !is_valid_function_name(&f.name); - - f.write_definition(writer, def, operator)?; } Ok(()) @@ -554,7 +553,7 @@ fn def_type_name<'a>(ty: &'a str, engine: &'a Engine) -> Cow<'a, str> { #[cfg(not(feature = "no_object"))] let ty = ty.replace(type_name::(), "Map"); - #[cfg(not(feature = "no_std"))] + #[cfg(not(feature = "no_time"))] let ty = ty.replace(type_name::(), "Instant"); let ty = ty.replace(type_name::(), "FnPtr"); diff --git a/src/api/deprecated.rs b/src/api/deprecated.rs index 6b6ad977..cda495db 100644 --- a/src/api/deprecated.rs +++ b/src/api/deprecated.rs @@ -4,7 +4,7 @@ use crate::func::RegisterNativeFunction; use crate::types::dynamic::Variant; use crate::{ Dynamic, Engine, EvalAltResult, FnPtr, Identifier, ImmutableString, NativeCallContext, - Position, RhaiResult, RhaiResultOf, Scope, AST, + Position, RhaiResult, RhaiResultOf, Scope, SharedModule, AST, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -354,6 +354,26 @@ impl Dynamic { } impl NativeCallContext<'_> { + /// Create a new [`NativeCallContext`]. + /// + /// # Unimplemented + /// + /// This method is deprecated. It is no longer implemented and always panics. + /// + /// Use [`FnPtr::call`] to call a function pointer directly. + /// + /// This method will be removed in the next major version. + #[deprecated( + since = "1.3.0", + note = "use `FnPtr::call` to call a function pointer directly." + )] + #[inline(always)] + #[must_use] + #[allow(unused_variables)] + pub fn new(engine: &Engine, fn_name: &str, lib: &[SharedModule]) -> Self { + unimplemented!("`NativeCallContext::new` is deprecated"); + } + /// Call a function inside the call context. /// /// # Deprecated diff --git a/src/api/eval.rs b/src/api/eval.rs index 4b22d99c..10cf9040 100644 --- a/src/api/eval.rs +++ b/src/api/eval.rs @@ -122,6 +122,7 @@ impl Engine { let ast = self.parse_global_expr( &mut stream.peekable(), &mut state, + |_| {}, #[cfg(not(feature = "no_optimize"))] OptimizationLevel::None, #[cfg(feature = "no_optimize")] @@ -185,18 +186,21 @@ impl Engine { ast: &AST, ) -> RhaiResultOf { let global = &mut GlobalRuntimeState::new(self); + let caches = &mut Caches::new(); - let result = self.eval_ast_with_scope_raw(scope, global, ast, 0)?; + let result = self.eval_ast_with_scope_raw(global, caches, scope, ast)?; #[cfg(feature = "debugging")] if self.debugger.is_some() { global.debugger.status = crate::eval::DebuggerStatus::Terminate; let lib = &[ #[cfg(not(feature = "no_function"))] - ast.as_ref(), + AsRef::::as_ref(ast).clone(), ]; + let mut this = Dynamic::NULL; let node = &crate::ast::Stmt::Noop(Position::NONE); - self.run_debugger(scope, global, lib, &mut None, node, 0)?; + + self.run_debugger(global, caches, lib, scope, &mut this, node)?; } let typ = self.map_type_name(result.type_name()); @@ -210,19 +214,23 @@ impl Engine { #[inline] pub(crate) fn eval_ast_with_scope_raw<'a>( &self, - scope: &mut Scope, global: &mut GlobalRuntimeState, + caches: &mut Caches, + + scope: &mut Scope, ast: &'a AST, - level: usize, ) -> RhaiResult { - let mut caches = Caches::new(); - global.source = ast.source_raw().clone(); + global.source = ast.source_raw().cloned(); #[cfg(not(feature = "no_module"))] let orig_embedded_module_resolver = std::mem::replace( &mut global.embedded_module_resolver, ast.resolver().cloned(), ); + #[cfg(not(feature = "no_module"))] + let global = &mut *crate::types::RestoreOnDrop::lock(global, move |g| { + g.embedded_module_resolver = orig_embedded_module_resolver + }); let statements = ast.statements(); @@ -230,24 +238,12 @@ impl Engine { return Ok(Dynamic::UNIT); } - let mut _lib = &[ + let lib = &[ #[cfg(not(feature = "no_function"))] - ast.as_ref(), - ][..]; - #[cfg(not(feature = "no_function"))] - if !ast.has_functions() { - _lib = &[]; - } + AsRef::::as_ref(ast).clone(), + ]; - let result = - self.eval_global_statements(scope, global, &mut caches, statements, _lib, level); - - #[cfg(not(feature = "no_module"))] - { - global.embedded_module_resolver = orig_embedded_module_resolver; - } - - result + self.eval_global_statements(global, caches, lib, scope, statements) } /// _(internals)_ Evaluate a list of statements with no `this` pointer. /// Exported under the `internals` feature only. @@ -261,14 +257,14 @@ impl Engine { #[inline(always)] pub fn eval_statements_raw( &self, - scope: &mut Scope, global: &mut GlobalRuntimeState, caches: &mut Caches, + lib: &[crate::SharedModule], + + scope: &mut Scope, statements: &[crate::ast::Stmt], - lib: &[&crate::Module], - level: usize, ) -> RhaiResult { - self.eval_global_statements(scope, global, caches, statements, lib, level) + self.eval_global_statements(global, caches, lib, scope, statements) } } diff --git a/src/api/events.rs b/src/api/events.rs index 01463d2b..a44ee2a4 100644 --- a/src/api/events.rs +++ b/src/api/events.rs @@ -169,10 +169,11 @@ impl Engine { /// let mut engine = Engine::new(); /// /// // Register a token mapper. + /// # #[allow(deprecated)] /// engine.on_parse_token(|token, _, _| { /// match token { /// // Convert all integer literals to strings - /// Token::IntegerConstant(n) => Token::StringConstant(n.to_string().into()), + /// Token::IntegerConstant(n) => Token::StringConstant(Box::new(n.to_string().into())), /// // Convert 'begin' .. 'end' to '{' .. '}' /// Token::Identifier(s) if &*s == "begin" => Token::LeftBrace, /// Token::Identifier(s) if &*s == "end" => Token::RightBrace, diff --git a/src/api/files.rs b/src/api/files.rs index ad2bbeae..b2a2ffd1 100644 --- a/src/api/files.rs +++ b/src/api/files.rs @@ -108,7 +108,7 @@ impl Engine { pub fn compile_file_with_scope(&self, scope: &Scope, path: PathBuf) -> RhaiResultOf { Self::read_file(&path).and_then(|contents| { let mut ast = self.compile_with_scope(scope, &contents)?; - ast.set_source(path.to_string_lossy()); + ast.set_source(path.to_string_lossy().as_ref()); Ok(ast) }) } diff --git a/src/api/json.rs b/src/api/json.rs index 034062bf..c22d682c 100644 --- a/src/api/json.rs +++ b/src/api/json.rs @@ -23,7 +23,7 @@ impl Engine { /// JSON sub-objects are handled transparently. /// /// This function can be used together with [`format_map_as_json`] to work with JSON texts - /// without using the [`serde`](https://crates.io/crates/serde) crate (which is heavy). + /// without using the [`serde_json`](https://crates.io/crates/serde_json) crate (which is heavy). /// /// # Example /// @@ -122,6 +122,7 @@ impl Engine { let ast = self.parse_global_expr( &mut stream.peekable(), &mut state, + |s| s.allow_unquoted_map_properties = false, #[cfg(not(feature = "no_optimize"))] OptimizationLevel::None, #[cfg(feature = "no_optimize")] @@ -137,7 +138,7 @@ impl Engine { /// Not available under `no_std`. /// /// This function can be used together with [`Engine::parse_json`] to work with JSON texts -/// without using the [`serde`](https://crates.io/crates/serde) crate (which is heavy). +/// without using the [`serde_json`](https://crates.io/crates/serde_json) crate (which is heavy). /// /// # Data types /// @@ -165,10 +166,7 @@ pub fn format_map_as_json(map: &Map) -> String { if let Some(val) = value.read_lock::() { result.push_str(&format_map_as_json(&*val)); - continue; - } - - if value.is::<()>() { + } else if value.is::<()>() { result.push_str("null"); } else { write!(result, "{:?}", value).unwrap(); diff --git a/src/api/limits.rs b/src/api/limits.rs index 18eb17f1..32daf13e 100644 --- a/src/api/limits.rs +++ b/src/api/limits.rs @@ -1,12 +1,31 @@ //! Settings for [`Engine`]'s limitations. #![cfg(not(feature = "unchecked"))] -use super::default_limits; use crate::Engine; use std::num::{NonZeroU64, NonZeroUsize}; #[cfg(feature = "no_std")] use std::prelude::v1::*; +pub mod default_limits { + #[cfg(debug_assertions)] + #[cfg(not(feature = "no_function"))] + pub const MAX_CALL_STACK_DEPTH: usize = 8; + #[cfg(debug_assertions)] + pub const MAX_EXPR_DEPTH: usize = 32; + #[cfg(not(feature = "no_function"))] + #[cfg(debug_assertions)] + pub const MAX_FUNCTION_EXPR_DEPTH: usize = 16; + + #[cfg(not(debug_assertions))] + #[cfg(not(feature = "no_function"))] + pub const MAX_CALL_STACK_DEPTH: usize = 64; + #[cfg(not(debug_assertions))] + pub const MAX_EXPR_DEPTH: usize = 64; + #[cfg(not(feature = "no_function"))] + #[cfg(not(debug_assertions))] + pub const MAX_FUNCTION_EXPR_DEPTH: usize = 32; +} + /// A type containing all the limits imposed by the [`Engine`]. /// /// Not available under `unchecked`. @@ -75,12 +94,34 @@ impl Limits { impl Default for Limits { #[inline(always)] + #[must_use] fn default() -> Self { Self::new() } } impl Engine { + /// Is there a data size limit set? + #[inline] + pub(crate) const fn has_data_size_limit(&self) -> bool { + self.limits.max_string_size.is_some() + || { + #[cfg(not(feature = "no_index"))] + { + self.limits.max_array_size.is_some() + } + #[cfg(feature = "no_index")] + false + } + || { + #[cfg(not(feature = "no_object"))] + { + self.limits.max_map_size.is_some() + } + #[cfg(feature = "no_object")] + false + } + } /// Set the maximum levels of function calls allowed for a script in order to avoid /// infinite recursion and stack overflows. /// @@ -93,12 +134,14 @@ impl Engine { } /// The maximum levels of function calls allowed for a script. /// - /// Not available under `unchecked` or `no_function`. - #[cfg(not(feature = "no_function"))] + /// Zero under `no_function`. #[inline(always)] #[must_use] pub const fn max_call_levels(&self) -> usize { - self.limits.max_call_stack_depth + #[cfg(not(feature = "no_function"))] + return self.limits.max_call_stack_depth; + #[cfg(feature = "no_function")] + return 0; } /// Set the maximum number of operations allowed for a script to run to avoid /// consuming too much resources (0 for unlimited). @@ -115,10 +158,9 @@ impl Engine { #[inline] #[must_use] pub const fn max_operations(&self) -> u64 { - if let Some(n) = self.limits.max_operations { - n.get() - } else { - 0 + match self.limits.max_operations { + Some(n) => n.get(), + None => 0, } } /// Set the maximum number of imported [modules][crate::Module] allowed for a script. @@ -132,12 +174,14 @@ impl Engine { } /// The maximum number of imported [modules][crate::Module] allowed for a script. /// - /// Not available under `unchecked` or `no_module`. - #[cfg(not(feature = "no_module"))] + /// Zero under `no_module`. #[inline(always)] #[must_use] pub const fn max_modules(&self) -> usize { - self.limits.max_modules + #[cfg(not(feature = "no_module"))] + return self.limits.max_modules; + #[cfg(feature = "no_module")] + return 0; } /// Set the depth limits for expressions (0 for unlimited). /// @@ -156,29 +200,27 @@ impl Engine { self } /// The depth limit for expressions (0 for unlimited). - /// - /// Not available under `unchecked`. #[inline] #[must_use] pub const fn max_expr_depth(&self) -> usize { - if let Some(n) = self.limits.max_expr_depth { - n.get() - } else { - 0 + match self.limits.max_expr_depth { + Some(n) => n.get(), + None => 0, } } /// The depth limit for expressions in functions (0 for unlimited). /// - /// Not available under `unchecked` or `no_function`. - #[cfg(not(feature = "no_function"))] + /// Zero under `no_function`. #[inline] #[must_use] pub const fn max_function_expr_depth(&self) -> usize { - if let Some(n) = self.limits.max_function_expr_depth { - n.get() - } else { - 0 - } + #[cfg(not(feature = "no_function"))] + return match self.limits.max_function_expr_depth { + Some(n) => n.get(), + None => 0, + }; + #[cfg(feature = "no_function")] + return 0; } /// Set the maximum length of [strings][crate::ImmutableString] (0 for unlimited). /// @@ -189,15 +231,12 @@ impl Engine { self } /// The maximum length of [strings][crate::ImmutableString] (0 for unlimited). - /// - /// Not available under `unchecked`. #[inline] #[must_use] pub const fn max_string_size(&self) -> usize { - if let Some(n) = self.limits.max_string_size { - n.get() - } else { - 0 + match self.limits.max_string_size { + Some(n) => n.get(), + None => 0, } } /// Set the maximum length of [arrays][crate::Array] (0 for unlimited). @@ -211,16 +250,17 @@ impl Engine { } /// The maximum length of [arrays][crate::Array] (0 for unlimited). /// - /// Not available under `unchecked` or `no_index`. - #[cfg(not(feature = "no_index"))] + /// Zero under `no_index`. #[inline] #[must_use] pub const fn max_array_size(&self) -> usize { - if let Some(n) = self.limits.max_array_size { - n.get() - } else { - 0 - } + #[cfg(not(feature = "no_index"))] + return match self.limits.max_array_size { + Some(n) => n.get(), + None => 0, + }; + #[cfg(feature = "no_index")] + return 0; } /// Set the maximum size of [object maps][crate::Map] (0 for unlimited). /// @@ -233,15 +273,16 @@ impl Engine { } /// The maximum size of [object maps][crate::Map] (0 for unlimited). /// - /// Not available under `unchecked` or `no_object`. - #[cfg(not(feature = "no_object"))] + /// Zero under `no_object`. #[inline] #[must_use] pub const fn max_map_size(&self) -> usize { - if let Some(n) = self.limits.max_map_size { - n.get() - } else { - 0 - } + #[cfg(not(feature = "no_object"))] + return match self.limits.max_map_size { + Some(n) => n.get(), + None => 0, + }; + #[cfg(feature = "no_object")] + return 0; } } diff --git a/src/api/mod.rs b/src/api/mod.rs index e9e03854..71973732 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -35,36 +35,12 @@ pub mod definitions; use crate::{Dynamic, Engine, Identifier}; -#[cfg(not(feature = "no_custom_syntax"))] -use crate::{engine::Precedence, tokenizer::Token}; - #[cfg(feature = "no_std")] use std::prelude::v1::*; pub mod default_limits { #[cfg(not(feature = "unchecked"))] - #[cfg(debug_assertions)] - #[cfg(not(feature = "no_function"))] - pub const MAX_CALL_STACK_DEPTH: usize = 8; - #[cfg(not(feature = "unchecked"))] - #[cfg(debug_assertions)] - pub const MAX_EXPR_DEPTH: usize = 32; - #[cfg(not(feature = "unchecked"))] - #[cfg(not(feature = "no_function"))] - #[cfg(debug_assertions)] - pub const MAX_FUNCTION_EXPR_DEPTH: usize = 16; - - #[cfg(not(feature = "unchecked"))] - #[cfg(not(debug_assertions))] - #[cfg(not(feature = "no_function"))] - pub const MAX_CALL_STACK_DEPTH: usize = 64; - #[cfg(not(feature = "unchecked"))] - #[cfg(not(debug_assertions))] - pub const MAX_EXPR_DEPTH: usize = 64; - #[cfg(not(feature = "unchecked"))] - #[cfg(not(feature = "no_function"))] - #[cfg(not(debug_assertions))] - pub const MAX_FUNCTION_EXPR_DEPTH: usize = 32; + pub use super::limits::default_limits::*; pub const MAX_DYNAMIC_PARAMETERS: usize = 16; } @@ -75,6 +51,7 @@ impl Engine { /// Not available under `no_module`. #[cfg(not(feature = "no_module"))] #[inline(always)] + #[must_use] pub fn module_resolver(&self) -> &dyn crate::ModuleResolver { &*self.module_resolver } @@ -170,12 +147,14 @@ impl Engine { keyword: impl AsRef, precedence: u8, ) -> Result<&mut Self, String> { - let precedence = - Precedence::new(precedence).ok_or_else(|| "precedence cannot be zero".to_string())?; + use crate::tokenizer::Token; + + let precedence = crate::engine::Precedence::new(precedence) + .ok_or_else(|| "precedence cannot be zero".to_string())?; let keyword = keyword.as_ref(); - match Token::lookup_from_syntax(keyword) { + match Token::lookup_symbol_from_syntax(keyword) { // Standard identifiers and reserved keywords are OK None | Some(Token::Reserved(..)) => (), // custom keywords are OK @@ -235,3 +214,71 @@ impl Engine { self } } + +#[cfg(feature = "unchecked")] +impl Engine { + /// The maximum levels of function calls allowed for a script. + /// + /// Always returns [`usize::MAX`] under `unchecked`. + #[inline(always)] + #[must_use] + pub const fn max_call_levels(&self) -> usize { + usize::MAX + } + /// The maximum number of operations allowed for a script to run (0 for unlimited). + /// + /// Always returns zero under `unchecked`. + #[inline(always)] + #[must_use] + pub const fn max_operations(&self) -> u64 { + 0 + } + /// The maximum number of imported [modules][crate::Module] allowed for a script. + /// + /// Always returns [`usize::MAX`] under `unchecked`. + #[inline(always)] + #[must_use] + pub const fn max_modules(&self) -> usize { + usize::MAX + } + /// The depth limit for expressions (0 for unlimited). + /// + /// Always returns zero under `unchecked`. + #[inline(always)] + #[must_use] + pub const fn max_expr_depth(&self) -> usize { + 0 + } + /// The depth limit for expressions in functions (0 for unlimited). + /// + /// Always returns zero under `unchecked`. + #[inline(always)] + #[must_use] + pub const fn max_function_expr_depth(&self) -> usize { + 0 + } + /// The maximum length of [strings][crate::ImmutableString] (0 for unlimited). + /// + /// Always returns zero under `unchecked`. + #[inline(always)] + #[must_use] + pub const fn max_string_size(&self) -> usize { + 0 + } + /// The maximum length of [arrays][crate::Array] (0 for unlimited). + /// + /// Always returns zero under `unchecked`. + #[inline(always)] + #[must_use] + pub const fn max_array_size(&self) -> usize { + 0 + } + /// The maximum size of [object maps][crate::Map] (0 for unlimited). + /// + /// Always returns zero under `unchecked`. + #[inline(always)] + #[must_use] + pub const fn max_map_size(&self) -> usize { + 0 + } +} diff --git a/src/api/options.rs b/src/api/options.rs index d5596cfd..42b57456 100644 --- a/src/api/options.rs +++ b/src/api/options.rs @@ -12,23 +12,25 @@ bitflags! { const IF_EXPR = 0b_0000_0000_0001; /// Is `switch` expression allowed? const SWITCH_EXPR = 0b_0000_0000_0010; + /// Are loop expressions allowed? + const LOOP_EXPR = 0b_0000_0000_0100; /// Is statement-expression allowed? - const STMT_EXPR = 0b_0000_0000_0100; + const STMT_EXPR = 0b_0000_0000_1000; /// Is anonymous function allowed? #[cfg(not(feature = "no_function"))] - const ANON_FN = 0b_0000_0000_1000; + const ANON_FN = 0b_0000_0001_0000; /// Is looping allowed? - const LOOPING = 0b_0000_0001_0000; + const LOOPING = 0b_0000_0010_0000; /// Is variables shadowing allowed? - const SHADOW = 0b_0000_0010_0000; + const SHADOW = 0b_0000_0100_0000; /// Strict variables mode? - const STRICT_VAR = 0b_0000_0100_0000; + const STRICT_VAR = 0b_0000_1000_0000; /// Raise error if an object map property does not exist? /// Returns `()` if `false`. #[cfg(not(feature = "no_object"))] - const FAIL_ON_INVALID_MAP_PROPERTY = 0b_0000_1000_0000; + const FAIL_ON_INVALID_MAP_PROPERTY = 0b_0001_0000_0000; /// Fast operators mode? - const FAST_OPS = 0b_0001_0000_0000; + const FAST_OPS = 0b_0010_0000_0000; } } @@ -81,6 +83,18 @@ impl Engine { pub fn set_allow_switch_expression(&mut self, enable: bool) { self.options.set(LangOptions::SWITCH_EXPR, enable); } + /// Are loop expressions allowed? + /// Default is `true`. + #[inline(always)] + #[must_use] + pub const fn allow_loop_expressions(&self) -> bool { + self.options.contains(LangOptions::LOOP_EXPR) + } + /// Set whether loop expressions are allowed. + #[inline(always)] + pub fn set_allow_loop_expressions(&mut self, enable: bool) { + self.options.set(LangOptions::LOOP_EXPR, enable); + } /// Is statement-expression allowed? /// Default is `true`. #[inline(always)] diff --git a/src/api/register.rs b/src/api/register.rs index 2b4e20ed..a335faa1 100644 --- a/src/api/register.rs +++ b/src/api/register.rs @@ -4,6 +4,7 @@ use crate::func::{FnCallArgs, RegisterNativeFunction, SendSync}; use crate::types::dynamic::Variant; use crate::{ Engine, FnAccess, FnNamespace, Identifier, Module, NativeCallContext, RhaiResultOf, Shared, + SharedModule, }; use std::any::{type_name, TypeId}; #[cfg(feature = "no_std")] @@ -16,12 +17,14 @@ impl Engine { /// Get the global namespace module (which is the fist module in `global_modules`). #[inline(always)] #[allow(dead_code)] + #[must_use] pub(crate) fn global_namespace(&self) -> &Module { self.global_modules.first().unwrap() } /// Get a mutable reference to the global namespace module /// (which is the first module in `global_modules`). #[inline(always)] + #[must_use] pub(crate) fn global_namespace_mut(&mut self) -> &mut Module { let module = self.global_modules.first_mut().unwrap(); Shared::get_mut(module).expect("not shared") @@ -224,12 +227,12 @@ impl Engine { #[inline(always)] pub fn register_type_with_name_raw( &mut self, - fully_qualified_type_path: impl Into, + type_path: impl Into, name: impl Into, ) -> &mut Self { // Add the pretty-print type name into the map self.global_namespace_mut() - .set_custom_type_raw(fully_qualified_type_path, name); + .set_custom_type_raw(type_path, name); self } /// Register a type iterator for an iterable type with the [`Engine`]. @@ -634,7 +637,7 @@ impl Engine { /// When searching for functions, modules loaded later are preferred. In other words, loaded /// modules are searched in reverse order. #[inline(always)] - pub fn register_global_module(&mut self, module: Shared) -> &mut Self { + pub fn register_global_module(&mut self, module: SharedModule) -> &mut Self { // Insert the module into the front. // The first module is always the global namespace. self.global_modules.insert(1, module); @@ -678,12 +681,12 @@ impl Engine { pub fn register_static_module( &mut self, name: impl AsRef, - module: Shared, + module: SharedModule, ) -> &mut Self { fn register_static_module_raw( - root: &mut std::collections::BTreeMap>, + root: &mut std::collections::BTreeMap, name: &str, - module: Shared, + module: SharedModule, ) { let separator = crate::tokenizer::Token::DoubleColon.syntax(); let separator = separator.as_ref(); diff --git a/src/api/run.rs b/src/api/run.rs index b361200a..551b5e98 100644 --- a/src/api/run.rs +++ b/src/api/run.rs @@ -2,7 +2,7 @@ use crate::eval::{Caches, GlobalRuntimeState}; use crate::parser::ParseState; -use crate::{Engine, Module, RhaiResultOf, Scope, AST}; +use crate::{Engine, RhaiResultOf, Scope, SharedModule, AST}; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -113,7 +113,7 @@ impl Engine { pub fn run_ast_with_scope(&self, scope: &mut Scope, ast: &AST) -> RhaiResultOf<()> { let caches = &mut Caches::new(); let global = &mut GlobalRuntimeState::new(self); - global.source = ast.source_raw().clone(); + global.source = ast.source_raw().cloned(); #[cfg(not(feature = "no_module"))] { @@ -122,16 +122,16 @@ impl Engine { let statements = ast.statements(); if !statements.is_empty() { - let lib = [ + let lib: &[SharedModule] = &[ #[cfg(not(feature = "no_function"))] - ast.as_ref(), + AsRef::::as_ref(ast).clone(), ]; - let lib = if lib.first().map_or(true, |m: &&Module| m.is_empty()) { - &lib[0..0] + let lib = if lib.first().map_or(true, |m| m.is_empty()) { + &[][..] } else { &lib }; - self.eval_global_statements(scope, global, caches, statements, lib, 0)?; + self.eval_global_statements(global, caches, lib, scope, statements)?; } #[cfg(feature = "debugging")] @@ -139,10 +139,11 @@ impl Engine { global.debugger.status = crate::eval::DebuggerStatus::Terminate; let lib = &[ #[cfg(not(feature = "no_function"))] - ast.as_ref(), + AsRef::::as_ref(ast).clone(), ]; + let mut this = crate::Dynamic::NULL; let node = &crate::ast::Stmt::Noop(crate::Position::NONE); - self.run_debugger(scope, global, lib, &mut None, node, 0)?; + self.run_debugger(global, caches, lib, scope, &mut this, node)?; } Ok(()) diff --git a/src/api/type_names.rs b/src/api/type_names.rs index f426154b..dda879fc 100644 --- a/src/api/type_names.rs +++ b/src/api/type_names.rs @@ -44,7 +44,7 @@ fn map_std_type_name(name: &str, shorthands: bool) -> &str { if name == type_name::() || name == "Map" { return if shorthands { "map" } else { "Map" }; } - #[cfg(not(feature = "no_std"))] + #[cfg(not(feature = "no_time"))] if name == type_name::() || name == "Instant" { return if shorthands { "timestamp" } else { "Instant" }; } @@ -108,11 +108,15 @@ fn map_std_type_name(name: &str, shorthands: bool) -> &str { /// Format a Rust type to be display-friendly. /// +/// * `rhai::` prefix is cleared. /// * `()` is cleared. +/// * `&mut` is cleared. /// * `INT` and `FLOAT` are expanded. /// * [`RhaiResult`][crate::RhaiResult] and [`RhaiResultOf`][crate::RhaiResultOf] are expanded. #[cfg(feature = "metadata")] pub fn format_type(typ: &str, is_return_type: bool) -> std::borrow::Cow { + const RESULT_TYPE: &str = "Result<"; + const ERROR_TYPE: &str = ",Box>"; const RHAI_RESULT_TYPE: &str = "RhaiResult"; const RHAI_RESULT_TYPE_EXPAND: &str = "Result>"; const RHAI_RESULT_OF_TYPE: &str = "RhaiResultOf<"; @@ -135,6 +139,10 @@ pub fn format_type(typ: &str, is_return_type: bool) -> std::borrow::Cow { } else { format!("&mut {r}").into() }; + } else if typ.contains(" ") { + let typ = typ.replace(" ", ""); + let r = format_type(&typ, is_return_type); + return r.into_owned().into(); } match typ { @@ -167,6 +175,12 @@ pub fn format_type(typ: &str, is_return_type: bool) -> std::borrow::Cow { .replace("{}", format_type(inner, false).trim()) .into() } + ty if ty.starts_with(RESULT_TYPE) && ty.ends_with(ERROR_TYPE) => { + let inner = &ty[RESULT_TYPE.len()..ty.len() - ERROR_TYPE.len()]; + RHAI_RESULT_OF_TYPE_EXPAND + .replace("{}", format_type(inner, false).trim()) + .into() + } ty => ty.into(), } } diff --git a/src/ast/ast.rs b/src/ast/ast.rs index da0454fe..c1cfbaed 100644 --- a/src/ast/ast.rs +++ b/src/ast/ast.rs @@ -1,10 +1,11 @@ //! Module defining the AST (abstract syntax tree). use super::{ASTFlags, Expr, FnAccess, Stmt, StmtBlock, StmtBlockContainer}; -use crate::{Dynamic, FnNamespace, Identifier, Position}; +use crate::{Dynamic, FnNamespace, ImmutableString, Position}; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{ + borrow::Borrow, fmt, hash::Hash, ops::{Add, AddAssign}, @@ -19,8 +20,7 @@ use std::{ #[derive(Clone)] pub struct AST { /// Source of the [`AST`]. - /// No source if string is empty. - source: Identifier, + source: Option, /// [`AST`] documentation. #[cfg(feature = "metadata")] doc: crate::SmartString, @@ -28,7 +28,7 @@ pub struct AST { body: StmtBlock, /// Script-defined functions. #[cfg(not(feature = "no_function"))] - lib: crate::Shared, + lib: crate::SharedModule, /// Embedded module resolver, if any. #[cfg(not(feature = "no_module"))] resolver: Option>, @@ -36,12 +36,15 @@ pub struct AST { impl Default for AST { #[inline(always)] + #[must_use] fn default() -> Self { Self::empty() } } impl fmt::Debug for AST { + #[cold] + #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut fp = f.debug_struct("AST"); @@ -67,14 +70,14 @@ impl fmt::Debug for AST { impl AST { /// Create a new [`AST`]. #[cfg(not(feature = "internals"))] - #[inline(always)] + #[inline] #[must_use] pub(crate) fn new( statements: impl IntoIterator, - #[cfg(not(feature = "no_function"))] functions: impl Into>, + #[cfg(not(feature = "no_function"))] functions: impl Into, ) -> Self { Self { - source: Identifier::new_const(), + source: None, #[cfg(feature = "metadata")] doc: crate::SmartString::new_const(), body: StmtBlock::new(statements, Position::NONE, Position::NONE), @@ -87,14 +90,14 @@ impl AST { /// _(internals)_ Create a new [`AST`]. /// Exported under the `internals` feature only. #[cfg(feature = "internals")] - #[inline(always)] + #[inline] #[must_use] pub fn new( statements: impl IntoIterator, - #[cfg(not(feature = "no_function"))] functions: impl Into>, + #[cfg(not(feature = "no_function"))] functions: impl Into, ) -> Self { Self { - source: Identifier::new_const(), + source: None, #[cfg(feature = "metadata")] doc: crate::SmartString::new_const(), body: StmtBlock::new(statements, Position::NONE, Position::NONE), @@ -106,12 +109,12 @@ impl AST { } /// Create a new [`AST`] with a source name. #[cfg(not(feature = "internals"))] - #[inline(always)] + #[inline] #[must_use] pub(crate) fn new_with_source( statements: impl IntoIterator, - #[cfg(not(feature = "no_function"))] functions: impl Into>, - source: impl Into, + #[cfg(not(feature = "no_function"))] functions: impl Into, + source: impl Into, ) -> Self { let mut ast = Self::new( statements, @@ -124,12 +127,12 @@ impl AST { /// _(internals)_ Create a new [`AST`] with a source name. /// Exported under the `internals` feature only. #[cfg(feature = "internals")] - #[inline(always)] + #[inline] #[must_use] pub fn new_with_source( statements: impl IntoIterator, - #[cfg(not(feature = "no_function"))] functions: impl Into>, - source: impl Into, + #[cfg(not(feature = "no_function"))] functions: impl Into, + source: impl Into, ) -> Self { let mut ast = Self::new( statements, @@ -144,7 +147,7 @@ impl AST { #[must_use] pub fn empty() -> Self { Self { - source: Identifier::new_const(), + source: None, #[cfg(feature = "metadata")] doc: crate::SmartString::new_const(), body: StmtBlock::NONE, @@ -158,33 +161,36 @@ impl AST { #[inline(always)] #[must_use] pub fn source(&self) -> Option<&str> { - if self.source.is_empty() { - None - } else { - Some(self.source.as_str()) - } + self.source.as_ref().map(|s| s.as_str()) } /// Get a reference to the source. #[inline(always)] #[must_use] - pub(crate) const fn source_raw(&self) -> &Identifier { - &self.source + pub(crate) const fn source_raw(&self) -> Option<&ImmutableString> { + self.source.as_ref() } /// Set the source. #[inline] - pub fn set_source(&mut self, source: impl Into) -> &mut Self { + pub fn set_source(&mut self, source: impl Into) -> &mut Self { let source = source.into(); + #[cfg(not(feature = "no_function"))] crate::Shared::get_mut(&mut self.lib) .as_mut() .map(|m| m.set_id(source.clone())); - self.source = source; + + if source.is_empty() { + self.source = None; + } else { + self.source = Some(source); + } + self } /// Clear the source. #[inline(always)] pub fn clear_source(&mut self) -> &mut Self { - self.source.clear(); + self.source = None; self } /// Get the documentation (if any). @@ -261,7 +267,7 @@ impl AST { #[cfg(not(feature = "no_function"))] #[inline(always)] #[must_use] - pub(crate) const fn shared_lib(&self) -> &crate::Shared { + pub(crate) const fn shared_lib(&self) -> &crate::SharedModule { &self.lib } /// _(internals)_ Get the internal shared [`Module`][crate::Module] containing all script-defined functions. @@ -272,7 +278,7 @@ impl AST { #[cfg(not(feature = "no_function"))] #[inline(always)] #[must_use] - pub const fn shared_lib(&self) -> &crate::Shared { + pub const fn shared_lib(&self) -> &crate::SharedModule { &self.lib } /// Get the embedded [module resolver][crate::ModuleResolver]. @@ -555,18 +561,18 @@ impl AST { lib }; - let mut _ast = if other.source.is_empty() { - Self::new( - merged, - #[cfg(not(feature = "no_function"))] - lib, - ) - } else { + let mut _ast = if let Some(ref source) = other.source { Self::new_with_source( merged, #[cfg(not(feature = "no_function"))] lib, - other.source.clone(), + source.clone(), + ) + } else { + Self::new( + merged, + #[cfg(not(feature = "no_function"))] + lib, ) }; @@ -662,7 +668,6 @@ impl AST { self.combine_filtered_impl(other, filter) } /// Combine one [`AST`] with another. The second [`AST`] is consumed. - #[inline] fn combine_filtered_impl( &mut self, other: Self, @@ -917,25 +922,54 @@ impl> AddAssign for AST { } } +impl Borrow<[Stmt]> for AST { + #[inline(always)] + #[must_use] + fn borrow(&self) -> &[Stmt] { + self.statements() + } +} + impl AsRef<[Stmt]> for AST { #[inline(always)] + #[must_use] fn as_ref(&self) -> &[Stmt] { self.statements() } } +#[cfg(not(feature = "no_function"))] +impl Borrow for AST { + #[inline(always)] + #[must_use] + fn borrow(&self) -> &crate::Module { + &self.shared_lib() + } +} + #[cfg(not(feature = "no_function"))] impl AsRef for AST { #[inline(always)] + #[must_use] fn as_ref(&self) -> &crate::Module { self.shared_lib().as_ref() } } #[cfg(not(feature = "no_function"))] -impl AsRef> for AST { +impl Borrow for AST { #[inline(always)] - fn as_ref(&self) -> &crate::Shared { + #[must_use] + fn borrow(&self) -> &crate::SharedModule { + self.shared_lib() + } +} + +#[cfg(not(feature = "no_function"))] +impl AsRef for AST { + #[inline(always)] + #[must_use] + fn as_ref(&self) -> &crate::SharedModule { self.shared_lib() } } @@ -952,19 +986,21 @@ pub enum ASTNode<'a> { } impl<'a> From<&'a Stmt> for ASTNode<'a> { + #[inline(always)] fn from(stmt: &'a Stmt) -> Self { Self::Stmt(stmt) } } impl<'a> From<&'a Expr> for ASTNode<'a> { + #[inline(always)] fn from(expr: &'a Expr) -> Self { Self::Expr(expr) } } impl PartialEq for ASTNode<'_> { - #[inline(always)] + #[inline] fn eq(&self, other: &Self) -> bool { match (self, other) { (Self::Stmt(x), Self::Stmt(y)) => ptr::eq(*x, *y), @@ -981,8 +1017,8 @@ impl ASTNode<'_> { #[must_use] pub fn position(&self) -> Position { match self { - ASTNode::Stmt(stmt) => stmt.position(), - ASTNode::Expr(expr) => expr.position(), + Self::Stmt(stmt) => stmt.position(), + Self::Expr(expr) => expr.position(), } } } diff --git a/src/ast/expr.rs b/src/ast/expr.rs index 3b07ecfd..163fdbc9 100644 --- a/src/ast/expr.rs +++ b/src/ast/expr.rs @@ -17,7 +17,7 @@ use std::{ fmt::Write, hash::Hash, iter::once, - num::{NonZeroU8, NonZeroUsize}, + num::{NonZeroU64, NonZeroU8, NonZeroUsize}, }; #[cfg(not(feature = "no_float"))] @@ -75,8 +75,8 @@ impl CustomExpr { /// Is this custom syntax self-terminated (i.e. no need for a semicolon terminator)? /// /// A self-terminated custom syntax always ends in `$block$`, `}` or `;` - #[must_use] #[inline(always)] + #[must_use] pub const fn is_self_terminated(&self) -> bool { self.self_terminated } @@ -86,7 +86,7 @@ impl CustomExpr { /// /// Two separate hashes are pre-calculated because of the following patterns: /// -/// ```js +/// ```rhai /// func(a, b, c); // Native: func(a, b, c) - 3 parameters /// // Script: func(a, b, c) - 3 parameters /// @@ -100,30 +100,34 @@ impl CustomExpr { /// /// Function call hashes are used in the following manner: /// -/// * First, the script hash is tried, which contains only the called function's name plus the -/// number of parameters. +/// * First, the script hash (if any) is tried, which contains only the called function's name plus +/// the number of parameters. /// /// * Next, the actual types of arguments are hashed and _combined_ with the native hash, which is -/// then used to search for a native function. In other words, a complete native function call -/// hash always contains the called function's name plus the types of the arguments. This is due -/// to possible function overloading for different parameter types. -#[derive(Clone, Copy, Eq, PartialEq, Hash, Default)] +/// then used to search for a native function. +/// +/// In other words, a complete native function call hash always contains the called function's +/// name plus the types of the arguments. This is due to possible function overloading for +/// different parameter types. +#[derive(Clone, Copy, Eq, PartialEq, Hash)] pub struct FnCallHashes { - /// Pre-calculated hash for a script-defined function (zero if native functions only). + /// Pre-calculated hash for a script-defined function ([`None`] if native functions only). #[cfg(not(feature = "no_function"))] - pub script: u64, + script: Option, /// Pre-calculated hash for a native Rust function with no parameter types. - pub native: u64, + native: NonZeroU64, } impl fmt::Debug for FnCallHashes { + #[cold] + #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { #[cfg(not(feature = "no_function"))] - if self.script != 0 { - return if self.script == self.native { + if let Some(script) = self.script { + return if script == self.native { fmt::Debug::fmt(&self.native, f) } else { - write!(f, "({}, {})", self.script, self.native) + write!(f, "({}, {})", script, self.native) }; } @@ -132,13 +136,13 @@ impl fmt::Debug for FnCallHashes { } impl From for FnCallHashes { - #[inline(always)] + #[inline] fn from(hash: u64) -> Self { - let hash = if hash == 0 { ALT_ZERO_HASH } else { hash }; + let hash = NonZeroU64::new(if hash == 0 { ALT_ZERO_HASH } else { hash }).unwrap(); Self { #[cfg(not(feature = "no_function"))] - script: hash, + script: Some(hash), native: hash, } } @@ -146,40 +150,61 @@ impl From for FnCallHashes { impl FnCallHashes { /// Create a [`FnCallHashes`] with only the native Rust hash. - #[inline(always)] + #[inline] #[must_use] - pub const fn from_native(hash: u64) -> Self { + pub fn from_native(hash: u64) -> Self { Self { #[cfg(not(feature = "no_function"))] - script: 0, - native: if hash == 0 { ALT_ZERO_HASH } else { hash }, + script: None, + native: NonZeroU64::new(if hash == 0 { ALT_ZERO_HASH } else { hash }).unwrap(), } } /// Create a [`FnCallHashes`] with both native Rust and script function hashes. - #[inline(always)] + #[inline] #[must_use] - pub const fn from_all(#[cfg(not(feature = "no_function"))] script: u64, native: u64) -> Self { + pub fn from_all(#[cfg(not(feature = "no_function"))] script: u64, native: u64) -> Self { Self { #[cfg(not(feature = "no_function"))] - script: if script == 0 { ALT_ZERO_HASH } else { script }, - native: if native == 0 { ALT_ZERO_HASH } else { native }, + script: NonZeroU64::new(if script == 0 { ALT_ZERO_HASH } else { script }), + native: NonZeroU64::new(if native == 0 { ALT_ZERO_HASH } else { native }).unwrap(), } } - /// Is this [`FnCallHashes`] native Rust only? + /// Is this [`FnCallHashes`] native-only? #[inline(always)] #[must_use] pub const fn is_native_only(&self) -> bool { #[cfg(not(feature = "no_function"))] - return self.script == 0; - + return self.script.is_none(); #[cfg(feature = "no_function")] return true; } + /// Get the native hash. + /// + /// The hash returned is never zero. + #[inline(always)] + #[must_use] + pub fn native(&self) -> u64 { + self.native.get() + } + /// Get the script hash. + /// + /// The hash returned is never zero. + /// + /// # Panics + /// + /// Panics if this [`FnCallHashes`] is native-only. + #[cfg(not(feature = "no_function"))] + #[inline(always)] + #[must_use] + pub fn script(&self) -> u64 { + assert!(self.script.is_some()); + self.script.as_ref().unwrap().get() + } } /// _(internals)_ A function call. /// Exported under the `internals` feature only. -#[derive(Clone, Default, Hash)] +#[derive(Clone, Hash)] pub struct FnCallExpr { /// Namespace of the function, if any. #[cfg(not(feature = "no_module"))] @@ -193,28 +218,27 @@ pub struct FnCallExpr { /// Does this function call capture the parent scope? pub capture_parent_scope: bool, /// Is this function call a native operator? - pub is_native_operator: bool, - /// [Position] of the function name. - pub pos: Position, + pub op_token: Option, } impl fmt::Debug for FnCallExpr { + #[cold] + #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut ff = f.debug_struct("FnCallExpr"); #[cfg(not(feature = "no_module"))] if !self.namespace.is_empty() { ff.field("namespace", &self.namespace); } - if self.capture_parent_scope { - ff.field("capture_parent_scope", &self.capture_parent_scope); - } - if self.is_native_operator { - ff.field("is_native_operator", &self.is_native_operator); - } ff.field("hash", &self.hashes) .field("name", &self.name) .field("args", &self.args); - ff.field("pos", &self.pos); + if let Some(ref token) = self.op_token { + ff.field("op_token", token); + } + if self.capture_parent_scope { + ff.field("capture_parent_scope", &self.capture_parent_scope); + } ff.finish() } } @@ -237,6 +261,16 @@ impl FnCallExpr { pub fn into_fn_call_expr(self, pos: Position) -> Expr { Expr::FnCall(self.into(), pos) } + /// Are all arguments constant? + #[inline] + #[must_use] + pub fn constant_args(&self) -> bool { + if self.args.is_empty() { + true + } else { + self.args.iter().all(Expr::is_constant) + } + } } /// A type that wraps a floating-point number and implements [`Hash`]. @@ -248,7 +282,7 @@ pub struct FloatWrapper(F); #[cfg(not(feature = "no_float"))] impl Hash for FloatWrapper { - #[inline(always)] + #[inline] fn hash(&self, state: &mut H) { self.0.to_ne_bytes().hash(state); } @@ -257,6 +291,7 @@ impl Hash for FloatWrapper { #[cfg(not(feature = "no_float"))] impl AsRef for FloatWrapper { #[inline(always)] + #[must_use] fn as_ref(&self) -> &F { &self.0 } @@ -265,6 +300,7 @@ impl AsRef for FloatWrapper { #[cfg(not(feature = "no_float"))] impl AsMut for FloatWrapper { #[inline(always)] + #[must_use] fn as_mut(&mut self) -> &mut F { &mut self.0 } @@ -290,7 +326,8 @@ impl DerefMut for FloatWrapper { #[cfg(not(feature = "no_float"))] impl fmt::Debug for FloatWrapper { - #[inline(always)] + #[cold] + #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(&self.0, f) } @@ -438,23 +475,26 @@ pub enum Expr { impl Default for Expr { #[inline(always)] + #[must_use] fn default() -> Self { Self::Unit(Position::NONE) } } impl fmt::Debug for Expr { + #[cold] + #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut display_pos = format!(" @ {:?}", self.start_position()); match self { - Self::DynamicConstant(value, ..) => write!(f, "{:?}", value), - Self::BoolConstant(value, ..) => write!(f, "{:?}", value), - Self::IntegerConstant(value, ..) => write!(f, "{:?}", value), + Self::DynamicConstant(value, ..) => write!(f, "{value:?}"), + Self::BoolConstant(value, ..) => write!(f, "{value:?}"), + Self::IntegerConstant(value, ..) => write!(f, "{value:?}"), #[cfg(not(feature = "no_float"))] - Self::FloatConstant(value, ..) => write!(f, "{:?}", value), - Self::CharConstant(value, ..) => write!(f, "{:?}", value), - Self::StringConstant(value, ..) => write!(f, "{:?}", value), + Self::FloatConstant(value, ..) => write!(f, "{value:?}"), + Self::CharConstant(value, ..) => write!(f, "{value:?}"), + Self::StringConstant(value, ..) => write!(f, "{value:?}"), Self::Unit(..) => f.write_str("()"), Self::InterpolatedString(x, ..) => { @@ -483,8 +523,12 @@ impl fmt::Debug for Expr { } } f.write_str(&x.3)?; + #[cfg(not(feature = "no_module"))] + if let Some(n) = x.1.index() { + write!(f, " #{n}")?; + } if let Some(n) = i.map_or_else(|| x.0, |n| NonZeroUsize::new(n.get() as usize)) { - write!(f, " #{}", n)?; + write!(f, " #{n}")?; } f.write_str(")") } @@ -588,7 +632,7 @@ impl Expr { let mut s = SmartString::new_const(); for segment in x.iter() { let v = segment.get_literal_value().unwrap(); - write!(&mut s, "{}", v).unwrap(); + write!(&mut s, "{v}").unwrap(); } s.into() } @@ -598,7 +642,7 @@ impl Expr { if !x.is_qualified() && x.args.len() == 1 && x.name == KEYWORD_FN_PTR => { if let Self::StringConstant(ref s, ..) = x.args[0] { - FnPtr::new(s).ok()?.into() + FnPtr::new(s.clone()).ok()?.into() } else { return None; } @@ -669,8 +713,7 @@ impl Expr { hashes: calc_fn_hash(None, f.fn_name(), 1).into(), args: once(Self::StringConstant(f.fn_name().into(), pos)).collect(), capture_parent_scope: false, - is_native_operator: false, - pos, + op_token: None, } .into(), pos, @@ -725,6 +768,8 @@ impl Expr { | Self::And(.., pos) | Self::Or(.., pos) | Self::Coalesce(.., pos) + | Self::FnCall(.., pos) + | Self::MethodCall(.., pos) | Self::Index(.., pos) | Self::Dot(.., pos) | Self::InterpolatedString(.., pos) @@ -733,8 +778,6 @@ impl Expr { #[cfg(not(feature = "no_custom_syntax"))] Self::Custom(.., pos) => *pos, - Self::FnCall(x, ..) | Self::MethodCall(x, ..) => x.pos, - Self::Stmt(x) => x.position(), } } diff --git a/src/ast/ident.rs b/src/ast/ident.rs index 13ab4ef9..ffc5f968 100644 --- a/src/ast/ident.rs +++ b/src/ast/ident.rs @@ -4,6 +4,7 @@ use crate::{ImmutableString, Position}; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{ + borrow::Borrow, fmt, hash::Hash, ops::{Deref, DerefMut}, @@ -20,14 +21,25 @@ pub struct Ident { } impl fmt::Debug for Ident { + #[cold] + #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{:?}", self.name)?; self.pos.debug_print(f) } } +impl Borrow for Ident { + #[inline(always)] + #[must_use] + fn borrow(&self) -> &str { + self.name.as_ref() + } +} + impl AsRef for Ident { #[inline(always)] + #[must_use] fn as_ref(&self) -> &str { self.name.as_ref() } diff --git a/src/ast/namespace.rs b/src/ast/namespace.rs index 800dba52..2888b25e 100644 --- a/src/ast/namespace.rs +++ b/src/ast/namespace.rs @@ -29,13 +29,15 @@ pub struct Namespace { } impl fmt::Debug for Namespace { + #[cold] + #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if self.is_empty() { return f.write_str("NONE"); } if let Some(index) = self.index { - write!(f, "{} -> ", index)?; + write!(f, "{index} -> ")?; } f.write_str( @@ -83,7 +85,7 @@ impl DerefMut for Namespace { } impl From> for Namespace { - #[inline(always)] + #[inline] fn from(mut path: Vec) -> Self { path.shrink_to_fit(); Self { @@ -94,7 +96,7 @@ impl From> for Namespace { } impl From> for Namespace { - #[inline(always)] + #[inline] fn from(mut path: StaticVec) -> Self { path.shrink_to_fit(); Self { index: None, path } diff --git a/src/ast/script_fn.rs b/src/ast/script_fn.rs index d40d6a5a..743bf2f5 100644 --- a/src/ast/script_fn.rs +++ b/src/ast/script_fn.rs @@ -20,9 +20,9 @@ use std::{fmt, hash::Hash}; #[derive(Debug, Clone)] pub struct EncapsulatedEnviron { /// Functions defined within the same [`AST`][crate::AST]. - pub lib: crate::Shared, + pub lib: crate::SharedModule, /// Imported [modules][crate::Module]. - pub imports: Box<[(ImmutableString, crate::Shared)]>, + pub imports: Box<[(ImmutableString, crate::SharedModule)]>, /// Globally-defined constants. pub constants: Option, } diff --git a/src/ast/stmt.rs b/src/ast/stmt.rs index 68b63ecd..541332c3 100644 --- a/src/ast/stmt.rs +++ b/src/ast/stmt.rs @@ -7,6 +7,7 @@ use crate::{calc_fn_hash, Position, StaticVec, INT}; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{ + borrow::Borrow, collections::BTreeMap, fmt, hash::Hash, @@ -19,16 +20,16 @@ use std::{ /// Exported under the `internals` feature only. /// /// This type may hold a straight assignment (i.e. not an op-assignment). -#[derive(Clone, Copy, Eq, PartialEq, Hash)] +#[derive(Clone, PartialEq, Hash)] pub struct OpAssignment { /// Hash of the op-assignment call. pub hash_op_assign: u64, /// Hash of the underlying operator call (for fallback). pub hash_op: u64, /// Op-assignment operator. - pub op_assign: &'static str, + pub op_assign: Token, /// Underlying operator. - pub op: &'static str, + pub op: Token, /// [Position] of the op-assignment operator. pub pos: Position, } @@ -41,8 +42,8 @@ impl OpAssignment { Self { hash_op_assign: 0, hash_op: 0, - op_assign: "=", - op: "=", + op_assign: Token::Equals, + op: Token::Equals, pos, } } @@ -60,7 +61,10 @@ impl OpAssignment { #[must_use] #[inline(always)] pub fn new_op_assignment(name: &str, pos: Position) -> Self { - Self::new_op_assignment_from_token(&Token::lookup_from_syntax(name).expect("operator"), pos) + Self::new_op_assignment_from_token( + &Token::lookup_symbol_from_syntax(name).expect("operator"), + pos, + ) } /// Create a new [`OpAssignment`] from a [`Token`]. /// @@ -71,12 +75,11 @@ impl OpAssignment { pub fn new_op_assignment_from_token(op: &Token, pos: Position) -> Self { let op_raw = op .get_base_op_from_assignment() - .expect("op-assignment operator") - .literal_syntax(); + .expect("op-assignment operator"); Self { hash_op_assign: calc_fn_hash(None, op.literal_syntax(), 2), - hash_op: calc_fn_hash(None, op_raw, 2), - op_assign: op.literal_syntax(), + hash_op: calc_fn_hash(None, op_raw.literal_syntax(), 2), + op_assign: op.clone(), op: op_raw, pos, } @@ -90,7 +93,7 @@ impl OpAssignment { #[inline(always)] pub fn new_op_assignment_from_base(name: &str, pos: Position) -> Self { Self::new_op_assignment_from_base_token( - &Token::lookup_from_syntax(name).expect("operator"), + &Token::lookup_symbol_from_syntax(name).expect("operator"), pos, ) } @@ -107,6 +110,8 @@ impl OpAssignment { } impl fmt::Debug for OpAssignment { + #[cold] + #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if self.is_op_assignment() { f.debug_struct("OpAssignment") @@ -179,11 +184,12 @@ pub enum RangeCase { } impl fmt::Debug for RangeCase { - #[inline] + #[cold] + #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::ExclusiveInt(r, n) => write!(f, "{}..{} => {}", r.start, r.end, n), - Self::InclusiveInt(r, n) => write!(f, "{}..={} => {}", *r.start(), *r.end(), n), + Self::ExclusiveInt(r, n) => write!(f, "{}..{} => {n}", r.start, r.end), + Self::InclusiveInt(r, n) => write!(f, "{}..={} => {n}", *r.start(), *r.end()), } } } @@ -207,6 +213,7 @@ impl IntoIterator for RangeCase { type IntoIter = Box>; #[inline(always)] + #[must_use] fn into_iter(self) -> Self::IntoIter { match self { Self::ExclusiveInt(r, ..) => Box::new(r), @@ -299,6 +306,10 @@ pub struct TryCatchBlock { pub catch_block: StmtBlock, } +/// Number of items to keep inline for [`StmtBlockContainer`]. +#[cfg(not(feature = "no_std"))] +const STMT_BLOCK_INLINE_SIZE: usize = 8; + /// _(internals)_ The underlying container type for [`StmtBlock`]. /// Exported under the `internals` feature only. /// @@ -306,7 +317,7 @@ pub struct TryCatchBlock { /// hold a statements block, with the assumption that most program blocks would container fewer than /// 8 statements, and those that do have a lot more statements. #[cfg(not(feature = "no_std"))] -pub type StmtBlockContainer = smallvec::SmallVec<[Stmt; 8]>; +pub type StmtBlockContainer = smallvec::SmallVec<[Stmt; STMT_BLOCK_INLINE_SIZE]>; /// _(internals)_ The underlying container type for [`StmtBlock`]. /// Exported under the `internals` feature only. @@ -436,8 +447,17 @@ impl DerefMut for StmtBlock { } } +impl Borrow<[Stmt]> for StmtBlock { + #[inline(always)] + #[must_use] + fn borrow(&self) -> &[Stmt] { + &self.block + } +} + impl AsRef<[Stmt]> for StmtBlock { #[inline(always)] + #[must_use] fn as_ref(&self) -> &[Stmt] { &self.block } @@ -445,12 +465,15 @@ impl AsRef<[Stmt]> for StmtBlock { impl AsMut<[Stmt]> for StmtBlock { #[inline(always)] + #[must_use] fn as_mut(&mut self) -> &mut [Stmt] { &mut self.block } } impl fmt::Debug for StmtBlock { + #[cold] + #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("Block")?; fmt::Debug::fmt(&self.block, f)?; @@ -484,9 +507,9 @@ impl From for StmtBlock { impl IntoIterator for StmtBlock { type Item = Stmt; #[cfg(not(feature = "no_std"))] - type IntoIter = smallvec::IntoIter<[Stmt; 8]>; + type IntoIter = smallvec::IntoIter<[Stmt; STMT_BLOCK_INLINE_SIZE]>; #[cfg(feature = "no_std")] - type IntoIter = smallvec::IntoIter<[Stmt; 3]>; + type IntoIter = smallvec::IntoIter<[Stmt; crate::STATIC_VEC_INLINE_SIZE]>; #[inline(always)] fn into_iter(self) -> Self::IntoIter { @@ -561,14 +584,14 @@ pub enum Stmt { TryCatch(Box, Position), /// [expression][Expr] Expr(Box), - /// `continue`/`break` + /// `continue`/`break` expr /// /// ### Flags /// /// * [`NONE`][ASTFlags::NONE] = `continue` /// * [`BREAK`][ASTFlags::BREAK] = `break` - BreakLoop(ASTFlags, Position), - /// `return`/`throw` + BreakLoop(Option>, ASTFlags, Position), + /// `return`/`throw` expr /// /// ### Flags /// @@ -585,20 +608,21 @@ pub enum Stmt { /// Not available under `no_module`. #[cfg(not(feature = "no_module"))] Export(Box<(Ident, Ident)>, Position), - /// Convert a variable to shared. + /// Convert a list of variables to shared. /// /// Not available under `no_closure`. /// /// # Notes /// /// This variant does not map to any language structure. It is currently only used only to - /// convert a normal variable into a shared variable when the variable is _captured_ by a closure. + /// convert normal variables into shared variables when they are _captured_ by a closure. #[cfg(not(feature = "no_closure"))] - Share(crate::ImmutableString, Position), + Share(Box, Position)>>), } impl Default for Stmt { #[inline(always)] + #[must_use] fn default() -> Self { Self::Noop(Position::NONE) } @@ -660,7 +684,7 @@ impl Stmt { Self::Export(.., pos) => *pos, #[cfg(not(feature = "no_closure"))] - Self::Share(.., pos) => *pos, + Self::Share(x) => x[0].2, } } /// Override the [position][Position] of this statement. @@ -692,7 +716,7 @@ impl Stmt { Self::Export(.., pos) => *pos = new_pos, #[cfg(not(feature = "no_closure"))] - Self::Share(.., pos) => *pos = new_pos, + Self::Share(x) => x.iter_mut().for_each(|(_, _, pos)| *pos = new_pos), } self diff --git a/src/bin/rhai-dbg.rs b/src/bin/rhai-dbg.rs index a83b680c..04284050 100644 --- a/src/bin/rhai-dbg.rs +++ b/src/bin/rhai-dbg.rs @@ -45,7 +45,6 @@ fn print_source(lines: &[String], pos: Position, offset: usize, window: (usize, if n == line { if let Some(pos) = pos.position() { let shift = offset + line_no_len + marker.len() + 2; - println!("{0:>1$}{2:>3$}", "│ ", shift, "\x1b[36m^\x1b[39m", pos + 10); } } @@ -76,7 +75,7 @@ fn print_current_source( } if !src.is_empty() { // Print just a line number for imported modules - println!("{} @ {:?}", src, pos); + println!("{src} @ {pos:?}"); } else { // Print the current source line print_source(lines, pos, 0, window); @@ -101,17 +100,16 @@ fn print_error(input: &str, mut err: EvalAltResult) { // Print error position if pos.is_none() { // No position - println!("{}", err); + println!("{err}"); } else { // Specific position - print line text - println!("{}{}", line_no, lines[pos.line().unwrap() - 1]); + println!("{line_no}{}", lines[pos.line().unwrap() - 1]); // Display position marker println!( - "{0:>1$} {2}", + "{0:>1$} {err}", "^", line_no.len() + pos.position().unwrap(), - err ); } } @@ -248,11 +246,11 @@ fn debug_callback( BreakPoint::AtPosition { .. } => (), BreakPoint::AtFunctionName { ref name, .. } | BreakPoint::AtFunctionCall { ref name, .. } => { - println!("! Call to function {}.", name) + println!("! Call to function {name}.") } #[cfg(not(feature = "no_object"))] BreakPoint::AtProperty { ref name, .. } => { - println!("! Property {} accessed.", name) + println!("! Property {name} accessed.") } _ => unreachable!(), } @@ -310,10 +308,11 @@ fn debug_callback( ["node"] => { if pos.is_none() { println!("{:?}", node); - } else if let Some(source) = source { - println!("{:?} {} @ {:?}", node, source, pos); } else { - println!("{:?} @ {:?}", node, pos); + match source { + Some(source) => println!("{node:?} {source} @ {pos:?}"), + None => println!("{node:?} @ {pos:?}"), + } } println!(); } @@ -327,7 +326,7 @@ fn debug_callback( ["list" | "l", n] if n.parse::().is_ok() => { let num = n.parse::().unwrap(); if num == 0 || num > lines.len() { - eprintln!("\x1b[31mInvalid line: {}\x1b[39m", num); + eprintln!("\x1b[31mInvalid line: {num}\x1b[39m"); } else { let pos = Position::new(num as u16, 0); print_current_source(&mut context, source, pos, lines, (3, 6)); @@ -339,24 +338,18 @@ fn debug_callback( ["over" | "o"] => break Ok(DebuggerCommand::StepOver), ["next" | "n"] => break Ok(DebuggerCommand::Next), ["scope"] => println!("{}", context.scope()), - ["print" | "p", "this"] => { - if let Some(value) = context.this_ptr() { - println!("=> {:?}", value); - } else { - println!("`this` pointer is unbound."); - } - } - ["print" | "p", var_name] => { - if let Some(value) = context.scope().get_value::(var_name) { - println!("=> {:?}", value); - } else { - eprintln!("Variable not found: {}", var_name); - } - } + ["print" | "p", "this"] => match context.this_ptr() { + Some(value) => println!("=> {value:?}"), + None => println!("`this` pointer is unbound."), + }, + ["print" | "p", var_name] => match context.scope().get_value::(var_name) { + Some(value) => println!("=> {value:?}"), + None => eprintln!("Variable not found: {var_name}"), + }, ["print" | "p"] => { println!("{}", context.scope().clone_visible()); if let Some(value) = context.this_ptr() { - println!("this = {:?}", value); + println!("this = {value:?}"); } } #[cfg(not(feature = "no_module"))] @@ -385,7 +378,7 @@ fn debug_callback( .iter() .rev() { - println!("{}", frame) + println!("{frame}") } } ["info" | "i", "break" | "b"] => Iterator::for_each( @@ -402,7 +395,7 @@ fn debug_callback( print!("{}", line_num); print_source(lines, *pos, line_num.len(), (0, 0)); } - _ => println!("[{}] {}", i + 1, bp), + _ => println!("[{}] {bp}", i + 1), }, ), ["enable" | "en", n] => { @@ -420,12 +413,12 @@ fn debug_callback( .get_mut(n - 1) .unwrap() .enable(true); - println!("Break-point #{} enabled.", n) + println!("Break-point #{n} enabled.") } else { - eprintln!("\x1b[31mInvalid break-point: {}\x1b[39m", n); + eprintln!("\x1b[31mInvalid break-point: {n}\x1b[39m"); } } else { - eprintln!("\x1b[31mInvalid break-point: '{}'\x1b[39m", n); + eprintln!("\x1b[31mInvalid break-point: '{n}'\x1b[39m"); } } ["disable" | "dis", n] => { @@ -443,12 +436,12 @@ fn debug_callback( .get_mut(n - 1) .unwrap() .enable(false); - println!("Break-point #{} disabled.", n) + println!("Break-point #{n} disabled.") } else { - eprintln!("\x1b[31mInvalid break-point: {}\x1b[39m", n); + eprintln!("\x1b[31mInvalid break-point: {n}\x1b[39m"); } } else { - eprintln!("\x1b[31mInvalid break-point: '{}'\x1b[39m", n); + eprintln!("\x1b[31mInvalid break-point: '{n}'\x1b[39m"); } } ["delete" | "d", n] => { @@ -464,12 +457,12 @@ fn debug_callback( .debugger .break_points_mut() .remove(n - 1); - println!("Break-point #{} deleted.", n) + println!("Break-point #{n} deleted.") } else { - eprintln!("\x1b[31mInvalid break-point: {}\x1b[39m", n); + eprintln!("\x1b[31mInvalid break-point: {n}\x1b[39m"); } } else { - eprintln!("\x1b[31mInvalid break-point: '{}'\x1b[39m", n); + eprintln!("\x1b[31mInvalid break-point: '{n}'\x1b[39m"); } } ["delete" | "d"] => { @@ -487,14 +480,14 @@ fn debug_callback( args, enabled: true, }; - println!("Break-point added for {}", bp); + println!("Break-point added for {bp}"); context .global_runtime_state_mut() .debugger .break_points_mut() .push(bp); } else { - eprintln!("\x1b[31mInvalid number of arguments: '{}'\x1b[39m", args); + eprintln!("\x1b[31mInvalid number of arguments: '{args}'\x1b[39m"); } } // Property name @@ -504,7 +497,7 @@ fn debug_callback( name: param[1..].into(), enabled: true, }; - println!("Break-point added for {}", bp); + println!("Break-point added for {bp}"); context .global_runtime_state_mut() .debugger @@ -523,18 +516,18 @@ fn debug_callback( if range.contains(&n) { let bp = rhai::debugger::BreakPoint::AtPosition { - source: source.unwrap_or("").into(), + source: source.map(|s| s.into()), pos: Position::new(n as u16, 0), enabled: true, }; - println!("Break-point added {}", bp); + println!("Break-point added {bp}"); context .global_runtime_state_mut() .debugger .break_points_mut() .push(bp); } else { - eprintln!("\x1b[31mInvalid line number: '{}'\x1b[39m", n); + eprintln!("\x1b[31mInvalid line number: '{n}'\x1b[39m"); } } // Function name parameter @@ -543,7 +536,7 @@ fn debug_callback( name: param.trim().into(), enabled: true, }; - println!("Break-point added for {}", bp); + println!("Break-point added for {bp}"); context .global_runtime_state_mut() .debugger @@ -553,11 +546,11 @@ fn debug_callback( #[cfg(not(feature = "no_position"))] ["break" | "b"] => { let bp = rhai::debugger::BreakPoint::AtPosition { - source: source.unwrap_or("").into(), + source: source.map(|s| s.into()), pos, enabled: true, }; - println!("Break-point added {}", bp); + println!("Break-point added {bp}"); context .global_runtime_state_mut() .debugger @@ -594,7 +587,7 @@ fn debug_callback( fn main() { let title = format!("Rhai Debugger (version {})", env!("CARGO_PKG_VERSION")); - println!("{}", title); + println!("{title}"); println!("{0:=<1$}", "", title.len()); // Initialize scripting engine diff --git a/src/bin/rhai-repl.rs b/src/bin/rhai-repl.rs index 00e01e8b..71801d82 100644 --- a/src/bin/rhai-repl.rs +++ b/src/bin/rhai-repl.rs @@ -26,17 +26,16 @@ fn print_error(input: &str, mut err: EvalAltResult) { // Print error position if pos.is_none() { // No position - println!("{}", err); + println!("{err}"); } else { // Specific position - print line text - println!("{}{}", line_no, lines[pos.line().unwrap() - 1]); + println!("{line_no}{}", lines[pos.line().unwrap() - 1]); // Display position marker println!( - "{0:>1$} {2}", + "{0:>1$} {err}", "^", line_no.len() + pos.position().unwrap(), - err ); } } @@ -119,7 +118,7 @@ fn load_script_files(engine: &mut Engine) { for filename in env::args().skip(1) { let filename = match Path::new(&filename).canonicalize() { Err(err) => { - eprintln!("Error script file path: {}\n{}", filename, err); + eprintln!("Error script file path: {filename}\n{err}"); exit(1); } Ok(f) => { @@ -164,7 +163,7 @@ fn load_script_files(engine: &mut Engine) { let filename = filename.to_string_lossy(); eprintln!("{:=<1$}", "", filename.len()); - eprintln!("{}", filename); + eprintln!("{filename}"); eprintln!("{:=<1$}", "", filename.len()); eprintln!(); @@ -277,13 +276,13 @@ mod sample_functions { #[rhai_fn(name = "test")] pub fn test2(x: &mut INT, y: INT, z: &str) { *x += y + (z.len() as INT); - println!("{} {} {}", x, y, z); + println!("{x} {y} {z}"); } } fn main() { let title = format!("Rhai REPL tool (version {})", env!("CARGO_PKG_VERSION")); - println!("{}", title); + println!("{title}"); println!("{0:=<1$}", "", title.len()); #[cfg(not(feature = "no_optimize"))] @@ -338,11 +337,11 @@ fn main() { history_offset += 1; } if input.contains('\n') { - println!("[{}] ~~~~", replacement_index); - println!("{}", input); + println!("[{replacement_index}] ~~~~"); + println!("{input}"); println!("~~~~"); } else { - println!("[{}] {}", replacement_index, input); + println!("[{replacement_index}] {input}"); } replacement_index = 0; } else { @@ -374,7 +373,7 @@ fn main() { Err(ReadlineError::Interrupted) | Err(ReadlineError::Eof) => break 'main_loop, Err(err) => { - eprintln!("Error: {:?}", err); + eprintln!("Error: {err:?}"); break 'main_loop; } } @@ -401,12 +400,12 @@ fn main() { "history" => { for (i, h) in rl.history().iter().enumerate() { match &h.split('\n').collect::>()[..] { - [line] => println!("[{}] {}", history_offset + i, line), + [line] => println!("[{}] {line}", history_offset + i), lines => { for (x, line) in lines.iter().enumerate() { let number = format!("[{}]", history_offset + i); if x == 0 { - println!("{} {}", number, line.trim_end()); + println!("{number} {}", line.trim_end()); } else { println!("{0:>1$} {2}", "", number.len(), line.trim_end()); } @@ -439,30 +438,30 @@ fn main() { continue; } "scope" => { - println!("{}", scope); + println!("{scope}"); continue; } #[cfg(not(feature = "no_optimize"))] "astu" => { // print the last un-optimized AST - println!("{:#?}\n", ast_u); + println!("{ast_u:#?}\n"); continue; } "ast" => { // print the last AST - println!("{:#?}\n", ast); + println!("{ast:#?}\n"); continue; } #[cfg(feature = "metadata")] "functions" => { // print a list of all registered functions for f in engine.gen_fn_signatures(false) { - println!("{}", f) + println!("{f}") } #[cfg(not(feature = "no_function"))] for f in main_ast.iter_functions() { - println!("{}", f) + println!("{f}") } println!(); @@ -482,27 +481,30 @@ fn main() { continue; } "!!" => { - if let Some(line) = rl.history().last() { - replacement = Some(line.clone()); - replacement_index = history_offset + rl.history().len() - 1; - } else { - eprintln!("No lines history!"); + match rl.history().last() { + Some(line) => { + replacement = Some(line.clone()); + replacement_index = history_offset + rl.history().len() - 1; + } + None => eprintln!("No lines history!"), } continue; } _ if cmd.starts_with("!?") => { let text = cmd[2..].trim(); - if let Some((n, line)) = rl + let history = rl .history() .iter() .rev() .enumerate() - .find(|&(.., h)| h.contains(text)) - { - replacement = Some(line.clone()); - replacement_index = history_offset + (rl.history().len() - 1 - n); - } else { - eprintln!("History line not found: {}", text); + .find(|&(.., h)| h.contains(text)); + + match history { + Some((n, line)) => { + replacement = Some(line.clone()); + replacement_index = history_offset + (rl.history().len() - 1 - n); + } + None => eprintln!("History line not found: {text}"), } continue; } @@ -558,7 +560,7 @@ fn main() { engine.eval_ast_with_scope::(&mut scope, &main_ast) }) { Ok(result) if !result.is::<()>() => { - println!("=> {:?}", result); + println!("=> {result:?}"); println!(); } Ok(_) => (), diff --git a/src/bin/rhai-run.rs b/src/bin/rhai-run.rs index e87e99b4..5bd464a3 100644 --- a/src/bin/rhai-run.rs +++ b/src/bin/rhai-run.rs @@ -7,12 +7,11 @@ fn eprint_error(input: &str, mut err: EvalAltResult) { let line = pos.line().unwrap(); let line_no = format!("{line}: "); - eprintln!("{}{}", line_no, lines[line - 1]); + eprintln!("{line_no}{}", lines[line - 1]); eprintln!( - "{:>1$} {2}", + "{:>1$} {err_msg}", "^", line_no.len() + pos.position().unwrap(), - err_msg ); eprintln!(); } @@ -24,7 +23,7 @@ fn eprint_error(input: &str, mut err: EvalAltResult) { if pos.is_none() { // No position - eprintln!("{}", err); + eprintln!("{err}"); } else { // Specific position eprint_line(&lines, pos, &err.to_string()) @@ -37,7 +36,7 @@ fn main() { for filename in env::args().skip(1) { let filename = match Path::new(&filename).canonicalize() { Err(err) => { - eprintln!("Error script file path: {}\n{}", filename, err); + eprintln!("Error script file path: {filename}\n{err}"); exit(1); } Ok(f) => match f.strip_prefix(std::env::current_dir().unwrap().canonicalize().unwrap()) @@ -94,7 +93,7 @@ fn main() { let filename = filename.to_string_lossy(); eprintln!("{:=<1$}", "", filename.len()); - eprintln!("{}", filename); + eprintln!("{filename}"); eprintln!("{:=<1$}", "", filename.len()); eprintln!(); diff --git a/src/config/hashing.rs b/src/config/hashing.rs new file mode 100644 index 00000000..dd93e812 --- /dev/null +++ b/src/config/hashing.rs @@ -0,0 +1,226 @@ +//! Fixed hashing seeds for stable hashing. +//! +//! Set to [`None`] to disable stable hashing. +//! +//! See [`set_rhai_ahash_seed`]. +//! +//! # Example +//! +//! ```rust +//! // Set the hashing seed to [1, 2, 3, 4] +//! rhai::config::hashing::set_ahash_seed(Some([1, 2, 3, 4])).unwrap(); +//! ``` +//! Alternatively, set this at compile time via the `RHAI_AHASH_SEED` environment variable. +//! +//! # Example +//! +//! ```sh +//! env RHAI_AHASH_SEED ="[236,800,954,213]" +//! ``` +// [236,800,954,213], haha funny yume nikki reference epic uboachan face numberworld nexus moment 100 + +use crate::config::hashing_env; +use core::panic::{RefUnwindSafe, UnwindSafe}; +#[cfg(feature = "no_std")] +use std::prelude::v1::*; +use std::{ + cell::UnsafeCell, + marker::PhantomData, + mem, + mem::MaybeUninit, + sync::atomic::{AtomicBool, AtomicUsize, Ordering}, +}; + +// omg its hokma from record team here to record our locks +// what does this do? +// so what this does is keep track of a global address in memory that acts as a global lock +// i stole this from crossbeam so read their docs for more +#[must_use] +struct HokmaLock { + lock: AtomicUsize, +} + +impl HokmaLock { + #[inline(always)] + pub const fn new() -> Self { + Self { + lock: AtomicUsize::new(0), + } + } + + pub fn write(&'static self) -> WhenTheHokmaSuppression { + loop { + let previous = self.lock.swap(1, Ordering::SeqCst); + + if previous != 1 { + return WhenTheHokmaSuppression { + hokma: self, + state: previous, + }; + } + } + } +} + +struct WhenTheHokmaSuppression { + hokma: &'static HokmaLock, + state: usize, +} + +impl WhenTheHokmaSuppression { + #[inline] + pub fn the_price_of_silence(self) { + self.hokma.lock.store(self.state, Ordering::SeqCst); + mem::forget(self) + } +} + +impl Drop for WhenTheHokmaSuppression { + #[inline] + fn drop(&mut self) { + self.hokma + .lock + .store(self.state.wrapping_add(2), Ordering::SeqCst) + } +} + +#[inline(always)] +#[must_use] +fn hokmalock(address: usize) -> &'static HokmaLock { + const LEN: usize = 787; + const LCK: HokmaLock = HokmaLock::new(); + static RECORDS: [HokmaLock; LEN] = [LCK; LEN]; + + &RECORDS[address % LEN] +} + +// Safety: lol, there is a reason its called "SusLock" +#[must_use] +struct SusLock +where + T: 'static, +{ + initialized: AtomicBool, + data: UnsafeCell>, + _marker: PhantomData, +} + +impl SusLock +where + T: 'static, +{ + #[inline] + pub const fn new() -> SusLock { + SusLock { + initialized: AtomicBool::new(false), + data: UnsafeCell::new(MaybeUninit::uninit()), + _marker: PhantomData, + } + } + + #[must_use] + pub fn get(&self) -> Option<&'static T> { + if self.initialized.load(Ordering::SeqCst) { + let hokma = hokmalock(unsafe { mem::transmute(self.data.get()) }); + // we forgo the optimistic read, because we don't really care + let guard = hokma.write(); + let val = { + let cast: *const T = self.data.get().cast(); + unsafe { mem::transmute::<*const T, &'static T>(cast) } + }; + guard.the_price_of_silence(); + Some(val) + } else { + return None; + } + } + + #[must_use] + pub fn get_or_init(&self, f: impl FnOnce() -> T) -> Option<&'static T> { + if !self.initialized.load(Ordering::SeqCst) { + let value = f(); + self.initialized.store(true, Ordering::SeqCst); + let hokma = hokmalock(unsafe { mem::transmute(self.data.get()) }); + hokma.write(); + unsafe { + self.data.get().write(MaybeUninit::new(value)); + } + } + + self.get() + } + + pub fn set(&self, value: T) -> Result<(), T> { + if self.initialized.load(Ordering::SeqCst) { + Err(value) + } else { + let _ = self.get_or_init(|| value); + Ok(()) + } + } +} + +unsafe impl Sync for SusLock where T: 'static {} +unsafe impl Send for SusLock where T: 'static {} +impl RefUnwindSafe for SusLock where T: 'static {} + +impl Drop for SusLock +where + T: 'static, +{ + #[inline] + fn drop(&mut self) { + if self.initialized.load(Ordering::SeqCst) { + unsafe { (&mut *self.data.get()).assume_init_drop() }; + } + } +} + +static AHASH_SEED: SusLock> = SusLock::new(); + +/// Set the hashing seed. This is used to hash functions etc. +/// +/// This is a static global value and affects every Rhai instance. +/// This should not be used _unless_ you know you need it. +/// +/// # Warning +/// +/// * You can only call this function **ONCE** for the entire duration of program execution. +/// * You **MUST** call this before performing **ANY** Rhai operation (e.g. creating an [`Engine`]). +/// +/// # Error +/// +/// Returns an error containing the existing hashing seed if already set. +/// +/// # Example +/// +/// ```rust +/// # use rhai::Engine; +/// // Set the hashing seed to [1, 2, 3, 4] +/// rhai::config::hashing::set_ahash_seed(Some([1, 2, 3, 4])).unwrap(); +/// +/// // Use Rhai AFTER setting the hashing seed +/// let engine = Engine::new(); +/// ``` +#[inline(always)] +pub fn set_ahash_seed(new_seed: Option<[u64; 4]>) -> Result<(), Option<[u64; 4]>> { + AHASH_SEED.set(new_seed) +} + +/// Get the current hashing Seed. +/// +/// If the seed is not yet defined, the `RHAI_AHASH_SEED` environment variable (if any) is used. +/// +/// Otherwise, the hashing seed is randomized to protect against DOS attacks. +/// +/// See [`set_rhai_ahash_seed`] for more. +#[inline] +#[must_use] +pub fn get_ahash_seed() -> &'static Option<[u64; 4]> { + const NONE: &'static Option<[u64; 4]> = &None; + + match AHASH_SEED.get_or_init(|| hashing_env::AHASH_SEED) { + Some(ash) => ash, + None => NONE, + } +} diff --git a/src/config/hashing_env.rs b/src/config/hashing_env.rs new file mode 100644 index 00000000..59930ad8 --- /dev/null +++ b/src/config/hashing_env.rs @@ -0,0 +1,3 @@ +//! This file is automatically recreated during build time by `build.rs` from `build.template`. + +pub(crate) const AHASH_SEED: Option<[u64; 4]> = None; diff --git a/src/config/mod.rs b/src/config/mod.rs new file mode 100644 index 00000000..35aac0af --- /dev/null +++ b/src/config/mod.rs @@ -0,0 +1,4 @@ +//! Configuration for Rhai. + +pub mod hashing; +mod hashing_env; diff --git a/src/engine.rs b/src/engine.rs index 3520108f..8d5df823 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -9,8 +9,8 @@ use crate::packages::{Package, StandardPackage}; use crate::tokenizer::Token; use crate::types::StringsInterner; use crate::{ - Dynamic, Identifier, ImmutableString, Locked, Module, OptimizationLevel, Position, RhaiResult, - Shared, StaticVec, + Dynamic, Identifier, ImmutableString, Locked, Module, OptimizationLevel, SharedModule, + StaticVec, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -86,16 +86,16 @@ pub const OP_INCLUSIVE_RANGE: &str = Token::InclusiveRange.literal_syntax(); /// /// let result = engine.eval::("40 + 2")?; /// -/// println!("Answer: {}", result); // prints 42 +/// println!("Answer: {result}"); // prints 42 /// # Ok(()) /// # } /// ``` pub struct Engine { /// A collection of all modules loaded into the global namespace of the Engine. - pub(crate) global_modules: StaticVec>, + pub(crate) global_modules: StaticVec, /// A collection of all sub-modules directly loaded into the Engine. #[cfg(not(feature = "no_module"))] - pub(crate) global_sub_modules: std::collections::BTreeMap>, + pub(crate) global_sub_modules: std::collections::BTreeMap, /// A module resolution service. #[cfg(not(feature = "no_module"))] @@ -150,7 +150,8 @@ pub struct Engine { } impl fmt::Debug for Engine { - #[inline] + #[cold] + #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut f = f.debug_struct("Engine"); @@ -189,6 +190,7 @@ impl fmt::Debug for Engine { impl Default for Engine { #[inline(always)] + #[must_use] fn default() -> Self { Self::new() } @@ -235,18 +237,14 @@ impl Engine { #[cfg(not(feature = "no_std"))] #[cfg(not(target_family = "wasm"))] { - engine.print = Box::new(|s| println!("{}", s)); - engine.debug = Box::new(|s, source, pos| { - source.map_or_else( - || { - if pos.is_none() { - println!("{}", s); - } else { - println!("{:?} | {}", pos, s); - } - }, - |source| println!("{} @ {:?} | {}", source, pos, s), - ) + engine.print = Box::new(|s| println!("{s}")); + engine.debug = Box::new(|s, source, pos| match (source, pos) { + (Some(source), crate::Position::NONE) => println!("{source} | {s}"), + #[cfg(not(feature = "no_position"))] + (Some(source), pos) => println!("{source} @ {pos:?} | {s}"), + (None, crate::Position::NONE) => println!("{s}"), + #[cfg(not(feature = "no_position"))] + (None, pos) => println!("{pos:?} | {s}"), }); } @@ -343,15 +341,4 @@ impl Engine { pub fn const_empty_string(&self) -> ImmutableString { self.get_interned_string("") } - - /// Check a result to ensure that it is valid. - #[inline] - pub(crate) fn check_return_value(&self, result: RhaiResult, _pos: Position) -> RhaiResult { - #[cfg(not(feature = "unchecked"))] - if let Ok(ref r) = result { - self.check_data_size(r, _pos)?; - } - - result - } } diff --git a/src/eval/cache.rs b/src/eval/cache.rs index cf96dc24..ee4f07a3 100644 --- a/src/eval/cache.rs +++ b/src/eval/cache.rs @@ -2,8 +2,7 @@ use crate::func::{CallableFunction, StraightHashMap}; use crate::types::BloomFilterU64; -use crate::{Identifier, StaticVec}; -use std::marker::PhantomData; +use crate::{ImmutableString, StaticVec}; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -14,7 +13,7 @@ pub struct FnResolutionCacheEntry { /// Function. pub func: CallableFunction, /// Optional source. - pub source: Option>, + pub source: Option, } /// _(internals)_ A function resolution cache with a bloom filter. @@ -45,21 +44,18 @@ impl FnResolutionCache { /// The following caches are contained inside this type: /// * A stack of [function resolution caches][FnResolutionCache] #[derive(Debug, Clone)] -pub struct Caches<'a> { +pub struct Caches { /// Stack of [function resolution caches][FnResolutionCache]. stack: StaticVec, - /// Take care of the lifetime parameter. - dummy: PhantomData<&'a ()>, } -impl Caches<'_> { +impl Caches { /// Create an empty [`Caches`]. #[inline(always)] #[must_use] pub const fn new() -> Self { Self { stack: StaticVec::new_const(), - dummy: PhantomData, } } /// Get the number of function resolution cache(s) in the stack. diff --git a/src/eval/chaining.rs b/src/eval/chaining.rs index 0e6869f9..ca813d86 100644 --- a/src/eval/chaining.rs +++ b/src/eval/chaining.rs @@ -4,7 +4,10 @@ use super::{Caches, GlobalRuntimeState, Target}; use crate::ast::{ASTFlags, Expr, OpAssignment}; use crate::types::dynamic::Union; -use crate::{Dynamic, Engine, FnArgsVec, Module, Position, RhaiResult, RhaiResultOf, Scope, ERR}; +use crate::types::RestoreOnDrop; +use crate::{ + Dynamic, Engine, FnArgsVec, Position, RhaiResult, RhaiResultOf, Scope, SharedModule, ERR, +}; use std::hash::Hash; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -40,17 +43,16 @@ impl Engine { &self, global: &mut GlobalRuntimeState, caches: &mut Caches, - lib: &[&Module], - this_ptr: &mut Option<&mut Dynamic>, + lib: &[SharedModule], + this_ptr: &mut Dynamic, target: &mut Target, root: (&str, Position), _parent: &Expr, + parent_options: ASTFlags, rhs: &Expr, - _parent_options: ASTFlags, idx_values: &mut FnArgsVec, chain_type: ChainType, - level: usize, - new_val: Option<(Dynamic, OpAssignment)>, + new_val: &mut Option<(Dynamic, &OpAssignment)>, ) -> RhaiResultOf<(Dynamic, bool)> { let is_ref_mut = target.is_ref(); @@ -61,7 +63,7 @@ impl Engine { #[cfg(not(feature = "no_index"))] ChainType::Indexing => { // Check for existence with the null conditional operator - if _parent_options.contains(ASTFlags::NEGATED) && target.is::<()>() { + if parent_options.contains(ASTFlags::NEGATED) && target.is::<()>() { return Ok((Dynamic::UNIT, false)); } @@ -70,26 +72,26 @@ impl Engine { match rhs { // xxx[idx].expr... | xxx[idx][expr]... Expr::Dot(x, options, x_pos) | Expr::Index(x, options, x_pos) - if !_parent_options.contains(ASTFlags::BREAK) => + if !parent_options.contains(ASTFlags::BREAK) => { #[cfg(feature = "debugging")] - self.run_debugger(scope, global, lib, this_ptr, _parent, level)?; + self.run_debugger(global, caches, lib, scope, this_ptr, _parent)?; - let idx_val = idx_values.pop().unwrap(); + let idx_val = &mut idx_values.pop().unwrap(); let mut idx_val_for_setter = idx_val.clone(); let idx_pos = x.lhs.start_position(); let rhs_chain = rhs.into(); let (try_setter, result) = { let mut obj = self.get_indexed_mut( - global, caches, lib, target, idx_val, idx_pos, false, true, level, + global, caches, lib, target, idx_val, idx_pos, false, true, )?; let is_obj_temp_val = obj.is_temp_value(); let obj_ptr = &mut obj; match self.eval_dot_index_chain_helper( - global, caches, lib, this_ptr, obj_ptr, root, rhs, &x.rhs, - *options, idx_values, rhs_chain, level, new_val, + global, caches, lib, this_ptr, obj_ptr, root, rhs, *options, + &x.rhs, idx_values, rhs_chain, new_val, ) { Ok((result, true)) if is_obj_temp_val => { (Some(obj.take_or_clone()), (result, true)) @@ -104,7 +106,7 @@ impl Engine { let idx = &mut idx_val_for_setter; let new_val = &mut new_val; self.call_indexer_set( - global, caches, lib, target, idx, new_val, is_ref_mut, level, + global, caches, lib, target, idx, new_val, is_ref_mut, ) .or_else(|e| match *e { ERR::ErrorIndexingType(..) => Ok((Dynamic::UNIT, false)), @@ -117,21 +119,20 @@ impl Engine { // xxx[rhs] op= new_val _ if new_val.is_some() => { #[cfg(feature = "debugging")] - self.run_debugger(scope, global, lib, this_ptr, _parent, level)?; + self.run_debugger(global, caches, lib, scope, this_ptr, _parent)?; - let (new_val, op_info) = new_val.expect("`Some`"); - let idx_val = idx_values.pop().unwrap(); - let mut idx_val2 = idx_val.clone(); + let (new_val, op_info) = new_val.take().expect("`Some`"); + let idx_val = &mut idx_values.pop().unwrap(); + let idx = &mut idx_val.clone(); - let try_setter = match self.get_indexed_mut( - global, caches, lib, target, idx_val, pos, true, false, level, - ) { + let try_setter = match self + .get_indexed_mut(global, caches, lib, target, idx, pos, true, false) + { // Indexed value is not a temp value - update directly Ok(ref mut obj_ptr) => { self.eval_op_assignment( - global, caches, lib, op_info, obj_ptr, root, new_val, level, + global, caches, lib, op_info, obj_ptr, root, new_val, )?; - #[cfg(not(feature = "unchecked"))] self.check_data_size(obj_ptr, op_info.pos)?; None } @@ -143,32 +144,30 @@ impl Engine { }; if let Some(mut new_val) = try_setter { - let idx = &mut idx_val2; - // Is this an op-assignment? if op_info.is_op_assignment() { - let idx = &mut idx.clone(); + let idx = &mut idx_val.clone(); + // Call the index getter to get the current value if let Ok(val) = - self.call_indexer_get(global, caches, lib, target, idx, level) + self.call_indexer_get(global, caches, lib, target, idx) { let mut val = val.into(); // Run the op-assignment self.eval_op_assignment( global, caches, lib, op_info, &mut val, root, new_val, - level, )?; // Replace new value new_val = val.take_or_clone(); - #[cfg(not(feature = "unchecked"))] self.check_data_size(&new_val, op_info.pos)?; } } // Try to call index setter let new_val = &mut new_val; + self.call_indexer_set( - global, caches, lib, target, idx, new_val, is_ref_mut, level, + global, caches, lib, target, idx_val, new_val, is_ref_mut, )?; } @@ -177,14 +176,12 @@ impl Engine { // xxx[rhs] _ => { #[cfg(feature = "debugging")] - self.run_debugger(scope, global, lib, this_ptr, _parent, level)?; + self.run_debugger(global, caches, lib, scope, this_ptr, _parent)?; - let idx_val = idx_values.pop().unwrap(); + let idx_val = &mut idx_values.pop().unwrap(); - self.get_indexed_mut( - global, caches, lib, target, idx_val, pos, false, true, level, - ) - .map(|v| (v.take_or_clone(), false)) + self.get_indexed_mut(global, caches, lib, target, idx_val, pos, false, true) + .map(|v| (v.take_or_clone(), false)) } } } @@ -192,7 +189,7 @@ impl Engine { #[cfg(not(feature = "no_object"))] ChainType::Dotting => { // Check for existence with the Elvis operator - if _parent_options.contains(ASTFlags::NEGATED) && target.is::<()>() { + if parent_options.contains(ASTFlags::NEGATED) && target.is::<()>() { return Ok((Dynamic::UNIT, false)); } @@ -200,28 +197,28 @@ impl Engine { // xxx.fn_name(arg_expr_list) Expr::MethodCall(x, pos) if !x.is_qualified() && new_val.is_none() => { #[cfg(feature = "debugging")] - let reset_debugger = - self.run_debugger_with_reset(scope, global, lib, this_ptr, rhs, level)?; + let reset = self + .run_debugger_with_reset(global, caches, lib, scope, this_ptr, rhs)?; + #[cfg(feature = "debugging")] + let global = &mut *RestoreOnDrop::lock(global, move |g| { + g.debugger.reset_status(reset) + }); let crate::ast::FnCallExpr { name, hashes, args, .. } = &**x; + // Truncate the index values upon exit let offset = idx_values.len() - args.len(); + let idx_values = + &mut *RestoreOnDrop::lock(idx_values, move |v| v.truncate(offset)); + let call_args = &mut idx_values[offset..]; let pos1 = args.get(0).map_or(Position::NONE, Expr::position); - let result = self.make_method_call( + self.make_method_call( global, caches, lib, name, *hashes, target, call_args, pos1, *pos, - level, - ); - - idx_values.truncate(offset); - - #[cfg(feature = "debugging")] - global.debugger.reset_status(reset_debugger); - - result + ) } // xxx.fn_name(...) = ??? Expr::MethodCall(..) if new_val.is_some() => { @@ -234,54 +231,53 @@ impl Engine { // {xxx:map}.id op= ??? Expr::Property(x, pos) if target.is::() && new_val.is_some() => { #[cfg(feature = "debugging")] - self.run_debugger(scope, global, lib, this_ptr, rhs, level)?; + self.run_debugger(global, caches, lib, scope, this_ptr, rhs)?; - let index = x.2.clone().into(); - let (new_val, op_info) = new_val.expect("`Some`"); + let index = &mut x.2.clone().into(); + let (new_val, op_info) = new_val.take().expect("`Some`"); { let val_target = &mut self.get_indexed_mut( - global, caches, lib, target, index, *pos, true, false, level, + global, caches, lib, target, index, *pos, true, false, )?; self.eval_op_assignment( - global, caches, lib, op_info, val_target, root, new_val, level, + global, caches, lib, op_info, val_target, root, new_val, )?; } - #[cfg(not(feature = "unchecked"))] self.check_data_size(target.source(), op_info.pos)?; Ok((Dynamic::UNIT, true)) } // {xxx:map}.id Expr::Property(x, pos) if target.is::() => { #[cfg(feature = "debugging")] - self.run_debugger(scope, global, lib, this_ptr, rhs, level)?; + self.run_debugger(global, caches, lib, scope, this_ptr, rhs)?; - let index = x.2.clone().into(); + let index = &mut x.2.clone().into(); let val = self.get_indexed_mut( - global, caches, lib, target, index, *pos, false, false, level, + global, caches, lib, target, index, *pos, false, false, )?; Ok((val.take_or_clone(), false)) } // xxx.id op= ??? Expr::Property(x, pos) if new_val.is_some() => { #[cfg(feature = "debugging")] - self.run_debugger(scope, global, lib, this_ptr, rhs, level)?; + self.run_debugger(global, caches, lib, scope, this_ptr, rhs)?; let ((getter, hash_get), (setter, hash_set), name) = &**x; - let (mut new_val, op_info) = new_val.expect("`Some`"); + let (mut new_val, op_info) = new_val.take().expect("`Some`"); if op_info.is_op_assignment() { let args = &mut [target.as_mut()]; let (mut orig_val, ..) = self - .call_native_fn( - global, caches, lib, getter, *hash_get, args, is_ref_mut, - false, *pos, level, + .exec_native_fn_call( + global, caches, lib, getter, None, *hash_get, args, is_ref_mut, + *pos, ) .or_else(|err| match *err { // Try an indexer if property does not exist ERR::ErrorDotExpr(..) => { let mut prop = name.into(); self.call_indexer_get( - global, caches, lib, target, &mut prop, level, + global, caches, lib, target, &mut prop, ) .map(|r| (r, false)) .map_err(|e| { @@ -298,7 +294,7 @@ impl Engine { let orig_val = &mut (&mut orig_val).into(); self.eval_op_assignment( - global, caches, lib, op_info, orig_val, root, new_val, level, + global, caches, lib, op_info, orig_val, root, new_val, )?; } @@ -306,9 +302,8 @@ impl Engine { } let args = &mut [target.as_mut(), &mut new_val]; - self.call_native_fn( - global, caches, lib, setter, *hash_set, args, is_ref_mut, false, *pos, - level, + self.exec_native_fn_call( + global, caches, lib, setter, None, *hash_set, args, is_ref_mut, *pos, ) .or_else(|err| match *err { // Try an indexer if property does not exist @@ -316,7 +311,7 @@ impl Engine { let idx = &mut name.into(); let new_val = &mut new_val; self.call_indexer_set( - global, caches, lib, target, idx, new_val, is_ref_mut, level, + global, caches, lib, target, idx, new_val, is_ref_mut, ) .map_err(|e| match *e { ERR::ErrorIndexingType(..) => err, @@ -329,27 +324,24 @@ impl Engine { // xxx.id Expr::Property(x, pos) => { #[cfg(feature = "debugging")] - self.run_debugger(scope, global, lib, this_ptr, rhs, level)?; + self.run_debugger(global, caches, lib, scope, this_ptr, rhs)?; let ((getter, hash_get), _, name) = &**x; let args = &mut [target.as_mut()]; - self.call_native_fn( - global, caches, lib, getter, *hash_get, args, is_ref_mut, false, *pos, - level, + self.exec_native_fn_call( + global, caches, lib, getter, None, *hash_get, args, is_ref_mut, *pos, ) .map_or_else( |err| match *err { // Try an indexer if property does not exist ERR::ErrorDotExpr(..) => { let mut prop = name.into(); - self.call_indexer_get( - global, caches, lib, target, &mut prop, level, - ) - .map(|r| (r, false)) - .map_err(|e| match *e { - ERR::ErrorIndexingType(..) => err, - _ => e, - }) + self.call_indexer_get(global, caches, lib, target, &mut prop) + .map(|r| (r, false)) + .map_err(|e| match *e { + ERR::ErrorIndexingType(..) => err, + _ => e, + }) } _ => Err(err), }, @@ -366,39 +358,43 @@ impl Engine { let val_target = &mut match x.lhs { Expr::Property(ref p, pos) => { #[cfg(feature = "debugging")] - self.run_debugger(scope, global, lib, this_ptr, _node, level)?; + self.run_debugger(global, caches, lib, scope, this_ptr, _node)?; - let index = p.2.clone().into(); + let index = &mut p.2.clone().into(); self.get_indexed_mut( - global, caches, lib, target, index, pos, false, true, level, + global, caches, lib, target, index, pos, false, true, )? } // {xxx:map}.fn_name(arg_expr_list)[expr] | {xxx:map}.fn_name(arg_expr_list).expr Expr::MethodCall(ref x, pos) if !x.is_qualified() => { #[cfg(feature = "debugging")] - let reset_debugger = self.run_debugger_with_reset( - scope, global, lib, this_ptr, _node, level, + let reset = self.run_debugger_with_reset( + global, caches, lib, scope, this_ptr, _node, )?; + #[cfg(feature = "debugging")] + let global = &mut *RestoreOnDrop::lock(global, move |g| { + g.debugger.reset_status(reset) + }); let crate::ast::FnCallExpr { name, hashes, args, .. } = &**x; + // Truncate the index values upon exit let offset = idx_values.len() - args.len(); + let idx_values = &mut *RestoreOnDrop::lock(idx_values, move |v| { + v.truncate(offset) + }); + let call_args = &mut idx_values[offset..]; let pos1 = args.get(0).map_or(Position::NONE, Expr::position); - let result = self.make_method_call( + self.make_method_call( global, caches, lib, name, *hashes, target, call_args, pos1, - pos, level, - ); - - idx_values.truncate(offset); - - #[cfg(feature = "debugging")] - global.debugger.reset_status(reset_debugger); - - result?.0.into() + pos, + )? + .0 + .into() } // {xxx:map}.module::fn_name(...) - syntax error Expr::MethodCall(..) => unreachable!( @@ -410,8 +406,8 @@ impl Engine { let rhs_chain = rhs.into(); self.eval_dot_index_chain_helper( - global, caches, lib, this_ptr, val_target, root, rhs, &x.rhs, *options, - idx_values, rhs_chain, level, new_val, + global, caches, lib, this_ptr, val_target, root, rhs, *options, &x.rhs, + idx_values, rhs_chain, new_val, ) .map_err(|err| err.fill_position(*x_pos)) } @@ -423,7 +419,7 @@ impl Engine { // xxx.prop[expr] | xxx.prop.expr Expr::Property(ref p, pos) => { #[cfg(feature = "debugging")] - self.run_debugger(scope, global, lib, this_ptr, _node, level)?; + self.run_debugger(global, caches, lib, scope, this_ptr, _node)?; let ((getter, hash_get), (setter, hash_set), name) = &**p; let rhs_chain = rhs.into(); @@ -432,16 +428,16 @@ impl Engine { // Assume getters are always pure let (mut val, ..) = self - .call_native_fn( - global, caches, lib, getter, *hash_get, args, is_ref_mut, - false, pos, level, + .exec_native_fn_call( + global, caches, lib, getter, None, *hash_get, args, + is_ref_mut, pos, ) .or_else(|err| match *err { // Try an indexer if property does not exist ERR::ErrorDotExpr(..) => { let mut prop = name.into(); self.call_indexer_get( - global, caches, lib, target, &mut prop, level, + global, caches, lib, target, &mut prop, ) .map(|r| (r, false)) .map_err( @@ -458,8 +454,8 @@ impl Engine { let (result, may_be_changed) = self .eval_dot_index_chain_helper( - global, caches, lib, this_ptr, val, root, rhs, &x.rhs, - *options, idx_values, rhs_chain, level, new_val, + global, caches, lib, this_ptr, val, root, rhs, *options, + &x.rhs, idx_values, rhs_chain, new_val, ) .map_err(|err| err.fill_position(*x_pos))?; @@ -468,9 +464,9 @@ impl Engine { // Re-use args because the first &mut parameter will not be consumed let mut arg_values = [target.as_mut(), val.as_mut()]; let args = &mut arg_values; - self.call_native_fn( - global, caches, lib, setter, *hash_set, args, is_ref_mut, - false, pos, level, + self.exec_native_fn_call( + global, caches, lib, setter, None, *hash_set, args, + is_ref_mut, pos, ) .or_else( |err| match *err { @@ -480,7 +476,7 @@ impl Engine { let new_val = val; self.call_indexer_set( global, caches, lib, target, idx, new_val, - is_ref_mut, level, + is_ref_mut, ) .or_else(|e| match *e { // If there is no setter, no need to feed it @@ -500,36 +496,43 @@ impl Engine { } // xxx.fn_name(arg_expr_list)[expr] | xxx.fn_name(arg_expr_list).expr Expr::MethodCall(ref f, pos) if !f.is_qualified() => { - #[cfg(feature = "debugging")] - let reset_debugger = self.run_debugger_with_reset( - scope, global, lib, this_ptr, _node, level, - )?; + let val = { + #[cfg(feature = "debugging")] + let reset = self.run_debugger_with_reset( + global, caches, lib, scope, this_ptr, _node, + )?; + #[cfg(feature = "debugging")] + let global = &mut *RestoreOnDrop::lock(global, move |g| { + g.debugger.reset_status(reset) + }); - let crate::ast::FnCallExpr { - name, hashes, args, .. - } = &**f; + let crate::ast::FnCallExpr { + name, hashes, args, .. + } = &**f; + + // Truncate the index values upon exit + let offset = idx_values.len() - args.len(); + let idx_values = + &mut *RestoreOnDrop::lock(idx_values, move |v| { + v.truncate(offset) + }); + + let call_args = &mut idx_values[offset..]; + let pos1 = args.get(0).map_or(Position::NONE, Expr::position); + + self.make_method_call( + global, caches, lib, name, *hashes, target, call_args, + pos1, pos, + )? + .0 + }; + + let val = &mut val.into(); let rhs_chain = rhs.into(); - let offset = idx_values.len() - args.len(); - let call_args = &mut idx_values[offset..]; - let pos1 = args.get(0).map_or(Position::NONE, Expr::position); - - let result = self.make_method_call( - global, caches, lib, name, *hashes, target, call_args, pos1, - pos, level, - ); - - idx_values.truncate(offset); - - #[cfg(feature = "debugging")] - global.debugger.reset_status(reset_debugger); - - let (val, _) = &mut result?; - let val = &mut val.into(); - self.eval_dot_index_chain_helper( - global, caches, lib, this_ptr, val, root, rhs, &x.rhs, - *options, idx_values, rhs_chain, level, new_val, + global, caches, lib, this_ptr, val, root, rhs, *options, + &x.rhs, idx_values, rhs_chain, new_val, ) .map_err(|err| err.fill_position(pos)) } @@ -551,14 +554,13 @@ impl Engine { /// Evaluate a dot/index chain. pub(crate) fn eval_dot_index_chain( &self, - scope: &mut Scope, global: &mut GlobalRuntimeState, caches: &mut Caches, - lib: &[&Module], - this_ptr: &mut Option<&mut Dynamic>, + lib: &[SharedModule], + scope: &mut Scope, + this_ptr: &mut Dynamic, expr: &Expr, - level: usize, - new_val: Option<(Dynamic, OpAssignment)>, + new_val: &mut Option<(Dynamic, &OpAssignment)>, ) -> RhaiResult { let chain_type = ChainType::from(expr); let (crate::ast::BinaryExpr { lhs, rhs }, options, op_pos) = match expr { @@ -595,8 +597,7 @@ impl Engine { // All other patterns - evaluate the arguments chain _ => { self.eval_dot_index_chain_arguments( - scope, global, caches, lib, this_ptr, rhs, options, chain_type, idx_values, 0, - level, + global, caches, lib, scope, this_ptr, rhs, options, chain_type, idx_values, )?; } } @@ -605,20 +606,19 @@ impl Engine { // id.??? or id[???] Expr::Variable(x, .., var_pos) => { #[cfg(feature = "debugging")] - self.run_debugger(scope, global, lib, this_ptr, lhs, level)?; - - #[cfg(not(feature = "unchecked"))] - self.inc_operations(&mut global.num_operations, *var_pos)?; + self.run_debugger(global, caches, lib, scope, this_ptr, lhs)?; + self.track_operation(global, *var_pos)?; let (mut target, ..) = - self.search_namespace(scope, global, lib, this_ptr, lhs, level)?; + self.search_namespace(global, caches, lib, scope, this_ptr, lhs)?; let obj_ptr = &mut target; let root = (x.3.as_str(), *var_pos); + let mut this = Dynamic::NULL; self.eval_dot_index_chain_helper( - global, caches, lib, &mut None, obj_ptr, root, expr, rhs, options, idx_values, - chain_type, level, new_val, + global, caches, lib, &mut this, obj_ptr, root, expr, options, rhs, idx_values, + chain_type, new_val, ) } // {expr}.??? = ??? or {expr}[???] = ??? @@ -626,14 +626,14 @@ impl Engine { // {expr}.??? or {expr}[???] expr => { let value = self - .eval_expr(scope, global, caches, lib, this_ptr, expr, level)? + .eval_expr(global, caches, lib, scope, this_ptr, expr)? .flatten(); let obj_ptr = &mut value.into(); let root = ("", expr.start_position()); self.eval_dot_index_chain_helper( - global, caches, lib, this_ptr, obj_ptr, root, expr, rhs, options, idx_values, - chain_type, level, new_val, + global, caches, lib, this_ptr, obj_ptr, root, expr, options, rhs, idx_values, + chain_type, new_val, ) } } @@ -644,41 +644,38 @@ impl Engine { /// Evaluate a chain of indexes and store the results in a [`FnArgsVec`]. fn eval_dot_index_chain_arguments( &self, - scope: &mut Scope, global: &mut GlobalRuntimeState, caches: &mut Caches, - lib: &[&Module], - this_ptr: &mut Option<&mut Dynamic>, + lib: &[SharedModule], + scope: &mut Scope, + this_ptr: &mut Dynamic, expr: &Expr, parent_options: ASTFlags, - _parent_chain_type: ChainType, + parent_chain_type: ChainType, idx_values: &mut FnArgsVec, - size: usize, - level: usize, ) -> RhaiResultOf<()> { - #[cfg(not(feature = "unchecked"))] - self.inc_operations(&mut global.num_operations, expr.position())?; + self.track_operation(global, expr.position())?; match expr { #[cfg(not(feature = "no_object"))] Expr::MethodCall(x, ..) - if _parent_chain_type == ChainType::Dotting && !x.is_qualified() => + if parent_chain_type == ChainType::Dotting && !x.is_qualified() => { for arg_expr in &x.args { idx_values.push( - self.get_arg_value(scope, global, caches, lib, this_ptr, arg_expr, level)? + self.get_arg_value(global, caches, lib, scope, this_ptr, arg_expr)? .0 .flatten(), ); } } #[cfg(not(feature = "no_object"))] - Expr::MethodCall(..) if _parent_chain_type == ChainType::Dotting => { + Expr::MethodCall(..) if parent_chain_type == ChainType::Dotting => { unreachable!("function call in dot chain should not be namespace-qualified") } #[cfg(not(feature = "no_object"))] - Expr::Property(..) if _parent_chain_type == ChainType::Dotting => (), + Expr::Property(..) if parent_chain_type == ChainType::Dotting => (), Expr::Property(..) => unreachable!("unexpected Expr::Property for indexing"), Expr::Index(x, options, ..) | Expr::Dot(x, options, ..) @@ -691,35 +688,33 @@ impl Engine { // Evaluate in left-to-right order match lhs { #[cfg(not(feature = "no_object"))] - Expr::Property(..) if _parent_chain_type == ChainType::Dotting => (), + Expr::Property(..) if parent_chain_type == ChainType::Dotting => (), Expr::Property(..) => unreachable!("unexpected Expr::Property for indexing"), #[cfg(not(feature = "no_object"))] Expr::MethodCall(x, ..) - if _parent_chain_type == ChainType::Dotting && !x.is_qualified() => + if parent_chain_type == ChainType::Dotting && !x.is_qualified() => { for arg_expr in &x.args { _arg_values.push( - self.get_arg_value( - scope, global, caches, lib, this_ptr, arg_expr, level, - )? - .0 - .flatten(), + self.get_arg_value(global, caches, lib, scope, this_ptr, arg_expr)? + .0 + .flatten(), ); } } #[cfg(not(feature = "no_object"))] - Expr::MethodCall(..) if _parent_chain_type == ChainType::Dotting => { + Expr::MethodCall(..) if parent_chain_type == ChainType::Dotting => { unreachable!("function call in dot chain should not be namespace-qualified") } #[cfg(not(feature = "no_object"))] - expr if _parent_chain_type == ChainType::Dotting => { + expr if parent_chain_type == ChainType::Dotting => { unreachable!("invalid dot expression: {:?}", expr); } #[cfg(not(feature = "no_index"))] - _ if _parent_chain_type == ChainType::Indexing => { + _ if parent_chain_type == ChainType::Indexing => { _arg_values.push( - self.eval_expr(scope, global, caches, lib, this_ptr, lhs, level)? + self.eval_expr(global, caches, lib, scope, this_ptr, lhs)? .flatten(), ); } @@ -730,8 +725,7 @@ impl Engine { let chain_type = expr.into(); self.eval_dot_index_chain_arguments( - scope, global, caches, lib, this_ptr, rhs, *options, chain_type, idx_values, - size, level, + global, caches, lib, scope, this_ptr, rhs, *options, chain_type, idx_values, )?; if !_arg_values.is_empty() { @@ -740,12 +734,12 @@ impl Engine { } #[cfg(not(feature = "no_object"))] - _ if _parent_chain_type == ChainType::Dotting => { + _ if parent_chain_type == ChainType::Dotting => { unreachable!("invalid dot expression: {:?}", expr); } #[cfg(not(feature = "no_index"))] - _ if _parent_chain_type == ChainType::Indexing => idx_values.push( - self.eval_expr(scope, global, caches, lib, this_ptr, expr, level)? + _ if parent_chain_type == ChainType::Indexing => idx_values.push( + self.eval_expr(global, caches, lib, scope, this_ptr, expr)? .flatten(), ), _ => unreachable!("unknown chained expression: {:?}", expr), @@ -760,21 +754,20 @@ impl Engine { &self, global: &mut GlobalRuntimeState, caches: &mut Caches, - lib: &[&Module], + lib: &[SharedModule], target: &mut Dynamic, idx: &mut Dynamic, - level: usize, ) -> RhaiResultOf { let args = &mut [target, idx]; let hash = global.hash_idx_get(); let fn_name = crate::engine::FN_IDX_GET; let pos = Position::NONE; - let level = level + 1; - self.call_native_fn( - global, caches, lib, fn_name, hash, args, true, false, pos, level, - ) - .map(|(r, ..)| r) + global.level += 1; + let global = &mut *RestoreOnDrop::lock(global, move |g| g.level -= 1); + + self.exec_native_fn_call(global, caches, lib, fn_name, None, hash, args, true, pos) + .map(|(r, ..)| r) } /// Call a set indexer. @@ -783,21 +776,22 @@ impl Engine { &self, global: &mut GlobalRuntimeState, caches: &mut Caches, - lib: &[&Module], + lib: &[SharedModule], target: &mut Dynamic, idx: &mut Dynamic, new_val: &mut Dynamic, is_ref_mut: bool, - level: usize, ) -> RhaiResultOf<(Dynamic, bool)> { let hash = global.hash_idx_set(); let args = &mut [target, idx, new_val]; let fn_name = crate::engine::FN_IDX_SET; let pos = Position::NONE; - let level = level + 1; - self.call_native_fn( - global, caches, lib, fn_name, hash, args, is_ref_mut, false, pos, level, + global.level += 1; + let global = &mut *RestoreOnDrop::lock(global, move |g| g.level -= 1); + + self.exec_native_fn_call( + global, caches, lib, fn_name, None, hash, args, is_ref_mut, pos, ) } @@ -807,16 +801,14 @@ impl Engine { &self, global: &mut GlobalRuntimeState, caches: &mut Caches, - lib: &[&Module], + lib: &[SharedModule], target: &'t mut Dynamic, - mut idx: Dynamic, + idx: &mut Dynamic, idx_pos: Position, _add_if_not_found: bool, use_indexers: bool, - level: usize, ) -> RhaiResultOf> { - #[cfg(not(feature = "unchecked"))] - self.inc_operations(&mut global.num_operations, Position::NONE)?; + self.track_operation(global, Position::NONE)?; match target { #[cfg(not(feature = "no_index"))] @@ -1018,7 +1010,7 @@ impl Engine { } _ if use_indexers => self - .call_indexer_get(global, caches, lib, target, &mut idx, level) + .call_indexer_get(global, caches, lib, target, idx) .map(Into::into), _ => Err(ERR::ErrorIndexingType( diff --git a/src/eval/data_check.rs b/src/eval/data_check.rs index 293b1d99..abda0e45 100644 --- a/src/eval/data_check.rs +++ b/src/eval/data_check.rs @@ -1,9 +1,9 @@ //! Data size checks during evaluation. #![cfg(not(feature = "unchecked"))] +use super::GlobalRuntimeState; use crate::types::dynamic::Union; -use crate::{Dynamic, Engine, Position, RhaiResultOf, ERR}; -use std::num::NonZeroUsize; +use crate::{Dynamic, Engine, Position, RhaiResult, RhaiResultOf, ERR}; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -15,46 +15,45 @@ impl Engine { /// # Panics /// /// Panics if any interior data is shared (should never happen). - #[cfg(not(feature = "unchecked"))] pub(crate) fn calc_data_sizes(value: &Dynamic, _top: bool) -> (usize, usize, usize) { match value.0 { #[cfg(not(feature = "no_index"))] Union::Array(ref arr, ..) => { arr.iter() - .fold((0, 0, 0), |(arrays, maps, strings), value| match value.0 { + .fold((0, 0, 0), |(ax, mx, sx), value| match value.0 { Union::Array(..) => { let (a, m, s) = Self::calc_data_sizes(value, false); - (arrays + a + 1, maps + m, strings + s) + (ax + a + 1, mx + m, sx + s) } - Union::Blob(ref a, ..) => (arrays + 1 + a.len(), maps, strings), + Union::Blob(ref a, ..) => (ax + 1 + a.len(), mx, sx), #[cfg(not(feature = "no_object"))] Union::Map(..) => { let (a, m, s) = Self::calc_data_sizes(value, false); - (arrays + a + 1, maps + m, strings + s) + (ax + a + 1, mx + m, sx + s) } - Union::Str(ref s, ..) => (arrays + 1, maps, strings + s.len()), - _ => (arrays + 1, maps, strings), + Union::Str(ref s, ..) => (ax + 1, mx, sx + s.len()), + _ => (ax + 1, mx, sx), }) } #[cfg(not(feature = "no_index"))] - Union::Blob(ref arr, ..) => (arr.len(), 0, 0), + Union::Blob(ref blob, ..) => (blob.len(), 0, 0), #[cfg(not(feature = "no_object"))] Union::Map(ref map, ..) => { map.values() - .fold((0, 0, 0), |(arrays, maps, strings), value| match value.0 { + .fold((0, 0, 0), |(ax, mx, sx), value| match value.0 { #[cfg(not(feature = "no_index"))] Union::Array(..) => { let (a, m, s) = Self::calc_data_sizes(value, false); - (arrays + a, maps + m + 1, strings + s) + (ax + a, mx + m + 1, sx + s) } #[cfg(not(feature = "no_index"))] - Union::Blob(ref a, ..) => (arrays + a.len(), maps, strings), + Union::Blob(ref a, ..) => (ax + a.len(), mx, sx), Union::Map(..) => { let (a, m, s) = Self::calc_data_sizes(value, false); - (arrays + a, maps + m + 1, strings + s) + (ax + a, mx + m + 1, sx + s) } - Union::Str(ref s, ..) => (arrays, maps + 1, strings + s.len()), - _ => (arrays, maps + 1, strings), + Union::Str(ref s, ..) => (ax, mx + 1, sx + s.len()), + _ => (ax, mx + 1, sx), }) } Union::Str(ref s, ..) => (0, 0, s.len()), @@ -70,65 +69,48 @@ impl Engine { } } - /// Is there a data size limit set? - #[cfg(not(feature = "unchecked"))] - pub(crate) const fn has_data_size_limit(&self) -> bool { - let mut _limited = self.limits.max_string_size.is_some(); - - #[cfg(not(feature = "no_index"))] - { - _limited = _limited || self.limits.max_array_size.is_some(); - } - #[cfg(not(feature = "no_object"))] - { - _limited = _limited || self.limits.max_map_size.is_some(); - } - - _limited - } - /// Raise an error if any data size exceeds limit. - #[cfg(not(feature = "unchecked"))] pub(crate) fn raise_err_if_over_data_size_limit( &self, - sizes: (usize, usize, usize), - pos: Position, + (_arr, _map, s): (usize, usize, usize), ) -> RhaiResultOf<()> { - let (_arr, _map, s) = sizes; - - if s > self + if self .limits .max_string_size - .map_or(usize::MAX, NonZeroUsize::get) + .map_or(false, |max| s > max.get()) { - return Err(ERR::ErrorDataTooLarge("Length of string".to_string(), pos).into()); + return Err( + ERR::ErrorDataTooLarge("Length of string".to_string(), Position::NONE).into(), + ); } #[cfg(not(feature = "no_index"))] - if _arr - > self - .limits - .max_array_size - .map_or(usize::MAX, NonZeroUsize::get) + if self + .limits + .max_array_size + .map_or(false, |max| _arr > max.get()) { - return Err(ERR::ErrorDataTooLarge("Size of array".to_string(), pos).into()); + return Err( + ERR::ErrorDataTooLarge("Size of array/BLOB".to_string(), Position::NONE).into(), + ); } #[cfg(not(feature = "no_object"))] - if _map - > self - .limits - .max_map_size - .map_or(usize::MAX, NonZeroUsize::get) + if self + .limits + .max_map_size + .map_or(false, |max| _map > max.get()) { - return Err(ERR::ErrorDataTooLarge("Size of object map".to_string(), pos).into()); + return Err( + ERR::ErrorDataTooLarge("Size of object map".to_string(), Position::NONE).into(), + ); } Ok(()) } /// Check whether the size of a [`Dynamic`] is within limits. - #[cfg(not(feature = "unchecked"))] + #[inline] pub(crate) fn check_data_size(&self, value: &Dynamic, pos: Position) -> RhaiResultOf<()> { // If no data size limits, just return if !self.has_data_size_limit() { @@ -137,35 +119,37 @@ impl Engine { let sizes = Self::calc_data_sizes(value, true); - self.raise_err_if_over_data_size_limit(sizes, pos) + self.raise_err_if_over_data_size_limit(sizes) + .map_err(|err| err.fill_position(pos)) } /// Raise an error if the size of a [`Dynamic`] is out of limits (if any). /// /// Not available under `unchecked`. - #[cfg(not(feature = "unchecked"))] #[inline(always)] pub fn ensure_data_size_within_limits(&self, value: &Dynamic) -> RhaiResultOf<()> { self.check_data_size(value, Position::NONE) } /// Check if the number of operations stay within limit. - #[cfg(not(feature = "unchecked"))] - pub(crate) fn inc_operations( + pub(crate) fn track_operation( &self, - num_operations: &mut u64, + global: &mut GlobalRuntimeState, pos: Position, ) -> RhaiResultOf<()> { - *num_operations += 1; + global.num_operations += 1; // Guard against too many operations - if self.max_operations() > 0 && *num_operations > self.max_operations() { + let max = self.max_operations(); + let num_operations = global.num_operations; + + if max > 0 && num_operations > max { return Err(ERR::ErrorTooManyOperations(pos).into()); } // Report progress - only in steps if let Some(ref progress) = self.progress { - if let Some(token) = progress(*num_operations) { + if let Some(token) = progress(num_operations) { // Terminate script if progress returns a termination token return Err(ERR::ErrorTerminated(token, pos).into()); } @@ -173,4 +157,14 @@ impl Engine { Ok(()) } + + /// Check a result to ensure that it is valid. + #[inline] + pub(crate) fn check_return_value(&self, result: RhaiResult, pos: Position) -> RhaiResult { + if let Ok(ref r) = result { + self.check_data_size(r, pos)?; + } + + result + } } diff --git a/src/eval/debugger.rs b/src/eval/debugger.rs index e98771e7..6eed7279 100644 --- a/src/eval/debugger.rs +++ b/src/eval/debugger.rs @@ -1,9 +1,11 @@ //! Module defining the debugging interface. #![cfg(feature = "debugging")] -use super::{EvalContext, GlobalRuntimeState}; +use super::{Caches, EvalContext, GlobalRuntimeState}; use crate::ast::{ASTNode, Expr, Stmt}; -use crate::{Dynamic, Engine, EvalAltResult, Identifier, Module, Position, RhaiResultOf, Scope}; +use crate::{ + Dynamic, Engine, EvalAltResult, ImmutableString, Position, RhaiResultOf, Scope, SharedModule, +}; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{fmt, iter::repeat, mem}; @@ -48,6 +50,7 @@ pub enum DebuggerCommand { impl Default for DebuggerCommand { #[inline(always)] + #[must_use] fn default() -> Self { Self::Continue } @@ -99,12 +102,10 @@ pub enum BreakPoint { /// Break at a particular position under a particular source. /// /// Not available under `no_position`. - /// - /// Source is empty if not available. #[cfg(not(feature = "no_position"))] AtPosition { /// Source (empty if not available) of the break-point. - source: Identifier, + source: Option, /// [Position] of the break-point. pos: Position, /// Is the break-point enabled? @@ -113,14 +114,14 @@ pub enum BreakPoint { /// Break at a particular function call. AtFunctionName { /// Function name. - name: Identifier, + name: ImmutableString, /// Is the break-point enabled? enabled: bool, }, /// Break at a particular function call with a particular number of arguments. AtFunctionCall { /// Function name. - name: Identifier, + name: ImmutableString, /// Number of arguments. args: usize, /// Is the break-point enabled? @@ -132,7 +133,7 @@ pub enum BreakPoint { #[cfg(not(feature = "no_object"))] AtProperty { /// Property name. - name: Identifier, + name: ImmutableString, /// Is the break-point enabled? enabled: bool, }, @@ -147,35 +148,30 @@ impl fmt::Display for BreakPoint { pos, enabled, } => { - if source.is_empty() { - write!(f, "@ {:?}", pos)?; - } else { - write!(f, "{} @ {:?}", source, pos)?; + if let Some(ref source) = source { + write!(f, "{source} ")?; } + write!(f, "@ {pos:?}")?; if !*enabled { f.write_str(" (disabled)")?; } Ok(()) } - Self::AtFunctionName { - name: fn_name, - enabled, - } => { - write!(f, "{} (...)", fn_name)?; + Self::AtFunctionName { name, enabled } => { + write!(f, "{name} (...)")?; if !*enabled { f.write_str(" (disabled)")?; } Ok(()) } Self::AtFunctionCall { - name: fn_name, + name, args, enabled, } => { write!( f, - "{} ({})", - fn_name, + "{name} ({})", repeat("_").take(*args).collect::>().join(", ") )?; if !*enabled { @@ -184,11 +180,8 @@ impl fmt::Display for BreakPoint { Ok(()) } #[cfg(not(feature = "no_object"))] - Self::AtProperty { - name: prop, - enabled, - } => { - write!(f, ".{}", prop)?; + Self::AtProperty { name, enabled } => { + write!(f, ".{name}")?; if !*enabled { f.write_str(" (disabled)")?; } @@ -230,11 +223,11 @@ impl BreakPoint { #[derive(Debug, Clone, Hash)] pub struct CallStackFrame { /// Function name. - pub fn_name: Identifier, + pub fn_name: ImmutableString, /// Copies of function call arguments, if any. pub args: crate::StaticVec, - /// Source of the function, empty if none. - pub source: Identifier, + /// Source of the function. + pub source: Option, /// [Position][`Position`] of the function call. pub pos: Position, } @@ -250,11 +243,10 @@ impl fmt::Display for CallStackFrame { fp.finish()?; if !self.pos.is_none() { - if self.source.is_empty() { - write!(f, " @ {:?}", self.pos)?; - } else { - write!(f, ": {} @ {:?}", self.source, self.pos)?; + if let Some(ref source) = self.source { + write!(f, ": {source}")?; } + write!(f, " @ {:?}", self.pos)?; } Ok(()) @@ -301,15 +293,15 @@ impl Debugger { #[inline(always)] pub(crate) fn push_call_stack_frame( &mut self, - fn_name: impl Into, + fn_name: ImmutableString, args: crate::StaticVec, - source: impl Into, + source: Option, pos: Position, ) { self.call_stack.push(CallStackFrame { fn_name: fn_name.into(), args, - source: source.into(), + source, pos, }); } @@ -336,7 +328,7 @@ impl Debugger { } /// Returns the first break-point triggered by a particular [`AST` Node][ASTNode]. #[must_use] - pub fn is_break_point(&self, src: &str, node: ASTNode) -> Option { + pub fn is_break_point(&self, src: Option<&str>, node: ASTNode) -> Option { let _src = src; self.break_points() @@ -348,11 +340,12 @@ impl Debugger { BreakPoint::AtPosition { pos, .. } if pos.is_none() => false, #[cfg(not(feature = "no_position"))] BreakPoint::AtPosition { source, pos, .. } if pos.is_beginning_of_line() => { - node.position().line().unwrap_or(0) == pos.line().unwrap() && _src == source + node.position().line().unwrap_or(0) == pos.line().unwrap() + && _src == source.as_ref().map(|s| s.as_str()) } #[cfg(not(feature = "no_position"))] BreakPoint::AtPosition { source, pos, .. } => { - node.position() == *pos && _src == source + node.position() == *pos && _src == source.as_ref().map(|s| s.as_str()) } BreakPoint::AtFunctionName { name, .. } => match node { ASTNode::Expr(Expr::FnCall(x, ..)) | ASTNode::Stmt(Stmt::FnCall(x, ..)) => { @@ -418,16 +411,16 @@ impl Engine { #[inline(always)] pub(crate) fn run_debugger<'a>( &self, - scope: &mut Scope, global: &mut GlobalRuntimeState, - lib: &[&Module], - this_ptr: &mut Option<&mut Dynamic>, + caches: &mut Caches, + lib: &[SharedModule], + scope: &mut Scope, + this_ptr: &mut Dynamic, node: impl Into>, - level: usize, ) -> RhaiResultOf<()> { if self.debugger.is_some() { if let Some(cmd) = - self.run_debugger_with_reset_raw(scope, global, lib, this_ptr, node, level)? + self.run_debugger_with_reset_raw(global, caches, lib, scope, this_ptr, node)? { global.debugger.status = cmd; } @@ -437,41 +430,41 @@ impl Engine { } /// Run the debugger callback if there is a debugging interface registered. /// - /// Returns `Some` if the debugger needs to be reactivated at the end of the block, statement or + /// Returns [`Some`] if the debugger needs to be reactivated at the end of the block, statement or /// function call. /// /// It is up to the [`Engine`] to reactivate the debugger. #[inline(always)] pub(crate) fn run_debugger_with_reset<'a>( &self, - scope: &mut Scope, global: &mut GlobalRuntimeState, - lib: &[&Module], - this_ptr: &mut Option<&mut Dynamic>, + caches: &mut Caches, + lib: &[SharedModule], + scope: &mut Scope, + this_ptr: &mut Dynamic, node: impl Into>, - level: usize, ) -> RhaiResultOf> { if self.debugger.is_some() { - self.run_debugger_with_reset_raw(scope, global, lib, this_ptr, node, level) + self.run_debugger_with_reset_raw(global, caches, lib, scope, this_ptr, node) } else { Ok(None) } } /// Run the debugger callback. /// - /// Returns `Some` if the debugger needs to be reactivated at the end of the block, statement or + /// Returns [`Some`] if the debugger needs to be reactivated at the end of the block, statement or /// function call. /// /// It is up to the [`Engine`] to reactivate the debugger. #[inline] pub(crate) fn run_debugger_with_reset_raw<'a>( &self, - scope: &mut Scope, global: &mut GlobalRuntimeState, - lib: &[&Module], - this_ptr: &mut Option<&mut Dynamic>, + caches: &mut Caches, + lib: &[SharedModule], + scope: &mut Scope, + this_ptr: &mut Dynamic, node: impl Into>, - level: usize, ) -> RhaiResultOf> { let node = node.into(); @@ -495,45 +488,37 @@ impl Engine { let event = match event { Some(e) => e, - None => { - if let Some(bp) = global.debugger.is_break_point(&global.source, node) { - DebuggerEvent::BreakPoint(bp) - } else { - return Ok(None); - } - } + None => match global.debugger.is_break_point(global.source(), node) { + Some(bp) => DebuggerEvent::BreakPoint(bp), + None => return Ok(None), + }, }; - self.run_debugger_raw(scope, global, lib, this_ptr, node, event, level) + self.run_debugger_raw(global, caches, lib, scope, this_ptr, node, event) } /// Run the debugger callback unconditionally. /// - /// Returns `Some` if the debugger needs to be reactivated at the end of the block, statement or + /// Returns [`Some`] if the debugger needs to be reactivated at the end of the block, statement or /// function call. /// /// It is up to the [`Engine`] to reactivate the debugger. #[inline] pub(crate) fn run_debugger_raw<'a>( &self, - scope: &mut Scope, global: &mut GlobalRuntimeState, - lib: &[&Module], - this_ptr: &mut Option<&mut Dynamic>, + caches: &mut Caches, + lib: &[SharedModule], + scope: &mut Scope, + this_ptr: &mut Dynamic, node: ASTNode<'a>, event: DebuggerEvent, - level: usize, ) -> Result, Box> { - let source = global.source.clone(); - let source = if source.is_empty() { - None - } else { - Some(source.as_str()) - }; - - let context = crate::EvalContext::new(self, scope, global, None, lib, this_ptr, level); + let src = global.source_raw().cloned(); + let src = src.as_ref().map(|s| s.as_str()); + let context = crate::EvalContext::new(self, global, caches, lib, scope, this_ptr); if let Some((.., ref on_debugger)) = self.debugger { - let command = on_debugger(context, event, node, source, node.position())?; + let command = on_debugger(context, event, node, src, node.position())?; match command { DebuggerCommand::Continue => { @@ -556,12 +541,12 @@ impl Engine { // Bump a level if it is a function call let level = match node { ASTNode::Expr(Expr::FnCall(..)) | ASTNode::Stmt(Stmt::FnCall(..)) => { - level + 1 + global.level + 1 } ASTNode::Stmt(Stmt::Expr(e)) if matches!(**e, Expr::FnCall(..)) => { - level + 1 + global.level + 1 } - _ => level, + _ => global.level, }; global.debugger.status = DebuggerStatus::FunctionExit(level); Ok(None) diff --git a/src/eval/eval_context.rs b/src/eval/eval_context.rs index 5ecad699..f8f2fd1a 100644 --- a/src/eval/eval_context.rs +++ b/src/eval/eval_context.rs @@ -1,42 +1,39 @@ //! Evaluation context. use super::{Caches, GlobalRuntimeState}; -use crate::{Dynamic, Engine, Module, Scope}; +use crate::{Dynamic, Engine, Module, Scope, SharedModule}; #[cfg(feature = "no_std")] use std::prelude::v1::*; /// Context of a script evaluation process. #[derive(Debug)] #[allow(dead_code)] -pub struct EvalContext<'a, 's, 'ps, 'g, 'pg, 'c, 'pc, 't, 'pt> { +pub struct EvalContext<'a, 's, 'ps, 'g, 'c, 't> { /// The current [`Engine`]. engine: &'a Engine, /// The current [`Scope`]. scope: &'s mut Scope<'ps>, /// The current [`GlobalRuntimeState`]. - global: &'g mut GlobalRuntimeState<'pg>, + global: &'g mut GlobalRuntimeState, /// The current [caches][Caches], if available. - caches: Option<&'c mut Caches<'pc>>, + caches: &'c mut Caches, /// The current stack of imported [modules][Module]. - lib: &'a [&'a Module], + lib: &'a [SharedModule], /// The current bound `this` pointer, if any. - this_ptr: &'t mut Option<&'pt mut Dynamic>, - /// The current nesting level of function calls. - level: usize, + this_ptr: &'t mut Dynamic, } -impl<'a, 's, 'ps, 'g, 'pg, 'c, 'pc, 't, 'pt> EvalContext<'a, 's, 'ps, 'g, 'pg, 'c, 'pc, 't, 'pt> { +impl<'a, 's, 'ps, 'g, 'c, 't> EvalContext<'a, 's, 'ps, 'g, 'c, 't> { /// Create a new [`EvalContext`]. #[inline(always)] #[must_use] pub fn new( engine: &'a Engine, + global: &'g mut GlobalRuntimeState, + caches: &'c mut Caches, + lib: &'a [SharedModule], scope: &'s mut Scope<'ps>, - global: &'g mut GlobalRuntimeState<'pg>, - caches: Option<&'c mut Caches<'pc>>, - lib: &'a [&'a Module], - this_ptr: &'t mut Option<&'pt mut Dynamic>, - level: usize, + this_ptr: &'t mut Dynamic, ) -> Self { Self { engine, @@ -45,7 +42,6 @@ impl<'a, 's, 'ps, 'g, 'pg, 'c, 'pc, 't, 'pt> EvalContext<'a, 's, 'ps, 'g, 'pg, ' caches, lib, this_ptr, - level, } } /// The current [`Engine`]. @@ -58,11 +54,7 @@ impl<'a, 's, 'ps, 'g, 'pg, 'c, 'pc, 't, 'pt> EvalContext<'a, 's, 'ps, 'g, 'pg, ' #[inline(always)] #[must_use] pub fn source(&self) -> Option<&str> { - if self.global.source.is_empty() { - None - } else { - Some(self.global.source.as_str()) - } + self.global.source() } /// The current [`Scope`]. #[inline(always)] @@ -108,39 +100,47 @@ impl<'a, 's, 'ps, 'g, 'pg, 'c, 'pc, 't, 'pt> EvalContext<'a, 's, 'ps, 'g, 'pg, ' #[cfg(feature = "internals")] #[inline(always)] #[must_use] - pub fn global_runtime_state_mut(&mut self) -> &mut &'g mut GlobalRuntimeState<'pg> { - &mut self.global + pub fn global_runtime_state_mut(&mut self) -> &mut GlobalRuntimeState { + self.global } /// Get an iterator over the namespaces containing definition of all script-defined functions. #[inline] pub fn iter_namespaces(&self) -> impl Iterator { - self.lib.iter().copied() + self.lib.iter().map(|m| m.as_ref()) } /// _(internals)_ The current set of namespaces containing definitions of all script-defined functions. /// Exported under the `internals` feature only. #[cfg(feature = "internals")] #[inline(always)] #[must_use] - pub const fn namespaces(&self) -> &[&Module] { + pub const fn namespaces(&self) -> &[SharedModule] { self.lib } /// The current bound `this` pointer, if any. - #[inline(always)] + #[inline] #[must_use] pub fn this_ptr(&self) -> Option<&Dynamic> { - self.this_ptr.as_ref().map(|v| &**v) + if self.this_ptr.is_null() { + None + } else { + Some(self.this_ptr) + } } /// Mutable reference to the current bound `this` pointer, if any. - #[inline(always)] + #[inline] #[must_use] - pub fn this_ptr_mut(&mut self) -> &mut Option<&'pt mut Dynamic> { - self.this_ptr + pub fn this_ptr_mut(&mut self) -> Option<&mut Dynamic> { + if self.this_ptr.is_null() { + None + } else { + Some(self.this_ptr) + } } /// The current nesting level of function calls. #[inline(always)] #[must_use] pub const fn call_level(&self) -> usize { - self.level + self.global.level } /// Evaluate an [expression tree][crate::Expression] within this [evaluation context][`EvalContext`]. @@ -177,32 +177,23 @@ impl<'a, 's, 'ps, 'g, 'pg, 'c, 'pc, 't, 'pt> EvalContext<'a, 's, 'ps, 'g, 'pg, ' ) -> crate::RhaiResult { let expr: &crate::ast::Expr = expr; - let mut new_caches = Caches::new(); - - let caches = match self.caches.as_mut() { - Some(c) => c, - None => &mut new_caches, - }; - match expr { crate::ast::Expr::Stmt(statements) => self.engine.eval_stmt_block( - self.scope, self.global, - caches, + self.caches, self.lib, + self.scope, self.this_ptr, statements, rewind_scope, - self.level, ), _ => self.engine.eval_expr( - self.scope, self.global, - caches, + self.caches, self.lib, + self.scope, self.this_ptr, expr, - self.level, ), } } diff --git a/src/eval/expr.rs b/src/eval/expr.rs index af762e20..0c18772a 100644 --- a/src/eval/expr.rs +++ b/src/eval/expr.rs @@ -1,19 +1,10 @@ //! Module defining functions for evaluating an expression. use super::{Caches, EvalContext, GlobalRuntimeState, Target}; -use crate::ast::{Expr, FnCallExpr, OpAssignment}; +use crate::ast::{Expr, OpAssignment}; use crate::engine::{KEYWORD_THIS, OP_CONCAT}; -use crate::eval::FnResolutionCacheEntry; -use crate::func::{ - calc_fn_params_hash, combine_hashes, gen_fn_call_signature, get_builtin_binary_op_fn, - CallableFunction, -}; use crate::types::dynamic::AccessMode; -use crate::{Dynamic, Engine, Module, Position, RhaiResult, RhaiResultOf, Scope, ERR}; -#[cfg(feature = "no_std")] -use hashbrown::hash_map::Entry; -#[cfg(not(feature = "no_std"))] -use std::collections::hash_map::Entry; +use crate::{Dynamic, Engine, Position, RhaiResult, RhaiResultOf, Scope, SharedModule, ERR}; use std::num::NonZeroUsize; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -27,18 +18,18 @@ impl Engine { &self, global: &GlobalRuntimeState, namespace: &crate::ast::Namespace, - ) -> Option> { + ) -> Option { assert!(!namespace.is_empty()); let root = namespace.root(); - // Qualified - check if the root module is directly indexed let index = if global.always_search_scope { None } else { namespace.index() }; + // Qualified - check if the root module is directly indexed if let Some(index) = index { let offset = global.num_imports() - index.get(); @@ -58,25 +49,26 @@ impl Engine { /// depending on whether the variable name is namespace-qualified. pub(crate) fn search_namespace<'s>( &self, - scope: &'s mut Scope, global: &mut GlobalRuntimeState, - lib: &[&Module], - this_ptr: &'s mut Option<&mut Dynamic>, + caches: &mut Caches, + lib: &[SharedModule], + + scope: &'s mut Scope, + this_ptr: &'s mut Dynamic, expr: &Expr, - level: usize, ) -> RhaiResultOf<(Target<'s>, Position)> { match expr { Expr::Variable(_, Some(_), _) => { - self.search_scope_only(scope, global, lib, this_ptr, expr, level) + self.search_scope_only(global, caches, lib, scope, this_ptr, expr) } Expr::Variable(v, None, _var_pos) => match &**v { // Normal variable access #[cfg(not(feature = "no_module"))] (_, ns, ..) if ns.is_empty() => { - self.search_scope_only(scope, global, lib, this_ptr, expr, level) + self.search_scope_only(global, caches, lib, scope, this_ptr, expr) } #[cfg(feature = "no_module")] - (_, (), ..) => self.search_scope_only(scope, global, lib, this_ptr, expr, level), + (_, (), ..) => self.search_scope_only(global, caches, lib, scope, this_ptr, expr), // Qualified variable access #[cfg(not(feature = "no_module"))] @@ -141,22 +133,24 @@ impl Engine { /// Panics if `expr` is not [`Expr::Variable`]. pub(crate) fn search_scope_only<'s>( &self, - scope: &'s mut Scope, global: &mut GlobalRuntimeState, - lib: &[&Module], - this_ptr: &'s mut Option<&mut Dynamic>, + caches: &mut Caches, + lib: &[SharedModule], + + scope: &'s mut Scope, + this_ptr: &'s mut Dynamic, expr: &Expr, - level: usize, ) -> RhaiResultOf<(Target<'s>, Position)> { // Make sure that the pointer indirection is taken only when absolutely necessary. let (index, var_pos) = match expr { // Check if the variable is `this` Expr::Variable(v, None, pos) if v.0.is_none() && v.3 == KEYWORD_THIS => { - return this_ptr.as_mut().map_or_else( - || Err(ERR::ErrorUnboundThis(*pos).into()), - |val| Ok(((*val).into(), *pos)), - ) + return if this_ptr.is_null() { + Err(ERR::ErrorUnboundThis(*pos).into()) + } else { + Ok((this_ptr.into(), *pos)) + }; } _ if global.always_search_scope => (0, expr.start_position()), Expr::Variable(.., Some(i), pos) => (i.get() as usize, *pos), @@ -165,7 +159,7 @@ impl Engine { Expr::Variable(v, None, pos) if lib .iter() - .flat_map(|&m| m.iter_script_fn()) + .flat_map(|m| m.iter_script_fn()) .any(|(_, _, f, ..)| f == v.3.as_str()) => { let val: Dynamic = @@ -178,7 +172,7 @@ impl Engine { // Check the variable resolver, if any if let Some(ref resolve_var) = self.resolve_var { - let context = EvalContext::new(self, scope, global, None, lib, this_ptr, level); + let context = EvalContext::new(self, global, caches, lib, scope, this_ptr); let var_name = expr.get_variable_name(true).expect("`Expr::Variable`"); match resolve_var(var_name, index, context) { Ok(Some(mut result)) => { @@ -196,8 +190,8 @@ impl Engine { // Find the variable in the scope let var_name = expr.get_variable_name(true).expect("`Expr::Variable`"); - match scope.get_index(var_name) { - Some((index, _)) => index, + match scope.search(var_name) { + Some(index) => index, None => { return match self.global_modules.iter().find_map(|m| m.get_var(var_name)) { Some(val) => Ok((val.into(), var_pos)), @@ -214,136 +208,6 @@ impl Engine { Ok((val.into(), var_pos)) } - /// Evaluate a function call expression. - pub(crate) fn eval_fn_call_expr( - &self, - scope: &mut Scope, - global: &mut GlobalRuntimeState, - caches: &mut Caches, - lib: &[&Module], - this_ptr: &mut Option<&mut Dynamic>, - expr: &FnCallExpr, - pos: Position, - level: usize, - ) -> RhaiResult { - let FnCallExpr { - name, hashes, args, .. - } = expr; - - // Short-circuit native binary operator call if under Fast Operators mode - if expr.is_native_operator && self.fast_operators() && (args.len() == 1 || args.len() == 2) - { - let mut lhs = self - .get_arg_value(scope, global, caches, lib, this_ptr, &args[0], level)? - .0 - .flatten(); - - let mut rhs = if args.len() == 2 { - self.get_arg_value(scope, global, caches, lib, this_ptr, &args[1], level)? - .0 - .flatten() - } else { - Dynamic::UNIT - }; - - let mut operands = [&mut lhs, &mut rhs]; - let operands = if args.len() == 2 { - &mut operands[..] - } else { - &mut operands[0..1] - }; - - let hash = calc_fn_params_hash(operands.iter().map(|a| a.type_id())); - let hash = combine_hashes(hashes.native, hash); - - let cache = caches.fn_resolution_cache_mut(); - let local_entry: CallableFunction; - - let func = match cache.map.entry(hash) { - Entry::Vacant(entry) => { - let func = if args.len() == 2 { - get_builtin_binary_op_fn(name, operands[0], operands[1]) - } else { - None - }; - - if let Some(f) = func { - if cache.filter.is_absent_and_set(hash) { - // Do not cache "one-hit wonders" - local_entry = CallableFunction::from_fn_builtin(f); - &local_entry - } else { - // Cache repeated calls - &entry - .insert(Some(FnResolutionCacheEntry { - func: CallableFunction::from_fn_builtin(f), - source: None, - })) - .as_ref() - .unwrap() - .func - } - } else { - let result = self.exec_fn_call( - None, global, caches, lib, name, *hashes, operands, false, false, pos, - level, - ); - return result.map(|(v, ..)| v); - } - } - Entry::Occupied(entry) => { - if let Some(entry) = entry.into_mut() { - &entry.func - } else { - let sig = gen_fn_call_signature(self, name, operands); - return Err(ERR::ErrorFunctionNotFound(sig, pos).into()); - } - } - }; - - let context = (self, name, None, &*global, lib, pos, level).into(); - let result = if func.is_plugin_fn() { - func.get_plugin_fn().unwrap().call(context, operands) - } else { - func.get_native_fn().unwrap()(context, operands) - }; - return self.check_return_value(result, pos); - } - - #[cfg(not(feature = "no_module"))] - if !expr.namespace.is_empty() { - // Qualified function call - let hash = hashes.native; - let namespace = &expr.namespace; - - return self.make_qualified_function_call( - scope, global, caches, lib, this_ptr, namespace, name, args, hash, pos, level, - ); - } - - // Normal function call - let (first_arg, args) = args.split_first().map_or_else( - || (None, args.as_ref()), - |(first, rest)| (Some(first), rest), - ); - - self.make_function_call( - scope, - global, - caches, - lib, - this_ptr, - name, - first_arg, - args, - *hashes, - expr.capture_parent_scope, - expr.is_native_operator, - pos, - level, - ) - } - /// Evaluate an expression. // // # Implementation Notes @@ -354,34 +218,30 @@ impl Engine { // Errors that are not recoverable, such as system errors or safety errors, can use `?`. pub(crate) fn eval_expr( &self, - scope: &mut Scope, global: &mut GlobalRuntimeState, caches: &mut Caches, - lib: &[&Module], - this_ptr: &mut Option<&mut Dynamic>, + lib: &[SharedModule], + + scope: &mut Scope, + this_ptr: &mut Dynamic, expr: &Expr, - level: usize, ) -> RhaiResult { // Coded this way for better branch prediction. // Popular branches are lifted out of the `match` statement into their own branches. // Function calls should account for a relatively larger portion of expressions because // binary operators are also function calls. - if let Expr::FnCall(x, ..) = expr { + if let Expr::FnCall(x, pos) = expr { #[cfg(feature = "debugging")] - let reset_debugger = - self.run_debugger_with_reset(scope, global, lib, this_ptr, expr, level)?; - - #[cfg(not(feature = "unchecked"))] - self.inc_operations(&mut global.num_operations, expr.position())?; - - let result = - self.eval_fn_call_expr(scope, global, caches, lib, this_ptr, x, x.pos, level); - + let reset = self.run_debugger_with_reset(global, caches, lib, scope, this_ptr, expr)?; #[cfg(feature = "debugging")] - global.debugger.reset_status(reset_debugger); + let global = &mut *crate::types::RestoreOnDrop::lock(global, move |g| { + g.debugger.reset_status(reset) + }); - return result; + self.track_operation(global, expr.position())?; + + return self.eval_fn_call_expr(global, caches, lib, scope, this_ptr, x, *pos); } // Then variable access. @@ -389,30 +249,32 @@ impl Engine { // will cost more than the mis-predicted `match` branch. if let Expr::Variable(x, index, var_pos) = expr { #[cfg(feature = "debugging")] - self.run_debugger(scope, global, lib, this_ptr, expr, level)?; + self.run_debugger(global, caches, lib, scope, this_ptr, expr)?; - #[cfg(not(feature = "unchecked"))] - self.inc_operations(&mut global.num_operations, expr.position())?; + self.track_operation(global, expr.position())?; return if index.is_none() && x.0.is_none() && x.3 == KEYWORD_THIS { - this_ptr - .as_deref() - .cloned() - .ok_or_else(|| ERR::ErrorUnboundThis(*var_pos).into()) + if this_ptr.is_null() { + ERR::ErrorUnboundThis(*var_pos).into() + } else { + Ok(this_ptr.clone()) + } } else { - self.search_namespace(scope, global, lib, this_ptr, expr, level) + self.search_namespace(global, caches, lib, scope, this_ptr, expr) .map(|(val, ..)| val.take_or_clone()) }; } #[cfg(feature = "debugging")] - let reset_debugger = - self.run_debugger_with_reset(scope, global, lib, this_ptr, expr, level)?; + let reset = self.run_debugger_with_reset(global, caches, lib, scope, this_ptr, expr)?; + #[cfg(feature = "debugging")] + let global = &mut *crate::types::RestoreOnDrop::lock(global, move |g| { + g.debugger.reset_status(reset) + }); - #[cfg(not(feature = "unchecked"))] - self.inc_operations(&mut global.num_operations, expr.position())?; + self.track_operation(global, expr.position())?; - let result = match expr { + match expr { // Constants Expr::DynamicConstant(x, ..) => Ok(x.as_ref().clone()), Expr::IntegerConstant(x, ..) => Ok((*x).into()), @@ -427,163 +289,115 @@ impl Engine { Expr::InterpolatedString(x, _) => { let mut concat = self.get_interned_string("").into(); let target = &mut concat; - let mut result = Ok(Dynamic::UNIT); let mut op_info = OpAssignment::new_op_assignment(OP_CONCAT, Position::NONE); let root = ("", Position::NONE); - for expr in &**x { - let item = - match self.eval_expr(scope, global, caches, lib, this_ptr, expr, level) { - Ok(r) => r, - err => { - result = err; - break; - } - }; + let result = x + .iter() + .try_for_each(|expr| { + let item = self.eval_expr(global, caches, lib, scope, this_ptr, expr)?; - op_info.pos = expr.start_position(); + op_info.pos = expr.start_position(); - if let Err(err) = self - .eval_op_assignment(global, caches, lib, op_info, target, root, item, level) - { - result = Err(err); - break; - } - } + self.eval_op_assignment(global, caches, lib, &op_info, target, root, item) + }) + .map(|_| concat.take_or_clone()); - self.check_return_value( - result.map(|_| concat.take_or_clone()), - expr.start_position(), - ) + self.check_return_value(result, expr.start_position()) } #[cfg(not(feature = "no_index"))] Expr::Array(x, ..) => { - let mut array = crate::Array::with_capacity(x.len()); - let mut result = Ok(Dynamic::UNIT); - #[cfg(not(feature = "unchecked"))] - let mut sizes = (0, 0, 0); + let mut total_data_sizes = (0, 0, 0); - for item_expr in &**x { - let value = match self - .eval_expr(scope, global, caches, lib, this_ptr, item_expr, level) - { - Ok(r) => r.flatten(), - err => { - result = err; - break; - } - }; + x.iter() + .try_fold( + crate::Array::with_capacity(x.len()), + |mut array, item_expr| { + let value = self + .eval_expr(global, caches, lib, scope, this_ptr, item_expr)? + .flatten(); - #[cfg(not(feature = "unchecked"))] - let val_sizes = Self::calc_data_sizes(&value, true); + #[cfg(not(feature = "unchecked"))] + if self.has_data_size_limit() { + let val_sizes = Self::calc_data_sizes(&value, true); - array.push(value); + total_data_sizes = ( + total_data_sizes.0 + val_sizes.0, + total_data_sizes.1 + val_sizes.1, + total_data_sizes.2 + val_sizes.2, + ); + self.raise_err_if_over_data_size_limit(total_data_sizes) + .map_err(|err| err.fill_position(item_expr.position()))?; + } - #[cfg(not(feature = "unchecked"))] - if self.has_data_size_limit() { - sizes = ( - sizes.0 + val_sizes.0, - sizes.1 + val_sizes.1, - sizes.2 + val_sizes.2, - ); - self.raise_err_if_over_data_size_limit(sizes, item_expr.position())?; - } - } + array.push(value); - result.map(|_| array.into()) + Ok(array) + }, + ) + .map(Into::into) } #[cfg(not(feature = "no_object"))] Expr::Map(x, ..) => { - let mut map = x.1.clone(); - let mut result = Ok(Dynamic::UNIT); - #[cfg(not(feature = "unchecked"))] - let mut sizes = (0, 0, 0); + let mut total_data_sizes = (0, 0, 0); - for (key, value_expr) in &x.0 { - let value = match self - .eval_expr(scope, global, caches, lib, this_ptr, value_expr, level) - { - Ok(r) => r.flatten(), - err => { - result = err; - break; + x.0.iter() + .try_fold(x.1.clone(), |mut map, (key, value_expr)| { + let value = self + .eval_expr(global, caches, lib, scope, this_ptr, value_expr)? + .flatten(); + + #[cfg(not(feature = "unchecked"))] + if self.has_data_size_limit() { + let delta = Self::calc_data_sizes(&value, true); + total_data_sizes = ( + total_data_sizes.0 + delta.0, + total_data_sizes.1 + delta.1, + total_data_sizes.2 + delta.2, + ); + self.raise_err_if_over_data_size_limit(total_data_sizes) + .map_err(|err| err.fill_position(value_expr.position()))?; } - }; - #[cfg(not(feature = "unchecked"))] - let delta = Self::calc_data_sizes(&value, true); + *map.get_mut(key.as_str()).unwrap() = value; - *map.get_mut(key.as_str()).unwrap() = value; - - #[cfg(not(feature = "unchecked"))] - if self.has_data_size_limit() { - sizes = (sizes.0 + delta.0, sizes.1 + delta.1, sizes.2 + delta.2); - self.raise_err_if_over_data_size_limit(sizes, value_expr.position())?; - } - } - - result.map(|_| map.into()) + Ok(map) + }) + .map(Into::into) } - Expr::And(x, ..) => { - let lhs = self - .eval_expr(scope, global, caches, lib, this_ptr, &x.lhs, level) - .and_then(|v| { - v.as_bool().map_err(|typ| { - self.make_type_mismatch_err::(typ, x.lhs.position()) - }) - }); + Expr::And(x, ..) => Ok((self + .eval_expr(global, caches, lib, scope, this_ptr, &x.lhs)? + .as_bool() + .map_err(|typ| self.make_type_mismatch_err::(typ, x.lhs.position()))? + && self + .eval_expr(global, caches, lib, scope, this_ptr, &x.rhs)? + .as_bool() + .map_err(|typ| self.make_type_mismatch_err::(typ, x.rhs.position()))?) + .into()), - if let Ok(true) = lhs { - self.eval_expr(scope, global, caches, lib, this_ptr, &x.rhs, level) - .and_then(|v| { - v.as_bool() - .map_err(|typ| { - self.make_type_mismatch_err::(typ, x.rhs.position()) - }) - .map(Into::into) - }) - } else { - lhs.map(Into::into) - } - } - - Expr::Or(x, ..) => { - let lhs = self - .eval_expr(scope, global, caches, lib, this_ptr, &x.lhs, level) - .and_then(|v| { - v.as_bool().map_err(|typ| { - self.make_type_mismatch_err::(typ, x.lhs.position()) - }) - }); - - if let Ok(false) = lhs { - self.eval_expr(scope, global, caches, lib, this_ptr, &x.rhs, level) - .and_then(|v| { - v.as_bool() - .map_err(|typ| { - self.make_type_mismatch_err::(typ, x.rhs.position()) - }) - .map(Into::into) - }) - } else { - lhs.map(Into::into) - } - } + Expr::Or(x, ..) => Ok((self + .eval_expr(global, caches, lib, scope, this_ptr, &x.lhs)? + .as_bool() + .map_err(|typ| self.make_type_mismatch_err::(typ, x.lhs.position()))? + || self + .eval_expr(global, caches, lib, scope, this_ptr, &x.rhs)? + .as_bool() + .map_err(|typ| self.make_type_mismatch_err::(typ, x.rhs.position()))?) + .into()), Expr::Coalesce(x, ..) => { - let lhs = self.eval_expr(scope, global, caches, lib, this_ptr, &x.lhs, level); + let value = self.eval_expr(global, caches, lib, scope, this_ptr, &x.lhs)?; - match lhs { - Ok(value) if value.is::<()>() => { - self.eval_expr(scope, global, caches, lib, this_ptr, &x.rhs, level) - } - Ok(_) | Err(_) => lhs, + if value.is::<()>() { + self.eval_expr(global, caches, lib, scope, this_ptr, &x.rhs) + } else { + Ok(value) } } @@ -601,8 +415,7 @@ impl Engine { *pos, )) })?; - let mut context = - EvalContext::new(self, scope, global, Some(caches), lib, this_ptr, level); + let mut context = EvalContext::new(self, global, caches, lib, scope, this_ptr); let result = (custom_def.func)(&mut context, &expressions, &custom.state); @@ -610,26 +423,19 @@ impl Engine { } Expr::Stmt(x) if x.is_empty() => Ok(Dynamic::UNIT), - Expr::Stmt(x) => { - self.eval_stmt_block(scope, global, caches, lib, this_ptr, x, true, level) - } + Expr::Stmt(x) => self.eval_stmt_block(global, caches, lib, scope, this_ptr, x, true), #[cfg(not(feature = "no_index"))] Expr::Index(..) => { - self.eval_dot_index_chain(scope, global, caches, lib, this_ptr, expr, level, None) + self.eval_dot_index_chain(global, caches, lib, scope, this_ptr, expr, &mut None) } #[cfg(not(feature = "no_object"))] Expr::Dot(..) => { - self.eval_dot_index_chain(scope, global, caches, lib, this_ptr, expr, level, None) + self.eval_dot_index_chain(global, caches, lib, scope, this_ptr, expr, &mut None) } _ => unreachable!("expression cannot be evaluated: {:?}", expr), - }; - - #[cfg(feature = "debugging")] - global.debugger.reset_status(reset_debugger); - - result + } } } diff --git a/src/eval/global_state.rs b/src/eval/global_state.rs index 775fdda0..94dcb837 100644 --- a/src/eval/global_state.rs +++ b/src/eval/global_state.rs @@ -1,15 +1,15 @@ //! Global runtime state. -use crate::{Dynamic, Engine, Identifier}; +use crate::{Dynamic, Engine, ImmutableString}; +use std::fmt; #[cfg(feature = "no_std")] use std::prelude::v1::*; -use std::{fmt, marker::PhantomData}; /// Collection of globally-defined constants. #[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_function"))] pub type GlobalConstants = - crate::Shared>>; + crate::Shared>>; /// _(internals)_ Global runtime states. /// Exported under the `internals` feature only. @@ -22,21 +22,24 @@ pub type GlobalConstants = // Most usage will be looking up a particular key from the list and then getting the module that // corresponds to that key. #[derive(Clone)] -pub struct GlobalRuntimeState<'a> { - /// Stack of module names. +pub struct GlobalRuntimeState { + /// Names of imported [modules][crate::Module]. #[cfg(not(feature = "no_module"))] - keys: crate::StaticVec, + imports: crate::StaticVec, /// Stack of imported [modules][crate::Module]. #[cfg(not(feature = "no_module"))] - modules: crate::StaticVec>, + modules: crate::StaticVec, /// Source of the current context. /// /// No source if the string is empty. - pub source: Identifier, + pub source: Option, /// Number of operations performed. pub num_operations: u64, /// Number of modules loaded. + #[cfg(not(feature = "no_module"))] pub num_modules_loaded: usize, + /// The current nesting level of function calls. + pub level: usize, /// Level of the current scope. /// /// The global (root) level is zero, a new block (or function call) is one level higher, and so on. @@ -69,24 +72,24 @@ pub struct GlobalRuntimeState<'a> { /// Debugging interface. #[cfg(feature = "debugging")] pub debugger: super::Debugger, - /// Take care of the lifetime parameter. - dummy: PhantomData<&'a ()>, } -impl GlobalRuntimeState<'_> { +impl GlobalRuntimeState { /// Create a new [`GlobalRuntimeState`] based on an [`Engine`]. #[inline(always)] #[must_use] pub fn new(engine: &Engine) -> Self { Self { #[cfg(not(feature = "no_module"))] - keys: crate::StaticVec::new_const(), + imports: crate::StaticVec::new_const(), #[cfg(not(feature = "no_module"))] modules: crate::StaticVec::new_const(), - source: Identifier::new_const(), + source: None, num_operations: 0, + #[cfg(not(feature = "no_module"))] num_modules_loaded: 0, scope_level: 0, + level: 0, always_search_scope: false, #[cfg(not(feature = "no_module"))] embedded_module_resolver: None, @@ -105,14 +108,11 @@ impl GlobalRuntimeState<'_> { } else { crate::eval::DebuggerStatus::CONTINUE }, - if let Some((ref init, ..)) = engine.debugger { - init(engine) - } else { - Dynamic::UNIT + match engine.debugger { + Some((ref init, ..)) => init(engine), + None => Dynamic::UNIT, }, ), - - dummy: PhantomData::default(), } } /// Get the length of the stack of globally-imported [modules][crate::Module]. @@ -122,7 +122,7 @@ impl GlobalRuntimeState<'_> { #[inline(always)] #[must_use] pub fn num_imports(&self) -> usize { - self.keys.len() + self.modules.len() } /// Get the globally-imported [module][crate::Module] at a particular index. /// @@ -130,7 +130,7 @@ impl GlobalRuntimeState<'_> { #[cfg(not(feature = "no_module"))] #[inline(always)] #[must_use] - pub fn get_shared_import(&self, index: usize) -> Option> { + pub fn get_shared_import(&self, index: usize) -> Option { self.modules.get(index).cloned() } /// Get a mutable reference to the globally-imported [module][crate::Module] at a @@ -144,7 +144,7 @@ impl GlobalRuntimeState<'_> { pub(crate) fn get_shared_import_mut( &mut self, index: usize, - ) -> Option<&mut crate::Shared> { + ) -> Option<&mut crate::SharedModule> { self.modules.get_mut(index) } /// Get the index of a globally-imported [module][crate::Module] by name. @@ -154,13 +154,11 @@ impl GlobalRuntimeState<'_> { #[inline] #[must_use] pub fn find_import(&self, name: &str) -> Option { - let len = self.keys.len(); - - self.keys + self.imports .iter() .rev() .position(|key| key.as_str() == name) - .map(|i| len - 1 - i) + .map(|i| self.imports.len() - 1 - i) } /// Push an imported [module][crate::Module] onto the stack. /// @@ -169,10 +167,10 @@ impl GlobalRuntimeState<'_> { #[inline(always)] pub fn push_import( &mut self, - name: impl Into, - module: impl Into>, + name: impl Into, + module: impl Into, ) { - self.keys.push(name.into()); + self.imports.push(name.into()); self.modules.push(module.into()); } /// Truncate the stack of globally-imported [modules][crate::Module] to a particular length. @@ -181,43 +179,51 @@ impl GlobalRuntimeState<'_> { #[cfg(not(feature = "no_module"))] #[inline(always)] pub fn truncate_imports(&mut self, size: usize) { - self.keys.truncate(size); + self.imports.truncate(size); self.modules.truncate(size); } /// Get an iterator to the stack of globally-imported [modules][crate::Module] in reverse order. /// /// Not available under `no_module`. #[cfg(not(feature = "no_module"))] - #[allow(dead_code)] #[inline] pub fn iter_imports(&self) -> impl Iterator { - self.keys + self.imports .iter() + .zip(self.modules.iter()) .rev() - .zip(self.modules.iter().rev()) .map(|(name, module)| (name.as_str(), &**module)) } /// Get an iterator to the stack of globally-imported [modules][crate::Module] in reverse order. /// /// Not available under `no_module`. #[cfg(not(feature = "no_module"))] - #[allow(dead_code)] #[inline] pub(crate) fn iter_imports_raw( &self, - ) -> impl Iterator)> { - self.keys.iter().rev().zip(self.modules.iter().rev()) + ) -> impl Iterator { + self.imports.iter().zip(self.modules.iter()).rev() } /// Get an iterator to the stack of globally-imported [modules][crate::Module] in forward order. /// /// Not available under `no_module`. #[cfg(not(feature = "no_module"))] - #[allow(dead_code)] #[inline] pub fn scan_imports_raw( &self, - ) -> impl Iterator)> { - self.keys.iter().zip(self.modules.iter()) + ) -> impl Iterator { + self.imports.iter().zip(self.modules.iter()) + } + /// Can the particular function with [`Dynamic`] parameter(s) exist in the stack of + /// globally-imported [modules][crate::Module]? + /// + /// Not available under `no_module`. + #[cfg(not(feature = "no_module"))] + #[inline(always)] + pub(crate) fn may_contain_dynamic_fn(&self, hash_script: u64) -> bool { + self.modules + .iter() + .any(|m| m.may_contain_dynamic_fn(hash_script)) } /// Does the specified function hash key exist in the stack of globally-imported /// [modules][crate::Module]? @@ -240,11 +246,11 @@ impl GlobalRuntimeState<'_> { pub fn get_qualified_fn( &self, hash: u64, - ) -> Option<(&crate::func::CallableFunction, Option<&str>)> { + ) -> Option<(&crate::func::CallableFunction, Option<&ImmutableString>)> { self.modules .iter() .rev() - .find_map(|m| m.get_qualified_fn(hash).map(|f| (f, m.id()))) + .find_map(|m| m.get_qualified_fn(hash).map(|f| (f, m.id_raw()))) } /// Does the specified [`TypeId`][std::any::TypeId] iterator exist in the stack of /// globally-imported [modules][crate::Module]? @@ -271,14 +277,17 @@ impl GlobalRuntimeState<'_> { .find_map(|m| m.get_qualified_iter(id)) } /// Get the current source. - #[inline] + #[inline(always)] #[must_use] pub fn source(&self) -> Option<&str> { - if self.source.is_empty() { - None - } else { - Some(self.source.as_str()) - } + self.source.as_ref().map(|s| s.as_str()) + } + /// Get the current source. + #[inline(always)] + #[must_use] + #[allow(dead_code)] + pub(crate) const fn source_raw(&self) -> Option<&ImmutableString> { + self.source.as_ref() } /// Get the pre-calculated index getter hash. #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] @@ -309,67 +318,64 @@ impl GlobalRuntimeState<'_> { } #[cfg(not(feature = "no_module"))] -impl IntoIterator for GlobalRuntimeState<'_> { - type Item = (crate::ImmutableString, crate::Shared); - type IntoIter = std::iter::Zip< - std::iter::Rev>, - std::iter::Rev; 3]>>, +impl IntoIterator for GlobalRuntimeState { + type Item = (ImmutableString, crate::SharedModule); + type IntoIter = std::iter::Rev< + std::iter::Zip< + smallvec::IntoIter<[ImmutableString; crate::STATIC_VEC_INLINE_SIZE]>, + smallvec::IntoIter<[crate::SharedModule; crate::STATIC_VEC_INLINE_SIZE]>, + >, >; - #[inline] fn into_iter(self) -> Self::IntoIter { - self.keys - .into_iter() - .rev() - .zip(self.modules.into_iter().rev()) + self.imports.into_iter().zip(self.modules.into_iter()).rev() } } #[cfg(not(feature = "no_module"))] -impl<'a> IntoIterator for &'a GlobalRuntimeState<'_> { - type Item = (&'a crate::ImmutableString, &'a crate::Shared); - type IntoIter = std::iter::Zip< - std::iter::Rev>, - std::iter::Rev>>, +impl<'a> IntoIterator for &'a GlobalRuntimeState { + type Item = (&'a ImmutableString, &'a crate::SharedModule); + type IntoIter = std::iter::Rev< + std::iter::Zip< + std::slice::Iter<'a, ImmutableString>, + std::slice::Iter<'a, crate::SharedModule>, + >, >; - #[inline] fn into_iter(self) -> Self::IntoIter { - let x = self.keys.iter().rev().zip(self.modules.iter().rev()); - x + self.imports.iter().zip(self.modules.iter()).rev() } } #[cfg(not(feature = "no_module"))] -impl, M: Into>> Extend<(K, M)> - for GlobalRuntimeState<'_> -{ +impl, M: Into> Extend<(K, M)> for GlobalRuntimeState { #[inline] fn extend>(&mut self, iter: T) { for (k, m) in iter { - self.keys.push(k.into()); + self.imports.push(k.into()); self.modules.push(m.into()); } } } -impl fmt::Debug for GlobalRuntimeState<'_> { - #[inline] +impl fmt::Debug for GlobalRuntimeState { + #[cold] + #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut f = f.debug_struct("GlobalRuntimeState"); #[cfg(not(feature = "no_module"))] - f.field("imports", &self.keys.iter().zip(self.modules.iter())); + f.field("imports", &self.scan_imports_raw().collect::>()); f.field("source", &self.source) - .field("num_operations", &self.num_operations) - .field("num_modules_loaded", &self.num_modules_loaded); + .field("num_operations", &self.num_operations); #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] f.field("fn_hash_indexing", &self.fn_hash_indexing); #[cfg(not(feature = "no_module"))] - f.field("embedded_module_resolver", &self.embedded_module_resolver); + f.field("num_modules_loaded", &self.num_modules_loaded) + .field("embedded_module_resolver", &self.embedded_module_resolver); #[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_function"))] diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 2daf2432..1daf85ae 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -22,3 +22,36 @@ pub use eval_context::EvalContext; pub use global_state::GlobalConstants; pub use global_state::GlobalRuntimeState; pub use target::{calc_index, calc_offset_len, Target}; + +#[cfg(feature = "unchecked")] +mod unchecked { + use crate::{eval::GlobalRuntimeState, Dynamic, Engine, Position, RhaiResult, RhaiResultOf}; + + impl Engine { + /// Check if the number of operations stay within limit. + #[inline(always)] + pub(crate) const fn track_operation( + &self, + _: &GlobalRuntimeState, + _: Position, + ) -> RhaiResultOf<()> { + Ok(()) + } + + /// Check whether the size of a [`Dynamic`] is within limits. + #[inline(always)] + pub(crate) const fn check_data_size(&self, _: &Dynamic, _: Position) -> RhaiResultOf<()> { + Ok(()) + } + + /// Check a result to ensure that it is valid. + #[inline(always)] + pub(crate) const fn check_return_value( + &self, + result: RhaiResult, + _: Position, + ) -> RhaiResult { + result + } + } +} diff --git a/src/eval/stmt.rs b/src/eval/stmt.rs index 60587b4f..9a086ed9 100644 --- a/src/eval/stmt.rs +++ b/src/eval/stmt.rs @@ -5,11 +5,10 @@ use crate::api::events::VarDefInfo; use crate::ast::{ ASTFlags, BinaryExpr, Expr, Ident, OpAssignment, Stmt, SwitchCasesCollection, TryCatchBlock, }; -use crate::func::get_hasher; +use crate::func::{get_builtin_op_assignment_fn, get_hasher}; use crate::types::dynamic::{AccessMode, Union}; -use crate::{ - Dynamic, Engine, ImmutableString, Module, Position, RhaiResult, RhaiResultOf, Scope, ERR, INT, -}; +use crate::types::RestoreOnDrop; +use crate::{Dynamic, Engine, Position, RhaiResult, RhaiResultOf, Scope, SharedModule, ERR, INT}; use std::hash::{Hash, Hasher}; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -25,49 +24,64 @@ impl Engine { // Errors that are not recoverable, such as system errors or safety errors, can use `?`. pub(crate) fn eval_stmt_block( &self, - scope: &mut Scope, global: &mut GlobalRuntimeState, caches: &mut Caches, - lib: &[&Module], - this_ptr: &mut Option<&mut Dynamic>, + lib: &[SharedModule], + scope: &mut Scope, + this_ptr: &mut Dynamic, statements: &[Stmt], restore_orig_state: bool, - level: usize, ) -> RhaiResult { if statements.is_empty() { return Ok(Dynamic::UNIT); } - let orig_always_search_scope = global.always_search_scope; + // Restore scope at end of block if necessary let orig_scope_len = scope.len(); + let scope = &mut *RestoreOnDrop::lock_if(restore_orig_state, scope, move |s| { + s.rewind(orig_scope_len); + }); + + // Restore global state at end of block if necessary + let orig_always_search_scope = global.always_search_scope; #[cfg(not(feature = "no_module"))] let orig_imports_len = global.num_imports(); - let orig_fn_resolution_caches_len = caches.fn_resolution_caches_len(); if restore_orig_state { global.scope_level += 1; } - let mut result = Ok(Dynamic::UNIT); + let global = &mut *RestoreOnDrop::lock_if(restore_orig_state, global, move |g| { + g.scope_level -= 1; - for stmt in statements { + #[cfg(not(feature = "no_module"))] + g.truncate_imports(orig_imports_len); + + // The impact of new local variables goes away at the end of a block + // because any new variables introduced will go out of scope + g.always_search_scope = orig_always_search_scope; + }); + + // Pop new function resolution caches at end of block + let orig_fn_resolution_caches_len = caches.fn_resolution_caches_len(); + let caches = &mut *RestoreOnDrop::lock(caches, move |c| { + c.rewind_fn_resolution_caches(orig_fn_resolution_caches_len) + }); + + // Run the statements + statements.iter().try_fold(Dynamic::UNIT, |_, stmt| { #[cfg(not(feature = "no_module"))] let imports_len = global.num_imports(); - result = self.eval_stmt( - scope, + let result = self.eval_stmt( global, caches, lib, + scope, this_ptr, stmt, restore_orig_state, - level, - ); - - if result.is_err() { - break; - } + )?; #[cfg(not(feature = "no_module"))] if matches!(stmt, Stmt::Import(..)) { @@ -78,10 +92,11 @@ impl Engine { .skip(imports_len) .any(|(.., m)| m.contains_indexed_global_functions()) { + // Different scenarios where the cache must be cleared - notice that this is + // expensive as all function resolutions must start again if caches.fn_resolution_caches_len() > orig_fn_resolution_caches_len { // When new module is imported with global functions and there is already - // a new cache, clear it - notice that this is expensive as all function - // resolutions must start again + // a new cache, just clear it caches.fn_resolution_cache_mut().clear(); } else if restore_orig_state { // When new module is imported with global functions, push a new cache @@ -92,23 +107,9 @@ impl Engine { } } } - } - // If imports list is modified, pop the functions lookup cache - caches.rewind_fn_resolution_caches(orig_fn_resolution_caches_len); - - if restore_orig_state { - scope.rewind(orig_scope_len); - global.scope_level -= 1; - #[cfg(not(feature = "no_module"))] - global.truncate_imports(orig_imports_len); - - // The impact of new local variables goes away at the end of a block - // because any new variables introduced will go out of scope - global.always_search_scope = orig_always_search_scope; - } - - result + Ok(result) + }) } /// Evaluate an op-assignment statement. @@ -116,72 +117,75 @@ impl Engine { &self, global: &mut GlobalRuntimeState, caches: &mut Caches, - lib: &[&Module], - op_info: OpAssignment, + lib: &[SharedModule], + op_info: &OpAssignment, target: &mut Target, root: (&str, Position), - new_val: Dynamic, - level: usize, + mut new_val: Dynamic, ) -> RhaiResultOf<()> { + // Assignment to constant variable? if target.is_read_only() { - // Assignment to constant variable return Err(ERR::ErrorAssignmentToConstant(root.0.to_string(), root.1).into()); } - let mut new_val = new_val; - if op_info.is_op_assignment() { let OpAssignment { hash_op_assign, hash_op, - op_assign, - op, + op_assign: op_assign_token, + op: op_token, pos: op_pos, } = op_info; let mut lock_guard = target.write_lock::().unwrap(); - let hash = hash_op_assign; + let hash = *hash_op_assign; let args = &mut [&mut *lock_guard, &mut new_val]; - let level = level + 1; - match self.call_native_fn( - global, caches, lib, op_assign, hash, args, true, true, op_pos, level, - ) { - Ok(_) => { - #[cfg(not(feature = "unchecked"))] - self.check_data_size(args[0], root.1)?; + if self.fast_operators() { + if let Some(func) = get_builtin_op_assignment_fn(op_assign_token, args[0], args[1]) + { + // Built-in found + let op = op_assign_token.literal_syntax(); + + global.level += 1; + let global = &*RestoreOnDrop::lock(global, move |g| g.level -= 1); + + let context = (self, op, None, global, lib, *op_pos).into(); + return func(context, args).map(|_| ()); } + } + + let op_assign = op_assign_token.literal_syntax(); + let op = op_token.literal_syntax(); + let token = Some(op_assign_token); + + match self.exec_native_fn_call( + global, caches, lib, op_assign, token, hash, args, true, *op_pos, + ) { + Ok(_) => (), Err(err) if matches!(*err, ERR::ErrorFunctionNotFound(ref f, ..) if f.starts_with(op_assign)) => { // Expand to `var = var op rhs` - let (value, ..) = self - .call_native_fn( - global, caches, lib, op, hash_op, args, true, false, op_pos, level, + let token = Some(op_token); + + *args[0] = self + .exec_native_fn_call( + global, caches, lib, op, token, *hash_op, args, true, *op_pos, ) - .map_err(|err| err.fill_position(op_info.pos))?; - - #[cfg(not(feature = "unchecked"))] - self.check_data_size(&value, root.1)?; - - *args[0] = value.flatten(); + .map_err(|err| err.fill_position(op_info.pos))? + .0 + .flatten(); } Err(err) => return Err(err), } + + self.check_data_size(args[0], root.1)?; } else { // Normal assignment *target.write_lock::().unwrap() = new_val; } - /* - if let Some(mut guard) = target.write_lock::() { - if guard.is::() { - let s = std::mem::take(&mut *guard).cast::(); - *guard = self.get_interned_string(s).into(); - } - } - */ - target.propagate_changed_value(op_info.pos) } @@ -195,34 +199,27 @@ impl Engine { // Errors that are not recoverable, such as system errors or safety errors, can use `?`. pub(crate) fn eval_stmt( &self, - scope: &mut Scope, global: &mut GlobalRuntimeState, caches: &mut Caches, - lib: &[&Module], - this_ptr: &mut Option<&mut Dynamic>, + lib: &[SharedModule], + scope: &mut Scope, + this_ptr: &mut Dynamic, stmt: &Stmt, rewind_scope: bool, - level: usize, ) -> RhaiResult { #[cfg(feature = "debugging")] - let reset_debugger = - self.run_debugger_with_reset(scope, global, lib, this_ptr, stmt, level)?; + let reset = self.run_debugger_with_reset(global, caches, lib, scope, this_ptr, stmt)?; + #[cfg(feature = "debugging")] + let global = &mut *RestoreOnDrop::lock(global, move |g| g.debugger.reset_status(reset)); // Coded this way for better branch prediction. // Popular branches are lifted out of the `match` statement into their own branches. // Function calls should account for a relatively larger portion of statements. - if let Stmt::FnCall(x, ..) = stmt { - #[cfg(not(feature = "unchecked"))] - self.inc_operations(&mut global.num_operations, stmt.position())?; + if let Stmt::FnCall(x, pos) = stmt { + self.track_operation(global, stmt.position())?; - let result = - self.eval_fn_call_expr(scope, global, caches, lib, this_ptr, x, x.pos, level); - - #[cfg(feature = "debugging")] - global.debugger.reset_status(reset_debugger); - - return result; + return self.eval_fn_call_expr(global, caches, lib, scope, this_ptr, x, *pos); } // Then assignments. @@ -231,153 +228,113 @@ impl Engine { if let Stmt::Assignment(x, ..) = stmt { let (op_info, BinaryExpr { lhs, rhs }) = &**x; - #[cfg(not(feature = "unchecked"))] - self.inc_operations(&mut global.num_operations, stmt.position())?; + self.track_operation(global, stmt.position())?; - let result = if let Expr::Variable(x, ..) = lhs { - let rhs_result = self - .eval_expr(scope, global, caches, lib, this_ptr, rhs, level) - .map(Dynamic::flatten); + if let Expr::Variable(x, ..) = lhs { + let rhs_val = self + .eval_expr(global, caches, lib, scope, this_ptr, rhs)? + .flatten(); - if let Ok(rhs_val) = rhs_result { - let search_result = - self.search_namespace(scope, global, lib, this_ptr, lhs, level); + let (mut lhs_ptr, pos) = + self.search_namespace(global, caches, lib, scope, this_ptr, lhs)?; - if let Ok(search_val) = search_result { - let (mut lhs_ptr, pos) = search_val; + let var_name = x.3.as_str(); - let var_name = x.3.as_str(); + #[cfg(not(feature = "no_closure"))] + // Also handle case where target is a `Dynamic` shared value + // (returned by a variable resolver, for example) + let is_temp_result = !lhs_ptr.is_ref() && !lhs_ptr.is_shared(); + #[cfg(feature = "no_closure")] + let is_temp_result = !lhs_ptr.is_ref(); - #[cfg(not(feature = "no_closure"))] - // Also handle case where target is a `Dynamic` shared value - // (returned by a variable resolver, for example) - let is_temp_result = !lhs_ptr.is_ref() && !lhs_ptr.is_shared(); - #[cfg(feature = "no_closure")] - let is_temp_result = !lhs_ptr.is_ref(); - - // Cannot assign to temp result from expression - if is_temp_result { - return Err( - ERR::ErrorAssignmentToConstant(var_name.to_string(), pos).into() - ); - } - - #[cfg(not(feature = "unchecked"))] - self.inc_operations(&mut global.num_operations, pos)?; - - let root = (var_name, pos); - let lhs_ptr = &mut lhs_ptr; - - self.eval_op_assignment( - global, caches, lib, *op_info, lhs_ptr, root, rhs_val, level, - ) - .map(|_| Dynamic::UNIT) - } else { - search_result.map(|_| Dynamic::UNIT) - } - } else { - rhs_result + // Cannot assign to temp result from expression + if is_temp_result { + return Err(ERR::ErrorAssignmentToConstant(var_name.to_string(), pos).into()); } - } else { - let (op_info, BinaryExpr { lhs, rhs }) = &**x; - let rhs_result = self - .eval_expr(scope, global, caches, lib, this_ptr, rhs, level) - .map(Dynamic::flatten); + self.track_operation(global, pos)?; - if let Ok(rhs_val) = rhs_result { - let rhs_val = if rhs_val.is::() { - self.get_interned_string(rhs_val.cast::()) - .into() - } else { - rhs_val - }; + let root = (var_name, pos); + let lhs_ptr = &mut lhs_ptr; - let _new_val = Some((rhs_val, *op_info)); + return self + .eval_op_assignment(global, caches, lib, op_info, lhs_ptr, root, rhs_val) + .map(|_| Dynamic::UNIT); + } - // Must be either `var[index] op= val` or `var.prop op= val` - match lhs { - // name op= rhs (handled above) - Expr::Variable(..) => { - unreachable!("Expr::Variable case is already handled") - } - // idx_lhs[idx_expr] op= rhs - #[cfg(not(feature = "no_index"))] - Expr::Index(..) => self - .eval_dot_index_chain( - scope, global, caches, lib, this_ptr, lhs, level, _new_val, - ) - .map(|_| Dynamic::UNIT), - // dot_lhs.dot_rhs op= rhs - #[cfg(not(feature = "no_object"))] - Expr::Dot(..) => self - .eval_dot_index_chain( - scope, global, caches, lib, this_ptr, lhs, level, _new_val, - ) - .map(|_| Dynamic::UNIT), - _ => unreachable!("cannot assign to expression: {:?}", lhs), - } + #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] + { + let rhs_val = self.eval_expr(global, caches, lib, scope, this_ptr, rhs)?; + + // Check if the result is a string. If so, intern it. + #[cfg(not(feature = "no_closure"))] + let is_string = !rhs_val.is_shared() && rhs_val.is::(); + #[cfg(feature = "no_closure")] + let is_string = rhs_val.is::(); + + let rhs_val = if is_string { + self.get_interned_string( + rhs_val.into_immutable_string().expect("`ImmutableString`"), + ) + .into() } else { - rhs_result + rhs_val.flatten() + }; + + let _new_val = &mut Some((rhs_val, op_info)); + + // Must be either `var[index] op= val` or `var.prop op= val` + return match lhs { + // name op= rhs (handled above) + Expr::Variable(..) => { + unreachable!("Expr::Variable case is already handled") + } + // idx_lhs[idx_expr] op= rhs + #[cfg(not(feature = "no_index"))] + Expr::Index(..) => self + .eval_dot_index_chain(global, caches, lib, scope, this_ptr, lhs, _new_val), + // dot_lhs.dot_rhs op= rhs + #[cfg(not(feature = "no_object"))] + Expr::Dot(..) => self + .eval_dot_index_chain(global, caches, lib, scope, this_ptr, lhs, _new_val), + _ => unreachable!("cannot assign to expression: {:?}", lhs), } - }; - - #[cfg(feature = "debugging")] - global.debugger.reset_status(reset_debugger); - - return result; + .map(|_| Dynamic::UNIT); + } } - #[cfg(not(feature = "unchecked"))] - self.inc_operations(&mut global.num_operations, stmt.position())?; + self.track_operation(global, stmt.position())?; - let result = match stmt { + match stmt { // No-op Stmt::Noop(..) => Ok(Dynamic::UNIT), // Expression as statement Stmt::Expr(expr) => self - .eval_expr(scope, global, caches, lib, this_ptr, expr, level) + .eval_expr(global, caches, lib, scope, this_ptr, expr) .map(Dynamic::flatten), // Block scope Stmt::Block(statements, ..) if statements.is_empty() => Ok(Dynamic::UNIT), - Stmt::Block(statements, ..) => self.eval_stmt_block( - scope, global, caches, lib, this_ptr, statements, true, level, - ), + Stmt::Block(statements, ..) => { + self.eval_stmt_block(global, caches, lib, scope, this_ptr, statements, true) + } // If statement Stmt::If(x, ..) => { let (expr, if_block, else_block) = &**x; let guard_val = self - .eval_expr(scope, global, caches, lib, this_ptr, expr, level) - .and_then(|v| { - v.as_bool().map_err(|typ| { - self.make_type_mismatch_err::(typ, expr.position()) - }) - }); + .eval_expr(global, caches, lib, scope, this_ptr, expr)? + .as_bool() + .map_err(|typ| self.make_type_mismatch_err::(typ, expr.position()))?; - match guard_val { - Ok(true) => { - if if_block.is_empty() { - Ok(Dynamic::UNIT) - } else { - self.eval_stmt_block( - scope, global, caches, lib, this_ptr, if_block, true, level, - ) - } - } - Ok(false) => { - if else_block.is_empty() { - Ok(Dynamic::UNIT) - } else { - self.eval_stmt_block( - scope, global, caches, lib, this_ptr, else_block, true, level, - ) - } - } - err => err.map(Into::into), + if guard_val && !if_block.is_empty() { + self.eval_stmt_block(global, caches, lib, scope, this_ptr, if_block, true) + } else if !guard_val && !else_block.is_empty() { + self.eval_stmt_block(global, caches, lib, scope, this_ptr, else_block, true) + } else { + Ok(Dynamic::UNIT) } } @@ -393,313 +350,264 @@ impl Engine { }, ) = &**x; - let value_result = - self.eval_expr(scope, global, caches, lib, this_ptr, expr, level); + let mut result = None; - if let Ok(value) = value_result { - let expr_result = if value.is_hashable() { - let hasher = &mut get_hasher(); - value.hash(hasher); - let hash = hasher.finish(); + let value = self.eval_expr(global, caches, lib, scope, this_ptr, expr)?; - // First check hashes - if let Some(case_blocks_list) = cases.get(&hash) { - assert!(!case_blocks_list.is_empty()); + if value.is_hashable() { + let hasher = &mut get_hasher(); + value.hash(hasher); + let hash = hasher.finish(); - let mut result = Ok(None); + // First check hashes + if let Some(case_blocks_list) = cases.get(&hash) { + assert!(!case_blocks_list.is_empty()); - for &index in case_blocks_list { - let block = &expressions[index]; + for &index in case_blocks_list { + let block = &expressions[index]; - let cond_result = match block.condition { - Expr::BoolConstant(b, ..) => Ok(b), - ref c => self - .eval_expr(scope, global, caches, lib, this_ptr, c, level) - .and_then(|v| { - v.as_bool().map_err(|typ| { - self.make_type_mismatch_err::( - typ, - c.position(), - ) - }) - }), - }; - - match cond_result { - Ok(true) => { - result = Ok(Some(&block.expr)); - break; - } - Ok(false) => (), - _ => { - result = cond_result.map(|_| None); - break; - } - } - } - - result - } else if value.is::() && !ranges.is_empty() { - // Then check integer ranges - let value = value.as_int().expect("`INT`"); - let mut result = Ok(None); - - for r in ranges.iter().filter(|r| r.contains(value)) { - let block = &expressions[r.index()]; - - let cond_result = match block.condition { - Expr::BoolConstant(b, ..) => Ok(b), - ref c => self - .eval_expr(scope, global, caches, lib, this_ptr, c, level) - .and_then(|v| { - v.as_bool().map_err(|typ| { - self.make_type_mismatch_err::( - typ, - c.position(), - ) - }) - }), - }; - - match cond_result { - Ok(true) => result = Ok(Some(&block.expr)), - Ok(false) => continue, - _ => result = cond_result.map(|_| None), - } + let cond_result = match block.condition { + Expr::BoolConstant(b, ..) => b, + ref c => self + .eval_expr(global, caches, lib, scope, this_ptr, c)? + .as_bool() + .map_err(|typ| { + self.make_type_mismatch_err::(typ, c.position()) + })?, + }; + if cond_result { + result = Some(&block.expr); break; } - - result - } else { - // Nothing matches - Ok(None) } - } else { - // Non-hashable - Ok(None) - }; + } else if value.is::() && !ranges.is_empty() { + // Then check integer ranges + let value = value.as_int().expect("`INT`"); - if let Ok(Some(expr)) = expr_result { - self.eval_expr(scope, global, caches, lib, this_ptr, expr, level) - } else if let Ok(None) = expr_result { - // Default match clause - def_case.as_ref().map_or(Ok(Dynamic::UNIT), |&index| { - let def_expr = &expressions[index].expr; - self.eval_expr(scope, global, caches, lib, this_ptr, def_expr, level) - }) - } else { - expr_result.map(|_| Dynamic::UNIT) + for r in ranges.iter().filter(|r| r.contains(value)) { + let block = &expressions[r.index()]; + + let cond_result = match block.condition { + Expr::BoolConstant(b, ..) => b, + ref c => self + .eval_expr(global, caches, lib, scope, this_ptr, c)? + .as_bool() + .map_err(|typ| { + self.make_type_mismatch_err::(typ, c.position()) + })?, + }; + + if cond_result { + result = Some(&block.expr); + break; + } + } } - } else { - value_result } + + result + .or_else(|| def_case.as_ref().map(|&index| &expressions[index].expr)) + .map_or(Ok(Dynamic::UNIT), |expr| { + self.eval_expr(global, caches, lib, scope, this_ptr, expr) + }) } // Loop - Stmt::While(x, ..) if matches!(x.0, Expr::Unit(..)) => loop { + Stmt::While(x, ..) if matches!(x.0, Expr::Unit(..) | Expr::BoolConstant(true, ..)) => { let (.., body) = &**x; if body.is_empty() { - #[cfg(not(feature = "unchecked"))] - self.inc_operations(&mut global.num_operations, body.position())?; - } else { - match self - .eval_stmt_block(scope, global, caches, lib, this_ptr, body, true, level) - { - Ok(_) => (), - Err(err) => match *err { - ERR::LoopBreak(false, ..) => (), - ERR::LoopBreak(true, ..) => break Ok(Dynamic::UNIT), - _ => break Err(err), - }, + loop { + self.track_operation(global, body.position())?; } } - }, - // While loop - Stmt::While(x, ..) => loop { - let (expr, body) = &**x; - - let condition = self - .eval_expr(scope, global, caches, lib, this_ptr, expr, level) - .and_then(|v| { - v.as_bool().map_err(|typ| { - self.make_type_mismatch_err::(typ, expr.position()) - }) - }); - - match condition { - Ok(false) => break Ok(Dynamic::UNIT), - Ok(true) if body.is_empty() => (), - Ok(true) => { - match self.eval_stmt_block( - scope, global, caches, lib, this_ptr, body, true, level, - ) { - Ok(_) => (), - Err(err) => match *err { - ERR::LoopBreak(false, ..) => (), - ERR::LoopBreak(true, ..) => break Ok(Dynamic::UNIT), - _ => break Err(err), - }, + loop { + if let Err(err) = + self.eval_stmt_block(global, caches, lib, scope, this_ptr, body, true) + { + match *err { + ERR::LoopBreak(false, ..) => (), + ERR::LoopBreak(true, value, ..) => break Ok(value), + _ => break Err(err), } } - err => break err.map(|_| Dynamic::UNIT), } - }, + } + + // While loop + Stmt::While(x, ..) => { + let (expr, body) = &**x; + + loop { + let condition = self + .eval_expr(global, caches, lib, scope, this_ptr, expr)? + .as_bool() + .map_err(|typ| self.make_type_mismatch_err::(typ, expr.position()))?; + + if !condition { + break Ok(Dynamic::UNIT); + } + + if body.is_empty() { + continue; + } + + if let Err(err) = + self.eval_stmt_block(global, caches, lib, scope, this_ptr, body, true) + { + match *err { + ERR::LoopBreak(false, ..) => (), + ERR::LoopBreak(true, value, ..) => break Ok(value), + _ => break Err(err), + } + } + } + } // Do loop - Stmt::Do(x, options, ..) => loop { + Stmt::Do(x, options, ..) => { let (expr, body) = &**x; let is_while = !options.contains(ASTFlags::NEGATED); - if !body.is_empty() { - match self - .eval_stmt_block(scope, global, caches, lib, this_ptr, body, true, level) - { - Ok(_) => (), - Err(err) => match *err { - ERR::LoopBreak(false, ..) => continue, - ERR::LoopBreak(true, ..) => break Ok(Dynamic::UNIT), - _ => break Err(err), - }, + loop { + if !body.is_empty() { + if let Err(err) = + self.eval_stmt_block(global, caches, lib, scope, this_ptr, body, true) + { + match *err { + ERR::LoopBreak(false, ..) => continue, + ERR::LoopBreak(true, value, ..) => break Ok(value), + _ => break Err(err), + } + } + } + + let condition = self + .eval_expr(global, caches, lib, scope, this_ptr, expr)? + .as_bool() + .map_err(|typ| self.make_type_mismatch_err::(typ, expr.position()))?; + + if condition ^ is_while { + break Ok(Dynamic::UNIT); } } - - let condition = self - .eval_expr(scope, global, caches, lib, this_ptr, expr, level) - .and_then(|v| { - v.as_bool().map_err(|typ| { - self.make_type_mismatch_err::(typ, expr.position()) - }) - }); - - match condition { - Ok(condition) if condition ^ is_while => break Ok(Dynamic::UNIT), - Ok(_) => (), - err => break err.map(|_| Dynamic::UNIT), - } - }, + } // For loop Stmt::For(x, ..) => { let (var_name, counter, expr, statements) = &**x; - let iter_result = self - .eval_expr(scope, global, caches, lib, this_ptr, expr, level) - .map(Dynamic::flatten); + let iter_obj = self + .eval_expr(global, caches, lib, scope, this_ptr, expr)? + .flatten(); - if let Ok(iter_obj) = iter_result { - let iter_type = iter_obj.type_id(); + let iter_type = iter_obj.type_id(); - // lib should only contain scripts, so technically they cannot have iterators + // lib should only contain scripts, so technically they cannot have iterators - // Search order: - // 1) Global namespace - functions registered via Engine::register_XXX - // 2) Global modules - packages - // 3) Imported modules - functions marked with global namespace - // 4) Global sub-modules - functions marked with global namespace - let func = self - .global_modules - .iter() - .find_map(|m| m.get_iter(iter_type)); + // Search order: + // 1) Global namespace - functions registered via Engine::register_XXX + // 2) Global modules - packages + // 3) Imported modules - functions marked with global namespace + // 4) Global sub-modules - functions marked with global namespace + let func = self + .global_modules + .iter() + .find_map(|m| m.get_iter(iter_type)); - #[cfg(not(feature = "no_module"))] - let func = func.or_else(|| global.get_iter(iter_type)).or_else(|| { - self.global_sub_modules - .values() - .find_map(|m| m.get_qualified_iter(iter_type)) - }); + #[cfg(not(feature = "no_module"))] + let func = func.or_else(|| global.get_iter(iter_type)).or_else(|| { + self.global_sub_modules + .values() + .find_map(|m| m.get_qualified_iter(iter_type)) + }); - if let Some(func) = func { - // Add the loop variables - let orig_scope_len = scope.len(); - let counter_index = if counter.is_empty() { - usize::MAX - } else { - scope.push(counter.name.clone(), 0 as INT); - scope.len() - 1 - }; + let func = func.ok_or_else(|| ERR::ErrorFor(expr.start_position()))?; - scope.push(var_name.name.clone(), ()); - let index = scope.len() - 1; + // Restore scope at end of statement + let orig_scope_len = scope.len(); + let scope = &mut *RestoreOnDrop::lock(scope, move |s| { + s.rewind(orig_scope_len); + }); - let mut loop_result = Ok(Dynamic::UNIT); + // Add the loop variables + let counter_index = if counter.is_empty() { + usize::MAX + } else { + scope.push(counter.name.clone(), 0 as INT); + scope.len() - 1 + }; - for (x, iter_value) in func(iter_obj).enumerate() { - // Increment counter - if counter_index < usize::MAX { - // As the variable increments from 0, this should always work - // since any overflow will first be caught below. - let index_value = x as INT; + scope.push(var_name.name.clone(), ()); + let index = scope.len() - 1; - #[cfg(not(feature = "unchecked"))] - if index_value > crate::MAX_USIZE_INT { - loop_result = Err(ERR::ErrorArithmetic( - format!("for-loop counter overflow: {x}"), - counter.pos, - ) - .into()); - break; - } + let mut result = Dynamic::UNIT; - *scope.get_mut_by_index(counter_index).write_lock().unwrap() = - Dynamic::from_int(index_value); - } + for (x, iter_value) in func(iter_obj).enumerate() { + // Increment counter + if counter_index < usize::MAX { + // As the variable increments from 0, this should always work + // since any overflow will first be caught below. + let index_value = x as INT; - let value = match iter_value { - Ok(v) => v.flatten(), - Err(err) => { - loop_result = Err(err.fill_position(expr.position())); - break; - } - }; - - *scope.get_mut_by_index(index).write_lock().unwrap() = value; - - #[cfg(not(feature = "unchecked"))] - if let Err(err) = self - .inc_operations(&mut global.num_operations, statements.position()) - { - loop_result = Err(err); - break; - } - - if statements.is_empty() { - continue; - } - - let result = self.eval_stmt_block( - scope, global, caches, lib, this_ptr, statements, true, level, - ); - - match result { - Ok(_) => (), - Err(err) => match *err { - ERR::LoopBreak(false, ..) => (), - ERR::LoopBreak(true, ..) => break, - _ => { - loop_result = Err(err); - break; - } - }, - } + #[cfg(not(feature = "unchecked"))] + if index_value > crate::MAX_USIZE_INT { + return Err(ERR::ErrorArithmetic( + format!("for-loop counter overflow: {x}"), + counter.pos, + ) + .into()); } - scope.rewind(orig_scope_len); - - loop_result - } else { - Err(ERR::ErrorFor(expr.start_position()).into()) + *scope.get_mut_by_index(counter_index).write_lock().unwrap() = + Dynamic::from_int(index_value); + } + + // Set loop value + let value = iter_value + .map_err(|err| err.fill_position(expr.position()))? + .flatten(); + + *scope.get_mut_by_index(index).write_lock().unwrap() = value; + + // Run block + self.track_operation(global, statements.position())?; + + if statements.is_empty() { + continue; + } + + match self + .eval_stmt_block(global, caches, lib, scope, this_ptr, statements, true) + { + Ok(_) => (), + Err(err) => match *err { + ERR::LoopBreak(false, ..) => (), + ERR::LoopBreak(true, value, ..) => { + result = value; + break; + } + _ => return Err(err), + }, } - } else { - iter_result } + + Ok(result) } // Continue/Break statement - Stmt::BreakLoop(options, pos) => { - Err(ERR::LoopBreak(options.contains(ASTFlags::BREAK), *pos).into()) + Stmt::BreakLoop(expr, options, pos) => { + let is_break = options.contains(ASTFlags::BREAK); + + let value = if let Some(ref expr) = expr { + self.eval_expr(global, caches, lib, scope, this_ptr, expr)? + } else { + Dynamic::UNIT + }; + + Err(ERR::LoopBreak(is_break, value, *pos).into()) } // Try/Catch statement @@ -713,12 +621,8 @@ impl Engine { catch_block, } = &**x; - let result = self - .eval_stmt_block(scope, global, caches, lib, this_ptr, try_block, true, level) - .map(|_| Dynamic::UNIT); - - match result { - Ok(_) => result, + match self.eval_stmt_block(global, caches, lib, scope, this_ptr, try_block, true) { + r @ Ok(_) => r, Err(err) if err.is_pseudo_error() => Err(err), Err(err) if !err.is_catchable() => Err(err), Err(mut err) => { @@ -727,7 +631,7 @@ impl Engine { #[cfg(feature = "no_object")] _ => { - err.take_position(); + let _ = err.take_position(); err.to_string().into() } #[cfg(not(feature = "no_object"))] @@ -737,8 +641,8 @@ impl Engine { err_map.insert("message".into(), err.to_string().into()); - if !global.source.is_empty() { - err_map.insert("source".into(), global.source.clone().into()); + if let Some(ref source) = global.source { + err_map.insert("source".into(), source.into()); } if !err_pos.is_none() { @@ -757,43 +661,42 @@ impl Engine { } }; + // Restore scope at end of block let orig_scope_len = scope.len(); + let scope = + &mut *RestoreOnDrop::lock_if(!catch_var.is_empty(), scope, move |s| { + s.rewind(orig_scope_len); + }); if !catch_var.is_empty() { scope.push(catch_var.clone(), err_value); } - let result = self.eval_stmt_block( - scope, + self.eval_stmt_block( global, caches, lib, + scope, this_ptr, catch_block, true, - level, - ); - - scope.rewind(orig_scope_len); - - match result { - Ok(_) => Ok(Dynamic::UNIT), - Err(result_err) => match *result_err { - // Re-throw exception - ERR::ErrorRuntime(Dynamic(Union::Unit(..)), pos) => { - err.set_position(pos); - Err(err) - } - _ => Err(result_err), - }, - } + ) + .map(|_| Dynamic::UNIT) + .map_err(|result_err| match *result_err { + // Re-throw exception + ERR::ErrorRuntime(Dynamic(Union::Unit(..)), pos) => { + err.set_position(pos); + err + } + _ => result_err, + }) } } } // Throw value Stmt::Return(Some(expr), options, pos) if options.contains(ASTFlags::BREAK) => self - .eval_expr(scope, global, caches, lib, this_ptr, expr, level) + .eval_expr(global, caches, lib, scope, this_ptr, expr) .and_then(|v| Err(ERR::ErrorRuntime(v.flatten(), *pos).into())), // Empty throw @@ -803,7 +706,7 @@ impl Engine { // Return value Stmt::Return(Some(expr), .., pos) => self - .eval_expr(scope, global, caches, lib, this_ptr, expr, level) + .eval_expr(global, caches, lib, scope, this_ptr, expr) .and_then(|v| Err(ERR::Return(v.flatten(), *pos).into())), // Empty return @@ -825,86 +728,67 @@ impl Engine { let export = options.contains(ASTFlags::EXPORTED); // Check variable definition filter - let result = if let Some(ref filter) = self.def_var_filter { + if let Some(ref filter) = self.def_var_filter { let will_shadow = scope.contains(var_name); - let nesting_level = global.scope_level; let is_const = access == AccessMode::ReadOnly; let info = VarDefInfo { name: var_name, is_const, - nesting_level, + nesting_level: global.scope_level, will_shadow, }; - let context = EvalContext::new(self, scope, global, None, lib, this_ptr, level); + let context = EvalContext::new(self, global, caches, lib, scope, this_ptr); - match filter(true, info, context) { - Ok(true) => None, - Ok(false) => { - Some(Err( - ERR::ErrorForbiddenVariable(var_name.to_string(), *pos).into() - )) - } - err @ Err(_) => Some(err), + if !filter(true, info, context)? { + return Err(ERR::ErrorForbiddenVariable(var_name.to_string(), *pos).into()); } + } + + // Evaluate initial value + let mut value = self + .eval_expr(global, caches, lib, scope, this_ptr, expr)? + .flatten(); + + let _alias = if !rewind_scope { + // Put global constants into global module + #[cfg(not(feature = "no_function"))] + #[cfg(not(feature = "no_module"))] + if global.scope_level == 0 + && access == AccessMode::ReadOnly + && lib.iter().any(|m| !m.is_empty()) + { + crate::func::locked_write(global.constants.get_or_insert_with(|| { + crate::Shared::new( + crate::Locked::new(std::collections::BTreeMap::new()), + ) + })) + .insert(var_name.name.clone(), value.clone()); + } + + if export { + Some(var_name) + } else { + None + } + } else if export { + unreachable!("exported variable not on global level"); } else { None }; - if let Some(result) = result { - result.map(|_| Dynamic::UNIT) + if let Some(index) = index { + value.set_access_mode(access); + *scope.get_mut_by_index(scope.len() - index.get()) = value; } else { - // Evaluate initial value - let value_result = self - .eval_expr(scope, global, caches, lib, this_ptr, expr, level) - .map(Dynamic::flatten); - - if let Ok(mut value) = value_result { - let _alias = if !rewind_scope { - // Put global constants into global module - #[cfg(not(feature = "no_function"))] - #[cfg(not(feature = "no_module"))] - if global.scope_level == 0 - && access == AccessMode::ReadOnly - && lib.iter().any(|m| !m.is_empty()) - { - crate::func::locked_write(global.constants.get_or_insert_with( - || { - crate::Shared::new(crate::Locked::new( - std::collections::BTreeMap::new(), - )) - }, - )) - .insert(var_name.name.clone(), value.clone()); - } - - if export { - Some(var_name) - } else { - None - } - } else if export { - unreachable!("exported variable not on global level"); - } else { - None - }; - - if let Some(index) = index { - value.set_access_mode(access); - *scope.get_mut_by_index(scope.len() - index.get()) = value; - } else { - scope.push_entry(var_name.name.clone(), access, value); - } - - #[cfg(not(feature = "no_module"))] - if let Some(alias) = _alias { - scope.add_alias_by_index(scope.len() - 1, alias.name.as_str().into()); - } - - Ok(Dynamic::UNIT) - } else { - value_result - } + scope.push_entry(var_name.name.clone(), access, value); } + + #[cfg(not(feature = "no_module"))] + if let Some(alias) = _alias { + scope.add_alias_by_index(scope.len() - 1, alias.name.as_str().into()); + } + + Ok(Dynamic::UNIT) } // Import statement @@ -913,67 +797,56 @@ impl Engine { let (expr, export) = &**x; // Guard against too many modules - #[cfg(not(feature = "unchecked"))] if global.num_modules_loaded >= self.max_modules() { return Err(ERR::ErrorTooManyModules(*_pos).into()); } - let path_result = self - .eval_expr(scope, global, caches, lib, this_ptr, expr, level) - .and_then(|v| { - let typ = v.type_name(); - v.try_cast::().ok_or_else(|| { - self.make_type_mismatch_err::( - typ, - expr.position(), - ) - }) - }); + let v = self.eval_expr(global, caches, lib, scope, this_ptr, expr)?; + let typ = v.type_name(); + let path = v.try_cast::().ok_or_else(|| { + self.make_type_mismatch_err::(typ, expr.position()) + })?; - if let Ok(path) = path_result { - use crate::ModuleResolver; + use crate::ModuleResolver; - let path_pos = expr.start_position(); + let path_pos = expr.start_position(); - let resolver = global.embedded_module_resolver.clone(); + let resolver = global.embedded_module_resolver.clone(); - let module_result = resolver - .as_ref() - .and_then(|r| match r.resolve_raw(self, global, &path, path_pos) { - Err(err) if matches!(*err, ERR::ErrorModuleNotFound(..)) => None, - result => Some(result), - }) - .or_else(|| { - Some( - self.module_resolver - .resolve_raw(self, global, &path, path_pos), - ) - }) - .unwrap_or_else(|| { - Err(ERR::ErrorModuleNotFound(path.to_string(), path_pos).into()) - }); + let module = resolver + .as_ref() + .and_then(|r| match r.resolve_raw(self, global, &path, path_pos) { + Err(err) if matches!(*err, ERR::ErrorModuleNotFound(..)) => None, + result => Some(result), + }) + .or_else(|| { + Some( + self.module_resolver + .resolve_raw(self, global, &path, path_pos), + ) + }) + .unwrap_or_else(|| { + Err(ERR::ErrorModuleNotFound(path.to_string(), path_pos).into()) + })?; - if let Ok(module) = module_result { - if !export.is_empty() { - if module.is_indexed() { - global.push_import(export.name.clone(), module); - } else { - // Index the module (making a clone copy if necessary) if it is not indexed - let mut m = crate::func::shared_take_or_clone(module); - m.build_index(); - global.push_import(export.name.clone(), m); - } - } - - global.num_modules_loaded += 1; - - Ok(Dynamic::UNIT) - } else { - module_result.map(|_| Dynamic::UNIT) - } + let (export, must_be_indexed) = if !export.is_empty() { + (export.name.clone(), true) } else { - path_result.map(|_| Dynamic::UNIT) + (self.get_interned_string(""), false) + }; + + if !must_be_indexed || module.is_indexed() { + global.push_import(export, module); + } else { + // Index the module (making a clone copy if necessary) if it is not indexed + let mut m = crate::func::shared_take_or_clone(module); + m.build_index(); + global.push_import(export, m); } + + global.num_modules_loaded += 1; + + Ok(Dynamic::UNIT) } // Export statement @@ -981,7 +854,7 @@ impl Engine { Stmt::Export(x, ..) => { let (Ident { name, pos, .. }, Ident { name: alias, .. }) = &**x; // Mark scope variables as public - if let Some((index, ..)) = scope.get_index(name) { + if let Some(index) = scope.search(name) { let alias = if alias.is_empty() { name } else { alias }.clone(); scope.add_alias_by_index(index, alias.into()); Ok(Dynamic::UNIT) @@ -992,26 +865,51 @@ impl Engine { // Share statement #[cfg(not(feature = "no_closure"))] - Stmt::Share(name, pos) => { - if let Some((index, ..)) = scope.get_index(name) { - let val = scope.get_mut_by_index(index); + Stmt::Share(x) => { + x.iter() + .try_for_each(|(name, index, pos)| { + if let Some(index) = index + .map(|n| scope.len() - n.get()) + .or_else(|| scope.search(name)) + { + let val = scope.get_mut_by_index(index); - if !val.is_shared() { - // Replace the variable with a shared value. - *val = std::mem::take(val).into_shared(); - } - Ok(Dynamic::UNIT) - } else { - Err(ERR::ErrorVariableNotFound(name.to_string(), *pos).into()) - } + if !val.is_shared() { + // Replace the variable with a shared value. + *val = std::mem::take(val).into_shared(); + } + Ok(()) + } else { + Err(ERR::ErrorVariableNotFound(name.to_string(), *pos).into()) + } + }) + .map(|_| Dynamic::UNIT) } _ => unreachable!("statement cannot be evaluated: {:?}", stmt), - }; + } + } - #[cfg(feature = "debugging")] - global.debugger.reset_status(reset_debugger); + /// Evaluate a list of statements with no `this` pointer. + /// This is commonly used to evaluate a list of statements in an [`AST`][crate::AST] or a script function body. + #[inline] + pub(crate) fn eval_global_statements( + &self, + global: &mut GlobalRuntimeState, + caches: &mut Caches, + lib: &[SharedModule], + scope: &mut Scope, + statements: &[Stmt], + ) -> RhaiResult { + let mut this = Dynamic::NULL; - result + self.eval_stmt_block(global, caches, lib, scope, &mut this, statements, false) + .or_else(|err| match *err { + ERR::Return(out, ..) => Ok(out), + ERR::LoopBreak(..) => { + unreachable!("no outer loop scope to break out of") + } + _ => Err(err), + }) } } diff --git a/src/eval/target.rs b/src/eval/target.rs index 9c0804ab..c48aa62a 100644 --- a/src/eval/target.rs +++ b/src/eval/target.rs @@ -2,9 +2,12 @@ use crate::types::dynamic::Variant; use crate::{Dynamic, Position, RhaiResultOf}; -use std::ops::{Deref, DerefMut}; #[cfg(feature = "no_std")] use std::prelude::v1::*; +use std::{ + borrow::Borrow, + ops::{Deref, DerefMut}, +}; // Calculate an offset+len pair given an actual length of the underlying array. // @@ -416,11 +419,20 @@ impl Deref for Target<'_> { impl AsRef for Target<'_> { #[inline(always)] + #[must_use] fn as_ref(&self) -> &Dynamic { self } } +impl Borrow for Target<'_> { + #[inline(always)] + #[must_use] + fn borrow(&self) -> &Dynamic { + self + } +} + impl DerefMut for Target<'_> { #[inline] fn deref_mut(&mut self) -> &mut Dynamic { @@ -440,6 +452,7 @@ impl DerefMut for Target<'_> { impl AsMut for Target<'_> { #[inline(always)] + #[must_use] fn as_mut(&mut self) -> &mut Dynamic { self } diff --git a/src/func/builtin.rs b/src/func/builtin.rs index 4ad42130..8ff37e67 100644 --- a/src/func/builtin.rs +++ b/src/func/builtin.rs @@ -2,8 +2,10 @@ use super::call::FnCallArgs; use super::native::FnBuiltin; -use crate::engine::OP_CONTAINS; -use crate::{Dynamic, ExclusiveRange, ImmutableString, InclusiveRange, INT}; +use crate::tokenizer::{Token, Token::*}; +use crate::{ + Dynamic, ExclusiveRange, ImmutableString, InclusiveRange, NativeCallContext, RhaiResult, INT, +}; use std::any::TypeId; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -63,11 +65,24 @@ fn is_numeric(type_id: TypeId) -> bool { false } +/// A function that returns `true`. +#[inline(always)] +#[must_use] +fn const_true_fn(_: NativeCallContext, _: &mut [&mut Dynamic]) -> RhaiResult { + Ok(Dynamic::TRUE) +} +/// A function that returns `false`. +#[inline(always)] +#[must_use] +fn const_false_fn(_: NativeCallContext, _: &mut [&mut Dynamic]) -> RhaiResult { + Ok(Dynamic::FALSE) +} + /// Build in common binary operator implementations to avoid the cost of calling a registered function. /// /// The return function will be registered as a _method_, so the first parameter cannot be consumed. #[must_use] -pub fn get_builtin_binary_op_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Option { +pub fn get_builtin_binary_op_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Option { let type1 = x.type_id(); let type2 = y.type_id(); @@ -131,46 +146,46 @@ pub fn get_builtin_binary_op_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Option return Some(impl_op!(INT => add(as_int, as_int))), - "-" => return Some(impl_op!(INT => subtract(as_int, as_int))), - "*" => return Some(impl_op!(INT => multiply(as_int, as_int))), - "/" => return Some(impl_op!(INT => divide(as_int, as_int))), - "%" => return Some(impl_op!(INT => modulo(as_int, as_int))), - "**" => return Some(impl_op!(INT => power(as_int, as_int))), - ">>" => return Some(impl_op!(INT => shift_right(as_int, as_int))), - "<<" => return Some(impl_op!(INT => shift_left(as_int, as_int))), + Plus => return Some(impl_op!(INT => add(as_int, as_int))), + Minus => return Some(impl_op!(INT => subtract(as_int, as_int))), + Multiply => return Some(impl_op!(INT => multiply(as_int, as_int))), + Divide => return Some(impl_op!(INT => divide(as_int, as_int))), + Modulo => return Some(impl_op!(INT => modulo(as_int, as_int))), + PowerOf => return Some(impl_op!(INT => power(as_int, as_int))), + RightShift => return Some(impl_op!(INT => shift_right(as_int, as_int))), + LeftShift => return Some(impl_op!(INT => shift_left(as_int, as_int))), _ => (), } #[cfg(feature = "unchecked")] match op { - "+" => return Some(impl_op!(INT => as_int + as_int)), - "-" => return Some(impl_op!(INT => as_int - as_int)), - "*" => return Some(impl_op!(INT => as_int * as_int)), - "/" => return Some(impl_op!(INT => as_int / as_int)), - "%" => return Some(impl_op!(INT => as_int % as_int)), - "**" => return Some(impl_op!(INT => as_int.pow(as_int as u32))), - ">>" => return Some(impl_op!(INT => as_int >> as_int)), - "<<" => return Some(impl_op!(INT => as_int << as_int)), + Plus => return Some(impl_op!(INT => as_int + as_int)), + Minus => return Some(impl_op!(INT => as_int - as_int)), + Multiply => return Some(impl_op!(INT => as_int * as_int)), + Divide => return Some(impl_op!(INT => as_int / as_int)), + Modulo => return Some(impl_op!(INT => as_int % as_int)), + PowerOf => return Some(impl_op!(INT => as_int.pow(as_int as u32))), + RightShift => return Some(impl_op!(INT => as_int >> as_int)), + LeftShift => return Some(impl_op!(INT => as_int << as_int)), _ => (), } return match op { - "==" => Some(impl_op!(INT => as_int == as_int)), - "!=" => Some(impl_op!(INT => as_int != as_int)), - ">" => Some(impl_op!(INT => as_int > as_int)), - ">=" => Some(impl_op!(INT => as_int >= as_int)), - "<" => Some(impl_op!(INT => as_int < as_int)), - "<=" => Some(impl_op!(INT => as_int <= as_int)), - "&" => Some(impl_op!(INT => as_int & as_int)), - "|" => Some(impl_op!(INT => as_int | as_int)), - "^" => Some(impl_op!(INT => as_int ^ as_int)), - ".." => Some(|_, args| { + EqualsTo => Some(impl_op!(INT => as_int == as_int)), + NotEqualsTo => Some(impl_op!(INT => as_int != as_int)), + GreaterThan => Some(impl_op!(INT => as_int > as_int)), + GreaterThanEqualsTo => Some(impl_op!(INT => as_int >= as_int)), + LessThan => Some(impl_op!(INT => as_int < as_int)), + LessThanEqualsTo => Some(impl_op!(INT => as_int <= as_int)), + Ampersand => Some(impl_op!(INT => as_int & as_int)), + Pipe => Some(impl_op!(INT => as_int | as_int)), + XOr => Some(impl_op!(INT => as_int ^ as_int)), + ExclusiveRange => Some(|_, args| { let x = args[0].as_int().expect(BUILTIN); let y = args[1].as_int().expect(BUILTIN); Ok((x..y).into()) }), - "..=" => Some(|_, args| { + InclusiveRange => Some(|_, args| { let x = args[0].as_int().expect(BUILTIN); let y = args[1].as_int().expect(BUILTIN); Ok((x..=y).into()) @@ -181,47 +196,65 @@ pub fn get_builtin_binary_op_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Option() { return match op { - "==" => Some(impl_op!(bool => as_bool == as_bool)), - "!=" => Some(impl_op!(bool => as_bool != as_bool)), - ">" => Some(impl_op!(bool => as_bool > as_bool)), - ">=" => Some(impl_op!(bool => as_bool >= as_bool)), - "<" => Some(impl_op!(bool => as_bool < as_bool)), - "<=" => Some(impl_op!(bool => as_bool <= as_bool)), - "&" => Some(impl_op!(bool => as_bool & as_bool)), - "|" => Some(impl_op!(bool => as_bool | as_bool)), - "^" => Some(impl_op!(bool => as_bool ^ as_bool)), + EqualsTo => Some(impl_op!(bool => as_bool == as_bool)), + NotEqualsTo => Some(impl_op!(bool => as_bool != as_bool)), + GreaterThan => Some(impl_op!(bool => as_bool > as_bool)), + GreaterThanEqualsTo => Some(impl_op!(bool => as_bool >= as_bool)), + LessThan => Some(impl_op!(bool => as_bool < as_bool)), + LessThanEqualsTo => Some(impl_op!(bool => as_bool <= as_bool)), + Ampersand => Some(impl_op!(bool => as_bool & as_bool)), + Pipe => Some(impl_op!(bool => as_bool | as_bool)), + XOr => Some(impl_op!(bool => as_bool ^ as_bool)), _ => None, }; } if type1 == TypeId::of::() { return match op { - "+" => Some(impl_op!(ImmutableString + ImmutableString)), - "-" => Some(impl_op!(ImmutableString - ImmutableString)), - "==" => Some(impl_op!(ImmutableString == ImmutableString)), - "!=" => Some(impl_op!(ImmutableString != ImmutableString)), - ">" => Some(impl_op!(ImmutableString > ImmutableString)), - ">=" => Some(impl_op!(ImmutableString >= ImmutableString)), - "<" => Some(impl_op!(ImmutableString < ImmutableString)), - "<=" => Some(impl_op!(ImmutableString <= ImmutableString)), - OP_CONTAINS => Some(impl_op!(ImmutableString.contains(ImmutableString.as_str()))), + Plus => Some(|_ctx, args| { + let s1 = &*args[0].read_lock::().expect(BUILTIN); + let s2 = &*args[1].read_lock::().expect(BUILTIN); + + #[cfg(not(feature = "unchecked"))] + if !s1.is_empty() && !s2.is_empty() { + let total_len = s1.len() + s2.len(); + _ctx.engine() + .raise_err_if_over_data_size_limit((0, 0, total_len))?; + } + + Ok((s1 + s2).into()) + }), + Minus => Some(impl_op!(ImmutableString - ImmutableString)), + EqualsTo => Some(impl_op!(ImmutableString == ImmutableString)), + NotEqualsTo => Some(impl_op!(ImmutableString != ImmutableString)), + GreaterThan => Some(impl_op!(ImmutableString > ImmutableString)), + GreaterThanEqualsTo => Some(impl_op!(ImmutableString >= ImmutableString)), + LessThan => Some(impl_op!(ImmutableString < ImmutableString)), + LessThanEqualsTo => Some(impl_op!(ImmutableString <= ImmutableString)), _ => None, }; } if type1 == TypeId::of::() { return match op { - "+" => Some(|_, args| { + Plus => Some(|_ctx, args| { let x = args[0].as_char().expect(BUILTIN); let y = args[1].as_char().expect(BUILTIN); - Ok(format!("{x}{y}").into()) + + let result = format!("{x}{y}"); + + #[cfg(not(feature = "unchecked"))] + _ctx.engine() + .raise_err_if_over_data_size_limit((0, 0, result.len()))?; + + Ok(result.into()) }), - "==" => Some(impl_op!(char => as_char == as_char)), - "!=" => Some(impl_op!(char => as_char != as_char)), - ">" => Some(impl_op!(char => as_char > as_char)), - ">=" => Some(impl_op!(char => as_char >= as_char)), - "<" => Some(impl_op!(char => as_char < as_char)), - "<=" => Some(impl_op!(char => as_char <= as_char)), + EqualsTo => Some(impl_op!(char => as_char == as_char)), + NotEqualsTo => Some(impl_op!(char => as_char != as_char)), + GreaterThan => Some(impl_op!(char => as_char > as_char)), + GreaterThanEqualsTo => Some(impl_op!(char => as_char >= as_char)), + LessThan => Some(impl_op!(char => as_char < as_char)), + LessThanEqualsTo => Some(impl_op!(char => as_char <= as_char)), _ => None, }; } @@ -231,7 +264,7 @@ pub fn get_builtin_binary_op_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Option Some(|_, args| { + Plus => Some(|_ctx, args| { let blob1 = &*args[0].read_lock::().expect(BUILTIN); let blob2 = &*args[1].read_lock::().expect(BUILTIN); @@ -240,21 +273,30 @@ pub fn get_builtin_binary_op_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Option Some(impl_op!(Blob == Blob)), - "!=" => Some(impl_op!(Blob != Blob)), + EqualsTo => Some(impl_op!(Blob == Blob)), + NotEqualsTo => Some(impl_op!(Blob != Blob)), _ => None, }; } if type1 == TypeId::of::<()>() { return match op { - "==" => Some(|_, _| Ok(Dynamic::TRUE)), - "!=" | ">" | ">=" | "<" | "<=" => Some(|_, _| Ok(Dynamic::FALSE)), + EqualsTo => Some(const_true_fn), + NotEqualsTo | GreaterThan | GreaterThanEqualsTo | LessThan | LessThanEqualsTo => { + Some(const_false_fn) + } _ => None, }; } @@ -265,19 +307,19 @@ pub fn get_builtin_binary_op_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Option { if (type1, type2) == (TypeId::of::<$x>(), TypeId::of::<$y>()) { return match op { - "+" => Some(impl_op!(FLOAT => $xx + $yy)), - "-" => Some(impl_op!(FLOAT => $xx - $yy)), - "*" => Some(impl_op!(FLOAT => $xx * $yy)), - "/" => Some(impl_op!(FLOAT => $xx / $yy)), - "%" => Some(impl_op!(FLOAT => $xx % $yy)), - "**" => Some(impl_op!(FLOAT => $xx.powf($yy as FLOAT))), - "==" => Some(impl_op!(FLOAT => $xx == $yy)), - "!=" => Some(impl_op!(FLOAT => $xx != $yy)), - ">" => Some(impl_op!(FLOAT => $xx > $yy)), - ">=" => Some(impl_op!(FLOAT => $xx >= $yy)), - "<" => Some(impl_op!(FLOAT => $xx < $yy)), - "<=" => Some(impl_op!(FLOAT => $xx <= $yy)), - _ => None, + Plus => Some(impl_op!(FLOAT => $xx + $yy)), + Minus => Some(impl_op!(FLOAT => $xx - $yy)), + Multiply => Some(impl_op!(FLOAT => $xx * $yy)), + Divide => Some(impl_op!(FLOAT => $xx / $yy)), + Modulo => Some(impl_op!(FLOAT => $xx % $yy)), + PowerOf => Some(impl_op!(FLOAT => $xx.powf($yy as FLOAT))), + EqualsTo => Some(impl_op!(FLOAT => $xx == $yy)), + NotEqualsTo => Some(impl_op!(FLOAT => $xx != $yy)), + GreaterThan => Some(impl_op!(FLOAT => $xx > $yy)), + GreaterThanEqualsTo => Some(impl_op!(FLOAT => $xx >= $yy)), + LessThan => Some(impl_op!(FLOAT => $xx < $yy)), + LessThanEqualsTo => Some(impl_op!(FLOAT => $xx <= $yy)), + _ => None, }; } }; @@ -295,17 +337,17 @@ pub fn get_builtin_binary_op_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Option { if (type1, type2) == (TypeId::of::<$x>(), TypeId::of::<$y>()) { #[cfg(not(feature = "unchecked"))] - use crate::packages::arithmetic::decimal_functions::*; + use crate::packages::arithmetic::decimal_functions::builtin::*; #[cfg(not(feature = "unchecked"))] match op { - "+" => return Some(impl_op!(from Decimal => add($xx, $yy))), - "-" => return Some(impl_op!(from Decimal => subtract($xx, $yy))), - "*" => return Some(impl_op!(from Decimal => multiply($xx, $yy))), - "/" => return Some(impl_op!(from Decimal => divide($xx, $yy))), - "%" => return Some(impl_op!(from Decimal => modulo($xx, $yy))), - "**" => return Some(impl_op!(from Decimal => power($xx, $yy))), - _ => () + Plus => return Some(impl_op!(from Decimal => add($xx, $yy))), + Minus => return Some(impl_op!(from Decimal => subtract($xx, $yy))), + Multiply => return Some(impl_op!(from Decimal => multiply($xx, $yy))), + Divide => return Some(impl_op!(from Decimal => divide($xx, $yy))), + Modulo => return Some(impl_op!(from Decimal => modulo($xx, $yy))), + PowerOf => return Some(impl_op!(from Decimal => power($xx, $yy))), + _ => () } #[cfg(feature = "unchecked")] @@ -313,23 +355,23 @@ pub fn get_builtin_binary_op_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Option return Some(impl_op!(from Decimal => $xx + $yy)), - "-" => return Some(impl_op!(from Decimal => $xx - $yy)), - "*" => return Some(impl_op!(from Decimal => $xx * $yy)), - "/" => return Some(impl_op!(from Decimal => $xx / $yy)), - "%" => return Some(impl_op!(from Decimal => $xx % $yy)), - "**" => return Some(impl_op!(from Decimal => $xx.powd($yy))), - _ => () + Plus => return Some(impl_op!(from Decimal => $xx + $yy)), + Minus => return Some(impl_op!(from Decimal => $xx - $yy)), + Multiply => return Some(impl_op!(from Decimal => $xx * $yy)), + Divide => return Some(impl_op!(from Decimal => $xx / $yy)), + Modulo => return Some(impl_op!(from Decimal => $xx % $yy)), + PowerOf => return Some(impl_op!(from Decimal => $xx.powd($yy))), + _ => () } return match op { - "==" => Some(impl_op!(from Decimal => $xx == $yy)), - "!=" => Some(impl_op!(from Decimal => $xx != $yy)), - ">" => Some(impl_op!(from Decimal => $xx > $yy)), - ">=" => Some(impl_op!(from Decimal => $xx >= $yy)), - "<" => Some(impl_op!(from Decimal => $xx < $yy)), - "<=" => Some(impl_op!(from Decimal => $xx <= $yy)), - _ => None + EqualsTo => Some(impl_op!(from Decimal => $xx == $yy)), + NotEqualsTo => Some(impl_op!(from Decimal => $xx != $yy)), + GreaterThan => Some(impl_op!(from Decimal => $xx > $yy)), + GreaterThanEqualsTo => Some(impl_op!(from Decimal => $xx >= $yy)), + LessThan => Some(impl_op!(from Decimal => $xx < $yy)), + LessThanEqualsTo => Some(impl_op!(from Decimal => $xx <= $yy)), + _ => None }; } }; @@ -354,17 +396,23 @@ pub fn get_builtin_binary_op_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Option Some(|_, args| { + Plus => Some(|_ctx, args| { let x = args[0].as_char().expect(BUILTIN); let y = &*args[1].read_lock::().expect(BUILTIN); - Ok(format!("{x}{y}").into()) + let result = format!("{x}{y}"); + + #[cfg(not(feature = "unchecked"))] + _ctx.engine() + .raise_err_if_over_data_size_limit((0, 0, result.len()))?; + + Ok(result.into()) }), - "==" => Some(impl_op!(get_s1s2(==))), - "!=" => Some(impl_op!(get_s1s2(!=))), - ">" => Some(impl_op!(get_s1s2(>))), - ">=" => Some(impl_op!(get_s1s2(>=))), - "<" => Some(impl_op!(get_s1s2(<))), - "<=" => Some(impl_op!(get_s1s2(<=))), + EqualsTo => Some(impl_op!(get_s1s2(==))), + NotEqualsTo => Some(impl_op!(get_s1s2(!=))), + GreaterThan => Some(impl_op!(get_s1s2(>))), + GreaterThanEqualsTo => Some(impl_op!(get_s1s2(>=))), + LessThan => Some(impl_op!(get_s1s2(<))), + LessThanEqualsTo => Some(impl_op!(get_s1s2(<=))), _ => None, }; } @@ -380,45 +428,50 @@ pub fn get_builtin_binary_op_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Option Some(|_, args| { + Plus => Some(|_ctx, args| { let x = &*args[0].read_lock::().expect(BUILTIN); let y = args[1].as_char().expect(BUILTIN); - Ok((x + y).into()) + let result = x + y; + + #[cfg(not(feature = "unchecked"))] + _ctx.engine() + .raise_err_if_over_data_size_limit((0, 0, result.len()))?; + + Ok(result.into()) }), - "-" => Some(|_, args| { + Minus => Some(|_, args| { let x = &*args[0].read_lock::().expect(BUILTIN); let y = args[1].as_char().expect(BUILTIN); Ok((x - y).into()) }), - "==" => Some(impl_op!(get_s1s2(==))), - "!=" => Some(impl_op!(get_s1s2(!=))), - ">" => Some(impl_op!(get_s1s2(>))), - ">=" => Some(impl_op!(get_s1s2(>=))), - "<" => Some(impl_op!(get_s1s2(<))), - "<=" => Some(impl_op!(get_s1s2(<=))), - OP_CONTAINS => Some(|_, args| { - let s = &*args[0].read_lock::().expect(BUILTIN); - let c = args[1].as_char().expect(BUILTIN); - Ok(s.contains(c).into()) - }), + EqualsTo => Some(impl_op!(get_s1s2(==))), + NotEqualsTo => Some(impl_op!(get_s1s2(!=))), + GreaterThan => Some(impl_op!(get_s1s2(>))), + GreaterThanEqualsTo => Some(impl_op!(get_s1s2(>=))), + LessThan => Some(impl_op!(get_s1s2(<))), + LessThanEqualsTo => Some(impl_op!(get_s1s2(<=))), _ => None, }; } // () op string if (type1, type2) == (TypeId::of::<()>(), TypeId::of::()) { return match op { - "+" => Some(|_, args| Ok(args[1].clone())), - "==" | ">" | ">=" | "<" | "<=" => Some(|_, _| Ok(Dynamic::FALSE)), - "!=" => Some(|_, _| Ok(Dynamic::TRUE)), + Plus => Some(|_, args| Ok(args[1].clone())), + EqualsTo | GreaterThan | GreaterThanEqualsTo | LessThan | LessThanEqualsTo => { + Some(const_false_fn) + } + NotEqualsTo => Some(const_true_fn), _ => None, }; } // string op () if (type1, type2) == (TypeId::of::(), TypeId::of::<()>()) { return match op { - "+" => Some(|_, args| Ok(args[0].clone())), - "==" | ">" | ">=" | "<" | "<=" => Some(|_, _| Ok(Dynamic::FALSE)), - "!=" => Some(|_, _| Ok(Dynamic::TRUE)), + Plus => Some(|_, args| Ok(args[0].clone())), + EqualsTo | GreaterThan | GreaterThanEqualsTo | LessThan | LessThanEqualsTo => { + Some(const_false_fn) + } + NotEqualsTo => Some(const_true_fn), _ => None, }; } @@ -428,22 +481,20 @@ pub fn get_builtin_binary_op_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Option() { use crate::Blob; - if type2 == TypeId::of::() { - return match op { - OP_CONTAINS => Some(|_, args| { - let blob = &*args[0].read_lock::().expect(BUILTIN); - let x = (args[1].as_int().expect("`INT`") & 0x0000_00ff) as u8; - Ok((!blob.is_empty() && blob.contains(&x)).into()) - }), - _ => None, - }; - } if type2 == TypeId::of::() { return match op { - "+" => Some(|_, args| { - let mut buf = [0_u8; 4]; + Plus => Some(|_ctx, args| { let mut blob = args[0].read_lock::().expect(BUILTIN).clone(); - let x = args[1].as_char().expect("`char`").encode_utf8(&mut buf); + let mut buf = [0_u8; 4]; + let x = args[1].as_char().expect(BUILTIN).encode_utf8(&mut buf); + + #[cfg(not(feature = "unchecked"))] + _ctx.engine().raise_err_if_over_data_size_limit(( + blob.len() + x.len(), + 0, + 0, + ))?; + blob.extend(x.as_bytes()); Ok(Dynamic::from_blob(blob)) }), @@ -452,17 +503,6 @@ pub fn get_builtin_binary_op_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Option(), TypeId::of::()) { - use crate::Map; - - return match op { - OP_CONTAINS => Some(impl_op!(Map.contains_key(ImmutableString.as_str()))), - _ => None, - }; - } - // Non-compatible ranges if (type1, type2) == ( @@ -476,48 +516,28 @@ pub fn get_builtin_binary_op_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Option None, - }; - } if type1 == type2 { return match op { - "==" => Some(impl_op!(ExclusiveRange == ExclusiveRange)), - "!=" => Some(impl_op!(ExclusiveRange != ExclusiveRange)), + EqualsTo => Some(impl_op!(ExclusiveRange == ExclusiveRange)), + NotEqualsTo => Some(impl_op!(ExclusiveRange != ExclusiveRange)), _ => None, }; } } if type1 == TypeId::of::() { - if type2 == TypeId::of::() { - return match op { - OP_CONTAINS => Some(|_, args| { - let range = &*args[0].read_lock::().expect(BUILTIN); - let x = args[1].as_int().expect("`INT`"); - Ok(range.contains(&x).into()) - }), - _ => None, - }; - } if type1 == type2 { return match op { - "==" => Some(impl_op!(InclusiveRange == InclusiveRange)), - "!=" => Some(impl_op!(InclusiveRange != InclusiveRange)), + EqualsTo => Some(impl_op!(InclusiveRange == InclusiveRange)), + NotEqualsTo => Some(impl_op!(InclusiveRange != InclusiveRange)), _ => None, }; } @@ -531,8 +551,10 @@ pub fn get_builtin_binary_op_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Option" | ">=" | "<" | "<=" => Some(|_, _| Ok(Dynamic::FALSE)), + NotEqualsTo => Some(const_true_fn), + EqualsTo | GreaterThan | GreaterThanEqualsTo | LessThan | LessThanEqualsTo => { + Some(const_false_fn) + } _ => None, } } else { @@ -544,8 +566,10 @@ pub fn get_builtin_binary_op_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Option" | ">=" | "<" | "<=" => Some(|_, _| Ok(Dynamic::FALSE)), + NotEqualsTo => Some(const_true_fn), + EqualsTo | GreaterThan | GreaterThanEqualsTo | LessThan | LessThanEqualsTo => { + Some(const_false_fn) + } _ => None, }; } @@ -558,7 +582,7 @@ pub fn get_builtin_binary_op_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Option Option { +pub fn get_builtin_op_assignment_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Option { let type1 = x.type_id(); let type2 = y.type_id(); @@ -610,49 +634,49 @@ pub fn get_builtin_op_assignment_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Optio #[cfg(not(feature = "unchecked"))] match op { - "+=" => return Some(impl_op!(INT => add(as_int, as_int))), - "-=" => return Some(impl_op!(INT => subtract(as_int, as_int))), - "*=" => return Some(impl_op!(INT => multiply(as_int, as_int))), - "/=" => return Some(impl_op!(INT => divide(as_int, as_int))), - "%=" => return Some(impl_op!(INT => modulo(as_int, as_int))), - "**=" => return Some(impl_op!(INT => power(as_int, as_int))), - ">>=" => return Some(impl_op!(INT => shift_right(as_int, as_int))), - "<<=" => return Some(impl_op!(INT => shift_left(as_int, as_int))), + PlusAssign => return Some(impl_op!(INT => add(as_int, as_int))), + MinusAssign => return Some(impl_op!(INT => subtract(as_int, as_int))), + MultiplyAssign => return Some(impl_op!(INT => multiply(as_int, as_int))), + DivideAssign => return Some(impl_op!(INT => divide(as_int, as_int))), + ModuloAssign => return Some(impl_op!(INT => modulo(as_int, as_int))), + PowerOfAssign => return Some(impl_op!(INT => power(as_int, as_int))), + RightShiftAssign => return Some(impl_op!(INT => shift_right(as_int, as_int))), + LeftShiftAssign => return Some(impl_op!(INT => shift_left(as_int, as_int))), _ => (), } #[cfg(feature = "unchecked")] match op { - "+=" => return Some(impl_op!(INT += as_int)), - "-=" => return Some(impl_op!(INT -= as_int)), - "*=" => return Some(impl_op!(INT *= as_int)), - "/=" => return Some(impl_op!(INT /= as_int)), - "%=" => return Some(impl_op!(INT %= as_int)), - "**=" => return Some(impl_op!(INT => as_int.pow(as_int as u32))), - ">>=" => return Some(impl_op!(INT >>= as_int)), - "<<=" => return Some(impl_op!(INT <<= as_int)), + PlusAssign => return Some(impl_op!(INT += as_int)), + MinusAssign => return Some(impl_op!(INT -= as_int)), + MultiplyAssign => return Some(impl_op!(INT *= as_int)), + DivideAssign => return Some(impl_op!(INT /= as_int)), + ModuloAssign => return Some(impl_op!(INT %= as_int)), + PowerOfAssign => return Some(impl_op!(INT => as_int.pow(as_int as u32))), + RightShiftAssign => return Some(impl_op!(INT >>= as_int)), + LeftShiftAssign => return Some(impl_op!(INT <<= as_int)), _ => (), } return match op { - "&=" => Some(impl_op!(INT &= as_int)), - "|=" => Some(impl_op!(INT |= as_int)), - "^=" => Some(impl_op!(INT ^= as_int)), + AndAssign => Some(impl_op!(INT &= as_int)), + OrAssign => Some(impl_op!(INT |= as_int)), + XOrAssign => Some(impl_op!(INT ^= as_int)), _ => None, }; } if type1 == TypeId::of::() { return match op { - "&=" => Some(impl_op!(bool = x && as_bool)), - "|=" => Some(impl_op!(bool = x || as_bool)), + AndAssign => Some(impl_op!(bool = x && as_bool)), + OrAssign => Some(impl_op!(bool = x || as_bool)), _ => None, }; } if type1 == TypeId::of::() { return match op { - "+=" => Some(|_, args| { + PlusAssign => Some(|_, args| { let y = args[1].as_char().expect(BUILTIN); let x = &mut *args[0].write_lock::().expect(BUILTIN); Ok((*x = format!("{x}{y}").into()).into()) @@ -663,13 +687,21 @@ pub fn get_builtin_op_assignment_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Optio if type1 == TypeId::of::() { return match op { - "+=" => Some(|_, args| { + PlusAssign => Some(|_ctx, args| { let (first, second) = args.split_first_mut().expect(BUILTIN); let x = &mut *first.write_lock::().expect(BUILTIN); let y = std::mem::take(second[0]).cast::(); + + #[cfg(not(feature = "unchecked"))] + if !x.is_empty() && !y.is_empty() { + let total_len = x.len() + y.len(); + _ctx.engine() + .raise_err_if_over_data_size_limit((0, 0, total_len))?; + } + Ok((*x += y).into()) }), - "-=" => Some(|_, args| { + MinusAssign => Some(|_, args| { let (first, second) = args.split_first_mut().expect(BUILTIN); let x = &mut *first.write_lock::().expect(BUILTIN); let y = std::mem::take(second[0]).cast::(); @@ -679,15 +711,55 @@ pub fn get_builtin_op_assignment_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Optio }; } + #[cfg(not(feature = "no_index"))] + if type1 == TypeId::of::() { + use crate::packages::array_basic::array_functions::*; + use crate::Array; + + return match op { + PlusAssign => Some(|_ctx, args| { + let x = std::mem::take(args[1]).cast::(); + + if x.is_empty() { + return Ok(Dynamic::UNIT); + } + + let _array_is_empty = args[0].read_lock::().expect(BUILTIN).is_empty(); + + #[cfg(not(feature = "unchecked"))] + if !_array_is_empty { + _ctx.engine().check_data_size( + &*args[0].read_lock().expect(BUILTIN), + crate::Position::NONE, + )?; + } + + let array = &mut *args[0].write_lock::().expect(BUILTIN); + + Ok(append(array, x).into()) + }), + _ => None, + }; + } + #[cfg(not(feature = "no_index"))] if type1 == TypeId::of::() { + use crate::packages::blob_basic::blob_functions::*; use crate::Blob; return match op { - "+=" => Some(|_, args| { + PlusAssign => Some(|_ctx, args| { let blob2 = std::mem::take(args[1]).cast::(); let blob1 = &mut *args[0].write_lock::().expect(BUILTIN); - Ok(crate::packages::blob_basic::blob_functions::append(blob1, blob2).into()) + + #[cfg(not(feature = "unchecked"))] + _ctx.engine().raise_err_if_over_data_size_limit(( + blob1.len() + blob2.len(), + 0, + 0, + ))?; + + Ok(append(blob1, blob2).into()) }), _ => None, }; @@ -699,13 +771,13 @@ pub fn get_builtin_op_assignment_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Optio ($x:ident, $xx:ident, $y:ty, $yy:ident) => { if (type1, type2) == (TypeId::of::<$x>(), TypeId::of::<$y>()) { return match op { - "+=" => Some(impl_op!($x += $yy)), - "-=" => Some(impl_op!($x -= $yy)), - "*=" => Some(impl_op!($x *= $yy)), - "/=" => Some(impl_op!($x /= $yy)), - "%=" => Some(impl_op!($x %= $yy)), - "**=" => Some(impl_op!($x => $xx.powf($yy as $x))), - _ => None, + PlusAssign => Some(impl_op!($x += $yy)), + MinusAssign => Some(impl_op!($x -= $yy)), + MultiplyAssign => Some(impl_op!($x *= $yy)), + DivideAssign => Some(impl_op!($x /= $yy)), + ModuloAssign => Some(impl_op!($x %= $yy)), + PowerOfAssign => Some(impl_op!($x => $xx.powf($yy as $x))), + _ => None, }; } } @@ -722,17 +794,17 @@ pub fn get_builtin_op_assignment_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Optio ($x:ident, $xx:ident, $y:ty, $yy:ident) => { if (type1, type2) == (TypeId::of::<$x>(), TypeId::of::<$y>()) { #[cfg(not(feature = "unchecked"))] - use crate::packages::arithmetic::decimal_functions::*; + use crate::packages::arithmetic::decimal_functions::builtin::*; #[cfg(not(feature = "unchecked"))] return match op { - "+=" => Some(impl_op!(from $x => add($xx, $yy))), - "-=" => Some(impl_op!(from $x => subtract($xx, $yy))), - "*=" => Some(impl_op!(from $x => multiply($xx, $yy))), - "/=" => Some(impl_op!(from $x => divide($xx, $yy))), - "%=" => Some(impl_op!(from $x => modulo($xx, $yy))), - "**=" => Some(impl_op!(from $x => power($xx, $yy))), - _ => None, + PlusAssign => Some(impl_op!(from $x => add($xx, $yy))), + MinusAssign => Some(impl_op!(from $x => subtract($xx, $yy))), + MultiplyAssign => Some(impl_op!(from $x => multiply($xx, $yy))), + DivideAssign => Some(impl_op!(from $x => divide($xx, $yy))), + ModuloAssign => Some(impl_op!(from $x => modulo($xx, $yy))), + PowerOfAssign => Some(impl_op!(from $x => power($xx, $yy))), + _ => None, }; #[cfg(feature = "unchecked")] @@ -740,13 +812,13 @@ pub fn get_builtin_op_assignment_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Optio #[cfg(feature = "unchecked")] return match op { - "+=" => Some(impl_op!(from $x += $yy)), - "-=" => Some(impl_op!(from $x -= $yy)), - "*=" => Some(impl_op!(from $x *= $yy)), - "/=" => Some(impl_op!(from $x /= $yy)), - "%=" => Some(impl_op!(from $x %= $yy)), - "**=" => Some(impl_op!(from $x => $xx.powd($yy))), - _ => None, + PlusAssign => Some(impl_op!(from $x += $yy)), + MinusAssign => Some(impl_op!(from $x -= $yy)), + MultiplyAssign => Some(impl_op!(from $x *= $yy)), + DivideAssign => Some(impl_op!(from $x /= $yy)), + ModuloAssign => Some(impl_op!(from $x %= $yy)), + PowerOfAssign => Some(impl_op!(from $x => $xx.powd($yy))), + _ => None, }; } }; @@ -761,25 +833,45 @@ pub fn get_builtin_op_assignment_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Optio // string op= char if (type1, type2) == (TypeId::of::(), TypeId::of::()) { return match op { - "+=" => Some(impl_op!(ImmutableString += as_char as char)), - "-=" => Some(impl_op!(ImmutableString -= as_char as char)), + PlusAssign => Some(|_ctx, args| { + let mut buf = [0_u8; 4]; + let ch = &*args[1].as_char().expect(BUILTIN).encode_utf8(&mut buf); + let mut x = args[0].write_lock::().expect(BUILTIN); + + #[cfg(not(feature = "unchecked"))] + _ctx.engine() + .raise_err_if_over_data_size_limit((0, 0, x.len() + ch.len()))?; + + Ok((*x += ch).into()) + }), + MinusAssign => Some(impl_op!(ImmutableString -= as_char as char)), _ => None, }; } // char op= string if (type1, type2) == (TypeId::of::(), TypeId::of::()) { return match op { - "+=" => Some(|_, args| { - let mut ch = args[0].as_char().expect(BUILTIN).to_string(); - ch.push_str( - args[1] - .read_lock::() - .expect(BUILTIN) - .as_str(), - ); + PlusAssign => Some(|_ctx, args| { + let ch = { + let s = &*args[1].read_lock::().expect(BUILTIN); - let mut x = args[0].write_lock::().expect(BUILTIN); - Ok((*x = ch.into()).into()) + if s.is_empty() { + return Ok(Dynamic::UNIT); + } + + let mut ch = args[0].as_char().expect(BUILTIN).to_string(); + + #[cfg(not(feature = "unchecked"))] + _ctx.engine() + .raise_err_if_over_data_size_limit((0, 0, ch.len() + s.len()))?; + + ch.push_str(s); + ch + }; + + *args[0].write_lock::().expect(BUILTIN) = ch.into(); + + Ok(Dynamic::UNIT) }), _ => None, }; @@ -791,21 +883,21 @@ pub fn get_builtin_op_assignment_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Optio use crate::packages::array_basic::array_functions::*; use crate::Array; - if type2 == TypeId::of::() { - return match op { - "+=" => Some(|_, args| { - let array2 = std::mem::take(args[1]).cast::(); - let array1 = &mut *args[0].write_lock::().expect(BUILTIN); - Ok(append(array1, array2).into()) - }), - _ => None, - }; - } return match op { - "+=" => Some(|_, args| { - let x = std::mem::take(args[1]); - let array = &mut *args[0].write_lock::().expect(BUILTIN); - Ok(push(array, x).into()) + PlusAssign => Some(|_ctx, args| { + { + let x = std::mem::take(args[1]); + let array = &mut *args[0].write_lock::().expect(BUILTIN); + push(array, x); + } + + #[cfg(not(feature = "unchecked"))] + _ctx.engine().check_data_size( + &*args[0].read_lock().expect(BUILTIN), + crate::Position::NONE, + )?; + + Ok(Dynamic::UNIT) }), _ => None, }; @@ -817,11 +909,18 @@ pub fn get_builtin_op_assignment_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Optio // blob op= int if (type1, type2) == (TypeId::of::(), TypeId::of::()) { + use crate::packages::blob_basic::blob_functions::*; + return match op { - "+=" => Some(|_, args| { - let x = args[1].as_int().expect("`INT`"); + PlusAssign => Some(|_ctx, args| { + let x = args[1].as_int().expect(BUILTIN); let blob = &mut *args[0].write_lock::().expect(BUILTIN); - Ok(crate::packages::blob_basic::blob_functions::push(blob, x).into()) + + #[cfg(not(feature = "unchecked"))] + _ctx.engine() + .raise_err_if_over_data_size_limit((blob.len() + 1, 0, 0))?; + + Ok(push(blob, x).into()) }), _ => None, }; @@ -829,11 +928,18 @@ pub fn get_builtin_op_assignment_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Optio // blob op= char if (type1, type2) == (TypeId::of::(), TypeId::of::()) { + use crate::packages::blob_basic::blob_functions::*; + return match op { - "+=" => Some(|_, args| { - let x = args[1].as_char().expect("`char`"); + PlusAssign => Some(|_ctx, args| { + let x = args[1].as_char().expect(BUILTIN); let blob = &mut *args[0].write_lock::().expect(BUILTIN); - Ok(crate::packages::blob_basic::blob_functions::append_char(blob, x).into()) + + #[cfg(not(feature = "unchecked"))] + _ctx.engine() + .raise_err_if_over_data_size_limit((blob.len() + 1, 0, 0))?; + + Ok(append_char(blob, x).into()) }), _ => None, }; @@ -841,11 +947,26 @@ pub fn get_builtin_op_assignment_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Optio // blob op= string if (type1, type2) == (TypeId::of::(), TypeId::of::()) { + use crate::packages::blob_basic::blob_functions::*; + return match op { - "+=" => Some(|_, args| { + PlusAssign => Some(|_ctx, args| { let s = std::mem::take(args[1]).cast::(); + + if s.is_empty() { + return Ok(Dynamic::UNIT); + } + let blob = &mut *args[0].write_lock::().expect(BUILTIN); - Ok(crate::packages::blob_basic::blob_functions::append_str(blob, &s).into()) + + #[cfg(not(feature = "unchecked"))] + _ctx.engine().raise_err_if_over_data_size_limit(( + blob.len() + s.len(), + 0, + 0, + ))?; + + Ok(append_str(blob, &s).into()) }), _ => None, }; diff --git a/src/func/call.rs b/src/func/call.rs index e91d63a8..38020404 100644 --- a/src/func/call.rs +++ b/src/func/call.rs @@ -1,18 +1,18 @@ //! Implement function-calling mechanism for [`Engine`]. -use super::callable_function::CallableFunction; -use super::{get_builtin_binary_op_fn, get_builtin_op_assignment_fn}; +use super::{get_builtin_binary_op_fn, get_builtin_op_assignment_fn, CallableFunction}; use crate::api::default_limits::MAX_DYNAMIC_PARAMETERS; -use crate::ast::{Expr, FnCallHashes, Stmt}; +use crate::ast::{Expr, FnCallExpr, FnCallHashes}; use crate::engine::{ KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY, KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_TYPE_OF, }; use crate::eval::{Caches, FnResolutionCacheEntry, GlobalRuntimeState}; +use crate::tokenizer::{is_valid_function_name, Token}; +use crate::types::RestoreOnDrop; use crate::{ - calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, Engine, FnArgsVec, FnPtr, - ImmutableString, Module, OptimizationLevel, Position, RhaiError, RhaiResult, RhaiResultOf, - Scope, ERR, + calc_fn_hash, calc_fn_hash_full, Dynamic, Engine, FnArgsVec, FnPtr, ImmutableString, + OptimizationLevel, Position, RhaiError, RhaiResult, RhaiResultOf, Scope, SharedModule, ERR, }; #[cfg(feature = "no_std")] use hashbrown::hash_map::Entry; @@ -89,9 +89,11 @@ impl<'a> ArgBackup<'a> { /// If `change_first_arg_to_copy` has been called, this function **MUST** be called _BEFORE_ /// exiting the current scope. Otherwise it is undefined behavior as the shorter lifetime will leak. #[inline(always)] - pub fn restore_first_arg(mut self, args: &mut FnCallArgs<'a>) { + pub fn restore_first_arg(&mut self, args: &mut FnCallArgs<'a>) { if let Some(p) = self.orig_mut.take() { args[0] = p; + } else { + unreachable!("`Some`"); } } } @@ -107,17 +109,14 @@ impl Drop for ArgBackup<'_> { } } +// Ensure no data races in function call arguments. #[cfg(not(feature = "no_closure"))] #[inline] -pub fn ensure_no_data_race( - fn_name: &str, - args: &FnCallArgs, - is_method_call: bool, -) -> RhaiResultOf<()> { +pub fn ensure_no_data_race(fn_name: &str, args: &FnCallArgs, is_ref_mut: bool) -> RhaiResultOf<()> { if let Some((n, ..)) = args .iter() .enumerate() - .skip(if is_method_call { 1 } else { 0 }) + .skip(if is_ref_mut { 1 } else { 0 }) .find(|(.., a)| a.is_locked()) { return Err(ERR::ErrorDataRace( @@ -130,48 +129,32 @@ pub fn ensure_no_data_race( Ok(()) } -/// Generate the signature for a function call. +/// Is a function name an anonymous function? +#[cfg(not(feature = "no_function"))] #[inline] #[must_use] -pub fn gen_fn_call_signature(engine: &Engine, fn_name: &str, args: &[&mut Dynamic]) -> String { - format!( - "{fn_name} ({})", - args.iter() - .map(|a| if a.is::() { - "&str | ImmutableString | String" - } else { - engine.map_type_name(a.type_name()) - }) - .collect::>() - .join(", ") - ) -} - -/// Generate the signature for a namespace-qualified function call. -/// -/// Not available under `no_module`. -#[cfg(not(feature = "no_module"))] -#[inline] -#[must_use] -pub fn gen_qualified_fn_call_signature( - engine: &Engine, - namespace: &crate::ast::Namespace, - fn_name: &str, - args: &[&mut Dynamic], -) -> String { - let (ns, sep) = ( - namespace.to_string(), - if namespace.is_empty() { - "" - } else { - crate::tokenizer::Token::DoubleColon.literal_syntax() - }, - ); - - format!("{ns}{sep}{}", gen_fn_call_signature(engine, fn_name, args)) +pub fn is_anonymous_fn(name: &str) -> bool { + name.starts_with(crate::engine::FN_ANONYMOUS) } impl Engine { + /// Generate the signature for a function call. + #[inline] + #[must_use] + fn gen_fn_call_signature(&self, fn_name: &str, args: &[&mut Dynamic]) -> String { + format!( + "{fn_name} ({})", + args.iter() + .map(|a| if a.is::() { + "&str | ImmutableString | String" + } else { + self.map_type_name(a.type_name()) + }) + .collect::>() + .join(", ") + ) + } + /// Resolve a normal (non-qualified) function call. /// /// Search order: @@ -186,20 +169,18 @@ impl Engine { _global: &GlobalRuntimeState, caches: &'s mut Caches, local_entry: &'s mut Option, - lib: &[&Module], - fn_name: &str, + lib: &[SharedModule], + op_token: Option<&Token>, hash_base: u64, args: Option<&mut FnCallArgs>, allow_dynamic: bool, - is_op_assignment: bool, ) -> Option<&'s FnResolutionCacheEntry> { if hash_base == 0 { return None; } let mut hash = args.as_ref().map_or(hash_base, |args| { - let hash_params = calc_fn_params_hash(args.iter().map(|a| a.type_id())); - combine_hashes(hash_base, hash_params) + calc_fn_hash_full(hash_base, args.iter().map(|a| a.type_id())) }); let cache = caches.fn_resolution_cache_mut(); @@ -215,9 +196,8 @@ impl Engine { loop { let func = lib .iter() - .copied() - .chain(self.global_modules.iter().map(|m| m.as_ref())) - .find_map(|m| m.get_fn(hash).map(|f| (f, m.id()))); + .chain(self.global_modules.iter()) + .find_map(|m| m.get_fn(hash).map(|f| (f, m.id_raw()))); #[cfg(not(feature = "no_module"))] let func = if args.is_none() { @@ -227,7 +207,7 @@ impl Engine { func.or_else(|| _global.get_qualified_fn(hash)).or_else(|| { self.global_sub_modules .values() - .find_map(|m| m.get_qualified_fn(hash).map(|f| (f, m.id()))) + .find_map(|m| m.get_qualified_fn(hash).map(|f| (f, m.id_raw()))) }) }; @@ -235,7 +215,7 @@ impl Engine { // Specific version found let new_entry = Some(FnResolutionCacheEntry { func: f.clone(), - source: s.map(|s| Box::new(s.into())), + source: s.cloned(), }); return if cache.filter.is_absent_and_set(hash) { // Do not cache "one-hit wonders" @@ -257,9 +237,7 @@ impl Engine { #[cfg(not(feature = "no_module"))] let is_dynamic = is_dynamic - || _global - .iter_imports_raw() - .any(|(_, m)| m.may_contain_dynamic_fn(hash_base)) + || _global.may_contain_dynamic_fn(hash_base) || self .global_sub_modules .values() @@ -278,25 +256,25 @@ impl Engine { } // Try to find a built-in version - let builtin = args.and_then(|args| { - if is_op_assignment { - let (first_arg, rest_args) = args.split_first().unwrap(); + let builtin = + args.and_then(|args| match op_token { + Some(token) if token.is_op_assignment() => { + let (first_arg, rest_args) = args.split_first().unwrap(); - get_builtin_op_assignment_fn(fn_name, *first_arg, rest_args[0]).map( - |f| FnResolutionCacheEntry { + get_builtin_op_assignment_fn(token, *first_arg, rest_args[0]) + .map(|f| FnResolutionCacheEntry { + func: CallableFunction::from_fn_builtin(f), + source: None, + }) + } + Some(token) => get_builtin_binary_op_fn(token, args[0], args[1]) + .map(|f| FnResolutionCacheEntry { func: CallableFunction::from_fn_builtin(f), source: None, - }, - ) - } else { - get_builtin_binary_op_fn(fn_name, args[0], args[1]).map(|f| { - FnResolutionCacheEntry { - func: CallableFunction::from_fn_builtin(f), - source: None, - } - }) - } - }); + }), + + None => None, + }); return if cache.filter.is_absent_and_set(hash) { // Do not cache "one-hit wonders" @@ -309,7 +287,8 @@ impl Engine { } // Try all permutations with `Dynamic` wildcards - let hash_params = calc_fn_params_hash( + hash = calc_fn_hash_full( + hash_base, args.as_ref() .expect("no permutations") .iter() @@ -324,7 +303,6 @@ impl Engine { } }), ); - hash = combine_hashes(hash_base, hash_params); bitmask += 1; } @@ -343,23 +321,19 @@ impl Engine { /// /// **DO NOT** reuse the argument values unless for the first `&mut` argument - /// all others are silently replaced by `()`! - pub(crate) fn call_native_fn( + pub(crate) fn exec_native_fn_call( &self, global: &mut GlobalRuntimeState, caches: &mut Caches, - lib: &[&Module], + lib: &[SharedModule], name: &str, + op_token: Option<&Token>, hash: u64, - args: &mut FnCallArgs, + mut args: &mut FnCallArgs, is_ref_mut: bool, - is_op_assign: bool, pos: Position, - level: usize, ) -> RhaiResultOf<(Dynamic, bool)> { - #[cfg(not(feature = "unchecked"))] - self.inc_operations(&mut global.num_operations, pos)?; - - let parent_source = global.source.clone(); + self.track_operation(global, pos)?; // Check if function access already in the cache let local_entry = &mut None; @@ -369,11 +343,10 @@ impl Engine { caches, local_entry, lib, - name, + op_token, hash, Some(args), true, - is_op_assign, ); if func.is_some() { @@ -383,68 +356,68 @@ impl Engine { #[cfg(feature = "debugging")] let orig_call_stack_len = global.debugger.call_stack().len(); - let mut _result = if let Some(FnResolutionCacheEntry { func, source }) = func { - assert!(func.is_native()); + let FnResolutionCacheEntry { func, source } = func.unwrap(); + assert!(func.is_native()); - let mut backup = ArgBackup::new(); + let backup = &mut ArgBackup::new(); - // Calling pure function but the first argument is a reference? - if is_ref_mut && func.is_pure() && !args.is_empty() { - // Clone the first argument - backup.change_first_arg_to_copy(args); - } + // Calling pure function but the first argument is a reference? + let swap = is_ref_mut && func.is_pure() && !args.is_empty(); - let source = match (source, parent_source.as_str()) { - (None, "") => None, - (None, s) => Some(s), - (Some(s), ..) => Some(s.as_str()), - }; + if swap { + // Clone the first argument + backup.change_first_arg_to_copy(args); + } - #[cfg(feature = "debugging")] - if self.debugger.is_some() { - global.debugger.push_call_stack_frame( - name, - args.iter().map(|v| (*v).clone()).collect(), - source.unwrap_or(""), - pos, - ); - } + let args = + &mut *RestoreOnDrop::lock_if(swap, &mut args, move |a| backup.restore_first_arg(a)); - // Run external function - let context = (self, name, source, &*global, lib, pos, level).into(); + #[cfg(feature = "debugging")] + if self.debugger.is_some() { + global.debugger.push_call_stack_frame( + self.get_interned_string(name), + args.iter().map(|v| (*v).clone()).collect(), + source.clone().or_else(|| global.source.clone()), + pos, + ); + } - let result = if func.is_plugin_fn() { - func.get_plugin_fn().unwrap().call(context, args) + // Run external function + let src = source.as_ref().map(|s| s.as_str()); + let context = (self, name, src, &*global, lib, pos).into(); + + let mut _result = if func.is_plugin_fn() { + let f = func.get_plugin_fn().unwrap(); + if !f.is_pure() && !args.is_empty() && args[0].is_read_only() { + Err(ERR::ErrorNonPureMethodCallOnConstant(name.to_string(), pos).into()) } else { - func.get_native_fn().unwrap()(context, args) - }; - - // Restore the original reference - backup.restore_first_arg(args); - - result + f.call(context, args) + } } else { - unreachable!("`Some`"); + func.get_native_fn().unwrap()(context, args) }; #[cfg(feature = "debugging")] { let trigger = match global.debugger.status { - crate::eval::DebuggerStatus::FunctionExit(n) => n >= level, + crate::eval::DebuggerStatus::FunctionExit(n) => n >= global.level, crate::eval::DebuggerStatus::Next(.., true) => true, _ => false, }; if trigger { let scope = &mut &mut Scope::new(); + let mut this = Dynamic::NULL; let node = crate::ast::Stmt::Noop(pos); let node = (&node).into(); let event = match _result { Ok(ref r) => crate::eval::DebuggerEvent::FunctionExitWithValue(r), Err(ref err) => crate::eval::DebuggerEvent::FunctionExitWithError(err), }; - match self.run_debugger_raw(scope, global, lib, &mut None, node, event, level) { - Ok(_) => (), - Err(err) => _result = Err(err), + + if let Err(err) = + self.run_debugger_raw(global, caches, lib, scope, &mut this, node, event) + { + _result = Err(err); } } @@ -475,12 +448,7 @@ impl Engine { let t = self.map_type_name(type_name::()).into(); ERR::ErrorMismatchOutputType(t, typ.into(), pos) })?; - let source = if global.source.is_empty() { - None - } else { - Some(global.source.as_str()) - }; - ((*self.debug)(&text, source, pos).into(), false) + ((*self.debug)(&text, global.source(), pos).into(), false) } _ => (result, is_method), }); @@ -549,7 +517,7 @@ impl Engine { // Raise error _ => { - Err(ERR::ErrorFunctionNotFound(gen_fn_call_signature(self, name, args), pos).into()) + Err(ERR::ErrorFunctionNotFound(self.gen_fn_call_signature(name, args), pos).into()) } } } @@ -567,17 +535,17 @@ impl Engine { /// all others are silently replaced by `()`! pub(crate) fn exec_fn_call( &self, - _scope: Option<&mut Scope>, global: &mut GlobalRuntimeState, caches: &mut Caches, - lib: &[&Module], + lib: &[SharedModule], + _scope: Option<&mut Scope>, fn_name: &str, + op_token: Option<&Token>, hashes: FnCallHashes, - args: &mut FnCallArgs, + mut args: &mut FnCallArgs, is_ref_mut: bool, _is_method_call: bool, pos: Position, - level: usize, ) -> RhaiResultOf<(Dynamic, bool)> { fn no_method_err(name: &str, pos: Position) -> RhaiResultOf<(Dynamic, bool)> { Err(ERR::ErrorRuntime( @@ -591,203 +559,159 @@ impl Engine { #[cfg(not(feature = "no_closure"))] ensure_no_data_race(fn_name, args, is_ref_mut)?; + global.level += 1; + let global = &mut *RestoreOnDrop::lock(global, move |g| g.level -= 1); + // These may be redirected from method style calls. - match fn_name { - // Handle type_of() - KEYWORD_TYPE_OF if args.len() == 1 => { - let typ = self.map_type_name(args[0].type_name()).to_string().into(); - return Ok((typ, false)); + if hashes.is_native_only() { + match fn_name { + // Handle type_of() + KEYWORD_TYPE_OF if args.len() == 1 => { + let typ = self.map_type_name(args[0].type_name()).to_string().into(); + return Ok((typ, false)); + } + + // Handle is_def_fn() + #[cfg(not(feature = "no_function"))] + crate::engine::KEYWORD_IS_DEF_FN + if args.len() == 2 && args[0].is::() && args[1].is::() => + { + let fn_name = args[0].read_lock::().expect("`FnPtr`"); + let num_params = args[1].as_int().expect("`INT`"); + + return Ok(( + if num_params < 0 || num_params > crate::MAX_USIZE_INT { + false + } else { + let hash_script = + calc_fn_hash(None, fn_name.as_str(), num_params as usize); + self.has_script_fn(global, caches, lib, hash_script) + } + .into(), + false, + )); + } + + // Handle is_shared() + #[cfg(not(feature = "no_closure"))] + crate::engine::KEYWORD_IS_SHARED if args.len() == 1 => { + return no_method_err(fn_name, pos) + } + + KEYWORD_FN_PTR | KEYWORD_EVAL | KEYWORD_IS_DEF_VAR if args.len() == 1 => { + return no_method_err(fn_name, pos) + } + + KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY if !args.is_empty() => { + return no_method_err(fn_name, pos) + } + + _ => (), } - - // Handle is_def_fn() - #[cfg(not(feature = "no_function"))] - crate::engine::KEYWORD_IS_DEF_FN - if args.len() == 2 && args[0].is::() && args[1].is::() => - { - let fn_name = args[0].read_lock::().expect("`FnPtr`"); - let num_params = args[1].as_int().expect("`INT`"); - - return Ok(( - if num_params < 0 || num_params > crate::MAX_USIZE_INT { - false - } else { - let hash_script = calc_fn_hash(None, fn_name.as_str(), num_params as usize); - self.has_script_fn(Some(global), caches, lib, hash_script) - } - .into(), - false, - )); - } - - // Handle is_shared() - #[cfg(not(feature = "no_closure"))] - crate::engine::KEYWORD_IS_SHARED if args.len() == 1 => { - return no_method_err(fn_name, pos) - } - - KEYWORD_FN_PTR | KEYWORD_EVAL | KEYWORD_IS_DEF_VAR if args.len() == 1 => { - return no_method_err(fn_name, pos) - } - - KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY if !args.is_empty() => { - return no_method_err(fn_name, pos) - } - - _ => (), } - let level = level + 1; - - // Script-defined function call? #[cfg(not(feature = "no_function"))] - let local_entry = &mut None; + if !hashes.is_native_only() { + // Script-defined function call? + let hash = hashes.script(); + let local_entry = &mut None; - #[cfg(not(feature = "no_function"))] - if let Some(FnResolutionCacheEntry { func, ref source }) = self - .resolve_fn( - global, - caches, - local_entry, - lib, - fn_name, - hashes.script, - None, - false, - false, - ) - .cloned() - { - // Script function call - assert!(func.is_script()); + if let Some(FnResolutionCacheEntry { func, ref source }) = self + .resolve_fn(global, caches, local_entry, lib, None, hash, None, false) + .cloned() + { + // Script function call + assert!(func.is_script()); - let func = func.get_script_fn_def().expect("script-defined function"); + let func = func.get_script_fn_def().expect("script-defined function"); - if func.body.is_empty() { - return Ok((Dynamic::UNIT, false)); + if func.body.is_empty() { + return Ok((Dynamic::UNIT, false)); + } + + let mut empty_scope; + let scope = match _scope { + Some(scope) => scope, + None => { + empty_scope = Scope::new(); + &mut empty_scope + } + }; + + let orig_source = mem::replace(&mut global.source, source.clone()); + let global = &mut *RestoreOnDrop::lock(global, move |g| g.source = orig_source); + + return if _is_method_call { + // Method call of script function - map first argument to `this` + let (first_arg, rest_args) = args.split_first_mut().unwrap(); + + self.call_script_fn( + global, caches, lib, scope, first_arg, func, rest_args, true, pos, + ) + } else { + // Normal call of script function + let backup = &mut ArgBackup::new(); + + // The first argument is a reference? + let swap = is_ref_mut && !args.is_empty(); + + if swap { + backup.change_first_arg_to_copy(args); + } + + let args = &mut *RestoreOnDrop::lock_if(swap, &mut args, move |a| { + backup.restore_first_arg(a) + }); + + let mut this = Dynamic::NULL; + + self.call_script_fn( + global, caches, lib, scope, &mut this, func, args, true, pos, + ) + } + .map(|r| (r, false)); } - - let mut empty_scope; - let scope = match _scope { - Some(scope) => scope, - None => { - empty_scope = Scope::new(); - &mut empty_scope - } - }; - - let orig_source = mem::replace( - &mut global.source, - source - .as_ref() - .map_or(crate::Identifier::new_const(), |s| (**s).clone()), - ); - - let result = if _is_method_call { - // Method call of script function - map first argument to `this` - let (first_arg, rest_args) = args.split_first_mut().unwrap(); - - self.call_script_fn( - scope, - global, - caches, - lib, - &mut Some(*first_arg), - func, - rest_args, - true, - pos, - level, - ) - } else { - // Normal call of script function - let mut backup = ArgBackup::new(); - - // The first argument is a reference? - if is_ref_mut && !args.is_empty() { - backup.change_first_arg_to_copy(args); - } - - let result = self.call_script_fn( - scope, global, caches, lib, &mut None, func, args, true, pos, level, - ); - - // Restore the original reference - backup.restore_first_arg(args); - - result - }; - - // Restore the original source - global.source = orig_source; - - return Ok((result?, false)); } // Native function call - let hash = hashes.native; - self.call_native_fn( - global, caches, lib, fn_name, hash, args, is_ref_mut, false, pos, level, - ) - } + let hash = hashes.native(); - /// Evaluate a list of statements with no `this` pointer. - /// This is commonly used to evaluate a list of statements in an [`AST`][crate::AST] or a script function body. - #[inline] - pub(crate) fn eval_global_statements( - &self, - scope: &mut Scope, - global: &mut GlobalRuntimeState, - caches: &mut Caches, - statements: &[Stmt], - lib: &[&Module], - level: usize, - ) -> RhaiResult { - self.eval_stmt_block( - scope, global, caches, lib, &mut None, statements, false, level, + self.exec_native_fn_call( + global, caches, lib, fn_name, op_token, hash, args, is_ref_mut, pos, ) - .or_else(|err| match *err { - ERR::Return(out, ..) => Ok(out), - ERR::LoopBreak(..) => { - unreachable!("no outer loop scope to break out of") - } - _ => Err(err), - }) } /// Evaluate an argument. #[inline] pub(crate) fn get_arg_value( &self, - scope: &mut Scope, global: &mut GlobalRuntimeState, caches: &mut Caches, - lib: &[&Module], - this_ptr: &mut Option<&mut Dynamic>, + lib: &[SharedModule], + scope: &mut Scope, + this_ptr: &mut Dynamic, arg_expr: &Expr, - level: usize, ) -> RhaiResultOf<(Dynamic, Position)> { - #[cfg(feature = "debugging")] - if self.debugger.is_some() { - if let Some(value) = arg_expr.get_literal_value() { - #[cfg(feature = "debugging")] - self.run_debugger(scope, global, lib, this_ptr, arg_expr, level)?; - return Ok((value, arg_expr.start_position())); - } + // Literal values + if let Some(value) = arg_expr.get_literal_value() { + self.track_operation(global, arg_expr.start_position())?; + + #[cfg(feature = "debugging")] + self.run_debugger(global, caches, lib, scope, this_ptr, arg_expr)?; + + return Ok((value, arg_expr.start_position())); } // Do not match function exit for arguments #[cfg(feature = "debugging")] - let reset_debugger = global.debugger.clear_status_if(|status| { + let reset = global.debugger.clear_status_if(|status| { matches!(status, crate::eval::DebuggerStatus::FunctionExit(..)) }); - - let result = self.eval_expr(scope, global, caches, lib, this_ptr, arg_expr, level); - - // Restore function exit status #[cfg(feature = "debugging")] - global.debugger.reset_status(reset_debugger); + let global = &mut *RestoreOnDrop::lock(global, move |g| g.debugger.reset_status(reset)); - Ok((result?, arg_expr.start_position())) + self.eval_expr(global, caches, lib, scope, this_ptr, arg_expr) + .map(|r| (r, arg_expr.start_position())) } /// Call a dot method. @@ -796,14 +720,13 @@ impl Engine { &self, global: &mut GlobalRuntimeState, caches: &mut Caches, - lib: &[&Module], + lib: &[SharedModule], fn_name: &str, mut hash: FnCallHashes, target: &mut crate::eval::Target, mut call_args: &mut [Dynamic], first_arg_pos: Position, fn_call_pos: Position, - level: usize, ) -> RhaiResultOf<(Dynamic, bool)> { let is_ref_mut = target.is_ref(); @@ -811,11 +734,21 @@ impl Engine { KEYWORD_FN_PTR_CALL if target.is::() => { // FnPtr call let fn_ptr = target.read_lock::().expect("`FnPtr`"); + + #[cfg(not(feature = "no_function"))] + let is_anon = fn_ptr.is_anonymous(); + #[cfg(feature = "no_function")] + let is_anon = false; + // Redirect function name let fn_name = fn_ptr.fn_name(); - let args_len = call_args.len() + fn_ptr.curry().len(); // Recalculate hashes - let new_hash = calc_fn_hash(None, fn_name, args_len).into(); + let args_len = call_args.len() + fn_ptr.curry().len(); + let new_hash = if !is_anon && !is_valid_function_name(fn_name) { + FnCallHashes::from_native(calc_fn_hash(None, fn_name, args_len)) + } else { + calc_fn_hash(None, fn_name, args_len).into() + }; // Arguments are passed as-is, adding the curried arguments let mut curry = FnArgsVec::with_capacity(fn_ptr.curry().len()); curry.extend(fn_ptr.curry().iter().cloned()); @@ -825,17 +758,17 @@ impl Engine { // Map it to name(args) in function-call style self.exec_fn_call( - None, global, caches, lib, + None, fn_name, + None, new_hash, &mut args, false, false, fn_call_pos, - level, ) } KEYWORD_FN_PTR_CALL => { @@ -849,20 +782,30 @@ impl Engine { // FnPtr call on object let fn_ptr = mem::take(&mut call_args[0]).cast::(); + + #[cfg(not(feature = "no_function"))] + let is_anon = fn_ptr.is_anonymous(); + #[cfg(feature = "no_function")] + let is_anon = false; + call_args = &mut call_args[1..]; // Redirect function name - let fn_name = fn_ptr.fn_name(); - let args_len = call_args.len() + fn_ptr.curry().len(); + let (fn_name, fn_curry) = fn_ptr.take_data(); // Recalculate hash - let new_hash = FnCallHashes::from_all( - #[cfg(not(feature = "no_function"))] - calc_fn_hash(None, fn_name, args_len), - calc_fn_hash(None, fn_name, args_len + 1), - ); + let args_len = call_args.len() + fn_curry.len(); + let new_hash = if !is_anon && !is_valid_function_name(&fn_name) { + FnCallHashes::from_native(calc_fn_hash(None, &fn_name, args_len + 1)) + } else { + FnCallHashes::from_all( + #[cfg(not(feature = "no_function"))] + calc_fn_hash(None, &fn_name, args_len), + calc_fn_hash(None, &fn_name, args_len + 1), + ) + }; // Replace the first argument with the object pointer, adding the curried arguments - let mut curry = FnArgsVec::with_capacity(fn_ptr.curry().len()); - curry.extend(fn_ptr.curry().iter().cloned()); + let mut curry = FnArgsVec::with_capacity(fn_curry.len()); + curry.extend(fn_curry.into_iter()); let mut args = FnArgsVec::with_capacity(curry.len() + call_args.len() + 1); args.push(target.as_mut()); args.extend(curry.iter_mut()); @@ -870,17 +813,17 @@ impl Engine { // Map it to name(args) in function-call style self.exec_fn_call( - None, global, caches, lib, - fn_name, + None, + &fn_name, + None, new_hash, &mut args, is_ref_mut, true, fn_call_pos, - level, ) } KEYWORD_FN_PTR_CURRY => { @@ -928,6 +871,11 @@ impl Engine { if let Some(map) = target.read_lock::() { if let Some(val) = map.get(fn_name) { if let Some(fn_ptr) = val.read_lock::() { + #[cfg(not(feature = "no_function"))] + let is_anon = fn_ptr.is_anonymous(); + #[cfg(feature = "no_function")] + let is_anon = false; + // Remap the function name _redirected = fn_ptr.fn_name_raw().clone(); fn_name = &_redirected; @@ -942,11 +890,19 @@ impl Engine { call_args = &mut _arg_values; } // Recalculate the hash based on the new function name and new arguments - hash = FnCallHashes::from_all( - #[cfg(not(feature = "no_function"))] - calc_fn_hash(None, fn_name, call_args.len()), - calc_fn_hash(None, fn_name, call_args.len() + 1), - ); + hash = if !is_anon && !is_valid_function_name(&fn_name) { + FnCallHashes::from_native(calc_fn_hash( + None, + fn_name, + call_args.len() + 1, + )) + } else { + FnCallHashes::from_all( + #[cfg(not(feature = "no_function"))] + calc_fn_hash(None, fn_name, call_args.len()), + calc_fn_hash(None, fn_name, call_args.len() + 1), + ) + }; } } }; @@ -957,17 +913,17 @@ impl Engine { args.extend(call_args.iter_mut()); self.exec_fn_call( - None, global, caches, lib, + None, fn_name, + None, hash, &mut args, is_ref_mut, true, fn_call_pos, - level, ) } }?; @@ -983,19 +939,18 @@ impl Engine { /// Call a function in normal function-call style. pub(crate) fn make_function_call( &self, - scope: &mut Scope, global: &mut GlobalRuntimeState, caches: &mut Caches, - lib: &[&Module], - this_ptr: &mut Option<&mut Dynamic>, + lib: &[SharedModule], + scope: &mut Scope, + this_ptr: &mut Dynamic, fn_name: &str, + op_token: Option<&Token>, first_arg: Option<&Expr>, args_expr: &[Expr], hashes: FnCallHashes, capture_scope: bool, - is_operator: bool, pos: Position, - level: usize, ) -> RhaiResult { let mut first_arg = first_arg; let mut a_expr = args_expr; @@ -1006,13 +961,13 @@ impl Engine { let redirected; // Handle call() - Redirect function call match name { - _ if is_operator => (), + _ if op_token.is_some() => (), // Handle call() KEYWORD_FN_PTR_CALL if total_args >= 1 => { let arg = first_arg.unwrap(); let (arg_value, arg_pos) = - self.get_arg_value(scope, global, caches, lib, this_ptr, arg, level)?; + self.get_arg_value(global, caches, lib, scope, this_ptr, arg)?; if !arg_value.is::() { let typ = self.map_type_name(arg_value.type_name()); @@ -1020,10 +975,17 @@ impl Engine { } let fn_ptr = arg_value.cast::(); - curry.extend(fn_ptr.curry().iter().cloned()); + + #[cfg(not(feature = "no_function"))] + let is_anon = fn_ptr.is_anonymous(); + #[cfg(feature = "no_function")] + let is_anon = false; + + let (fn_name, fn_curry) = fn_ptr.take_data(); + curry.extend(fn_curry.into_iter()); // Redirect function name - redirected = fn_ptr.take_data().0; + redirected = fn_name; name = &redirected; // Shift the arguments @@ -1035,7 +997,8 @@ impl Engine { // Recalculate hash let args_len = total_args + curry.len(); - hashes = if hashes.is_native_only() { + + hashes = if !is_anon && !is_valid_function_name(name) { FnCallHashes::from_native(calc_fn_hash(None, name, args_len)) } else { calc_fn_hash(None, name, args_len).into() @@ -1045,7 +1008,7 @@ impl Engine { KEYWORD_FN_PTR if total_args == 1 => { let arg = first_arg.unwrap(); let (arg_value, arg_pos) = - self.get_arg_value(scope, global, caches, lib, this_ptr, arg, level)?; + self.get_arg_value(global, caches, lib, scope, this_ptr, arg)?; // Fn - only in function call style return arg_value @@ -1060,7 +1023,7 @@ impl Engine { KEYWORD_FN_PTR_CURRY if total_args > 1 => { let first = first_arg.unwrap(); let (arg_value, arg_pos) = - self.get_arg_value(scope, global, caches, lib, this_ptr, first, level)?; + self.get_arg_value(global, caches, lib, scope, this_ptr, first)?; if !arg_value.is::() { let typ = self.map_type_name(arg_value.type_name()); @@ -1072,7 +1035,7 @@ impl Engine { // Append the new curried arguments to the existing list. let fn_curry = a_expr.iter().try_fold(fn_curry, |mut curried, expr| { let (value, ..) = - self.get_arg_value(scope, global, caches, lib, this_ptr, expr, level)?; + self.get_arg_value(global, caches, lib, scope, this_ptr, expr)?; curried.push(value); Ok::<_, RhaiError>(curried) })?; @@ -1085,7 +1048,7 @@ impl Engine { crate::engine::KEYWORD_IS_SHARED if total_args == 1 => { let arg = first_arg.unwrap(); let (arg_value, ..) = - self.get_arg_value(scope, global, caches, lib, this_ptr, arg, level)?; + self.get_arg_value(global, caches, lib, scope, this_ptr, arg)?; return Ok(arg_value.is_shared().into()); } @@ -1094,14 +1057,14 @@ impl Engine { crate::engine::KEYWORD_IS_DEF_FN if total_args == 2 => { let first = first_arg.unwrap(); let (arg_value, arg_pos) = - self.get_arg_value(scope, global, caches, lib, this_ptr, first, level)?; + self.get_arg_value(global, caches, lib, scope, this_ptr, first)?; let fn_name = arg_value .into_immutable_string() .map_err(|typ| self.make_type_mismatch_err::(typ, arg_pos))?; let (arg_value, arg_pos) = - self.get_arg_value(scope, global, caches, lib, this_ptr, &a_expr[0], level)?; + self.get_arg_value(global, caches, lib, scope, this_ptr, &a_expr[0])?; let num_params = arg_value .as_int() @@ -1111,7 +1074,7 @@ impl Engine { false } else { let hash_script = calc_fn_hash(None, &fn_name, num_params as usize); - self.has_script_fn(Some(global), caches, lib, hash_script) + self.has_script_fn(global, caches, lib, hash_script) } .into()); } @@ -1120,7 +1083,7 @@ impl Engine { KEYWORD_IS_DEF_VAR if total_args == 1 => { let arg = first_arg.unwrap(); let (arg_value, arg_pos) = - self.get_arg_value(scope, global, caches, lib, this_ptr, arg, level)?; + self.get_arg_value(global, caches, lib, scope, this_ptr, arg)?; let var_name = arg_value .into_immutable_string() .map_err(|typ| self.make_type_mismatch_err::(typ, arg_pos))?; @@ -1131,32 +1094,35 @@ impl Engine { KEYWORD_EVAL if total_args == 1 => { // eval - only in function call style let orig_scope_len = scope.len(); + #[cfg(not(feature = "no_module"))] + let orig_imports_len = global.num_imports(); let arg = first_arg.unwrap(); let (arg_value, pos) = - self.get_arg_value(scope, global, caches, lib, this_ptr, arg, level)?; - let script = &arg_value + self.get_arg_value(global, caches, lib, scope, this_ptr, arg)?; + let s = &arg_value .into_immutable_string() .map_err(|typ| self.make_type_mismatch_err::(typ, pos))?; - let result = self.eval_script_expr_in_place( - scope, - global, - caches, - lib, - script, - pos, - level + 1, - ); + + global.level += 1; + let global = &mut *RestoreOnDrop::lock(global, move |g| g.level -= 1); + + let result = self.eval_script_expr_in_place(global, caches, lib, scope, s, pos); // IMPORTANT! If the eval defines new variables in the current scope, // all variable offsets from this point on will be mis-aligned. - if scope.len() != orig_scope_len { + // The same is true for imports. + let scope_changed = scope.len() != orig_scope_len; + #[cfg(not(feature = "no_module"))] + let scope_changed = scope_changed || global.num_imports() != orig_imports_len; + + if scope_changed { global.always_search_scope = true; } return result.map_err(|err| { ERR::ErrorInFunctionCall( KEYWORD_EVAL.to_string(), - global.source.to_string(), + global.source().unwrap_or("").to_string(), err, pos, ) @@ -1182,7 +1148,7 @@ impl Engine { .copied() .chain(a_expr.iter()) .try_for_each(|expr| { - self.get_arg_value(scope, global, caches, lib, this_ptr, expr, level) + self.get_arg_value(global, caches, lib, scope, this_ptr, expr) .map(|(value, ..)| arg_values.push(value.flatten())) })?; args.extend(curry.iter_mut()); @@ -1193,8 +1159,8 @@ impl Engine { return self .exec_fn_call( - scope, global, caches, lib, name, hashes, &mut args, is_ref_mut, false, pos, - level, + global, caches, lib, scope, name, op_token, hashes, &mut args, is_ref_mut, + false, pos, ) .map(|(v, ..)| v); } @@ -1210,23 +1176,22 @@ impl Engine { let first_expr = first_arg.unwrap(); #[cfg(feature = "debugging")] - self.run_debugger(scope, global, lib, this_ptr, first_expr, level)?; + self.run_debugger(global, caches, lib, scope, this_ptr, first_expr)?; // func(x, ...) -> x.func(...) a_expr.iter().try_for_each(|expr| { - self.get_arg_value(scope, global, caches, lib, this_ptr, expr, level) + self.get_arg_value(global, caches, lib, scope, this_ptr, expr) .map(|(value, ..)| arg_values.push(value.flatten())) })?; let (mut target, _pos) = - self.search_namespace(scope, global, lib, this_ptr, first_expr, level)?; + self.search_namespace(global, caches, lib, scope, this_ptr, first_expr)?; if target.is_read_only() { target = target.into_owned(); } - #[cfg(not(feature = "unchecked"))] - self.inc_operations(&mut global.num_operations, _pos)?; + self.track_operation(global, _pos)?; #[cfg(not(feature = "no_closure"))] let target_is_shared = target.is_shared(); @@ -1247,7 +1212,7 @@ impl Engine { .into_iter() .chain(a_expr.iter()) .try_for_each(|expr| { - self.get_arg_value(scope, global, caches, lib, this_ptr, expr, level) + self.get_arg_value(global, caches, lib, scope, this_ptr, expr) .map(|(value, ..)| arg_values.push(value.flatten())) })?; args.extend(curry.iter_mut()); @@ -1257,7 +1222,7 @@ impl Engine { } self.exec_fn_call( - None, global, caches, lib, name, hashes, &mut args, is_ref_mut, false, pos, level, + global, caches, lib, None, name, op_token, hashes, &mut args, is_ref_mut, false, pos, ) .map(|(v, ..)| v) } @@ -1266,17 +1231,16 @@ impl Engine { #[cfg(not(feature = "no_module"))] pub(crate) fn make_qualified_function_call( &self, - scope: &mut Scope, global: &mut GlobalRuntimeState, caches: &mut Caches, - lib: &[&Module], - this_ptr: &mut Option<&mut Dynamic>, + lib: &[SharedModule], + scope: &mut Scope, + this_ptr: &mut Dynamic, namespace: &crate::ast::Namespace, fn_name: &str, args_expr: &[Expr], hash: u64, pos: Position, - level: usize, ) -> RhaiResult { let mut arg_values = FnArgsVec::with_capacity(args_expr.len()); let mut args = FnArgsVec::with_capacity(args_expr.len()); @@ -1290,23 +1254,22 @@ impl Engine { // and avoid cloning the value if !args_expr.is_empty() && args_expr[0].is_variable_access(true) { #[cfg(feature = "debugging")] - self.run_debugger(scope, global, lib, this_ptr, &args_expr[0], level)?; + self.run_debugger(global, caches, lib, scope, this_ptr, &args_expr[0])?; // func(x, ...) -> x.func(...) arg_values.push(Dynamic::UNIT); args_expr.iter().skip(1).try_for_each(|expr| { - self.get_arg_value(scope, global, caches, lib, this_ptr, expr, level) + self.get_arg_value(global, caches, lib, scope, this_ptr, expr) .map(|(value, ..)| arg_values.push(value.flatten())) })?; // Get target reference to first argument let first_arg = &args_expr[0]; let (target, _pos) = - self.search_scope_only(scope, global, lib, this_ptr, first_arg, level)?; + self.search_scope_only(global, caches, lib, scope, this_ptr, first_arg)?; - #[cfg(not(feature = "unchecked"))] - self.inc_operations(&mut global.num_operations, _pos)?; + self.track_operation(global, _pos)?; #[cfg(not(feature = "no_closure"))] let target_is_shared = target.is_shared(); @@ -1327,7 +1290,7 @@ impl Engine { } else { // func(..., ...) or func(mod::x, ...) args_expr.iter().try_for_each(|expr| { - self.get_arg_value(scope, global, caches, lib, this_ptr, expr, level) + self.get_arg_value(global, caches, lib, scope, this_ptr, expr) .map(|(value, ..)| arg_values.push(value.flatten())) })?; args.extend(arg_values.iter_mut()); @@ -1343,12 +1306,8 @@ impl Engine { let mut func = match module.get_qualified_fn(hash) { // Then search native Rust functions None => { - #[cfg(not(feature = "unchecked"))] - self.inc_operations(&mut global.num_operations, pos)?; - - let hash_params = calc_fn_params_hash(args.iter().map(|a| a.type_id())); - let hash_qualified_fn = combine_hashes(hash, hash_params); - + self.track_operation(global, pos)?; + let hash_qualified_fn = calc_fn_hash_full(hash, args.iter().map(|a| a.type_id())); module.get_qualified_fn(hash_qualified_fn) } r => r, @@ -1366,19 +1325,20 @@ impl Engine { // Try all permutations with `Dynamic` wildcards while bitmask < max_bitmask { - let hash_params = calc_fn_params_hash(args.iter().enumerate().map(|(i, a)| { - let mask = 1usize << (num_args - i - 1); - if bitmask & mask == 0 { - a.type_id() - } else { - // Replace with `Dynamic` - TypeId::of::() - } - })); - let hash_qualified_fn = combine_hashes(hash, hash_params); + let hash_qualified_fn = calc_fn_hash_full( + hash, + args.iter().enumerate().map(|(i, a)| { + let mask = 1usize << (num_args - i - 1); + if bitmask & mask == 0 { + a.type_id() + } else { + // Replace with `Dynamic` + TypeId::of::() + } + }), + ); - #[cfg(not(feature = "unchecked"))] - self.inc_operations(&mut global.num_operations, pos)?; + self.track_operation(global, pos)?; if let Some(f) = module.get_qualified_fn(hash_qualified_fn) { func = Some(f); @@ -1397,65 +1357,71 @@ impl Engine { } } - let level = level + 1; + global.level += 1; + let global = &mut *RestoreOnDrop::lock(global, move |g| g.level -= 1); match func { #[cfg(not(feature = "no_function"))] Some(f) if f.is_script() => { let fn_def = f.get_script_fn_def().expect("script-defined function"); let new_scope = &mut Scope::new(); - let mut source = module.id_raw().clone(); - mem::swap(&mut global.source, &mut source); + let mut this = Dynamic::NULL; - let result = self.call_script_fn( - new_scope, global, caches, lib, &mut None, fn_def, &mut args, true, pos, level, - ); + let orig_source = mem::replace(&mut global.source, module.id_raw().cloned()); + let global = &mut *RestoreOnDrop::lock(global, move |g| g.source = orig_source); - global.source = source; - - result + self.call_script_fn( + global, caches, lib, new_scope, &mut this, fn_def, &mut args, true, pos, + ) } Some(f) if f.is_plugin_fn() => { - let context = (self, fn_name, module.id(), &*global, lib, pos, level).into(); - let result = f - .get_plugin_fn() - .expect("plugin function") - .clone() - .call(context, &mut args); + let context = (self, fn_name, module.id(), &*global, lib, pos).into(); + let f = f.get_plugin_fn().expect("plugin function"); + let result = if !f.is_pure() && !args.is_empty() && args[0].is_read_only() { + Err(ERR::ErrorNonPureMethodCallOnConstant(fn_name.to_string(), pos).into()) + } else { + f.call(context, &mut args) + }; self.check_return_value(result, pos) } Some(f) if f.is_native() => { let func = f.get_native_fn().expect("native function"); - let context = (self, fn_name, module.id(), &*global, lib, pos, level).into(); + let context = (self, fn_name, module.id(), &*global, lib, pos).into(); let result = func(context, &mut args); self.check_return_value(result, pos) } Some(f) => unreachable!("unknown function type: {:?}", f), - None => Err(ERR::ErrorFunctionNotFound( - gen_qualified_fn_call_signature(self, namespace, fn_name, &args), - pos, - ) - .into()), + None => { + let sig = if namespace.is_empty() { + self.gen_fn_call_signature(fn_name, &args) + } else { + format!( + "{namespace}{}{}", + crate::tokenizer::Token::DoubleColon.literal_syntax(), + self.gen_fn_call_signature(fn_name, &args) + ) + }; + + Err(ERR::ErrorFunctionNotFound(sig, pos).into()) + } } } /// Evaluate a text script in place - used primarily for 'eval'. pub(crate) fn eval_script_expr_in_place( &self, - scope: &mut Scope, global: &mut GlobalRuntimeState, caches: &mut Caches, - lib: &[&Module], + lib: &[SharedModule], + scope: &mut Scope, script: &str, _pos: Position, - level: usize, ) -> RhaiResult { - #[cfg(not(feature = "unchecked"))] - self.inc_operations(&mut global.num_operations, _pos)?; + self.track_operation(global, _pos)?; let script = script.trim(); @@ -1486,6 +1452,84 @@ impl Engine { } // Evaluate the AST - self.eval_global_statements(scope, global, caches, statements, lib, level) + self.eval_global_statements(global, caches, lib, scope, statements) + } + + /// Evaluate a function call expression. + pub(crate) fn eval_fn_call_expr( + &self, + global: &mut GlobalRuntimeState, + caches: &mut Caches, + lib: &[SharedModule], + scope: &mut Scope, + this_ptr: &mut Dynamic, + expr: &FnCallExpr, + pos: Position, + ) -> RhaiResult { + let FnCallExpr { + #[cfg(not(feature = "no_module"))] + namespace, + name, + hashes, + args, + op_token, + capture_parent_scope: capture, + .. + } = expr; + + let op_token = op_token.as_ref(); + + // Short-circuit native binary operator call if under Fast Operators mode + if op_token.is_some() && self.fast_operators() && args.len() == 2 { + let mut lhs = self + .get_arg_value(global, caches, lib, scope, this_ptr, &args[0])? + .0 + .flatten(); + + let mut rhs = self + .get_arg_value(global, caches, lib, scope, this_ptr, &args[1])? + .0 + .flatten(); + + let operands = &mut [&mut lhs, &mut rhs]; + + if let Some(func) = + get_builtin_binary_op_fn(op_token.as_ref().unwrap(), operands[0], operands[1]) + { + // Built-in found + global.level += 1; + let global = &*RestoreOnDrop::lock(global, move |g| g.level -= 1); + + let context = (self, name.as_str(), None, global, lib, pos).into(); + return func(context, operands); + } + + return self + .exec_fn_call( + global, caches, lib, None, name, op_token, *hashes, operands, false, false, pos, + ) + .map(|(v, ..)| v); + } + + #[cfg(not(feature = "no_module"))] + if !namespace.is_empty() { + // Qualified function call + let hash = hashes.native(); + + return self.make_qualified_function_call( + global, caches, lib, scope, this_ptr, namespace, name, args, hash, pos, + ); + } + + // Normal function call + let (first_arg, args) = args.split_first().map_or_else( + || (None, args.as_ref()), + |(first, rest)| (Some(first), rest), + ); + + self.make_function_call( + global, caches, lib, scope, this_ptr, name, op_token, first_arg, args, *hashes, + *capture, pos, + ) } } diff --git a/src/func/callable_function.rs b/src/func/callable_function.rs index c08eb845..f11d41a7 100644 --- a/src/func/callable_function.rs +++ b/src/func/callable_function.rs @@ -27,12 +27,14 @@ pub enum CallableFunction { } impl fmt::Debug for CallableFunction { + #[cold] + #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::Pure(..) => write!(f, "NativePureFunction"), - Self::Method(..) => write!(f, "NativeMethod"), - Self::Iterator(..) => write!(f, "NativeIterator"), - Self::Plugin(..) => write!(f, "PluginFunction"), + Self::Pure(..) => f.write_str("NativePureFunction"), + Self::Method(..) => f.write_str("NativeMethod"), + Self::Iterator(..) => f.write_str("NativeIterator"), + Self::Plugin(..) => f.write_str("PluginFunction"), #[cfg(not(feature = "no_function"))] Self::Script(fn_def) => fmt::Debug::fmt(fn_def, f), @@ -43,10 +45,10 @@ impl fmt::Debug for CallableFunction { impl fmt::Display for CallableFunction { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::Pure(..) => write!(f, "NativePureFunction"), - Self::Method(..) => write!(f, "NativeMethod"), - Self::Iterator(..) => write!(f, "NativeIterator"), - Self::Plugin(..) => write!(f, "PluginFunction"), + Self::Pure(..) => f.write_str("NativePureFunction"), + Self::Method(..) => f.write_str("NativeMethod"), + Self::Iterator(..) => f.write_str("NativeIterator"), + Self::Plugin(..) => f.write_str("PluginFunction"), #[cfg(not(feature = "no_function"))] Self::Script(s) => fmt::Display::fmt(s, f), @@ -197,7 +199,7 @@ impl CallableFunction { Self::Script(..) => None, } } - /// Create a new [`CallableFunction::Method`] from `FnBuiltin`. + /// Create a new [`CallableFunction::Method`] from a built-in function. #[inline(always)] #[must_use] pub fn from_fn_builtin(func: FnBuiltin) -> Self { diff --git a/src/func/func.rs b/src/func/func.rs index f74873c1..7d211879 100644 --- a/src/func/func.rs +++ b/src/func/func.rs @@ -46,6 +46,7 @@ pub trait Func { /// func(123, "hello")? == false; // call the anonymous function /// # Ok(()) /// # } + #[must_use] fn create_from_ast(self, ast: AST, entry_point: &str) -> Self::Output; /// Create a Rust closure from a script. @@ -79,6 +80,7 @@ pub trait Func { /// # Ok(()) /// # } /// ``` + #[must_use] fn create_from_script(self, script: &str, entry_point: &str) -> ParseResult; } diff --git a/src/func/hashing.rs b/src/func/hashing.rs index bc29f67b..83b2fed1 100644 --- a/src/func/hashing.rs +++ b/src/func/hashing.rs @@ -1,5 +1,6 @@ //! Module containing utilities to hash functions and function calls. +use crate::config; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{ @@ -40,6 +41,7 @@ pub struct StraightHasher(u64); impl Hasher for StraightHasher { #[inline(always)] + #[must_use] fn finish(&self) -> u64 { self.0 } @@ -65,6 +67,7 @@ impl BuildHasher for StraightHasherBuilder { type Hasher = StraightHasher; #[inline(always)] + #[must_use] fn build_hasher(&self) -> Self::Hasher { StraightHasher(ALT_ZERO_HASH) } @@ -74,7 +77,12 @@ impl BuildHasher for StraightHasherBuilder { #[inline(always)] #[must_use] pub fn get_hasher() -> ahash::AHasher { - ahash::AHasher::default() + match config::hashing::get_ahash_seed() { + Some([seed1, seed2, seed3, seed4]) if seed1 | seed2 | seed3 | seed4 != 0 => { + ahash::RandomState::with_seeds(*seed1, *seed2, *seed3, *seed4).build_hasher() + } + _ => ahash::AHasher::default(), + } } /// Calculate a non-zero [`u64`] hash key from a namespace-qualified variable name. @@ -148,7 +156,7 @@ pub fn calc_fn_hash<'a>( } } -/// Calculate a non-zero [`u64`] hash key from a list of parameter types. +/// Calculate a non-zero [`u64`] hash key from a base [`u64`] hash key and a list of parameter types. /// /// Parameter types are passed in via [`TypeId`] values from an iterator. /// @@ -157,10 +165,12 @@ pub fn calc_fn_hash<'a>( /// If the hash happens to be zero, it is mapped to `DEFAULT_HASH`. #[inline] #[must_use] -pub fn calc_fn_params_hash( +pub fn calc_fn_hash_full( + base: u64, params: impl IntoIterator>, ) -> u64 { let s = &mut get_hasher(); + base.hash(s); let iter = params.into_iter(); let len = iter.len(); iter.for_each(|t| { @@ -173,17 +183,3 @@ pub fn calc_fn_params_hash( r => r, } } - -/// Combine two [`u64`] hashes by taking the XOR of them. -/// -/// # Zeros -/// -/// If the hash happens to be zero, it is mapped to `DEFAULT_HASH`. -#[inline(always)] -#[must_use] -pub const fn combine_hashes(a: u64, b: u64) -> u64 { - match a ^ b { - 0 => ALT_ZERO_HASH, - r => r, - } -} diff --git a/src/func/mod.rs b/src/func/mod.rs index 8298a63e..d5659854 100644 --- a/src/func/mod.rs +++ b/src/func/mod.rs @@ -13,15 +13,17 @@ pub mod script; pub use args::FuncArgs; pub use builtin::{get_builtin_binary_op_fn, get_builtin_op_assignment_fn}; -#[cfg(not(feature = "no_module"))] -pub use call::gen_qualified_fn_call_signature; -pub use call::{gen_fn_call_signature, FnCallArgs}; +#[cfg(not(feature = "no_closure"))] +pub use call::ensure_no_data_race; +#[cfg(not(feature = "no_function"))] +pub use call::is_anonymous_fn; +pub use call::FnCallArgs; pub use callable_function::CallableFunction; #[cfg(not(feature = "no_function"))] pub use func::Func; -pub use hashing::{ - calc_fn_hash, calc_fn_params_hash, calc_var_hash, combine_hashes, get_hasher, StraightHashMap, -}; +pub use hashing::{calc_fn_hash, calc_fn_hash_full, calc_var_hash, get_hasher, StraightHashMap}; +#[cfg(feature = "internals")] +pub use native::NativeCallContextStore; pub use native::{ locked_read, locked_write, shared_get_mut, shared_make_mut, shared_take, shared_take_or_clone, shared_try_take, FnAny, FnPlugin, IteratorFn, Locked, NativeCallContext, SendSync, Shared, diff --git a/src/func/native.rs b/src/func/native.rs index f421c262..de74f6c6 100644 --- a/src/func/native.rs +++ b/src/func/native.rs @@ -4,11 +4,11 @@ use super::call::FnCallArgs; use crate::ast::FnCallHashes; use crate::eval::{Caches, GlobalRuntimeState}; use crate::plugin::PluginFunction; -use crate::tokenizer::{Token, TokenizeState}; +use crate::tokenizer::{is_valid_function_name, Token, TokenizeState}; use crate::types::dynamic::Variant; use crate::{ calc_fn_hash, Dynamic, Engine, EvalContext, FuncArgs, Module, Position, RhaiResult, - RhaiResultOf, StaticVec, VarDefInfo, ERR, + RhaiResultOf, SharedModule, StaticVec, VarDefInfo, ERR, }; use std::any::type_name; #[cfg(feature = "no_std")] @@ -72,91 +72,73 @@ pub struct NativeCallContext<'a> { /// Function source, if any. source: Option<&'a str>, /// The current [`GlobalRuntimeState`], if any. - global: Option<&'a GlobalRuntimeState<'a>>, + global: &'a GlobalRuntimeState, /// The current stack of loaded [modules][Module]. - lib: &'a [&'a Module], + lib: &'a [SharedModule], /// [Position] of the function call. pos: Position, - /// The current nesting level of function calls. - level: usize, } -impl<'a, M: AsRef<[&'a Module]> + ?Sized, S: AsRef + 'a + ?Sized> +/// _(internals)_ Context of a native Rust function call. +/// Exported under the `internals` feature only. +#[cfg(feature = "internals")] +#[derive(Debug, Clone)] +pub struct NativeCallContextStore { + /// Name of function called. + pub fn_name: String, + /// Function source, if any. + pub source: Option, + /// The current [`GlobalRuntimeState`], if any. + pub global: GlobalRuntimeState, + /// The current stack of loaded [modules][Module]. + pub lib: StaticVec, + /// [Position] of the function call. + pub pos: Position, +} + +#[cfg(feature = "internals")] +impl NativeCallContextStore { + /// Create a [`NativeCallContext`] from a [`NativeCallContextClone`]. + #[inline(always)] + #[must_use] + pub fn create_context<'a>(&'a self, engine: &'a Engine) -> NativeCallContext<'a> { + NativeCallContext::from_stored_data(engine, self) + } +} + +impl<'a> From<( &'a Engine, - &'a S, - Option<&'a S>, - &'a GlobalRuntimeState<'a>, - &'a M, + &'a str, + Option<&'a str>, + &'a GlobalRuntimeState, + &'a [SharedModule], Position, - usize, )> for NativeCallContext<'a> { #[inline(always)] fn from( value: ( &'a Engine, - &'a S, - Option<&'a S>, + &'a str, + Option<&'a str>, &'a GlobalRuntimeState, - &'a M, + &'a [SharedModule], Position, - usize, ), ) -> Self { Self { engine: value.0, - fn_name: value.1.as_ref(), - source: value.2.map(<_>::as_ref), - global: Some(value.3), - lib: value.4.as_ref(), + fn_name: value.1, + source: value.2, + global: value.3, + lib: value.4, pos: value.5, - level: value.6, - } - } -} - -impl<'a, M: AsRef<[&'a Module]> + ?Sized, S: AsRef + 'a + ?Sized> - From<(&'a Engine, &'a S, &'a M)> for NativeCallContext<'a> -{ - #[inline(always)] - fn from(value: (&'a Engine, &'a S, &'a M)) -> Self { - Self { - engine: value.0, - fn_name: value.1.as_ref(), - source: None, - global: None, - lib: value.2.as_ref(), - pos: Position::NONE, - level: 0, } } } impl<'a> NativeCallContext<'a> { - /// _(internals)_ Create a new [`NativeCallContext`]. - /// Exported under the `metadata` feature only. - #[deprecated( - since = "1.3.0", - note = "`NativeCallContext::new` will be moved under `internals`. Use `FnPtr::call` to call a function pointer directly." - )] - #[inline(always)] - #[must_use] - pub fn new( - engine: &'a Engine, - fn_name: &'a (impl AsRef + 'a + ?Sized), - lib: &'a [&Module], - ) -> Self { - Self { - engine, - fn_name: fn_name.as_ref(), - source: None, - global: None, - lib, - pos: Position::NONE, - level: 0, - } - } /// _(internals)_ Create a new [`NativeCallContext`]. /// Exported under the `internals` feature only. /// @@ -167,23 +149,52 @@ impl<'a> NativeCallContext<'a> { #[must_use] pub fn new_with_all_fields( engine: &'a Engine, - fn_name: &'a (impl AsRef + 'a + ?Sized), - source: Option<&'a (impl AsRef + 'a + ?Sized)>, + fn_name: &'a str, + source: Option<&'a str>, global: &'a GlobalRuntimeState, - lib: &'a [&Module], + lib: &'a [SharedModule], pos: Position, - level: usize, ) -> Self { Self { engine, - fn_name: fn_name.as_ref(), - source: source.map(<_>::as_ref), - global: Some(global), + fn_name, + source, + global, lib, pos, - level, } } + + /// _(internals)_ Create a [`NativeCallContext`] from a [`NativeCallContextClone`]. + /// Exported under the `internals` feature only. + #[cfg(feature = "internals")] + #[inline] + #[must_use] + pub fn from_stored_data(engine: &'a Engine, context: &'a NativeCallContextStore) -> Self { + Self { + engine, + fn_name: &context.fn_name, + source: context.source.as_ref().map(String::as_str), + global: &context.global, + lib: &context.lib, + pos: context.pos, + } + } + /// _(internals)_ Store this [`NativeCallContext`] into a [`NativeCallContextClone`]. + /// Exported under the `internals` feature only. + #[cfg(feature = "internals")] + #[inline] + #[must_use] + pub fn store_data(&self) -> NativeCallContextStore { + NativeCallContextStore { + fn_name: self.fn_name.to_string(), + source: self.source.map(|s| s.to_string()), + global: self.global.clone(), + lib: self.lib.iter().cloned().collect(), + pos: self.pos, + } + } + /// The current [`Engine`]. #[inline(always)] #[must_use] @@ -206,7 +217,7 @@ impl<'a> NativeCallContext<'a> { #[inline(always)] #[must_use] pub const fn call_level(&self) -> usize { - self.level + self.global.level } /// The current source. #[inline(always)] @@ -218,7 +229,7 @@ impl<'a> NativeCallContext<'a> { #[inline(always)] #[must_use] pub fn tag(&self) -> Option<&Dynamic> { - self.global.as_ref().map(|g| &g.tag) + Some(&self.global.tag) } /// Get an iterator over the current set of modules imported via `import` statements /// in reverse order. @@ -227,7 +238,7 @@ impl<'a> NativeCallContext<'a> { #[cfg(not(feature = "no_module"))] #[inline] pub fn iter_imports(&self) -> impl Iterator { - self.global.iter().flat_map(|&g| g.iter_imports()) + self.global.iter_imports() } /// Get an iterator over the current set of modules imported via `import` statements in reverse order. #[cfg(not(feature = "no_module"))] @@ -235,8 +246,8 @@ impl<'a> NativeCallContext<'a> { #[inline] pub(crate) fn iter_imports_raw( &self, - ) -> impl Iterator)> { - self.global.iter().flat_map(|&g| g.iter_imports_raw()) + ) -> impl Iterator { + self.global.iter_imports_raw() } /// _(internals)_ The current [`GlobalRuntimeState`], if any. /// Exported under the `internals` feature only. @@ -245,21 +256,21 @@ impl<'a> NativeCallContext<'a> { #[cfg(feature = "internals")] #[inline(always)] #[must_use] - pub const fn global_runtime_state(&self) -> Option<&GlobalRuntimeState> { + pub const fn global_runtime_state(&self) -> &GlobalRuntimeState { self.global } /// Get an iterator over the namespaces containing definitions of all script-defined functions /// in reverse order (i.e. parent namespaces are iterated after child namespaces). #[inline] pub fn iter_namespaces(&self) -> impl Iterator { - self.lib.iter().copied() + self.lib.iter().map(|m| m.as_ref()) } /// _(internals)_ The current stack of namespaces containing definitions of all script-defined functions. /// Exported under the `internals` feature only. #[cfg(feature = "internals")] #[inline(always)] #[must_use] - pub const fn namespaces(&self) -> &[&Module] { + pub const fn namespaces(&self) -> &[SharedModule] { self.lib } /// Call a function inside the call context with the provided arguments. @@ -274,7 +285,7 @@ impl<'a> NativeCallContext<'a> { let mut args: StaticVec<_> = arg_values.iter_mut().collect(); - let result = self.call_fn_raw(fn_name, false, false, &mut args)?; + let result = self._call_fn_raw(fn_name, false, false, false, &mut args)?; let typ = self.engine().map_type_name(result.type_name()); @@ -283,7 +294,32 @@ impl<'a> NativeCallContext<'a> { ERR::ErrorMismatchOutputType(t, typ.into(), Position::NONE).into() }) } - /// Call a function inside the call context. + /// Call a registered native Rust function inside the call context with the provided arguments. + /// + /// This is often useful because Rust functions typically only want to cross-call other + /// registered Rust functions and not have to worry about scripted functions hijacking the + /// process unknowingly (or deliberately). + #[inline] + pub fn call_native_fn( + &self, + fn_name: impl AsRef, + args: impl FuncArgs, + ) -> RhaiResultOf { + let mut arg_values = StaticVec::new_const(); + args.parse(&mut arg_values); + + let mut args: StaticVec<_> = arg_values.iter_mut().collect(); + + let result = self._call_fn_raw(fn_name, true, false, false, &mut args)?; + + let typ = self.engine().map_type_name(result.type_name()); + + result.try_cast().ok_or_else(|| { + let t = self.engine().map_type_name(type_name::()).into(); + ERR::ErrorMismatchOutputType(t, typ.into(), Position::NONE).into() + }) + } + /// Call a function (native Rust or scripted) inside the call context. /// /// If `is_method_call` is [`true`], the first argument is assumed to be the `this` pointer for /// a script-defined function (or the object of a method call). @@ -302,6 +338,7 @@ impl<'a> NativeCallContext<'a> { /// /// If `is_ref_mut` is [`true`], the first argument is assumed to be passed by reference and is /// not consumed. + #[inline(always)] pub fn call_fn_raw( &self, fn_name: impl AsRef, @@ -309,15 +346,81 @@ impl<'a> NativeCallContext<'a> { is_method_call: bool, args: &mut [&mut Dynamic], ) -> RhaiResult { - let mut global = self - .global - .cloned() - .unwrap_or_else(|| GlobalRuntimeState::new(self.engine())); - let mut caches = Caches::new(); + let name = fn_name.as_ref(); + let native_only = !is_valid_function_name(name); + #[cfg(not(feature = "no_function"))] + let native_only = native_only && !crate::parser::is_anonymous_fn(name); + + self._call_fn_raw(fn_name, native_only, is_ref_mut, is_method_call, args) + } + /// Call a registered native Rust function inside the call context. + /// + /// This is often useful because Rust functions typically only want to cross-call other + /// registered Rust functions and not have to worry about scripted functions hijacking the + /// process unknowingly (or deliberately). + /// + /// # WARNING - Low Level API + /// + /// This function is very low level. + /// + /// # Arguments + /// + /// All arguments may be _consumed_, meaning that they may be replaced by `()`. This is to avoid + /// unnecessarily cloning the arguments. + /// + /// **DO NOT** reuse the arguments after this call. If they are needed afterwards, clone them + /// _before_ calling this function. + /// + /// If `is_ref_mut` is [`true`], the first argument is assumed to be passed by reference and is + /// not consumed. + #[inline(always)] + pub fn call_native_fn_raw( + &self, + fn_name: impl AsRef, + is_ref_mut: bool, + args: &mut [&mut Dynamic], + ) -> RhaiResult { + self._call_fn_raw(fn_name, true, is_ref_mut, false, args) + } + + /// Call a function (native Rust or scripted) inside the call context. + fn _call_fn_raw( + &self, + fn_name: impl AsRef, + native_only: bool, + is_ref_mut: bool, + is_method_call: bool, + args: &mut [&mut Dynamic], + ) -> RhaiResult { + let mut global = &mut self.global.clone(); + let caches = &mut Caches::new(); let fn_name = fn_name.as_ref(); + let op_token = Token::lookup_symbol_from_syntax(fn_name); + let op_token = op_token.as_ref(); let args_len = args.len(); + global.level += 1; + + if native_only { + return self + .engine() + .exec_native_fn_call( + global, + caches, + self.lib, + fn_name, + op_token, + calc_fn_hash(None, fn_name, args_len), + args, + is_ref_mut, + Position::NONE, + ) + .map(|(r, ..)| r); + } + + // Native or script + let hash = if is_method_call { FnCallHashes::from_all( #[cfg(not(feature = "no_function"))] @@ -330,17 +433,17 @@ impl<'a> NativeCallContext<'a> { self.engine() .exec_fn_call( - None, - &mut global, - &mut caches, + global, + caches, self.lib, + None, fn_name, + op_token, hash, args, is_ref_mut, is_method_call, Position::NONE, - self.level + 1, ) .map(|(r, ..)| r) } diff --git a/src/func/plugin.rs b/src/func/plugin.rs index 7c4f84fe..b4a9b9d0 100644 --- a/src/func/plugin.rs +++ b/src/func/plugin.rs @@ -29,4 +29,14 @@ pub trait PluginFunction { /// Is this plugin function a method? #[must_use] fn is_method_call(&self) -> bool; + + /// Is this plugin function pure? + /// + /// This defaults to `true` such that any old implementation that has constant-checking code + /// inside the function itself will continue to work. + #[inline(always)] + #[must_use] + fn is_pure(&self) -> bool { + true + } } diff --git a/src/func/register.rs b/src/func/register.rs index 37cc6bd0..8899548b 100644 --- a/src/func/register.rs +++ b/src/func/register.rs @@ -86,33 +86,37 @@ pub trait RegisterNativeFunction { /// _(metadata)_ Get the type name of this function's return value. /// Exported under the `metadata` feature only. #[cfg(feature = "metadata")] + #[inline(always)] #[must_use] - fn return_type_name() -> &'static str; + fn return_type_name() -> &'static str { + std::any::type_name::() + } } const EXPECT_ARGS: &str = "arguments"; macro_rules! check_constant { - ($ctx:ident, $args:ident) => { + ($abi:ident, $ctx:ident, $args:ident) => { #[cfg(any(not(feature = "no_object"), not(feature = "no_index")))] - { - let args_len = $args.len(); - - if args_len > 0 && $args[0].is_read_only() { - let deny = match $ctx.fn_name() { - #[cfg(not(feature = "no_object"))] - f if args_len == 2 && f.starts_with(crate::engine::FN_SET) => true, - #[cfg(not(feature = "no_index"))] - crate::engine::FN_IDX_SET if args_len == 3 => true, - _ => false, - }; - if deny { - return Err(crate::ERR::ErrorAssignmentToConstant( - String::new(), - crate::Position::NONE, - ) - .into()); + if stringify!($abi) == "Method" && !$args.is_empty() { + let deny = match $args.len() { + #[cfg(not(feature = "no_index"))] + 3 if $ctx.fn_name() == crate::engine::FN_IDX_SET && $args[0].is_read_only() => true, + #[cfg(not(feature = "no_object"))] + 2 if $ctx.fn_name().starts_with(crate::engine::FN_SET) + && $args[0].is_read_only() => + { + true } + _ => false, + }; + + if deny { + return Err(crate::ERR::ErrorNonPureMethodCallOnConstant( + $ctx.fn_name().to_string(), + crate::Position::NONE, + ) + .into()); } } }; @@ -138,11 +142,10 @@ macro_rules! def_register { #[inline(always)] fn param_types() -> Box<[TypeId]> { vec![$(TypeId::of::<$par>()),*].into_boxed_slice() } #[cfg(feature = "metadata")] #[inline(always)] fn param_names() -> Box<[&'static str]> { vec![$(std::any::type_name::<$param>()),*].into_boxed_slice() } #[cfg(feature = "metadata")] #[inline(always)] fn return_type() -> TypeId { TypeId::of::() } - #[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { std::any::type_name::() } #[inline(always)] fn into_callable_function(self) -> CallableFunction { CallableFunction::$abi(Shared::new(move |_ctx: NativeCallContext, args: &mut FnCallArgs| { // The arguments are assumed to be of the correct number and types! - check_constant!(_ctx, args); + check_constant!($abi, _ctx, args); let mut _drain = args.iter_mut(); $($let $par = ($clone)(_drain.next().expect(EXPECT_ARGS)); )* @@ -164,11 +167,10 @@ macro_rules! def_register { #[inline(always)] fn param_types() -> Box<[TypeId]> { vec![$(TypeId::of::<$par>()),*].into_boxed_slice() } #[cfg(feature = "metadata")] #[inline(always)] fn param_names() -> Box<[&'static str]> { vec![$(std::any::type_name::<$param>()),*].into_boxed_slice() } #[cfg(feature = "metadata")] #[inline(always)] fn return_type() -> TypeId { TypeId::of::() } - #[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { std::any::type_name::() } #[inline(always)] fn into_callable_function(self) -> CallableFunction { CallableFunction::$abi(Shared::new(move |ctx: NativeCallContext, args: &mut FnCallArgs| { // The arguments are assumed to be of the correct number and types! - check_constant!(ctx, args); + check_constant!($abi, ctx, args); let mut _drain = args.iter_mut(); $($let $par = ($clone)(_drain.next().expect(EXPECT_ARGS)); )* @@ -194,7 +196,7 @@ macro_rules! def_register { #[inline(always)] fn into_callable_function(self) -> CallableFunction { CallableFunction::$abi(Shared::new(move |_ctx: NativeCallContext, args: &mut FnCallArgs| { // The arguments are assumed to be of the correct number and types! - check_constant!(_ctx, args); + check_constant!($abi, _ctx, args); let mut _drain = args.iter_mut(); $($let $par = ($clone)(_drain.next().expect(EXPECT_ARGS)); )* @@ -217,7 +219,7 @@ macro_rules! def_register { #[inline(always)] fn into_callable_function(self) -> CallableFunction { CallableFunction::$abi(Shared::new(move |ctx: NativeCallContext, args: &mut FnCallArgs| { // The arguments are assumed to be of the correct number and types! - check_constant!(ctx, args); + check_constant!($abi, ctx, args); let mut _drain = args.iter_mut(); $($let $par = ($clone)(_drain.next().expect(EXPECT_ARGS)); )* diff --git a/src/func/script.rs b/src/func/script.rs index 8ccc876b..db3fa542 100644 --- a/src/func/script.rs +++ b/src/func/script.rs @@ -4,7 +4,7 @@ use super::call::FnCallArgs; use crate::ast::ScriptFnDef; use crate::eval::{Caches, GlobalRuntimeState}; -use crate::{Dynamic, Engine, Module, Position, RhaiError, RhaiResult, Scope, ERR}; +use crate::{Dynamic, Engine, Position, RhaiError, RhaiResult, Scope, SharedModule, ERR}; use std::mem; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -24,16 +24,15 @@ impl Engine { /// **DO NOT** reuse the argument values unless for the first `&mut` argument - all others are silently replaced by `()`! pub(crate) fn call_script_fn( &self, - scope: &mut Scope, global: &mut GlobalRuntimeState, caches: &mut Caches, - lib: &[&Module], - this_ptr: &mut Option<&mut Dynamic>, + lib: &[SharedModule], + scope: &mut Scope, + this_ptr: &mut Dynamic, fn_def: &ScriptFnDef, args: &mut FnCallArgs, rewind_scope: bool, pos: Position, - level: usize, ) -> RhaiResult { #[cold] #[inline(never)] @@ -54,7 +53,7 @@ impl Engine { Err(ERR::ErrorInFunctionCall( name, - source.unwrap_or_else(|| global.source.to_string()), + source.unwrap_or_else(|| global.source().unwrap_or("").to_string()), err, pos, ) @@ -63,12 +62,10 @@ impl Engine { assert!(fn_def.params.len() == args.len()); - #[cfg(not(feature = "unchecked"))] - self.inc_operations(&mut global.num_operations, pos)?; + self.track_operation(global, pos)?; // Check for stack overflow - #[cfg(not(feature = "unchecked"))] - if level > self.max_call_levels() { + if global.level > self.max_call_levels() { return Err(ERR::ErrorStackOverflow(pos).into()); } @@ -129,8 +126,8 @@ impl Engine { lib } else { caches.push_fn_resolution_cache(); - lib_merged.push(&**fn_lib); - lib_merged.extend(lib.iter().copied()); + lib_merged.push(fn_lib.clone()); + lib_merged.extend(lib.iter().cloned()); &lib_merged }, Some(mem::replace(&mut global.constants, constants.clone())), @@ -142,20 +139,19 @@ impl Engine { #[cfg(feature = "debugging")] { let node = crate::ast::Stmt::Noop(fn_def.body.position()); - self.run_debugger(scope, global, lib, this_ptr, &node, level)?; + self.run_debugger(global, caches, lib, scope, this_ptr, &node)?; } // Evaluate the function let mut _result = self .eval_stmt_block( - scope, global, caches, lib, + scope, this_ptr, &fn_def.body, rewind_scope, - level, ) .or_else(|err| match *err { // Convert return statement to return value @@ -181,7 +177,7 @@ impl Engine { #[cfg(feature = "debugging")] { let trigger = match global.debugger.status { - crate::eval::DebuggerStatus::FunctionExit(n) => n >= level, + crate::eval::DebuggerStatus::FunctionExit(n) => n >= global.level, crate::eval::DebuggerStatus::Next(.., true) => true, _ => false, }; @@ -192,7 +188,7 @@ impl Engine { Ok(ref r) => crate::eval::DebuggerEvent::FunctionExitWithValue(r), Err(ref err) => crate::eval::DebuggerEvent::FunctionExitWithError(err), }; - match self.run_debugger_raw(scope, global, lib, this_ptr, node, event, level) { + match self.run_debugger_raw(global, caches, lib, scope, this_ptr, node, event) { Ok(_) => (), Err(err) => _result = Err(err), } @@ -228,9 +224,9 @@ impl Engine { #[must_use] pub(crate) fn has_script_fn( &self, - _global: Option<&GlobalRuntimeState>, + _global: &GlobalRuntimeState, caches: &mut Caches, - lib: &[&Module], + lib: &[SharedModule], hash_script: u64, ) -> bool { let cache = caches.fn_resolution_cache_mut(); @@ -247,16 +243,13 @@ impl Engine { #[cfg(not(feature = "no_module"))] let result = result || // Then check imported modules - _global.map_or(false, |m| m.contains_qualified_fn(hash_script)) + _global.contains_qualified_fn(hash_script) // Then check sub-modules || self.global_sub_modules.values().any(|m| m.contains_qualified_fn(hash_script)); - if !result { - if cache.filter.is_absent(hash_script) { - cache.filter.mark(hash_script); - } else { - cache.map.insert(hash_script, None); - } + if !result && !cache.filter.is_absent_and_set(hash_script) { + // Do not cache "one-hit wonders" + cache.map.insert(hash_script, None); } result diff --git a/src/lib.rs b/src/lib.rs index 8a83e879..4991287d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -83,6 +83,7 @@ use std::prelude::v1::*; // Internal modules mod api; mod ast; +pub mod config; mod engine; mod eval; mod func; @@ -206,7 +207,7 @@ pub use eval::EvalContext; pub use func::{NativeCallContext, RegisterNativeFunction}; pub use module::{FnNamespace, Module}; pub use tokenizer::Position; -#[cfg(not(feature = "no_std"))] +#[cfg(not(feature = "no_time"))] pub use types::Instant; pub use types::{ Dynamic, EvalAltResult, FnPtr, ImmutableString, LexError, ParseError, ParseErrorType, Scope, @@ -227,7 +228,7 @@ pub mod debugger { /// An identifier in Rhai. [`SmartString`](https://crates.io/crates/smartstring) is used because most /// identifiers are ASCII and short, fewer than 23 characters, so they can be stored inline. #[cfg(not(feature = "internals"))] -pub(crate) type Identifier = SmartString; +type Identifier = SmartString; /// An identifier in Rhai. [`SmartString`](https://crates.io/crates/smartstring) is used because most /// identifiers are ASCII and short, fewer than 23 characters, so they can be stored inline. @@ -240,7 +241,10 @@ pub use func::Shared; /// Alias to [`RefCell`][std::cell::RefCell] or [`RwLock`][std::sync::RwLock] depending on the `sync` feature flag. pub use func::Locked; -pub(crate) use func::{calc_fn_hash, calc_fn_params_hash, calc_var_hash, combine_hashes}; +use func::{calc_fn_hash, calc_fn_hash_full, calc_var_hash}; + +/// A shared [`Module`]. +type SharedModule = Shared; pub use rhai_codegen::*; @@ -343,6 +347,9 @@ pub use eval::{Caches, FnResolutionCache, FnResolutionCacheEntry, GlobalRuntimeS #[cfg(feature = "metadata")] pub use api::definitions::Definitions; +/// Number of items to keep inline for [`StaticVec`]. +const STATIC_VEC_INLINE_SIZE: usize = 3; + /// Alias to [`smallvec::SmallVec<[T; 3]>`](https://crates.io/crates/smallvec), which is a /// specialized [`Vec`] backed by a small, inline, fixed-size array when there are ≤ 3 items stored. /// @@ -375,7 +382,7 @@ pub use api::definitions::Definitions; /// most scripts load fewer than 4 external modules; most module paths contain fewer than 4 levels /// (e.g. `std::collections::map::HashMap` is 4 levels and it is just about as long as they get). #[cfg(not(feature = "internals"))] -type StaticVec = smallvec::SmallVec<[T; 3]>; +type StaticVec = smallvec::SmallVec<[T; STATIC_VEC_INLINE_SIZE]>; /// _(internals)_ Alias to [`smallvec::SmallVec<[T; 3]>`](https://crates.io/crates/smallvec), /// which is a [`Vec`] backed by a small, inline, fixed-size array when there are ≤ 3 items stored. @@ -410,7 +417,11 @@ type StaticVec = smallvec::SmallVec<[T; 3]>; /// most scripts load fewer than 4 external modules; most module paths contain fewer than 4 levels /// (e.g. `std::collections::map::HashMap` is 4 levels and it is just about as long as they get). #[cfg(feature = "internals")] -pub type StaticVec = smallvec::SmallVec<[T; 3]>; +pub type StaticVec = smallvec::SmallVec<[T; STATIC_VEC_INLINE_SIZE]>; + +/// Number of items to keep inline for [`FnArgsVec`]. +#[cfg(not(feature = "no_closure"))] +const FN_ARGS_VEC_INLINE_SIZE: usize = 5; /// Inline arguments storage for function calls. /// @@ -425,14 +436,14 @@ pub type StaticVec = smallvec::SmallVec<[T; 3]>; /// /// Under `no_closure`, this type aliases to [`StaticVec`][crate::StaticVec] instead. #[cfg(not(feature = "no_closure"))] -type FnArgsVec = smallvec::SmallVec<[T; 5]>; +type FnArgsVec = smallvec::SmallVec<[T; FN_ARGS_VEC_INLINE_SIZE]>; /// Inline arguments storage for function calls. /// This type aliases to [`StaticVec`][crate::StaticVec]. #[cfg(feature = "no_closure")] type FnArgsVec = crate::StaticVec; -pub(crate) type SmartString = smartstring::SmartString; +type SmartString = smartstring::SmartString; // Compiler guards against mutually-exclusive feature flags diff --git a/src/module/mod.rs b/src/module/mod.rs index a07a82d3..a8676655 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -9,8 +9,8 @@ use crate::func::{ }; use crate::types::{dynamic::Variant, BloomFilterU64, CustomTypesCollection}; use crate::{ - calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, Identifier, ImmutableString, - NativeCallContext, RhaiResultOf, Shared, SmartString, StaticVec, + calc_fn_hash, calc_fn_hash_full, Dynamic, Identifier, ImmutableString, NativeCallContext, + RhaiResultOf, Shared, SharedModule, SmartString, StaticVec, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -72,7 +72,7 @@ pub struct FuncInfo { /// Function access mode. pub access: FnAccess, /// Function name. - pub name: Identifier, + pub name: ImmutableString, /// Number of parameters. pub num_params: usize, /// Parameter types (if applicable). @@ -150,9 +150,10 @@ pub fn calc_native_fn_hash<'a>( fn_name: &str, params: &[TypeId], ) -> u64 { - let hash_script = calc_fn_hash(modules, fn_name, params.len()); - let hash_params = calc_fn_params_hash(params.iter().copied()); - combine_hashes(hash_script, hash_params) + calc_fn_hash_full( + calc_fn_hash(modules, fn_name, params.len()), + params.iter().copied(), + ) } /// A module which may contain variables, sub-modules, external Rust functions, @@ -160,8 +161,7 @@ pub fn calc_native_fn_hash<'a>( #[derive(Clone)] pub struct Module { /// ID identifying the module. - /// No ID if string is empty. - id: Identifier, + id: Option, /// Module documentation. #[cfg(feature = "metadata")] doc: crate::SmartString, @@ -172,7 +172,7 @@ pub struct Module { /// Custom types. custom_types: Option, /// Sub-modules. - modules: Option>>, + modules: Option>, /// [`Module`] variables. variables: Option>, /// Flattened collection of all [`Module`] variables, including those in sub-modules. @@ -196,12 +196,15 @@ pub struct Module { impl Default for Module { #[inline(always)] + #[must_use] fn default() -> Self { Self::new() } } impl fmt::Debug for Module { + #[cold] + #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut d = f.debug_struct("Module"); @@ -289,7 +292,7 @@ impl Module { #[must_use] pub fn with_capacity(capacity: usize) -> Self { Self { - id: Identifier::new_const(), + id: None, #[cfg(feature = "metadata")] doc: crate::SmartString::new_const(), internal: false, @@ -321,18 +324,14 @@ impl Module { #[inline] #[must_use] pub fn id(&self) -> Option<&str> { - if self.id_raw().is_empty() { - None - } else { - Some(self.id_raw()) - } + self.id.as_ref().map(|s| s.as_str()) } /// Get the ID of the [`Module`] as an [`Identifier`], if any. #[inline(always)] #[must_use] - pub(crate) const fn id_raw(&self) -> &Identifier { - &self.id + pub(crate) const fn id_raw(&self) -> Option<&ImmutableString> { + self.id.as_ref() } /// Set the ID of the [`Module`]. @@ -348,8 +347,15 @@ impl Module { /// assert_eq!(module.id(), Some("hello")); /// ``` #[inline(always)] - pub fn set_id(&mut self, id: impl Into) -> &mut Self { - self.id = id.into(); + pub fn set_id(&mut self, id: impl Into) -> &mut Self { + let id = id.into(); + + if id.is_empty() { + self.id = None; + } else { + self.id = Some(id); + } + self } @@ -367,7 +373,7 @@ impl Module { /// ``` #[inline(always)] pub fn clear_id(&mut self) -> &mut Self { - self.id.clear(); + self.id = None; self } @@ -431,7 +437,7 @@ impl Module { /// Clear the [`Module`]. #[inline(always)] pub fn clear(&mut self) { - self.id.clear(); + self.id = None; #[cfg(feature = "metadata")] self.doc.clear(); self.internal = false; @@ -491,12 +497,12 @@ impl Module { #[inline(always)] pub fn set_custom_type_raw( &mut self, - type_name: impl Into, + type_path: impl Into, name: impl Into, ) -> &mut Self { self.custom_types .get_or_insert_with(CustomTypesCollection::new) - .add(type_name, name); + .add(type_path, name); self } /// Get the display name of a registered custom type. @@ -748,7 +754,7 @@ impl Module { #[cfg(not(feature = "no_module"))] #[inline] #[must_use] - pub(crate) fn get_sub_modules_mut(&mut self) -> &mut BTreeMap> { + pub(crate) fn get_sub_modules_mut(&mut self) -> &mut BTreeMap { // We must assume that the user has changed the sub-modules // (otherwise why take a mutable reference?) self.all_functions = None; @@ -816,7 +822,7 @@ impl Module { pub fn set_sub_module( &mut self, name: impl Into, - sub_module: impl Into>, + sub_module: impl Into, ) -> &mut Self { self.modules .get_or_insert_with(|| Default::default()) @@ -1022,8 +1028,7 @@ impl Module { let name = name.as_ref(); let hash_script = calc_fn_hash(None, name, param_types.len()); - let hash_params = calc_fn_params_hash(param_types.iter().copied()); - let hash_fn = combine_hashes(hash_script, hash_params); + let hash_fn = calc_fn_hash_full(hash_script, param_types.iter().copied()); if is_dynamic { self.dynamic_functions_filter.mark(hash_script); @@ -1739,10 +1744,9 @@ impl Module { } if let Some(ref variables) = other.variables { - if let Some(ref mut m) = self.variables { - m.extend(variables.iter().map(|(k, v)| (k.clone(), v.clone()))); - } else { - self.variables = other.variables.clone(); + match self.variables { + Some(ref mut m) => m.extend(variables.iter().map(|(k, v)| (k.clone(), v.clone()))), + None => self.variables = other.variables.clone(), } } @@ -1764,10 +1768,9 @@ impl Module { self.dynamic_functions_filter += &other.dynamic_functions_filter; if let Some(ref type_iterators) = other.type_iterators { - if let Some(ref mut t) = self.type_iterators { - t.extend(type_iterators.iter().map(|(&k, v)| (k, v.clone()))); - } else { - self.type_iterators = other.type_iterators.clone(); + match self.type_iterators { + Some(ref mut t) => t.extend(type_iterators.iter().map(|(&k, v)| (k, v.clone()))), + None => self.type_iterators = other.type_iterators.clone(), } } self.all_functions = None; @@ -1827,7 +1830,7 @@ impl Module { /// Get an iterator to the sub-modules in the [`Module`]. #[inline] - pub fn iter_sub_modules(&self) -> impl Iterator)> { + pub fn iter_sub_modules(&self) -> impl Iterator { self.modules .iter() .flat_map(|m| m.iter().map(|(k, m)| (k.as_str(), m))) @@ -1981,7 +1984,9 @@ impl Module { let orig_constants = std::mem::take(&mut global.constants); // Run the script - let result = engine.eval_ast_with_scope_raw(&mut scope, global, ast, 0); + let caches = &mut crate::eval::Caches::new(); + + let result = engine.eval_ast_with_scope_raw(global, caches, &mut scope, ast); // Create new module let mut module = Module::new(); @@ -2077,7 +2082,7 @@ impl Module { }); } - module.set_id(ast.source_raw().clone()); + module.id = ast.source_raw().cloned(); #[cfg(feature = "metadata")] module.set_doc(ast.doc()); diff --git a/src/module/resolvers/collection.rs b/src/module/resolvers/collection.rs index 7aab4bd0..5310e6a8 100644 --- a/src/module/resolvers/collection.rs +++ b/src/module/resolvers/collection.rs @@ -1,7 +1,10 @@ -use crate::{Engine, Module, ModuleResolver, Position, RhaiResultOf, Shared, ERR}; +use crate::{ + Engine, ModuleResolver, Position, RhaiResultOf, SharedModule, StaticVec, ERR, + STATIC_VEC_INLINE_SIZE, +}; #[cfg(feature = "no_std")] use std::prelude::v1::*; -use std::{ops::AddAssign, slice::Iter, vec::IntoIter}; +use std::{ops::AddAssign, slice::Iter}; /// [Module] resolution service that holds a collection of module resolvers, /// to be searched in sequential order. @@ -21,7 +24,7 @@ use std::{ops::AddAssign, slice::Iter, vec::IntoIter}; /// engine.set_module_resolver(collection); /// ``` #[derive(Default)] -pub struct ModuleResolversCollection(Vec>); +pub struct ModuleResolversCollection(StaticVec>); impl ModuleResolversCollection { /// Create a new [`ModuleResolversCollection`]. @@ -42,8 +45,8 @@ impl ModuleResolversCollection { /// ``` #[inline(always)] #[must_use] - pub fn new() -> Self { - Self(Vec::new()) + pub const fn new() -> Self { + Self(StaticVec::new_const()) } /// Append a [module resolver][ModuleResolver] to the end. #[inline(always)] @@ -109,9 +112,10 @@ impl ModuleResolversCollection { impl IntoIterator for ModuleResolversCollection { type Item = Box; - type IntoIter = IntoIter>; + type IntoIter = smallvec::IntoIter<[Box; STATIC_VEC_INLINE_SIZE]>; #[inline(always)] + #[must_use] fn into_iter(self) -> Self::IntoIter { self.0.into_iter() } @@ -134,7 +138,7 @@ impl ModuleResolver for ModuleResolversCollection { source_path: Option<&str>, path: &str, pos: Position, - ) -> RhaiResultOf> { + ) -> RhaiResultOf { for resolver in &self.0 { match resolver.resolve(engine, source_path, path, pos) { Ok(module) => return Ok(module), diff --git a/src/module/resolvers/dummy.rs b/src/module/resolvers/dummy.rs index f5782ebc..4da90566 100644 --- a/src/module/resolvers/dummy.rs +++ b/src/module/resolvers/dummy.rs @@ -1,4 +1,4 @@ -use crate::{Engine, Module, ModuleResolver, Position, RhaiResultOf, Shared, ERR}; +use crate::{Engine, ModuleResolver, Position, RhaiResultOf, SharedModule, ERR}; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -45,7 +45,7 @@ impl ModuleResolver for DummyModuleResolver { _: Option<&str>, path: &str, pos: Position, - ) -> RhaiResultOf> { + ) -> RhaiResultOf { Err(ERR::ErrorModuleNotFound(path.into(), pos).into()) } } diff --git a/src/module/resolvers/file.rs b/src/module/resolvers/file.rs index ee71ce13..ee56ac18 100644 --- a/src/module/resolvers/file.rs +++ b/src/module/resolvers/file.rs @@ -4,7 +4,8 @@ use crate::eval::GlobalRuntimeState; use crate::func::{locked_read, locked_write}; use crate::{ - Engine, Identifier, Locked, Module, ModuleResolver, Position, RhaiResultOf, Scope, Shared, ERR, + Engine, Identifier, Locked, Module, ModuleResolver, Position, RhaiResultOf, Scope, Shared, + SharedModule, ERR, }; use std::{ @@ -51,11 +52,12 @@ pub struct FileModuleResolver { extension: Identifier, cache_enabled: bool, scope: Scope<'static>, - cache: Locked>>, + cache: Locked>, } impl Default for FileModuleResolver { #[inline(always)] + #[must_use] fn default() -> Self { Self::new() } @@ -194,8 +196,8 @@ impl FileModuleResolver { /// Get a reference to the file module resolver's [scope][Scope]. /// /// The [scope][Scope] is used for compiling module scripts. - #[must_use] #[inline(always)] + #[must_use] pub const fn scope(&self) -> &Scope { &self.scope } @@ -211,8 +213,8 @@ impl FileModuleResolver { /// Get a mutable reference to the file module resolver's [scope][Scope]. /// /// The [scope][Scope] is used for compiling module scripts. - #[must_use] #[inline(always)] + #[must_use] pub fn scope_mut(&mut self) -> &mut Scope<'static> { &mut self.scope } @@ -257,7 +259,7 @@ impl FileModuleResolver { /// The next time this path is resolved, the script file will be loaded once again. #[inline] #[must_use] - pub fn clear_cache_for_path(&mut self, path: impl AsRef) -> Option> { + pub fn clear_cache_for_path(&mut self, path: impl AsRef) -> Option { locked_write(&self.cache) .remove_entry(path.as_ref()) .map(|(.., v)| v) @@ -292,7 +294,7 @@ impl FileModuleResolver { source: Option<&str>, path: &str, pos: Position, - ) -> Result, Box> { + ) -> Result> { // Load relative paths from source if there is no base path specified let source_path = global .as_ref() @@ -321,10 +323,9 @@ impl FileModuleResolver { let scope = Scope::new(); - let m: Shared<_> = if let Some(global) = global { - Module::eval_ast_as_new_raw(engine, scope, global, &ast) - } else { - Module::eval_ast_as_new(scope, &ast, engine) + let m: Shared<_> = match global { + Some(global) => Module::eval_ast_as_new_raw(engine, scope, global, &ast), + None => Module::eval_ast_as_new(scope, &ast, engine), } .map_err(|err| Box::new(ERR::ErrorInModule(path.to_string(), err, pos)))? .into(); @@ -344,7 +345,7 @@ impl ModuleResolver for FileModuleResolver { global: &mut GlobalRuntimeState, path: &str, pos: Position, - ) -> RhaiResultOf> { + ) -> RhaiResultOf { self.impl_resolve(engine, Some(global), None, path, pos) } @@ -355,7 +356,7 @@ impl ModuleResolver for FileModuleResolver { source: Option<&str>, path: &str, pos: Position, - ) -> RhaiResultOf> { + ) -> RhaiResultOf { self.impl_resolve(engine, None, source, path, pos) } diff --git a/src/module/resolvers/mod.rs b/src/module/resolvers/mod.rs index a7f2d5c4..6316a845 100644 --- a/src/module/resolvers/mod.rs +++ b/src/module/resolvers/mod.rs @@ -1,6 +1,6 @@ use crate::eval::GlobalRuntimeState; use crate::func::SendSync; -use crate::{Engine, Module, Position, RhaiResultOf, Shared, AST}; +use crate::{Engine, Position, RhaiResultOf, SharedModule, AST}; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -25,7 +25,7 @@ pub trait ModuleResolver: SendSync { source: Option<&str>, path: &str, pos: Position, - ) -> RhaiResultOf>; + ) -> RhaiResultOf; /// Resolve a module based on a path string, given a [`GlobalRuntimeState`]. /// @@ -38,7 +38,7 @@ pub trait ModuleResolver: SendSync { global: &mut GlobalRuntimeState, path: &str, pos: Position, - ) -> RhaiResultOf> { + ) -> RhaiResultOf { self.resolve(engine, global.source(), path, pos) } diff --git a/src/module/resolvers/stat.rs b/src/module/resolvers/stat.rs index 4019fc6d..bd2b2638 100644 --- a/src/module/resolvers/stat.rs +++ b/src/module/resolvers/stat.rs @@ -1,5 +1,6 @@ use crate::{ - Engine, Identifier, Module, ModuleResolver, Position, RhaiResultOf, Shared, SmartString, ERR, + Engine, Identifier, Module, ModuleResolver, Position, RhaiResultOf, SharedModule, SmartString, + ERR, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -27,7 +28,7 @@ use std::{ /// engine.set_module_resolver(resolver); /// ``` #[derive(Debug, Clone, Default)] -pub struct StaticModuleResolver(BTreeMap>); +pub struct StaticModuleResolver(BTreeMap); impl StaticModuleResolver { /// Create a new [`StaticModuleResolver`]. @@ -65,7 +66,7 @@ impl StaticModuleResolver { } /// Remove a [module][Module] given its path. #[inline(always)] - pub fn remove(&mut self, path: &str) -> Option> { + pub fn remove(&mut self, path: &str) -> Option { self.0.remove(path) } /// Does the path exist? @@ -80,12 +81,12 @@ impl StaticModuleResolver { } /// Get an iterator of all the [modules][Module]. #[inline] - pub fn iter(&self) -> impl Iterator)> { + pub fn iter(&self) -> impl Iterator { self.0.iter().map(|(k, v)| (k.as_str(), v)) } /// Get a mutable iterator of all the [modules][Module]. #[inline] - pub fn iter_mut(&mut self) -> impl Iterator)> { + pub fn iter_mut(&mut self) -> impl Iterator { self.0.iter_mut().map(|(k, v)| (k.as_str(), v)) } /// Get an iterator of all the [module][Module] paths. @@ -95,7 +96,7 @@ impl StaticModuleResolver { } /// Get an iterator of all the [modules][Module]. #[inline(always)] - pub fn values(&self) -> impl Iterator> { + pub fn values(&self) -> impl Iterator { self.0.values() } /// Remove all [modules][Module]. @@ -130,18 +131,19 @@ impl StaticModuleResolver { } impl IntoIterator for StaticModuleResolver { - type Item = (Identifier, Shared); - type IntoIter = IntoIter>; + type Item = (Identifier, SharedModule); + type IntoIter = IntoIter; #[inline(always)] + #[must_use] fn into_iter(self) -> Self::IntoIter { self.0.into_iter() } } impl<'a> IntoIterator for &'a StaticModuleResolver { - type Item = (&'a Identifier, &'a Shared); - type IntoIter = Iter<'a, SmartString, Shared>; + type Item = (&'a Identifier, &'a SharedModule); + type IntoIter = Iter<'a, SmartString, SharedModule>; #[inline(always)] fn into_iter(self) -> Self::IntoIter { @@ -157,7 +159,7 @@ impl ModuleResolver for StaticModuleResolver { _: Option<&str>, path: &str, pos: Position, - ) -> RhaiResultOf> { + ) -> RhaiResultOf { self.0 .get(path) .cloned() diff --git a/src/optimizer.rs b/src/optimizer.rs index b9a4dd97..9bf57005 100644 --- a/src/optimizer.rs +++ b/src/optimizer.rs @@ -8,11 +8,11 @@ use crate::engine::{KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT, use crate::eval::{Caches, GlobalRuntimeState}; use crate::func::builtin::get_builtin_binary_op_fn; use crate::func::hashing::get_hasher; -use crate::tokenizer::{Span, Token}; +use crate::tokenizer::Token; use crate::types::dynamic::AccessMode; use crate::{ - calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, Engine, FnPtr, Identifier, - ImmutableString, Position, Scope, StaticVec, AST, INT, + calc_fn_hash, calc_fn_hash_full, Dynamic, Engine, FnPtr, Identifier, ImmutableString, Position, + Scope, StaticVec, AST, INT, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -38,6 +38,7 @@ pub enum OptimizationLevel { impl Default for OptimizationLevel { #[inline(always)] + #[must_use] fn default() -> Self { Self::Simple } @@ -49,18 +50,18 @@ struct OptimizerState<'a> { /// Has the [`AST`] been changed during this pass? changed: bool, /// Collection of constants to use for eager function evaluations. - variables: StaticVec<(Identifier, AccessMode, Option)>, + variables: StaticVec<(Identifier, AccessMode, Dynamic)>, /// Activate constants propagation? propagate_constants: bool, /// An [`Engine`] instance for eager function evaluation. engine: &'a Engine, /// The global runtime state. - global: GlobalRuntimeState<'a>, + global: GlobalRuntimeState, /// Function resolution caches. - caches: Caches<'a>, + caches: Caches, /// [Module][crate::Module] containing script-defined functions. #[cfg(not(feature = "no_function"))] - lib: &'a [&'a crate::Module], + lib: &'a [crate::SharedModule], /// Optimization level. optimization_level: OptimizationLevel, } @@ -70,7 +71,7 @@ impl<'a> OptimizerState<'a> { #[inline(always)] pub fn new( engine: &'a Engine, - #[cfg(not(feature = "no_function"))] lib: &'a [&'a crate::Module], + #[cfg(not(feature = "no_function"))] lib: &'a [crate::SharedModule], optimization_level: OptimizationLevel, ) -> Self { Self { @@ -107,12 +108,7 @@ impl<'a> OptimizerState<'a> { } /// Add a new variable to the list. #[inline(always)] - pub fn push_var( - &mut self, - name: impl Into, - access: AccessMode, - value: Option, - ) { + pub fn push_var(&mut self, name: impl Into, access: AccessMode, value: Dynamic) { self.variables.push((name.into(), access, value)); } /// Look up a constant from the list. @@ -126,7 +122,8 @@ impl<'a> OptimizerState<'a> { if n == name { return match access { AccessMode::ReadWrite => None, - AccessMode::ReadOnly => value.as_ref(), + AccessMode::ReadOnly if value.is_null() => None, + AccessMode::ReadOnly => Some(value), }; } } @@ -138,28 +135,27 @@ impl<'a> OptimizerState<'a> { pub fn call_fn_with_constant_arguments( &mut self, fn_name: &str, + op_token: Option<&Token>, arg_values: &mut [Dynamic], - ) -> Option { + ) -> Dynamic { #[cfg(not(feature = "no_function"))] let lib = self.lib; #[cfg(feature = "no_function")] let lib = &[]; self.engine - .call_native_fn( + .exec_native_fn_call( &mut self.global, &mut self.caches, lib, fn_name, + op_token, calc_fn_hash(None, fn_name, arg_values.len()), &mut arg_values.iter_mut().collect::>(), false, - false, Position::NONE, - 0, ) - .ok() - .map(|(v, ..)| v) + .map_or(Dynamic::NULL, |(v, ..)| v) } } @@ -169,26 +165,30 @@ fn has_native_fn_override( hash_script: u64, arg_types: impl AsRef<[TypeId]>, ) -> bool { - let hash_params = calc_fn_params_hash(arg_types.as_ref().iter().copied()); - let hash = combine_hashes(hash_script, hash_params); + let hash = calc_fn_hash_full(hash_script, arg_types.as_ref().iter().copied()); // First check the global namespace and packages, but skip modules that are standard because // they should never conflict with system functions. - let result = engine + if engine .global_modules .iter() .filter(|m| !m.standard) - .any(|m| m.contains_fn(hash)); + .any(|m| m.contains_fn(hash)) + { + return true; + } - #[cfg(not(feature = "no_module"))] // Then check sub-modules - let result = result - || engine - .global_sub_modules - .values() - .any(|m| m.contains_qualified_fn(hash)); + #[cfg(not(feature = "no_module"))] + if engine + .global_sub_modules + .values() + .any(|m| m.contains_qualified_fn(hash)) + { + return true; + } - result + false } /// Optimize a block of [statements][Stmt]. @@ -254,7 +254,7 @@ fn optimize_stmt_block( }); // Optimize each statement in the block - for stmt in statements.iter_mut() { + for stmt in &mut statements { match stmt { Stmt::Var(x, options, ..) => { if options.contains(ASTFlags::CONSTANT) { @@ -265,13 +265,13 @@ fn optimize_stmt_block( state.push_var( x.0.as_str(), AccessMode::ReadOnly, - x.1.get_literal_value(), + x.1.get_literal_value().unwrap_or(Dynamic::NULL), ); } } else { // Add variables into the state optimize_expr(&mut x.1, state, false); - state.push_var(x.0.as_str(), AccessMode::ReadWrite, None); + state.push_var(x.0.as_str(), AccessMode::ReadWrite, Dynamic::NULL); } } // Optimize the statement @@ -432,15 +432,15 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b if !x.0.is_op_assignment() && x.1.lhs.is_variable_access(true) && matches!(&x.1.rhs, Expr::FnCall(x2, ..) - if Token::lookup_from_syntax(&x2.name).map_or(false, |t| t.has_op_assignment()) + if Token::lookup_symbol_from_syntax(&x2.name).map_or(false, |t| t.has_op_assignment()) && x2.args.len() == 2 && x2.args[0].get_variable_name(true) == x.1.lhs.get_variable_name(true) ) => { match x.1.rhs { - Expr::FnCall(ref mut x2, ..) => { + Expr::FnCall(ref mut x2, pos) => { state.set_dirty(); - x.0 = OpAssignment::new_op_assignment_from_base(&x2.name, x2.pos); + x.0 = OpAssignment::new_op_assignment_from_base(&x2.name, pos); x.1.rhs = mem::take(&mut x2.args[1]); } ref expr => unreachable!("Expr::FnCall expected but gets {:?}", expr), @@ -550,13 +550,14 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b // switch const { case if condition => stmt, _ => def } => if condition { stmt } else { def } optimize_expr(&mut b.condition, state, false); - let else_stmt = if let Some(index) = def_case { - let mut def_stmt = - Stmt::Expr(mem::take(&mut expressions[*index].expr).into()); - optimize_stmt(&mut def_stmt, state, true); - def_stmt.into() - } else { - StmtBlock::NONE + let else_stmt = match def_case { + Some(index) => { + let mut def_stmt = + Stmt::Expr(mem::take(&mut expressions[*index].expr).into()); + optimize_stmt(&mut def_stmt, state, true); + def_stmt.into() + } + _ => StmtBlock::NONE, }; *stmt = Stmt::If( @@ -615,13 +616,14 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b // switch const { range if condition => stmt, _ => def } => if condition { stmt } else { def } optimize_expr(&mut condition, state, false); - let else_stmt = if let Some(index) = def_case { - let mut def_stmt = - Stmt::Expr(mem::take(&mut expressions[*index].expr).into()); - optimize_stmt(&mut def_stmt, state, true); - def_stmt.into() - } else { - StmtBlock::NONE + let else_stmt = match def_case { + Some(index) => { + let mut def_stmt = + Stmt::Expr(mem::take(&mut expressions[*index].expr).into()); + optimize_stmt(&mut def_stmt, state, true); + def_stmt.into() + } + _ => StmtBlock::NONE, }; let if_stmt = @@ -664,12 +666,13 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b // Promote the default case state.set_dirty(); - if let Some(index) = def_case { - let mut def_stmt = Stmt::Expr(mem::take(&mut expressions[*index].expr).into()); - optimize_stmt(&mut def_stmt, state, true); - *stmt = def_stmt; - } else { - *stmt = StmtBlock::empty(*pos).into(); + match def_case { + Some(index) => { + let mut def_stmt = Stmt::Expr(mem::take(&mut expressions[*index].expr).into()); + optimize_stmt(&mut def_stmt, state, true); + *stmt = def_stmt; + } + _ => *stmt = StmtBlock::empty(*pos).into(), } } // switch @@ -688,7 +691,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b optimize_expr(match_expr, state, false); // Optimize blocks - for b in expressions.iter_mut() { + for b in expressions.as_mut() { optimize_expr(&mut b.condition, state, false); optimize_expr(&mut b.expr, state, false); @@ -776,50 +779,6 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b *condition = Expr::Unit(*pos); } **body = optimize_stmt_block(mem::take(&mut **body), state, false, true, false); - - if body.len() == 1 { - match body[0] { - // while expr { break; } -> { expr; } - Stmt::BreakLoop(options, pos) if options.contains(ASTFlags::BREAK) => { - // Only a single break statement - turn into running the guard expression once - state.set_dirty(); - if condition.is_unit() { - *stmt = Stmt::Noop(pos); - } else { - let mut statements = vec![Stmt::Expr(mem::take(condition).into())]; - if preserve_result { - statements.push(Stmt::Noop(pos)); - } - *stmt = (statements, Span::new(pos, Position::NONE)).into(); - }; - } - _ => (), - } - } - } - // do { block } until true -> { block } - Stmt::Do(x, options, ..) - if matches!(x.0, Expr::BoolConstant(true, ..)) - && options.contains(ASTFlags::NEGATED) => - { - state.set_dirty(); - *stmt = ( - optimize_stmt_block(mem::take(&mut *x.1), state, false, true, false), - x.1.span(), - ) - .into(); - } - // do { block } while false -> { block } - Stmt::Do(x, options, ..) - if matches!(x.0, Expr::BoolConstant(false, ..)) - && !options.contains(ASTFlags::NEGATED) => - { - state.set_dirty(); - *stmt = ( - optimize_stmt_block(mem::take(&mut *x.1), state, false, true, false), - x.1.span(), - ) - .into(); } // do { block } while|until expr Stmt::Do(x, ..) => { @@ -893,27 +852,23 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b Stmt::Expr(expr) => { optimize_expr(expr, state, false); - match &mut **expr { - // func(...) - Expr::FnCall(x, pos) => { - state.set_dirty(); - *stmt = Stmt::FnCall(mem::take(x), *pos); - } - // {...}; - Expr::Stmt(x) => { - if x.is_empty() { - state.set_dirty(); - *stmt = Stmt::Noop(x.position()); - } else { - state.set_dirty(); - *stmt = mem::take(&mut **x).into(); - } - } - // expr; - _ => (), + if matches!(**expr, Expr::FnCall(..) | Expr::Stmt(..)) { + state.set_dirty(); + *stmt = match *mem::take(expr) { + // func(...); + Expr::FnCall(x, pos) => Stmt::FnCall(x, pos), + // {}; + Expr::Stmt(x) if x.is_empty() => Stmt::Noop(x.position()), + // {...}; + Expr::Stmt(x) => (*x).into(), + _ => unreachable!(), + }; } } + // break expr; + Stmt::BreakLoop(Some(ref mut expr), ..) => optimize_expr(expr, state, false), + // return expr; Stmt::Return(Some(ref mut expr), ..) => optimize_expr(expr, state, false), @@ -1137,7 +1092,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) { && state.optimization_level == OptimizationLevel::Simple // simple optimizations && x.args.len() == 1 && x.name == KEYWORD_FN_PTR - && x.args[0].is_constant() + && x.constant_args() => { let fn_name = match x.args[0] { Expr::StringConstant(ref s, ..) => s.clone().into(), @@ -1161,8 +1116,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) { Expr::FnCall(x, pos) if !x.is_qualified() // Non-qualified && state.optimization_level == OptimizationLevel::Simple // simple optimizations - && x.args.iter().all(Expr::is_constant) // all arguments are constants - //&& !is_valid_identifier(x.chars()) // cannot be scripted + && x.constant_args() // all arguments are constants => { let arg_values = &mut x.args.iter().map(|e| e.get_literal_value().unwrap()).collect::>(); let arg_types: StaticVec<_> = arg_values.iter().map(Dynamic::type_id).collect(); @@ -1181,15 +1135,15 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) { return; } // Overloaded operators can override built-in. - _ if x.args.len() == 2 && (state.engine.fast_operators() || !has_native_fn_override(state.engine, x.hashes.native, &arg_types)) => { - if let Some(result) = get_builtin_binary_op_fn(&x.name, &arg_values[0], &arg_values[1]) + _ if x.args.len() == 2 && x.op_token.is_some() && (state.engine.fast_operators() || !has_native_fn_override(state.engine, x.hashes.native(), &arg_types)) => { + if let Some(result) = get_builtin_binary_op_fn(x.op_token.as_ref().unwrap(), &arg_values[0], &arg_values[1]) .and_then(|f| { #[cfg(not(feature = "no_function"))] let lib = state.lib; #[cfg(feature = "no_function")] - let lib = &[]; + let lib = &[][..]; - let context = (state.engine, &x.name, lib).into(); + let context = (state.engine, x.name.as_str(),None, &state.global, lib, *pos).into(); let (first, second) = arg_values.split_first_mut().unwrap(); (f)(context, &mut [ first, &mut second[0] ]).ok() }) { @@ -1204,7 +1158,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) { x.args.iter_mut().for_each(|a| optimize_expr(a, state, false)); // Move constant arguments - for arg in x.args.iter_mut() { + for arg in &mut x.args { match arg { Expr::DynamicConstant(..) | Expr::Unit(..) | Expr::StringConstant(..) | Expr::CharConstant(..) @@ -1225,25 +1179,25 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) { Expr::FnCall(x, pos) if !x.is_qualified() // non-qualified && state.optimization_level == OptimizationLevel::Full // full optimizations - && x.args.iter().all(Expr::is_constant) // all arguments are constants + && x.constant_args() // all arguments are constants => { // First search for script-defined functions (can override built-in) #[cfg(not(feature = "no_function"))] - let has_script_fn = state.lib.iter().copied().any(|m| m.get_script_fn(&x.name, x.args.len()).is_some()); + let has_script_fn = !x.hashes.is_native_only() && state.lib.iter().find_map(|m| m.get_script_fn(&x.name, x.args.len())).is_some(); #[cfg(feature = "no_function")] let has_script_fn = false; if !has_script_fn { - let arg_values = &mut x.args.iter().map(|e| e.get_literal_value().unwrap()).collect::>(); + let arg_values = &mut x.args.iter().map(Expr::get_literal_value).collect::>>().unwrap(); let result = match x.name.as_str() { - KEYWORD_TYPE_OF if arg_values.len() == 1 => Some(state.engine.map_type_name(arg_values[0].type_name()).into()), + KEYWORD_TYPE_OF if arg_values.len() == 1 => state.engine.map_type_name(arg_values[0].type_name()).into(), #[cfg(not(feature = "no_closure"))] - crate::engine::KEYWORD_IS_SHARED if arg_values.len() == 1 => Some(Dynamic::FALSE), - _ => state.call_fn_with_constant_arguments(&x.name, arg_values) + crate::engine::KEYWORD_IS_SHARED if arg_values.len() == 1 => Dynamic::FALSE, + _ => state.call_fn_with_constant_arguments(&x.name, x.op_token.as_ref(), arg_values) }; - if let Some(result) = result { + if !result.is_null() { state.set_dirty(); *expr = Expr::from_dynamic(result, *pos); return; @@ -1254,7 +1208,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) { } // id(args ..) or xxx.id(args ..) -> optimize function call arguments - Expr::FnCall(x, ..) | Expr::MethodCall(x, ..) => for arg in x.args.iter_mut() { + Expr::FnCall(x, ..) | Expr::MethodCall(x, ..) => for arg in &mut x.args { optimize_expr(arg, state, false); // Move constant arguments @@ -1303,7 +1257,7 @@ fn optimize_top_level( statements: StmtBlockContainer, engine: &Engine, scope: &Scope, - #[cfg(not(feature = "no_function"))] lib: &[&crate::Module], + #[cfg(not(feature = "no_function"))] lib: &[crate::SharedModule], optimization_level: OptimizationLevel, ) -> StmtBlockContainer { let mut statements = statements; @@ -1329,15 +1283,15 @@ fn optimize_top_level( .rev() .flat_map(|m| m.iter_var()) { - state.push_var(name, AccessMode::ReadOnly, Some(value.clone())); + state.push_var(name, AccessMode::ReadOnly, value.clone()); } // Add constants and variables from the scope for (name, constant, value) in scope.iter() { if constant { - state.push_var(name, AccessMode::ReadOnly, Some(value)); + state.push_var(name, AccessMode::ReadOnly, value); } else { - state.push_var(name, AccessMode::ReadWrite, None); + state.push_var(name, AccessMode::ReadWrite, Dynamic::NULL); } } @@ -1357,7 +1311,7 @@ pub fn optimize_into_ast( let mut statements = statements; #[cfg(not(feature = "no_function"))] - let lib = { + let lib: crate::Shared<_> = { let mut module = crate::Module::new(); if optimization_level != OptimizationLevel::None { @@ -1378,7 +1332,7 @@ pub fn optimize_into_ast( }); } - let lib2 = &[&lib2]; + let lib2 = &[lib2.into()]; for fn_def in functions { let mut fn_def = crate::func::shared_take_or_clone(fn_def); @@ -1396,7 +1350,7 @@ pub fn optimize_into_ast( } } - module + module.into() }; statements.shrink_to_fit(); @@ -1409,7 +1363,7 @@ pub fn optimize_into_ast( engine, scope, #[cfg(not(feature = "no_function"))] - &[&lib], + &[lib.clone()], optimization_level, ), }, diff --git a/src/packages/arithmetic.rs b/src/packages/arithmetic.rs index 2cba468e..3dafc1ae 100644 --- a/src/packages/arithmetic.rs +++ b/src/packages/arithmetic.rs @@ -488,39 +488,29 @@ mod f64_functions { #[cfg(feature = "decimal")] #[export_module] pub mod decimal_functions { - use num_traits::Pow; - use rust_decimal::{prelude::Zero, Decimal, MathematicalOps}; + use rust_decimal::{prelude::Zero, Decimal}; - #[rhai_fn(skip, return_raw)] - pub fn add(x: Decimal, y: Decimal) -> RhaiResultOf { - if cfg!(not(feature = "unchecked")) { + #[cfg(not(feature = "unchecked"))] + pub mod builtin { + use rust_decimal::MathematicalOps; + + #[rhai_fn(return_raw)] + pub fn add(x: Decimal, y: Decimal) -> RhaiResultOf { x.checked_add(y) .ok_or_else(|| make_err(format!("Addition overflow: {x} + {y}"))) - } else { - Ok(x + y) } - } - #[rhai_fn(skip, return_raw)] - pub fn subtract(x: Decimal, y: Decimal) -> RhaiResultOf { - if cfg!(not(feature = "unchecked")) { + #[rhai_fn(return_raw)] + pub fn subtract(x: Decimal, y: Decimal) -> RhaiResultOf { x.checked_sub(y) .ok_or_else(|| make_err(format!("Subtraction overflow: {x} - {y}"))) - } else { - Ok(x - y) } - } - #[rhai_fn(skip, return_raw)] - pub fn multiply(x: Decimal, y: Decimal) -> RhaiResultOf { - if cfg!(not(feature = "unchecked")) { + #[rhai_fn(return_raw)] + pub fn multiply(x: Decimal, y: Decimal) -> RhaiResultOf { x.checked_mul(y) .ok_or_else(|| make_err(format!("Multiplication overflow: {x} * {y}"))) - } else { - Ok(x * y) } - } - #[rhai_fn(skip, return_raw)] - pub fn divide(x: Decimal, y: Decimal) -> RhaiResultOf { - if cfg!(not(feature = "unchecked")) { + #[rhai_fn(return_raw)] + pub fn divide(x: Decimal, y: Decimal) -> RhaiResultOf { // Detect division by zero if y == Decimal::zero() { Err(make_err(format!("Division by zero: {x} / {y}"))) @@ -528,26 +518,16 @@ pub mod decimal_functions { x.checked_div(y) .ok_or_else(|| make_err(format!("Division overflow: {x} / {y}"))) } - } else { - Ok(x / y) } - } - #[rhai_fn(skip, return_raw)] - pub fn modulo(x: Decimal, y: Decimal) -> RhaiResultOf { - if cfg!(not(feature = "unchecked")) { + #[rhai_fn(return_raw)] + pub fn modulo(x: Decimal, y: Decimal) -> RhaiResultOf { x.checked_rem(y) .ok_or_else(|| make_err(format!("Modulo division by zero or overflow: {x} % {y}"))) - } else { - Ok(x % y) } - } - #[rhai_fn(skip, return_raw)] - pub fn power(x: Decimal, y: Decimal) -> RhaiResultOf { - if cfg!(not(feature = "unchecked")) { + #[rhai_fn(return_raw)] + pub fn power(x: Decimal, y: Decimal) -> RhaiResultOf { x.checked_powd(y) .ok_or_else(|| make_err(format!("Exponential overflow: {x} ** {y}"))) - } else { - Ok(x.pow(y)) } } #[rhai_fn(name = "-")] diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index 21abbde9..098b7f7d 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -236,9 +236,8 @@ pub mod array_functions { } let check_sizes = match item.0 { - crate::types::dynamic::Union::Array(..) | crate::types::dynamic::Union::Str(..) => { - true - } + crate::types::dynamic::Union::Str(..) => true, + crate::types::dynamic::Union::Array(..) => true, #[cfg(not(feature = "no_object"))] crate::types::dynamic::Union::Map(..) => true, _ => false, @@ -260,7 +259,7 @@ pub mod array_functions { s1 += s2; _ctx.engine() - .raise_err_if_over_data_size_limit((a1, m1, s1), Position::NONE)?; + .raise_err_if_over_data_size_limit((a1, m1, s1))?; guard.push(item.clone()); arr_len += 1; @@ -834,9 +833,9 @@ pub mod array_functions { return Ok(false); } - for item in array.iter_mut() { + for item in array { if ctx - .call_fn_raw(OP_EQUALS, true, false, &mut [item, &mut value.clone()]) + .call_native_fn_raw(OP_EQUALS, true, &mut [item, &mut value.clone()]) .or_else(|err| match *err { ERR::ErrorFunctionNotFound(ref fn_sig, ..) if fn_sig.starts_with(OP_EQUALS) => { if item.type_id() == value.type_id() { @@ -928,7 +927,7 @@ pub mod array_functions { for (i, item) in array.iter_mut().enumerate().skip(start) { if ctx - .call_fn_raw(OP_EQUALS, true, false, &mut [item, &mut value.clone()]) + .call_native_fn_raw(OP_EQUALS, true, &mut [item, &mut value.clone()]) .or_else(|err| match *err { ERR::ErrorFunctionNotFound(ref fn_sig, ..) if fn_sig.starts_with(OP_EQUALS) => { if item.type_id() == value.type_id() { @@ -2314,7 +2313,7 @@ pub mod array_functions { for (a1, a2) in array1.iter_mut().zip(array2.iter_mut()) { if !ctx - .call_fn_raw(OP_EQUALS, true, false, &mut [a1, a2]) + .call_native_fn_raw(OP_EQUALS, true, &mut [a1, a2]) .or_else(|err| match *err { ERR::ErrorFunctionNotFound(ref fn_sig, ..) if fn_sig.starts_with(OP_EQUALS) => { if a1.type_id() == a2.type_id() { diff --git a/src/packages/blob_basic.rs b/src/packages/blob_basic.rs index a3bbd532..2e51b108 100644 --- a/src/packages/blob_basic.rs +++ b/src/packages/blob_basic.rs @@ -80,11 +80,8 @@ pub mod blob_functions { // Check if blob will be over max size limit #[cfg(not(feature = "unchecked"))] - if _ctx.engine().max_array_size() > 0 && len > _ctx.engine().max_array_size() { - return Err( - crate::ERR::ErrorDataTooLarge("Size of BLOB".to_string(), Position::NONE).into(), - ); - } + _ctx.engine() + .raise_err_if_over_data_size_limit((len, 0, 0))?; let mut blob = Blob::new(); blob.resize(len, (value & 0x0000_00ff) as u8); @@ -146,6 +143,21 @@ pub mod blob_functions { pub fn is_empty(blob: &mut Blob) -> bool { blob.len() == 0 } + /// Return `true` if the BLOB contains a specified byte value. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world!"; + /// + /// print(text.contains('h')); // prints true + /// + /// print(text.contains('x')); // prints false + /// ``` + #[rhai_fn(name = "contains")] + pub fn contains(blob: &mut Blob, value: INT) -> bool { + blob.contains(&((value & 0x0000_00ff) as u8)) + } /// Get the byte value at the `index` position in the BLOB. /// /// * If `index` < 0, position counts from the end of the BLOB (`-1` is the last element). @@ -349,7 +361,6 @@ pub mod blob_functions { let _ctx = ctx; // Check if blob will be over max size limit - #[cfg(not(feature = "unchecked"))] if _ctx.engine().max_array_size() > 0 && len > _ctx.engine().max_array_size() { return Err( crate::ERR::ErrorDataTooLarge("Size of BLOB".to_string(), Position::NONE).into(), diff --git a/src/packages/debugging.rs b/src/packages/debugging.rs index e48571a8..4e3cbc35 100644 --- a/src/packages/debugging.rs +++ b/src/packages/debugging.rs @@ -33,57 +33,47 @@ mod debugging_functions { #[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_index"))] pub fn back_trace(ctx: NativeCallContext) -> Array { - if let Some(global) = ctx.global_runtime_state() { - global - .debugger - .call_stack() - .iter() - .rev() - .filter(|crate::debugger::CallStackFrame { fn_name, args, .. }| { - fn_name != "back_trace" || !args.is_empty() - }) - .map( - |frame @ crate::debugger::CallStackFrame { - fn_name: _fn_name, - args: _args, - source: _source, - pos: _pos, - }| { - let display = frame.to_string(); + ctx.global_runtime_state() + .debugger + .call_stack() + .iter() + .rev() + .filter(|crate::debugger::CallStackFrame { fn_name, args, .. }| { + fn_name.as_str() != "back_trace" || !args.is_empty() + }) + .map( + |frame @ crate::debugger::CallStackFrame { + fn_name: _fn_name, + args: _args, + source: _source, + pos: _pos, + }| { + let display = frame.to_string(); - #[cfg(not(feature = "no_object"))] - { - let mut map = Map::new(); - map.insert("display".into(), display.into()); - map.insert("fn_name".into(), _fn_name.into()); - if !_args.is_empty() { - map.insert( - "args".into(), - Dynamic::from_array(_args.clone().to_vec()), - ); - } - if !_source.is_empty() { - map.insert("source".into(), _source.into()); - } - if !_pos.is_none() { - map.insert( - "line".into(), - (_pos.line().unwrap() as crate::INT).into(), - ); - map.insert( - "position".into(), - (_pos.position().unwrap_or(0) as crate::INT).into(), - ); - } - Dynamic::from_map(map) + #[cfg(not(feature = "no_object"))] + { + let mut map = Map::new(); + map.insert("display".into(), display.into()); + map.insert("fn_name".into(), _fn_name.into()); + if !_args.is_empty() { + map.insert("args".into(), Dynamic::from_array(_args.clone().to_vec())); } - #[cfg(feature = "no_object")] - display.into() - }, - ) - .collect() - } else { - Array::new() - } + if let Some(source) = _source { + map.insert("source".into(), source.into()); + } + if !_pos.is_none() { + map.insert("line".into(), (_pos.line().unwrap() as crate::INT).into()); + map.insert( + "position".into(), + (_pos.position().unwrap_or(0) as crate::INT).into(), + ); + } + Dynamic::from_map(map) + } + #[cfg(feature = "no_object")] + display.into() + }, + ) + .collect() } } diff --git a/src/packages/fn_basic.rs b/src/packages/fn_basic.rs index 505a08b5..130646c3 100644 --- a/src/packages/fn_basic.rs +++ b/src/packages/fn_basic.rs @@ -27,7 +27,7 @@ mod fn_ptr_functions { /// ``` #[rhai_fn(name = "name", get = "name", pure)] pub fn name(fn_ptr: &mut FnPtr) -> ImmutableString { - fn_ptr.fn_name_raw().into() + fn_ptr.fn_name_raw().clone() } /// Return `true` if the function is an anonymous function. diff --git a/src/packages/iter_basic.rs b/src/packages/iter_basic.rs index 6ec293cc..163f93a4 100644 --- a/src/packages/iter_basic.rs +++ b/src/packages/iter_basic.rs @@ -47,6 +47,8 @@ pub struct StepRange { } impl Debug for StepRange { + #[cold] + #[inline(never)] fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_tuple(&format!("StepRange<{}>", type_name::())) .field(&self.from) @@ -120,7 +122,7 @@ impl FusedIterator for StepRange {} // Bit-field iterator with step #[derive(Debug, Clone, Hash, Eq, PartialEq)] -pub struct BitRange(INT, INT, usize); +pub struct BitRange(INT, usize); impl BitRange { pub fn new(value: INT, from: INT, len: INT) -> RhaiResultOf { @@ -136,7 +138,7 @@ impl BitRange { len as usize }; - Ok(Self(value, 1 << from, len)) + Ok(Self(value >> from, len)) } } @@ -144,19 +146,19 @@ impl Iterator for BitRange { type Item = bool; fn next(&mut self) -> Option { - if self.2 == 0 { + if self.1 == 0 { None } else { - let r = (self.0 & self.1) != 0; - self.1 <<= 1; - self.2 -= 1; + let r = (self.0 & 0x0001) != 0; + self.0 >>= 1; + self.1 -= 1; Some(r) } } #[inline(always)] fn size_hint(&self) -> (usize, Option) { - (self.2, Some(self.2)) + (self.1, Some(self.1)) } } @@ -165,7 +167,7 @@ impl FusedIterator for BitRange {} impl ExactSizeIterator for BitRange { #[inline(always)] fn len(&self) -> usize { - self.2 + self.1 } } @@ -668,6 +670,12 @@ mod range_functions { pub fn is_empty_exclusive(range: &mut ExclusiveRange) -> bool { range.is_empty() } + /// Return `true` if the range contains a specified value. + #[rhai_fn(name = "contains")] + pub fn contains_exclusive(range: &mut ExclusiveRange, value: INT) -> bool { + range.contains(&value) + } + /// Return the start of the inclusive range. #[rhai_fn(get = "start", name = "start", pure)] pub fn start_inclusive(range: &mut InclusiveRange) -> INT { @@ -695,4 +703,9 @@ mod range_functions { pub fn is_empty_inclusive(range: &mut InclusiveRange) -> bool { range.is_empty() } + /// Return `true` if the range contains a specified value. + #[rhai_fn(name = "contains")] + pub fn contains_inclusive(range: &mut InclusiveRange, value: INT) -> bool { + range.contains(&value) + } } diff --git a/src/packages/lang_core.rs b/src/packages/lang_core.rs index 5eaa5ea3..d423691d 100644 --- a/src/packages/lang_core.rs +++ b/src/packages/lang_core.rs @@ -88,7 +88,6 @@ mod core_functions { #[cfg(feature = "f32_float")] std::thread::sleep(std::time::Duration::from_secs_f32(seconds)); } - /// Block the current thread for a particular number of `seconds`. #[cfg(not(feature = "no_std"))] pub fn sleep(seconds: INT) { @@ -97,6 +96,23 @@ mod core_functions { } std::thread::sleep(std::time::Duration::from_secs(seconds as u64)); } + + /// Parse a JSON string into a value. + /// + /// # Example + /// + /// ```rhai + /// let m = parse_json(`{"a":1, "b":2, "c":3}`); + /// + /// print(m); // prints #{"a":1, "b":2, "c":3} + /// ``` + #[cfg(not(feature = "no_index"))] + #[cfg(not(feature = "no_object"))] + #[cfg(feature = "metadata")] + #[rhai_fn(return_raw)] + pub fn parse_json(_ctx: NativeCallContext, json: &str) -> RhaiResultOf { + serde_json::from_str(json).map_err(|err| err.to_string().into()) + } } #[cfg(not(feature = "no_function"))] diff --git a/src/packages/map_basic.rs b/src/packages/map_basic.rs index 09ffdce6..e5174c1b 100644 --- a/src/packages/map_basic.rs +++ b/src/packages/map_basic.rs @@ -2,7 +2,7 @@ use crate::engine::OP_EQUALS; use crate::plugin::*; -use crate::{def_package, format_map_as_json, Dynamic, ImmutableString, Map, RhaiResultOf, INT}; +use crate::{def_package, Dynamic, ImmutableString, Map, NativeCallContext, RhaiResultOf, INT}; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -30,6 +30,20 @@ mod map_functions { pub fn is_empty(map: &mut Map) -> bool { map.len() == 0 } + /// Returns `true` if the object map contains a specified property. + /// + /// # Example + /// + /// ```rhai + /// let m = #{a: 1, b: 2, c: 3}; + /// + /// print(m.contains("b")); // prints true + /// + /// print(m.contains("x")); // prints false + /// ``` + pub fn contains(map: &mut Map, property: &str) -> bool { + map.contains_key(property) + } /// Get the value of the `property` in the object map and return a copy. /// /// If `property` does not exist in the object map, `()` is returned. @@ -68,10 +82,11 @@ mod map_functions { /// print(m); // prints "#{a: 1, b: 42, c: 3, x: 0}" /// ``` pub fn set(map: &mut Map, property: &str, value: Dynamic) { - if let Some(value_ref) = map.get_mut(property) { - *value_ref = value; - } else { - map.insert(property.into(), value); + match map.get_mut(property) { + Some(value_ref) => *value_ref = value, + _ => { + map.insert(property.into(), value); + } } } /// Clear the object map. @@ -198,7 +213,7 @@ mod map_functions { for (m1, v1) in map1 { if let Some(v2) = map2.get_mut(m1) { let equals = ctx - .call_fn_raw(OP_EQUALS, true, false, &mut [v1, v2])? + .call_native_fn_raw(OP_EQUALS, true, &mut [v1, v2])? .as_bool() .unwrap_or(false); @@ -290,6 +305,9 @@ mod map_functions { /// print(m.to_json()); // prints {"a":1, "b":2, "c":3} /// ``` pub fn to_json(map: &mut Map) -> String { - format_map_as_json(map) + #[cfg(feature = "metadata")] + return serde_json::to_string(map).unwrap_or_else(|_| "ERROR".into()); + #[cfg(not(feature = "metadata"))] + return crate::format_map_as_json(map); } } diff --git a/src/packages/mod.rs b/src/packages/mod.rs index ab4df033..dfa3d8d1 100644 --- a/src/packages/mod.rs +++ b/src/packages/mod.rs @@ -1,6 +1,6 @@ //! Module containing all built-in _packages_ available to Rhai, plus facilities to define custom packages. -use crate::{Engine, Module, Shared}; +use crate::{Engine, Module, SharedModule}; pub(crate) mod arithmetic; pub(crate) mod array_basic; @@ -38,18 +38,21 @@ pub use pkg_core::CorePackage; pub use pkg_std::StandardPackage; pub use string_basic::BasicStringPackage; pub use string_more::MoreStringPackage; -#[cfg(not(feature = "no_std"))] +#[cfg(not(feature = "no_time"))] pub use time_basic::BasicTimePackage; /// Trait that all packages must implement. pub trait Package { /// Initialize the package. /// Functions should be registered into `module` here. + #[cold] fn init(module: &mut Module); /// Initialize the package with an [`Engine`]. /// /// Perform tasks such as registering custom operators/syntax. + #[cold] + #[inline] #[allow(unused_variables)] fn init_engine(engine: &mut Engine) {} @@ -65,6 +68,8 @@ pub trait Package { /// /// package.register_into_engine(&mut engine); /// ``` + #[cold] + #[inline] fn register_into_engine(&self, engine: &mut Engine) -> &Self { Self::init_engine(engine); engine.register_global_module(self.as_shared_module()); @@ -84,6 +89,8 @@ pub trait Package { /// package.register_into_engine_as(&mut engine, "core"); /// ``` #[cfg(not(feature = "no_module"))] + #[cold] + #[inline] fn register_into_engine_as(&self, engine: &mut Engine, name: &str) -> &Self { Self::init_engine(engine); engine.register_static_module(name, self.as_shared_module()); @@ -92,7 +99,7 @@ pub trait Package { /// Get a reference to a shared module from this package. #[must_use] - fn as_shared_module(&self) -> Shared; + fn as_shared_module(&self) -> SharedModule; } /// Macro that makes it easy to define a _package_ (which is basically a shared [module][Module]) @@ -133,7 +140,6 @@ macro_rules! def_package { fn as_shared_module(&self) -> $crate::Shared<$crate::Module> { self.0.clone() } - #[inline] fn init($lib: &mut $crate::Module) { $($( $(#[$base_meta])* { <$base_pkg>::init($lib); } @@ -141,7 +147,6 @@ macro_rules! def_package { $block } - #[inline] fn init_engine(_engine: &mut $crate::Engine) { $($( $(#[$base_meta])* { <$base_pkg>::init_engine(_engine); } @@ -156,6 +161,7 @@ macro_rules! def_package { impl Default for $package { #[inline(always)] + #[must_use] fn default() -> Self { Self::new() } @@ -193,12 +199,16 @@ macro_rules! def_package { } impl Default for $package { + #[inline(always)] + #[must_use] fn default() -> Self { Self::new() } } impl $package { + #[inline] + #[must_use] pub fn new() -> Self { let mut module = $root::Module::new(); ::init(&mut module); @@ -229,12 +239,16 @@ macro_rules! def_package { } impl Default for $package { + #[inline(always)] + #[must_use] fn default() -> Self { Self::new() } } impl $package { + #[inline] + #[must_use] pub fn new() -> Self { let mut module = $root::Module::new(); ::init(&mut module); diff --git a/src/packages/pkg_std.rs b/src/packages/pkg_std.rs index e7d4f1ff..b054b3ae 100644 --- a/src/packages/pkg_std.rs +++ b/src/packages/pkg_std.rs @@ -26,7 +26,7 @@ def_package! { #[cfg(not(feature = "no_index"))] BasicArrayPackage, #[cfg(not(feature = "no_index"))] BasicBlobPackage, #[cfg(not(feature = "no_object"))] BasicMapPackage, - #[cfg(not(feature = "no_std"))] BasicTimePackage, + #[cfg(not(feature = "no_time"))] BasicTimePackage, MoreStringPackage { lib.standard = true; diff --git a/src/packages/string_basic.rs b/src/packages/string_basic.rs index d8c3845a..a0763ada 100644 --- a/src/packages/string_basic.rs +++ b/src/packages/string_basic.rs @@ -38,7 +38,7 @@ pub fn print_with_func( ctx: &NativeCallContext, value: &mut Dynamic, ) -> crate::ImmutableString { - match ctx.call_fn_raw(fn_name, true, false, &mut [value]) { + match ctx.call_native_fn_raw(fn_name, true, &mut [value]) { Ok(result) if result.is::() => { result.into_immutable_string().expect("`ImmutableString`") } diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index da53a127..72c4a6ca 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -239,10 +239,9 @@ mod string_functions { /// Clear the string, making it empty. pub fn clear(string: &mut ImmutableString) { if !string.is_empty() { - if let Some(s) = string.get_mut() { - s.clear(); - } else { - *string = ImmutableString::new(); + match string.get_mut() { + Some(s) => s.clear(), + _ => *string = ImmutableString::new(), } } } @@ -287,17 +286,20 @@ mod string_functions { /// print(text); // prints "hello" /// ``` pub fn trim(string: &mut ImmutableString) { - if let Some(s) = string.get_mut() { - let trimmed = s.trim(); + match string.get_mut() { + Some(s) => { + let trimmed = s.trim(); - if trimmed != s { - *s = trimmed.into(); + if trimmed != s { + *s = trimmed.into(); + } } - } else { - let trimmed = string.trim(); + None => { + let trimmed = string.trim(); - if trimmed != string { - *string = trimmed.into(); + if trimmed != string { + *string = trimmed.into(); + } } } } @@ -506,6 +508,37 @@ mod string_functions { *character = to_lower_char(*character); } + /// Return `true` if the string contains a specified string. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world!"; + /// + /// print(text.contains("hello")); // prints true + /// + /// print(text.contains("hey")); // prints false + /// ``` + pub fn contains(string: &str, match_string: &str) -> bool { + string.contains(match_string) + } + + /// Return `true` if the string contains a specified character. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world!"; + /// + /// print(text.contains('h')); // prints true + /// + /// print(text.contains('x')); // prints false + /// ``` + #[rhai_fn(name = "contains")] + pub fn contains_char(string: &str, character: char) -> bool { + string.contains(character).into() + } + /// Return `true` if the string starts with a specified string. /// /// # Example @@ -1185,7 +1218,6 @@ mod string_functions { let _ctx = ctx; // Check if string will be over max size limit - #[cfg(not(feature = "unchecked"))] if _ctx.engine().max_string_size() > 0 && len > _ctx.engine().max_string_size() { return Err(crate::ERR::ErrorDataTooLarge( "Length of string".to_string(), @@ -1203,7 +1235,6 @@ mod string_functions { p.push(character); } - #[cfg(not(feature = "unchecked"))] if _ctx.engine().max_string_size() > 0 && string.len() > _ctx.engine().max_string_size() { return Err(crate::ERR::ErrorDataTooLarge( @@ -1247,7 +1278,6 @@ mod string_functions { let _ctx = ctx; // Check if string will be over max size limit - #[cfg(not(feature = "unchecked"))] if _ctx.engine().max_string_size() > 0 && len > _ctx.engine().max_string_size() { return Err(crate::ERR::ErrorDataTooLarge( "Length of string".to_string(), @@ -1272,7 +1302,6 @@ mod string_functions { } } - #[cfg(not(feature = "unchecked"))] if _ctx.engine().max_string_size() > 0 && string.len() > _ctx.engine().max_string_size() { return Err(crate::ERR::ErrorDataTooLarge( diff --git a/src/packages/time_basic.rs b/src/packages/time_basic.rs index 16ade536..59e198b8 100644 --- a/src/packages/time_basic.rs +++ b/src/packages/time_basic.rs @@ -1,8 +1,8 @@ -#![cfg(not(feature = "no_std"))] +#![cfg(not(feature = "no_time"))] use super::arithmetic::make_err as make_arithmetic_err; use crate::plugin::*; -use crate::{def_package, Dynamic, EvalAltResult, RhaiResult, RhaiResultOf, INT}; +use crate::{def_package, Dynamic, RhaiResult, RhaiResultOf, INT}; #[cfg(not(feature = "no_float"))] use crate::FLOAT; diff --git a/src/parser.rs b/src/parser.rs index 20600e0b..fed55ac3 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -8,7 +8,7 @@ use crate::ast::{ SwitchCasesCollection, TryCatchBlock, }; use crate::engine::{Precedence, KEYWORD_THIS, OP_CONTAINS}; -use crate::eval::GlobalRuntimeState; +use crate::eval::{Caches, GlobalRuntimeState}; use crate::func::{hashing::get_hasher, StraightHashMap}; use crate::tokenizer::{ is_keyword_function, is_valid_function_name, is_valid_identifier, Token, TokenStream, @@ -34,10 +34,6 @@ pub type ParseResult = Result; type FnLib = StraightHashMap>; -const KEYWORD_SEMICOLON: &str = Token::SemiColon.literal_syntax(); - -const KEYWORD_CLOSE_BRACE: &str = Token::RightBrace.literal_syntax(); - /// Invalid variable name that acts as a search barrier in a [`Scope`]. const SCOPE_SEARCH_BARRIER_MARKER: &str = "$ BARRIER $"; @@ -47,9 +43,6 @@ const NEVER_ENDS: &str = "`Token`"; /// Unroll `switch` ranges no larger than this. const SMALL_SWITCH_RANGE: INT = 16; -/// Number of string interners used: two additional for property getters/setters if not `no_object` -const NUM_INTERNERS: usize = if cfg!(feature = "no_object") { 1 } else { 3 }; - /// _(internals)_ A type that encapsulates the current state of the parser. /// Exported under the `internals` feature only. pub struct ParseState<'e> { @@ -58,11 +51,11 @@ pub struct ParseState<'e> { /// Controls whether parsing of an expression should stop given the next token. pub expr_filter: fn(&Token) -> bool, /// String interners. - interned_strings: [StringsInterner<'e>; NUM_INTERNERS], + interned_strings: StringsInterner<'e>, /// External [scope][Scope] with constants. pub scope: &'e Scope<'e>, /// Global runtime state. - pub global: GlobalRuntimeState<'e>, + pub global: GlobalRuntimeState, /// Encapsulates a local stack with variable names to simulate an actual runtime scope. pub stack: Scope<'e>, /// Size of the local variables stack upon entry of the current block scope. @@ -88,6 +81,8 @@ pub struct ParseState<'e> { } impl fmt::Debug for ParseState<'_> { + #[cold] + #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut f = f.debug_struct("ParseState"); @@ -111,12 +106,12 @@ impl fmt::Debug for ParseState<'_> { impl<'e> ParseState<'e> { /// Create a new [`ParseState`]. - #[inline(always)] + #[inline] #[must_use] pub fn new( engine: &Engine, scope: &'e Scope, - interned_strings: [StringsInterner<'e>; NUM_INTERNERS], + interned_strings: StringsInterner<'e>, tokenizer_control: TokenizerControl, ) -> Self { Self { @@ -179,12 +174,11 @@ impl<'e> ParseState<'e> { /// /// # Return value: `(index, is_func_name)` /// - /// * `index`: `None` when the variable name is not found in the `stack`, + /// * `index`: [`None`] when the variable name is not found in the `stack`, /// otherwise the index value. /// /// * `is_func_name`: `true` if the variable is actually the name of a function /// (in which case it will be converted into a function pointer). - #[inline] #[must_use] pub fn access_var( &mut self, @@ -199,7 +193,6 @@ impl<'e> ParseState<'e> { #[cfg(not(feature = "no_function"))] let is_func_name = _lib.values().any(|f| f.name == name); - #[cfg(feature = "no_function")] let is_func_name = false; @@ -230,13 +223,12 @@ impl<'e> ParseState<'e> { /// Returns the offset to be deducted from `Stack::len`, /// i.e. the top element of the [`ParseState`] is offset 1. /// - /// Returns `None` when the variable name is not found in the [`ParseState`]. + /// Returns [`None`] when the variable name is not found in the [`ParseState`]. /// /// # Panics /// /// Panics when called under `no_module`. #[cfg(not(feature = "no_module"))] - #[inline] #[must_use] pub fn find_module(&self, name: &str) -> Option { self.imports @@ -254,61 +246,69 @@ impl<'e> ParseState<'e> { &mut self, text: impl AsRef + Into, ) -> ImmutableString { - self.interned_strings[0].get(text) + self.interned_strings.get(text) } /// Get an interned property getter, creating one if it is not yet interned. #[cfg(not(feature = "no_object"))] - #[inline(always)] + #[inline] #[must_use] pub fn get_interned_getter( &mut self, text: impl AsRef + Into, ) -> ImmutableString { - self.interned_strings[1] - .get_with_mapper(|s| crate::engine::make_getter(s.as_ref()).into(), text) + self.interned_strings.get_with_mapper( + crate::engine::FN_GET, + |s| crate::engine::make_getter(s.as_ref()).into(), + text, + ) } /// Get an interned property setter, creating one if it is not yet interned. #[cfg(not(feature = "no_object"))] - #[inline(always)] + #[inline] #[must_use] pub fn get_interned_setter( &mut self, text: impl AsRef + Into, ) -> ImmutableString { - self.interned_strings[2] - .get_with_mapper(|s| crate::engine::make_setter(s.as_ref()).into(), text) + self.interned_strings.get_with_mapper( + crate::engine::FN_SET, + |s| crate::engine::make_setter(s.as_ref()).into(), + text, + ) } } /// A type that encapsulates all the settings for a particular parsing function. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -struct ParseSettings { +pub(crate) struct ParseSettings { /// Is the construct being parsed located at global level? - at_global_level: bool, + pub at_global_level: bool, /// Is the construct being parsed located inside a function definition? #[cfg(not(feature = "no_function"))] - in_fn_scope: bool, + pub in_fn_scope: bool, /// Is the construct being parsed located inside a closure definition? #[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_closure"))] - in_closure: bool, + pub in_closure: bool, /// Is the construct being parsed located inside a breakable loop? - is_breakable: bool, + pub is_breakable: bool, /// Allow statements in blocks? - allow_statements: bool, + pub allow_statements: bool, + /// Allow unquoted map properties? + pub allow_unquoted_map_properties: bool, /// Language options in effect (overrides Engine options). - options: LangOptions, + pub options: LangOptions, /// Current expression nesting level. - level: usize, + pub level: usize, /// Current position. - pos: Position, + pub pos: Position, } impl ParseSettings { /// Create a new `ParseSettings` with one higher expression level. - #[inline(always)] + #[inline] #[must_use] pub const fn level_up(&self) -> Self { Self { @@ -412,7 +412,6 @@ impl Expr { } /// Make sure that the next expression is not a statement expression (i.e. wrapped in `{}`). -#[inline] fn ensure_not_statement_expr( input: &mut TokenStream, type_name: &(impl ToString + ?Sized), @@ -424,7 +423,6 @@ fn ensure_not_statement_expr( } /// Make sure that the next expression is not a mis-typed assignment (i.e. `a = b` instead of `a == b`). -#[inline] fn ensure_not_assignment(input: &mut TokenStream) -> ParseResult<()> { match input.peek().expect(NEVER_ENDS) { (Token::Equals, pos) => Err(LexError::ImproperSymbol( @@ -441,7 +439,6 @@ fn ensure_not_assignment(input: &mut TokenStream) -> ParseResult<()> { /// # Panics /// /// Panics if the next token is not the expected one. -#[inline] fn eat_token(input: &mut TokenStream, expected_token: Token) -> Position { let (t, pos) = input.next().expect(NEVER_ENDS); @@ -457,7 +454,6 @@ fn eat_token(input: &mut TokenStream, expected_token: Token) -> Position { } /// Match a particular [token][Token], consuming it if matched. -#[inline] fn match_token(input: &mut TokenStream, token: Token) -> (bool, Position) { let (t, pos) = input.peek().expect(NEVER_ENDS); if *t == token { @@ -468,13 +464,12 @@ fn match_token(input: &mut TokenStream, token: Token) -> (bool, Position) { } /// Parse a variable name. -#[inline] fn parse_var_name(input: &mut TokenStream) -> ParseResult<(SmartString, Position)> { match input.next().expect(NEVER_ENDS) { // Variable name - (Token::Identifier(s), pos) => Ok((s, pos)), + (Token::Identifier(s), pos) => Ok((*s, pos)), // Reserved keyword - (Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => { + (Token::Reserved(s), pos) if is_valid_identifier(s.as_str()) => { Err(PERR::Reserved(s.to_string()).into_err(pos)) } // Bad identifier @@ -486,13 +481,12 @@ fn parse_var_name(input: &mut TokenStream) -> ParseResult<(SmartString, Position /// Parse a symbol. #[cfg(not(feature = "no_custom_syntax"))] -#[inline] fn parse_symbol(input: &mut TokenStream) -> ParseResult<(SmartString, Position)> { match input.next().expect(NEVER_ENDS) { // Symbol (token, pos) if token.is_standard_symbol() => Ok((token.literal_syntax().into(), pos)), // Reserved symbol - (Token::Reserved(s), pos) if !is_valid_identifier(s.chars()) => Ok((s, pos)), + (Token::Reserved(s), pos) if !is_valid_identifier(s.as_str()) => Ok((*s, pos)), // Bad symbol (Token::LexError(err), pos) => Err(err.into_err(pos)), // Not a symbol @@ -616,12 +610,11 @@ impl Engine { return Ok(FnCallExpr { name: state.get_interned_string(id), capture_parent_scope, - is_native_operator: false, + op_token: None, #[cfg(not(feature = "no_module"))] namespace, hashes, args, - pos: settings.pos, } .into_fn_call_expr(settings.pos)); } @@ -684,12 +677,11 @@ impl Engine { return Ok(FnCallExpr { name: state.get_interned_string(id), capture_parent_scope, - is_native_operator: false, + op_token: None, #[cfg(not(feature = "no_module"))] namespace, hashes, args, - pos: settings.pos, } .into_fn_call_expr(settings.pos)); } @@ -907,7 +899,6 @@ impl Engine { loop { const MISSING_RBRACKET: &str = "to end this array literal"; - #[cfg(not(feature = "unchecked"))] if self.max_array_size() > 0 && array.len() >= self.max_array_size() { return Err(PERR::LiteralTooLarge( "Size of array literal".to_string(), @@ -999,16 +990,19 @@ impl Engine { } let (name, pos) = match input.next().expect(NEVER_ENDS) { + (Token::Identifier(..), pos) if !settings.allow_unquoted_map_properties => { + return Err(PERR::PropertyExpected.into_err(pos)) + } (Token::Identifier(s) | Token::StringConstant(s), pos) => { - if map.iter().any(|(p, ..)| **p == s) { + if map.iter().any(|(p, ..)| **p == *s) { return Err(PERR::DuplicatedProperty(s.to_string()).into_err(pos)); } - (s, pos) + (*s, pos) } (Token::InterpolatedString(..), pos) => { return Err(PERR::PropertyExpected.into_err(pos)) } - (Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => { + (Token::Reserved(s), pos) if is_valid_identifier(s.as_str()) => { return Err(PERR::Reserved(s.to_string()).into_err(pos)); } (Token::LexError(err), pos) => return Err(err.into_err(pos)), @@ -1041,7 +1035,6 @@ impl Engine { } }; - #[cfg(not(feature = "unchecked"))] if self.max_map_size() > 0 && map.len() >= self.max_map_size() { return Err(PERR::LiteralTooLarge( "Number of properties in object map literal".to_string(), @@ -1342,7 +1335,7 @@ impl Engine { Token::IntegerConstant(x) => Expr::IntegerConstant(x, settings.pos), Token::CharConstant(c) => Expr::CharConstant(c, settings.pos), Token::StringConstant(s) => { - Expr::StringConstant(state.get_interned_string(s), settings.pos) + Expr::StringConstant(state.get_interned_string(*s), settings.pos) } Token::True => Expr::BoolConstant(true, settings.pos), Token::False => Expr::BoolConstant(false, settings.pos), @@ -1356,7 +1349,7 @@ impl Engine { } #[cfg(feature = "decimal")] Token::DecimalConstant(x) => { - let x = (*x).into(); + let x = (**x).into(); input.next(); Expr::DynamicConstant(Box::new(x), settings.pos) } @@ -1377,6 +1370,23 @@ impl Engine { self.parse_if(input, state, lib, settings.level_up())? .into(), )), + // Loops are allowed to act as expressions + Token::While | Token::Loop if settings.options.contains(LangOptions::LOOP_EXPR) => { + Expr::Stmt(Box::new( + self.parse_while_loop(input, state, lib, settings.level_up())? + .into(), + )) + } + Token::Do if settings.options.contains(LangOptions::LOOP_EXPR) => Expr::Stmt(Box::new( + self.parse_do(input, state, lib, settings.level_up())? + .into(), + )), + Token::For if settings.options.contains(LangOptions::LOOP_EXPR) => { + Expr::Stmt(Box::new( + self.parse_for(input, state, lib, settings.level_up())? + .into(), + )) + } // Switch statement is allowed to act as expressions Token::Switch if settings.options.contains(LangOptions::SWITCH_EXPR) => { Expr::Stmt(Box::new( @@ -1438,7 +1448,7 @@ impl Engine { ..settings }; - let result = self.parse_anon_fn(input, &mut new_state, lib, new_settings); + let result = self.parse_anon_fn(input, &mut new_state, state, lib, new_settings); // Restore parse state state.interned_strings = new_state.interned_strings; @@ -1461,7 +1471,7 @@ impl Engine { // Under Strict Variables mode, this is not allowed. Err(PERR::VariableUndefined(name.to_string()).into_err(*pos)) } else { - Ok::<_, ParseError>(()) + Ok(()) } }, )?; @@ -1479,7 +1489,7 @@ impl Engine { match input.next().expect(NEVER_ENDS) { (Token::InterpolatedString(s), ..) if s.is_empty() => (), (Token::InterpolatedString(s), pos) => { - segments.push(Expr::StringConstant(s.into(), pos)) + segments.push(Expr::StringConstant(state.get_interned_string(*s), pos)) } token => { unreachable!("Token::InterpolatedString expected but gets {:?}", token) @@ -1502,14 +1512,16 @@ impl Engine { match input.next().expect(NEVER_ENDS) { (Token::StringConstant(s), pos) => { if !s.is_empty() { - segments.push(Expr::StringConstant(s.into(), pos)); + segments + .push(Expr::StringConstant(state.get_interned_string(*s), pos)); } // End the interpolated string if it is terminated by a back-tick. break; } (Token::InterpolatedString(s), pos) => { if !s.is_empty() { - segments.push(Expr::StringConstant(s.into(), pos)); + segments + .push(Expr::StringConstant(state.get_interned_string(*s), pos)); } } (Token::LexError(err), pos) @@ -1574,7 +1586,7 @@ impl Engine { state.allow_capture = true; } Expr::Variable( - (None, ns, 0, state.get_interned_string(s)).into(), + (None, ns, 0, state.get_interned_string(*s)).into(), None, settings.pos, ) @@ -1587,7 +1599,7 @@ impl Engine { // Once the identifier consumed we must enable next variables capturing state.allow_capture = true; } - let name = state.get_interned_string(s); + let name = state.get_interned_string(*s); Expr::Variable((None, ns, 0, name).into(), None, settings.pos) } // Normal variable access @@ -1612,7 +1624,7 @@ impl Engine { None } }); - let name = state.get_interned_string(s); + let name = state.get_interned_string(*s); Expr::Variable((index, ns, 0, name).into(), short_index, settings.pos) } } @@ -1634,7 +1646,7 @@ impl Engine { // Function call is allowed to have reserved keyword Token::LeftParen | Token::Bang | Token::Unit if is_keyword_function(&s) => { Expr::Variable( - (None, ns, 0, state.get_interned_string(s)).into(), + (None, ns, 0, state.get_interned_string(*s)).into(), None, settings.pos, ) @@ -1642,7 +1654,7 @@ impl Engine { // Access to `this` as a variable is OK within a function scope #[cfg(not(feature = "no_function"))] _ if &*s == KEYWORD_THIS && settings.in_fn_scope => Expr::Variable( - (None, ns, 0, state.get_interned_string(s)).into(), + (None, ns, 0, state.get_interned_string(*s)).into(), None, settings.pos, ), @@ -1888,7 +1900,7 @@ impl Engine { // -expr Token::Minus | Token::UnaryMinus => { let token = token.clone(); - let pos = eat_token(input, token); + let pos = eat_token(input, token.clone()); match self.parse_unary(input, state, lib, settings.level_up())? { // Negative integer @@ -1914,12 +1926,13 @@ impl Engine { args.shrink_to_fit(); Ok(FnCallExpr { + #[cfg(not(feature = "no_module"))] + namespace: Default::default(), name: state.get_interned_string("-"), hashes: FnCallHashes::from_native(calc_fn_hash(None, "-", 1)), args, - pos, - is_native_operator: true, - ..Default::default() + op_token: Some(token), + capture_parent_scope: false, } .into_fn_call_expr(pos)) } @@ -1928,7 +1941,7 @@ impl Engine { // +expr Token::Plus | Token::UnaryPlus => { let token = token.clone(); - let pos = eat_token(input, token); + let pos = eat_token(input, token.clone()); match self.parse_unary(input, state, lib, settings.level_up())? { expr @ Expr::IntegerConstant(..) => Ok(expr), @@ -1942,12 +1955,13 @@ impl Engine { args.shrink_to_fit(); Ok(FnCallExpr { + #[cfg(not(feature = "no_module"))] + namespace: Default::default(), name: state.get_interned_string("+"), hashes: FnCallHashes::from_native(calc_fn_hash(None, "+", 1)), args, - pos, - is_native_operator: true, - ..Default::default() + op_token: Some(token), + capture_parent_scope: false, } .into_fn_call_expr(pos)) } @@ -1955,18 +1969,21 @@ impl Engine { } // !expr Token::Bang => { + let token = token.clone(); let pos = eat_token(input, Token::Bang); + let mut args = StaticVec::new_const(); args.push(self.parse_unary(input, state, lib, settings.level_up())?); args.shrink_to_fit(); Ok(FnCallExpr { + #[cfg(not(feature = "no_module"))] + namespace: Default::default(), name: state.get_interned_string("!"), hashes: FnCallHashes::from_native(calc_fn_hash(None, "!", 1)), args, - pos, - is_native_operator: true, - ..Default::default() + op_token: Some(token), + capture_parent_scope: false, } .into_fn_call_expr(pos)) } @@ -2181,11 +2198,15 @@ impl Engine { // lhs.func(...) (lhs, Expr::FnCall(mut func, func_pos)) => { // Recalculate hash - func.hashes = FnCallHashes::from_all( - #[cfg(not(feature = "no_function"))] - calc_fn_hash(None, &func.name, func.args.len()), - calc_fn_hash(None, &func.name, func.args.len() + 1), - ); + func.hashes = if is_valid_function_name(&func.name) { + FnCallHashes::from_all( + #[cfg(not(feature = "no_function"))] + calc_fn_hash(None, &func.name, func.args.len()), + calc_fn_hash(None, &func.name, func.args.len() + 1), + ) + } else { + FnCallHashes::from_native(calc_fn_hash(None, &func.name, func.args.len() + 1)) + }; let rhs = Expr::MethodCall(func, func_pos); Ok(Expr::Dot(BinaryExpr { lhs, rhs }.into(), op_flags, op_pos)) @@ -2227,11 +2248,19 @@ impl Engine { // lhs.func().dot_rhs or lhs.func()[idx_rhs] Expr::FnCall(mut func, func_pos) => { // Recalculate hash - func.hashes = FnCallHashes::from_all( - #[cfg(not(feature = "no_function"))] - calc_fn_hash(None, &func.name, func.args.len()), - calc_fn_hash(None, &func.name, func.args.len() + 1), - ); + func.hashes = if is_valid_function_name(&func.name) { + FnCallHashes::from_all( + #[cfg(not(feature = "no_function"))] + calc_fn_hash(None, &func.name, func.args.len()), + calc_fn_hash(None, &func.name, func.args.len() + 1), + ) + } else { + FnCallHashes::from_native(calc_fn_hash( + None, + &func.name, + func.args.len() + 1, + )) + }; let new_lhs = BinaryExpr { lhs: Expr::MethodCall(func, func_pos), @@ -2283,10 +2312,10 @@ impl Engine { #[cfg(not(feature = "no_custom_syntax"))] Token::Custom(c) => self .custom_keywords - .get(c) + .get(&**c) .copied() .ok_or_else(|| PERR::Reserved(c.to_string()).into_err(*current_pos))?, - Token::Reserved(c) if !is_valid_identifier(c.chars()) => { + Token::Reserved(c) if !is_valid_identifier(c) => { return Err(PERR::UnknownOperator(c.to_string()).into_err(*current_pos)) } _ => current_op.precedence(), @@ -2308,10 +2337,10 @@ impl Engine { #[cfg(not(feature = "no_custom_syntax"))] Token::Custom(c) => self .custom_keywords - .get(c) + .get(&**c) .copied() .ok_or_else(|| PERR::Reserved(c.to_string()).into_err(*next_pos))?, - Token::Reserved(c) if !is_valid_identifier(c.chars()) => { + Token::Reserved(c) if !is_valid_identifier(c) => { return Err(PERR::UnknownOperator(c.to_string()).into_err(*next_pos)) } _ => next_op.precedence(), @@ -2335,13 +2364,11 @@ impl Engine { let op = op_token.syntax(); let hash = calc_fn_hash(None, &op, 2); - - let op_base = FnCallExpr { - name: state.get_interned_string(op.as_ref()), - hashes: FnCallHashes::from_native(hash), - pos, - is_native_operator: !is_valid_function_name(&op), - ..Default::default() + let is_valid_script_function = is_valid_function_name(&op); + let operator_token = if is_valid_script_function { + None + } else { + Some(op_token.clone()) }; let mut args = StaticVec::new_const(); @@ -2349,9 +2376,19 @@ impl Engine { args.push(rhs); args.shrink_to_fit(); + let mut op_base = FnCallExpr { + #[cfg(not(feature = "no_module"))] + namespace: Default::default(), + name: state.get_interned_string(op.as_ref()), + hashes: FnCallHashes::from_native(hash), + args, + op_token: operator_token, + capture_parent_scope: false, + }; + root = match op_token { // '!=' defaults to true when passed invalid operands - Token::NotEqualsTo => FnCallExpr { args, ..op_base }.into_fn_call_expr(pos), + Token::NotEqualsTo => op_base.into_fn_call_expr(pos), // Comparison operators default to false when passed invalid operands Token::EqualsTo @@ -2359,61 +2396,36 @@ impl Engine { | Token::LessThanEqualsTo | Token::GreaterThan | Token::GreaterThanEqualsTo => { - let pos = args[0].start_position(); - FnCallExpr { args, ..op_base }.into_fn_call_expr(pos) + let pos = op_base.args[0].start_position(); + op_base.into_fn_call_expr(pos) } Token::Or => { - let rhs = args.pop().unwrap(); - let current_lhs = args.pop().unwrap(); - Expr::Or( - BinaryExpr { - lhs: current_lhs.ensure_bool_expr()?, - rhs: rhs.ensure_bool_expr()?, - } - .into(), - pos, - ) + let rhs = op_base.args.pop().unwrap().ensure_bool_expr()?; + let lhs = op_base.args.pop().unwrap().ensure_bool_expr()?; + Expr::Or(BinaryExpr { lhs: lhs, rhs: rhs }.into(), pos) } Token::And => { - let rhs = args.pop().unwrap(); - let current_lhs = args.pop().unwrap(); - Expr::And( - BinaryExpr { - lhs: current_lhs.ensure_bool_expr()?, - rhs: rhs.ensure_bool_expr()?, - } - .into(), - pos, - ) + let rhs = op_base.args.pop().unwrap().ensure_bool_expr()?; + let lhs = op_base.args.pop().unwrap().ensure_bool_expr()?; + Expr::And(BinaryExpr { lhs: lhs, rhs: rhs }.into(), pos) } Token::DoubleQuestion => { - let rhs = args.pop().unwrap(); - let current_lhs = args.pop().unwrap(); - Expr::Coalesce( - BinaryExpr { - lhs: current_lhs, - rhs, - } - .into(), - pos, - ) + let rhs = op_base.args.pop().unwrap(); + let lhs = op_base.args.pop().unwrap(); + Expr::Coalesce(BinaryExpr { lhs, rhs }.into(), pos) } Token::In => { // Swap the arguments - let current_lhs = args.remove(0); - let pos = current_lhs.start_position(); - args.push(current_lhs); - args.shrink_to_fit(); + let lhs = op_base.args.remove(0); + let pos = lhs.start_position(); + op_base.args.push(lhs); + op_base.args.shrink_to_fit(); // Convert into a call to `contains` - FnCallExpr { - hashes: calc_fn_hash(None, OP_CONTAINS, 2).into(), - args, - name: state.get_interned_string(OP_CONTAINS), - ..op_base - } - .into_fn_call_expr(pos) + op_base.hashes = calc_fn_hash(None, OP_CONTAINS, 2).into(); + op_base.name = state.get_interned_string(OP_CONTAINS); + op_base.into_fn_call_expr(pos) } #[cfg(not(feature = "no_custom_syntax"))] @@ -2423,24 +2435,17 @@ impl Engine { .get(s.as_str()) .map_or(false, Option::is_some) => { - let hash = calc_fn_hash(None, &s, 2); - let pos = args[0].start_position(); - - FnCallExpr { - hashes: if is_valid_function_name(&s) { - hash.into() - } else { - FnCallHashes::from_native(hash) - }, - args, - ..op_base - } - .into_fn_call_expr(pos) + op_base.hashes = if is_valid_script_function { + calc_fn_hash(None, &s, 2).into() + } else { + FnCallHashes::from_native(calc_fn_hash(None, &s, 2)) + }; + op_base.into_fn_call_expr(pos) } _ => { - let pos = args[0].start_position(); - FnCallExpr { args, ..op_base }.into_fn_call_expr(pos) + let pos = op_base.args[0].start_position(); + op_base.into_fn_call_expr(pos) } }; } @@ -2459,9 +2464,11 @@ impl Engine { pos: Position, ) -> ParseResult { use crate::api::custom_syntax::markers::*; + const KEYWORD_SEMICOLON: &str = Token::SemiColon.literal_syntax(); + const KEYWORD_CLOSE_BRACE: &str = Token::RightBrace.literal_syntax(); let mut settings = settings; - let mut inputs = StaticVec::::new(); + let mut inputs = StaticVec::new_const(); let mut segments = StaticVec::new_const(); let mut tokens = StaticVec::new_const(); @@ -2578,7 +2585,7 @@ impl Engine { }, CUSTOM_SYNTAX_MARKER_STRING => match input.next().expect(NEVER_ENDS) { (Token::StringConstant(s), pos) => { - let s = state.get_interned_string(s); + let s = state.get_interned_string(*s); inputs.push(Expr::StringConstant(s.clone(), pos)); segments.push(s); tokens.push(state.get_interned_string(CUSTOM_SYNTAX_MARKER_STRING)); @@ -2891,23 +2898,24 @@ impl Engine { if let Some(ref filter) = self.def_var_filter { let will_shadow = state.stack.iter().any(|(v, ..)| v == name); - let level = settings.level; + state.global.level = settings.level; let is_const = access == AccessMode::ReadOnly; let info = VarDefInfo { name: &name, is_const, - nesting_level: level, + nesting_level: state.global.level, will_shadow, }; - let mut this_ptr = None; + let caches = &mut Caches::new(); + let mut this = Dynamic::NULL; + let context = EvalContext::new( self, - &mut state.stack, &mut state.global, - None, + caches, &[], - &mut this_ptr, - level, + &mut state.stack, + &mut this, ); match filter(false, info, context) { @@ -2986,24 +2994,24 @@ impl Engine { // import expr ... let expr = self.parse_expr(input, state, lib, settings.level_up())?; - // import expr; - if !match_token(input, Token::As).0 { - let empty = Ident { + let export = if !match_token(input, Token::As).0 { + // import expr; + Ident { name: state.get_interned_string(""), pos: Position::NONE, - }; - return Ok(Stmt::Import((expr, empty).into(), settings.pos)); - } + } + } else { + // import expr as name ... + let (name, pos) = parse_var_name(input)?; + Ident { + name: state.get_interned_string(name), + pos, + } + }; - // import expr as name ... - let (name, pos) = parse_var_name(input)?; - let name = state.get_interned_string(name); - state.imports.push(name.clone()); + state.imports.push(export.name.clone()); - Ok(Stmt::Import( - (expr, Ident { name, pos }).into(), - settings.pos, - )) + Ok(Stmt::Import((expr, export).into(), settings.pos)) } /// Parse an export statement. @@ -3228,7 +3236,7 @@ impl Engine { match input.next().expect(NEVER_ENDS).0 { Token::Comment(comment) => { - comments.push(comment); + comments.push(*comment); match input.peek().expect(NEVER_ENDS) { (Token::Fn | Token::Private, ..) => break, @@ -3320,6 +3328,7 @@ impl Engine { in_closure: false, is_breakable: false, allow_statements: true, + allow_unquoted_map_properties: settings.allow_unquoted_map_properties, level: 0, options, pos, @@ -3378,11 +3387,26 @@ impl Engine { Token::Continue if self.allow_looping() && settings.is_breakable => { let pos = eat_token(input, Token::Continue); - Ok(Stmt::BreakLoop(ASTFlags::NONE, pos)) + Ok(Stmt::BreakLoop(None, ASTFlags::NONE, pos)) } Token::Break if self.allow_looping() && settings.is_breakable => { let pos = eat_token(input, Token::Break); - Ok(Stmt::BreakLoop(ASTFlags::BREAK, pos)) + + let expr = match input.peek().expect(NEVER_ENDS) { + // `break` at + (Token::EOF, ..) => None, + // `break` at end of block + (Token::RightBrace, ..) => None, + // `break;` + (Token::SemiColon, ..) => None, + // `break` with expression + _ => Some( + self.parse_expr(input, state, lib, settings.level_up())? + .into(), + ), + }; + + Ok(Stmt::BreakLoop(expr, ASTFlags::BREAK, pos)) } Token::Continue | Token::Break if self.allow_looping() => { Err(PERR::LoopBreak.into_err(token_pos)) @@ -3562,7 +3586,7 @@ impl Engine { return Err(PERR::FnDuplicatedParam(name.to_string(), s.to_string()) .into_err(pos)); } - let s = state.get_interned_string(s); + let s = state.get_interned_string(*s); state.stack.push(s.clone(), ()); params.push((s, pos)); } @@ -3622,6 +3646,8 @@ impl Engine { #[cfg(not(feature = "no_closure"))] fn make_curry_from_externals( state: &mut ParseState, + parent: &mut ParseState, + lib: &FnLib, fn_expr: Expr, externals: StaticVec, pos: Position, @@ -3641,16 +3667,20 @@ impl Engine { .iter() .cloned() .map(|crate::ast::Ident { name, pos }| { - #[cfg(not(feature = "no_module"))] - let ns = crate::ast::Namespace::NONE; - #[cfg(feature = "no_module")] - let ns = (); - - Expr::Variable((None, ns, 0, name).into(), None, pos) + let (index, is_func) = parent.access_var(&name, lib, pos); + let idx = match index { + Some(n) if !is_func && n.get() <= u8::MAX as usize => { + NonZeroU8::new(n.get() as u8) + } + _ => None, + }; + Expr::Variable((index, Default::default(), 0, name).into(), idx, pos) }), ); let expr = FnCallExpr { + #[cfg(not(feature = "no_module"))] + namespace: Default::default(), name: state.get_interned_string(crate::engine::KEYWORD_FN_PTR_CURRY), hashes: FnCallHashes::from_native(calc_fn_hash( None, @@ -3658,19 +3688,24 @@ impl Engine { num_externals + 1, )), args, - pos, - ..Default::default() + op_token: None, + capture_parent_scope: false, } .into_fn_call_expr(pos); // Convert the entire expression into a statement block, then insert the relevant // [`Share`][Stmt::Share] statements. - let mut statements = StaticVec::with_capacity(externals.len() + 1); - statements.extend( + let mut statements = StaticVec::with_capacity(2); + statements.push(Stmt::Share( externals .into_iter() - .map(|crate::ast::Ident { name, pos }| Stmt::Share(name, pos)), - ); + .map(|crate::ast::Ident { name, pos }| { + let (index, _) = parent.access_var(&name, lib, pos); + (name, index, pos) + }) + .collect::>() + .into(), + )); statements.push(Stmt::Expr(expr.into())); Expr::Stmt(crate::ast::StmtBlock::new(statements, pos, Position::NONE).into()) } @@ -3681,6 +3716,7 @@ impl Engine { &self, input: &mut TokenStream, state: &mut ParseState, + parent: &mut ParseState, lib: &mut FnLib, settings: ParseSettings, ) -> ParseResult<(Expr, ScriptFnDef)> { @@ -3700,7 +3736,7 @@ impl Engine { PERR::FnDuplicatedParam(String::new(), s.to_string()).into_err(pos) ); } - let s = state.get_interned_string(s); + let s = state.get_interned_string(*s); state.stack.push(s.clone(), ()); params_list.push(s); } @@ -3777,7 +3813,8 @@ impl Engine { let expr = Expr::DynamicConstant(Box::new(fn_ptr.into()), settings.pos); #[cfg(not(feature = "no_closure"))] - let expr = Self::make_curry_from_externals(state, expr, externals, settings.pos); + let expr = + Self::make_curry_from_externals(state, parent, lib, expr, externals, settings.pos); Ok((expr, script)) } @@ -3787,16 +3824,17 @@ impl Engine { &self, input: &mut TokenStream, state: &mut ParseState, + process_settings: impl Fn(&mut ParseSettings), _optimization_level: OptimizationLevel, ) -> ParseResult { let mut functions = StraightHashMap::default(); let mut options = self.options; - options.remove(LangOptions::STMT_EXPR); + options.remove(LangOptions::STMT_EXPR | LangOptions::LOOP_EXPR); #[cfg(not(feature = "no_function"))] options.remove(LangOptions::ANON_FN); - let settings = ParseSettings { + let mut settings = ParseSettings { at_global_level: true, #[cfg(not(feature = "no_function"))] in_fn_scope: false, @@ -3805,10 +3843,13 @@ impl Engine { in_closure: false, is_breakable: false, allow_statements: false, + allow_unquoted_map_properties: true, level: 0, options, - pos: Position::NONE, + pos: Position::START, }; + process_settings(&mut settings); + let expr = self.parse_expr(input, state, &mut functions, settings)?; assert!(functions.is_empty()); @@ -3847,25 +3888,27 @@ impl Engine { &self, input: &mut TokenStream, state: &mut ParseState, + process_settings: impl Fn(&mut ParseSettings), ) -> ParseResult<(StmtBlockContainer, StaticVec>)> { let mut statements = StmtBlockContainer::new_const(); let mut functions = StraightHashMap::default(); + let mut settings = ParseSettings { + at_global_level: true, + #[cfg(not(feature = "no_function"))] + in_fn_scope: false, + #[cfg(not(feature = "no_function"))] + #[cfg(not(feature = "no_closure"))] + in_closure: false, + is_breakable: false, + allow_statements: true, + allow_unquoted_map_properties: true, + options: self.options, + level: 0, + pos: Position::START, + }; + process_settings(&mut settings); while !input.peek().expect(NEVER_ENDS).0.is_eof() { - let settings = ParseSettings { - at_global_level: true, - #[cfg(not(feature = "no_function"))] - in_fn_scope: false, - #[cfg(not(feature = "no_function"))] - #[cfg(not(feature = "no_closure"))] - in_closure: false, - is_breakable: false, - allow_statements: true, - options: self.options, - level: 0, - pos: Position::NONE, - }; - let stmt = self.parse_stmt(input, state, &mut functions, settings)?; if stmt.is_noop() { @@ -3912,7 +3955,7 @@ impl Engine { state: &mut ParseState, _optimization_level: OptimizationLevel, ) -> ParseResult { - let (statements, _lib) = self.parse_global_level(input, state)?; + let (statements, _lib) = self.parse_global_level(input, state, |_| {})?; #[cfg(not(feature = "no_optimize"))] return Ok(crate::optimizer::optimize_into_ast( diff --git a/src/serde/de.rs b/src/serde/de.rs index 76c31c11..42d4739f 100644 --- a/src/serde/de.rs +++ b/src/serde/de.rs @@ -8,12 +8,17 @@ use serde::{Deserialize, Deserializer}; use std::prelude::v1::*; use std::{any::type_name, fmt}; -/// Deserializer for [`Dynamic`][crate::Dynamic] which is kept as a reference. -/// -/// The reference is necessary because the deserialized type may hold references -/// (especially `&str`) to the source [`Dynamic`][crate::Dynamic]. -struct DynamicDeserializer<'a> { - value: &'a Dynamic, +/// Deserializer for [`Dynamic`][crate::Dynamic]. +pub struct DynamicDeserializer<'de>(&'de Dynamic); + +impl<'de> IntoDeserializer<'de, RhaiError> for &'de Dynamic { + type Deserializer = DynamicDeserializer<'de>; + + #[inline(always)] + #[must_use] + fn into_deserializer(self) -> Self::Deserializer { + DynamicDeserializer(self) + } } impl<'de> DynamicDeserializer<'de> { @@ -21,28 +26,28 @@ impl<'de> DynamicDeserializer<'de> { /// /// The reference is necessary because the deserialized type may hold references /// (especially `&str`) to the source [`Dynamic`][crate::Dynamic]. + #[inline(always)] #[must_use] - pub const fn from_dynamic(value: &'de Dynamic) -> Self { - Self { value } + pub const fn new(value: &'de Dynamic) -> Self { + Self(value) } /// Shortcut for a type conversion error. + #[cold] + #[inline(always)] fn type_error(&self) -> RhaiResultOf { self.type_error_str(type_name::()) } /// Shortcut for a type conversion error. + #[cold] + #[inline(never)] fn type_error_str(&self, error: &str) -> RhaiResultOf { - Err(ERR::ErrorMismatchOutputType( - error.into(), - self.value.type_name().into(), - Position::NONE, + Err( + ERR::ErrorMismatchOutputType(error.into(), self.0.type_name().into(), Position::NONE) + .into(), ) - .into()) } - fn deserialize_int>( - &mut self, - v: crate::INT, - visitor: V, - ) -> RhaiResultOf { + #[inline(always)] + fn deserialize_int>(self, v: crate::INT, visitor: V) -> RhaiResultOf { #[cfg(not(feature = "only_i32"))] return visitor.visit_i64(v); #[cfg(feature = "only_i32")] @@ -102,10 +107,12 @@ impl<'de> DynamicDeserializer<'de> { /// # } /// ``` pub fn from_dynamic<'de, T: Deserialize<'de>>(value: &'de Dynamic) -> RhaiResultOf { - T::deserialize(&mut DynamicDeserializer::from_dynamic(value)) + T::deserialize(DynamicDeserializer::new(value)) } impl Error for RhaiError { + #[cold] + #[inline(never)] fn custom(err: T) -> Self { LexError::ImproperSymbol(String::new(), err.to_string()) .into_err(Position::NONE) @@ -113,11 +120,13 @@ impl Error for RhaiError { } } -impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { +impl<'de> Deserializer<'de> for DynamicDeserializer<'de> { type Error = RhaiError; fn deserialize_any>(self, visitor: V) -> RhaiResultOf { - match self.value.0 { + match self.0 .0 { + Union::Null => unreachable!(), + Union::Unit(..) => self.deserialize_unit(visitor), Union::Bool(..) => self.deserialize_bool(visitor), Union::Str(..) => self.deserialize_str(visitor), @@ -149,7 +158,7 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { #[cfg(not(feature = "no_object"))] Union::Map(..) => self.deserialize_map(visitor), Union::FnPtr(..) => self.type_error(), - #[cfg(not(feature = "no_std"))] + #[cfg(not(feature = "no_time"))] Union::TimeStamp(..) => self.type_error(), Union::Variant(ref value, ..) if value.is::() => self.deserialize_i8(visitor), @@ -171,110 +180,110 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { } fn deserialize_bool>(self, visitor: V) -> RhaiResultOf { - visitor.visit_bool(self.value.as_bool().or_else(|_| self.type_error())?) + visitor.visit_bool(self.0.as_bool().or_else(|_| self.type_error())?) } fn deserialize_i8>(self, visitor: V) -> RhaiResultOf { - if let Ok(v) = self.value.as_int() { + if let Ok(v) = self.0.as_int() { self.deserialize_int(v, visitor) } else { - self.value + self.0 .downcast_ref::() .map_or_else(|| self.type_error(), |&x| visitor.visit_i8(x)) } } fn deserialize_i16>(self, visitor: V) -> RhaiResultOf { - if let Ok(v) = self.value.as_int() { + if let Ok(v) = self.0.as_int() { self.deserialize_int(v, visitor) } else { - self.value + self.0 .downcast_ref::() .map_or_else(|| self.type_error(), |&x| visitor.visit_i16(x)) } } fn deserialize_i32>(self, visitor: V) -> RhaiResultOf { - if let Ok(v) = self.value.as_int() { + if let Ok(v) = self.0.as_int() { self.deserialize_int(v, visitor) } else if cfg!(feature = "only_i32") { self.type_error() } else { - self.value + self.0 .downcast_ref::() .map_or_else(|| self.type_error(), |&x| visitor.visit_i32(x)) } } fn deserialize_i64>(self, visitor: V) -> RhaiResultOf { - if let Ok(v) = self.value.as_int() { + if let Ok(v) = self.0.as_int() { self.deserialize_int(v, visitor) } else if cfg!(not(feature = "only_i32")) { self.type_error() } else { - self.value + self.0 .downcast_ref::() .map_or_else(|| self.type_error(), |&x| visitor.visit_i64(x)) } } fn deserialize_i128>(self, visitor: V) -> RhaiResultOf { - if let Ok(v) = self.value.as_int() { + if let Ok(v) = self.0.as_int() { self.deserialize_int(v, visitor) } else if cfg!(not(feature = "only_i32")) { self.type_error() } else { - self.value + self.0 .downcast_ref::() .map_or_else(|| self.type_error(), |&x| visitor.visit_i128(x)) } } fn deserialize_u8>(self, visitor: V) -> RhaiResultOf { - if let Ok(v) = self.value.as_int() { + if let Ok(v) = self.0.as_int() { self.deserialize_int(v, visitor) } else { - self.value + self.0 .downcast_ref::() .map_or_else(|| self.type_error(), |&x| visitor.visit_u8(x)) } } fn deserialize_u16>(self, visitor: V) -> RhaiResultOf { - if let Ok(v) = self.value.as_int() { + if let Ok(v) = self.0.as_int() { self.deserialize_int(v, visitor) } else { - self.value + self.0 .downcast_ref::() .map_or_else(|| self.type_error(), |&x| visitor.visit_u16(x)) } } fn deserialize_u32>(self, visitor: V) -> RhaiResultOf { - if let Ok(v) = self.value.as_int() { + if let Ok(v) = self.0.as_int() { self.deserialize_int(v, visitor) } else { - self.value + self.0 .downcast_ref::() .map_or_else(|| self.type_error(), |&x| visitor.visit_u32(x)) } } fn deserialize_u64>(self, visitor: V) -> RhaiResultOf { - if let Ok(v) = self.value.as_int() { + if let Ok(v) = self.0.as_int() { self.deserialize_int(v, visitor) } else { - self.value + self.0 .downcast_ref::() .map_or_else(|| self.type_error(), |&x| visitor.visit_u64(x)) } } fn deserialize_u128>(self, visitor: V) -> RhaiResultOf { - if let Ok(v) = self.value.as_int() { + if let Ok(v) = self.0.as_int() { self.deserialize_int(v, visitor) } else { - self.value + self.0 .downcast_ref::() .map_or_else(|| self.type_error(), |&x| visitor.visit_u128(x)) } @@ -283,7 +292,7 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { fn deserialize_f32>(self, _visitor: V) -> RhaiResultOf { #[cfg(not(feature = "no_float"))] return self - .value + .0 .downcast_ref::() .map_or_else(|| self.type_error(), |&x| _visitor.visit_f32(x)); @@ -293,7 +302,7 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { use rust_decimal::prelude::ToPrimitive; return self - .value + .0 .downcast_ref::() .and_then(|&x| x.to_f32()) .map_or_else(|| self.type_error(), |v| _visitor.visit_f32(v)); @@ -307,7 +316,7 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { fn deserialize_f64>(self, _visitor: V) -> RhaiResultOf { #[cfg(not(feature = "no_float"))] return self - .value + .0 .downcast_ref::() .map_or_else(|| self.type_error(), |&x| _visitor.visit_f64(x)); @@ -317,7 +326,7 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { use rust_decimal::prelude::ToPrimitive; return self - .value + .0 .downcast_ref::() .and_then(|&x| x.to_f64()) .map_or_else(|| self.type_error(), |v| _visitor.visit_f64(v)); @@ -329,13 +338,13 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { } fn deserialize_char>(self, visitor: V) -> RhaiResultOf { - self.value + self.0 .downcast_ref::() .map_or_else(|| self.type_error(), |&x| visitor.visit_char(x)) } fn deserialize_str>(self, visitor: V) -> RhaiResultOf { - self.value.downcast_ref::().map_or_else( + self.0.downcast_ref::().map_or_else( || self.type_error(), |x| visitor.visit_borrowed_str(x.as_str()), ) @@ -348,7 +357,7 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { fn deserialize_bytes>(self, _visitor: V) -> RhaiResultOf { #[cfg(not(feature = "no_index"))] return self - .value + .0 .downcast_ref::() .map_or_else(|| self.type_error(), |x| _visitor.visit_bytes(x)); @@ -361,7 +370,7 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { } fn deserialize_option>(self, visitor: V) -> RhaiResultOf { - if self.value.is::<()>() { + if self.0.is::<()>() { visitor.visit_none() } else { visitor.visit_some(self) @@ -369,7 +378,7 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { } fn deserialize_unit>(self, visitor: V) -> RhaiResultOf { - self.value + self.0 .downcast_ref::<()>() .map_or_else(|| self.type_error(), |_| visitor.visit_unit()) } @@ -392,7 +401,7 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { fn deserialize_seq>(self, _visitor: V) -> RhaiResultOf { #[cfg(not(feature = "no_index"))] - return self.value.downcast_ref::().map_or_else( + return self.0.downcast_ref::().map_or_else( || self.type_error(), |arr| _visitor.visit_seq(IterateDynamicArray::new(arr.iter())), ); @@ -416,7 +425,7 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { fn deserialize_map>(self, _visitor: V) -> RhaiResultOf { #[cfg(not(feature = "no_object"))] - return self.value.downcast_ref::().map_or_else( + return self.0.downcast_ref::().map_or_else( || self.type_error(), |map| { _visitor.visit_map(IterateMap::new( @@ -445,11 +454,11 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { _variants: &'static [&'static str], visitor: V, ) -> RhaiResultOf { - if let Some(s) = self.value.read_lock::() { + if let Some(s) = self.0.read_lock::() { visitor.visit_enum(s.as_str().into_deserializer()) } else { #[cfg(not(feature = "no_object"))] - return self.value.downcast_ref::().map_or_else( + return self.0.downcast_ref::().map_or_else( || self.type_error(), |map| { let mut iter = map.iter(); @@ -458,7 +467,7 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { if let (Some((key, value)), None) = (first, second) { visitor.visit_enum(EnumDeserializer { tag: key, - content: DynamicDeserializer::from_dynamic(value), + content: DynamicDeserializer::new(value), }) } else { self.type_error() @@ -470,10 +479,12 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { } } + #[inline(always)] fn deserialize_identifier>(self, visitor: V) -> RhaiResultOf { self.deserialize_str(visitor) } + #[inline(always)] fn deserialize_ignored_any>(self, visitor: V) -> RhaiResultOf { self.deserialize_any(visitor) } @@ -481,13 +492,14 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { /// `SeqAccess` implementation for arrays. #[cfg(not(feature = "no_index"))] -struct IterateDynamicArray<'a, ITER: Iterator> { +struct IterateDynamicArray<'de, ITER: Iterator> { /// Iterator for a stream of [`Dynamic`][crate::Dynamic] values. iter: ITER, } #[cfg(not(feature = "no_index"))] -impl<'a, ITER: Iterator> IterateDynamicArray<'a, ITER> { +impl<'de, ITER: Iterator> IterateDynamicArray<'de, ITER> { + #[inline(always)] #[must_use] pub const fn new(iter: ITER) -> Self { Self { iter } @@ -495,8 +507,8 @@ impl<'a, ITER: Iterator> IterateDynamicArray<'a, ITER> { } #[cfg(not(feature = "no_index"))] -impl<'a: 'de, 'de, ITER: Iterator> serde::de::SeqAccess<'de> - for IterateDynamicArray<'a, ITER> +impl<'de, ITER: Iterator> serde::de::SeqAccess<'de> + for IterateDynamicArray<'de, ITER> { type Error = RhaiError; @@ -506,17 +518,15 @@ impl<'a: 'de, 'de, ITER: Iterator> serde::de::SeqAccess<'de> ) -> RhaiResultOf> { // Deserialize each item coming out of the iterator. match self.iter.next() { + Some(item) => seed.deserialize(item.into_deserializer()).map(Some), None => Ok(None), - Some(item) => seed - .deserialize(&mut DynamicDeserializer::from_dynamic(item)) - .map(Some), } } } /// `MapAccess` implementation for maps. #[cfg(not(feature = "no_object"))] -struct IterateMap<'a, K: Iterator, V: Iterator> { +struct IterateMap<'de, K: Iterator, V: Iterator> { // Iterator for a stream of [`Dynamic`][crate::Dynamic] keys. keys: K, // Iterator for a stream of [`Dynamic`][crate::Dynamic] values. @@ -524,7 +534,8 @@ struct IterateMap<'a, K: Iterator, V: Iterator, V: Iterator> IterateMap<'a, K, V> { +impl<'de, K: Iterator, V: Iterator> IterateMap<'de, K, V> { + #[inline(always)] #[must_use] pub const fn new(keys: K, values: V) -> Self { Self { keys, values } @@ -532,8 +543,8 @@ impl<'a, K: Iterator, V: Iterator> IterateMa } #[cfg(not(feature = "no_object"))] -impl<'a: 'de, 'de, K: Iterator, V: Iterator> - serde::de::MapAccess<'de> for IterateMap<'a, K, V> +impl<'de, K: Iterator, V: Iterator> serde::de::MapAccess<'de> + for IterateMap<'de, K, V> { type Error = RhaiError; @@ -542,11 +553,9 @@ impl<'a: 'de, 'de, K: Iterator, V: Iterator> seed: S, ) -> RhaiResultOf> { // Deserialize each `Identifier` key coming out of the keys iterator. - match self.keys.next() { + match self.keys.next().map(<_>::into_deserializer) { + Some(d) => seed.deserialize(d).map(Some), None => Ok(None), - Some(item) => seed - .deserialize(&mut super::str::StringSliceDeserializer::from_str(item)) - .map(Some), } } @@ -555,20 +564,18 @@ impl<'a: 'de, 'de, K: Iterator, V: Iterator> seed: S, ) -> RhaiResultOf { // Deserialize each value item coming out of the iterator. - seed.deserialize(&mut DynamicDeserializer::from_dynamic( - self.values.next().unwrap(), - )) + seed.deserialize(self.values.next().unwrap().into_deserializer()) } } #[cfg(not(feature = "no_object"))] -struct EnumDeserializer<'t, 'de: 't> { - tag: &'t str, +struct EnumDeserializer<'de> { + tag: &'de str, content: DynamicDeserializer<'de>, } #[cfg(not(feature = "no_object"))] -impl<'t, 'de> serde::de::EnumAccess<'de> for EnumDeserializer<'t, 'de> { +impl<'de> serde::de::EnumAccess<'de> for EnumDeserializer<'de> { type Error = RhaiError; type Variant = Self; @@ -582,26 +589,30 @@ impl<'t, 'de> serde::de::EnumAccess<'de> for EnumDeserializer<'t, 'de> { } #[cfg(not(feature = "no_object"))] -impl<'t, 'de> serde::de::VariantAccess<'de> for EnumDeserializer<'t, 'de> { +impl<'de> serde::de::VariantAccess<'de> for EnumDeserializer<'de> { type Error = RhaiError; - fn unit_variant(mut self) -> RhaiResultOf<()> { - Deserialize::deserialize(&mut self.content) + #[inline(always)] + fn unit_variant(self) -> RhaiResultOf<()> { + Deserialize::deserialize(self.content) } + #[inline(always)] fn newtype_variant_seed>( - mut self, + self, seed: T, ) -> RhaiResultOf { - seed.deserialize(&mut self.content) + seed.deserialize(self.content) } - fn tuple_variant>(mut self, len: usize, visitor: V) -> RhaiResultOf { + #[inline(always)] + fn tuple_variant>(self, len: usize, visitor: V) -> RhaiResultOf { self.content.deserialize_tuple(len, visitor) } + #[inline(always)] fn struct_variant>( - mut self, + self, fields: &'static [&'static str], visitor: V, ) -> RhaiResultOf { diff --git a/src/serde/deserialize.rs b/src/serde/deserialize.rs index 2f79ef6a..2534a1d5 100644 --- a/src/serde/deserialize.rs +++ b/src/serde/deserialize.rs @@ -1,31 +1,41 @@ //! Implementations of [`serde::Deserialize`]. -use crate::{Dynamic, ImmutableString, INT}; -use serde::de::{Deserialize, Deserializer, Error, Visitor}; +use crate::{Dynamic, Identifier, ImmutableString, Scope, INT}; +use serde::{ + de::{Error, SeqAccess, Visitor}, + Deserialize, Deserializer, +}; use std::fmt; #[cfg(feature = "no_std")] use std::prelude::v1::*; struct DynamicVisitor; -impl<'d> Visitor<'d> for DynamicVisitor { +impl<'de> Visitor<'de> for DynamicVisitor { type Value = Dynamic; + #[cold] + #[inline(never)] fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str("any type that can be converted into a Dynamic") } + #[inline(always)] fn visit_bool(self, v: bool) -> Result { Ok(v.into()) } + #[inline(always)] fn visit_i8(self, v: i8) -> Result { Ok(INT::from(v).into()) } + #[inline(always)] fn visit_i16(self, v: i16) -> Result { Ok(INT::from(v).into()) } + #[inline(always)] fn visit_i32(self, v: i32) -> Result { Ok(INT::from(v).into()) } + #[inline] fn visit_i64(self, v: i64) -> Result { #[cfg(not(feature = "only_i32"))] { @@ -38,12 +48,15 @@ impl<'d> Visitor<'d> for DynamicVisitor { self.visit_i32(v as i32) } } + #[inline(always)] fn visit_u8(self, v: u8) -> Result { Ok(INT::from(v).into()) } + #[inline(always)] fn visit_u16(self, v: u16) -> Result { Ok(INT::from(v).into()) } + #[inline] fn visit_u32(self, v: u32) -> Result { #[cfg(not(feature = "only_i32"))] { @@ -56,6 +69,7 @@ impl<'d> Visitor<'d> for DynamicVisitor { self.visit_i32(v as i32) } } + #[inline] fn visit_u64(self, v: u64) -> Result { #[cfg(not(feature = "only_i32"))] if v > i64::MAX as u64 { @@ -72,6 +86,7 @@ impl<'d> Visitor<'d> for DynamicVisitor { } #[cfg(not(feature = "no_float"))] + #[inline(always)] fn visit_f32(self, v: f32) -> Result { #[cfg(not(feature = "f32_float"))] return self.visit_f64(v as f64); @@ -79,6 +94,7 @@ impl<'d> Visitor<'d> for DynamicVisitor { return Ok(v.into()); } #[cfg(not(feature = "no_float"))] + #[inline(always)] fn visit_f64(self, v: f64) -> Result { #[cfg(not(feature = "f32_float"))] return Ok(v.into()); @@ -88,6 +104,7 @@ impl<'d> Visitor<'d> for DynamicVisitor { #[cfg(feature = "no_float")] #[cfg(feature = "decimal")] + #[inline] fn visit_f32(self, v: f32) -> Result { use rust_decimal::Decimal; use std::convert::TryFrom; @@ -98,6 +115,7 @@ impl<'d> Visitor<'d> for DynamicVisitor { } #[cfg(feature = "no_float")] #[cfg(feature = "decimal")] + #[inline] fn visit_f64(self, v: f64) -> Result { use rust_decimal::Decimal; use std::convert::TryFrom; @@ -107,29 +125,35 @@ impl<'d> Visitor<'d> for DynamicVisitor { .map_err(Error::custom) } + #[inline(always)] fn visit_char(self, v: char) -> Result { self.visit_string(v.to_string()) } + #[inline(always)] fn visit_str(self, v: &str) -> Result { Ok(v.into()) } + #[inline(always)] fn visit_borrowed_str(self, v: &str) -> Result { self.visit_str(v) } + #[inline(always)] fn visit_string(self, v: String) -> Result { Ok(v.into()) } + #[inline(always)] fn visit_unit(self) -> Result { Ok(Dynamic::UNIT) } - fn visit_newtype_struct>(self, de: D) -> Result { + #[inline(always)] + fn visit_newtype_struct>(self, de: D) -> Result { Deserialize::deserialize(de) } #[cfg(not(feature = "no_index"))] - fn visit_seq>(self, mut seq: A) -> Result { + fn visit_seq>(self, mut seq: A) -> Result { let mut arr = crate::Array::new(); while let Some(v) = seq.next_element()? { @@ -140,7 +164,7 @@ impl<'d> Visitor<'d> for DynamicVisitor { } #[cfg(not(feature = "no_object"))] - fn visit_map>(self, mut map: M) -> Result { + fn visit_map>(self, mut map: M) -> Result { let mut m = crate::Map::new(); while let Some((k, v)) = map.next_entry()? { @@ -151,15 +175,68 @@ impl<'d> Visitor<'d> for DynamicVisitor { } } -impl<'d> Deserialize<'d> for Dynamic { - fn deserialize>(de: D) -> Result { - de.deserialize_any(DynamicVisitor) +impl<'de> Deserialize<'de> for Dynamic { + #[inline(always)] + fn deserialize>(deserializer: D) -> Result { + deserializer.deserialize_any(DynamicVisitor) } } -impl<'d> Deserialize<'d> for ImmutableString { - fn deserialize>(de: D) -> Result { - let s: String = Deserialize::deserialize(de)?; +impl<'de> Deserialize<'de> for ImmutableString { + #[inline(always)] + fn deserialize>(deserializer: D) -> Result { + let s: String = Deserialize::deserialize(deserializer)?; Ok(s.into()) } } + +impl<'de> Deserialize<'de> for Scope<'de> { + #[inline(always)] + fn deserialize>(deserializer: D) -> Result { + #[derive(Debug, Clone, Hash, Deserialize)] + struct ScopeEntry { + pub name: Identifier, + pub value: Dynamic, + #[serde(default)] + pub is_constant: bool, + } + + struct VecVisitor; + + impl<'de> Visitor<'de> for VecVisitor { + type Value = Scope<'static>; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a sequence") + } + + #[inline] + fn visit_seq(self, mut access: A) -> Result + where + A: SeqAccess<'de>, + { + let mut scope = match access.size_hint() { + Some(size) => Scope::with_capacity(size), + None => Scope::new(), + }; + + while let Some(ScopeEntry { + name, + value, + is_constant, + }) = access.next_element()? + { + if is_constant { + scope.push_constant_dynamic(name, value); + } else { + scope.push_dynamic(name, value); + } + } + + Ok(scope) + } + } + + deserializer.deserialize_seq(VecVisitor) + } +} diff --git a/src/serde/mod.rs b/src/serde/mod.rs index 42b57e6e..4c78a939 100644 --- a/src/serde/mod.rs +++ b/src/serde/mod.rs @@ -6,7 +6,6 @@ mod deserialize; mod metadata; mod ser; mod serialize; -mod str; -pub use de::from_dynamic; -pub use ser::to_dynamic; +pub use de::{from_dynamic, DynamicDeserializer}; +pub use ser::{to_dynamic, DynamicSerializer}; diff --git a/src/serde/ser.rs b/src/serde/ser.rs index 1cdc8e28..4104fd19 100644 --- a/src/serde/ser.rs +++ b/src/serde/ser.rs @@ -1,6 +1,6 @@ //! Implement serialization support of [`Dynamic`][crate::Dynamic] for [`serde`]. -use crate::{Dynamic, Position, RhaiError, RhaiResult, RhaiResultOf, ERR}; +use crate::{Dynamic, Identifier, Position, RhaiError, RhaiResult, RhaiResultOf, ERR}; use serde::ser::{ Error, SerializeMap, SerializeSeq, SerializeStruct, SerializeTuple, SerializeTupleStruct, }; @@ -9,10 +9,10 @@ use std::fmt; #[cfg(feature = "no_std")] use std::prelude::v1::*; -/// Serializer for [`Dynamic`][crate::Dynamic] which is kept as a reference. -struct DynamicSerializer { +/// Serializer for [`Dynamic`][crate::Dynamic]. +pub struct DynamicSerializer { /// Buffer to hold a temporary key. - _key: Dynamic, + _key: Identifier, /// Buffer to hold a temporary value. _value: Dynamic, } @@ -20,10 +20,10 @@ struct DynamicSerializer { impl DynamicSerializer { /// Create a [`DynamicSerializer`] from a [`Dynamic`][crate::Dynamic] value. #[must_use] - pub const fn new(_value: Dynamic) -> Self { + pub const fn new(value: Dynamic) -> Self { Self { - _key: Dynamic::UNIT, - _value, + _key: Identifier::new_const(), + _value: value, } } } @@ -105,10 +105,12 @@ impl Serializer for &mut DynamicSerializer { #[cfg(feature = "no_object")] type SerializeStructVariant = serde::ser::Impossible; + #[inline(always)] fn serialize_bool(self, v: bool) -> RhaiResultOf { Ok(v.into()) } + #[inline(always)] fn serialize_i8(self, v: i8) -> RhaiResultOf { #[cfg(not(feature = "only_i32"))] return self.serialize_i64(i64::from(v)); @@ -116,6 +118,7 @@ impl Serializer for &mut DynamicSerializer { return self.serialize_i32(i32::from(v)); } + #[inline(always)] fn serialize_i16(self, v: i16) -> RhaiResultOf { #[cfg(not(feature = "only_i32"))] return self.serialize_i64(i64::from(v)); @@ -123,6 +126,7 @@ impl Serializer for &mut DynamicSerializer { return self.serialize_i32(i32::from(v)); } + #[inline(always)] fn serialize_i32(self, v: i32) -> RhaiResultOf { #[cfg(not(feature = "only_i32"))] return self.serialize_i64(i64::from(v)); @@ -130,6 +134,7 @@ impl Serializer for &mut DynamicSerializer { return Ok(v.into()); } + #[inline] fn serialize_i64(self, v: i64) -> RhaiResultOf { #[cfg(not(feature = "only_i32"))] { @@ -143,6 +148,7 @@ impl Serializer for &mut DynamicSerializer { } } + #[inline] fn serialize_i128(self, v: i128) -> RhaiResultOf { #[cfg(not(feature = "only_i32"))] if v > i64::MAX as i128 { @@ -158,6 +164,7 @@ impl Serializer for &mut DynamicSerializer { } } + #[inline(always)] fn serialize_u8(self, v: u8) -> RhaiResultOf { #[cfg(not(feature = "only_i32"))] return self.serialize_i64(i64::from(v)); @@ -165,6 +172,7 @@ impl Serializer for &mut DynamicSerializer { return self.serialize_i32(i32::from(v)); } + #[inline(always)] fn serialize_u16(self, v: u16) -> RhaiResultOf { #[cfg(not(feature = "only_i32"))] return self.serialize_i64(i64::from(v)); @@ -172,6 +180,7 @@ impl Serializer for &mut DynamicSerializer { return self.serialize_i32(i32::from(v)); } + #[inline] fn serialize_u32(self, v: u32) -> RhaiResultOf { #[cfg(not(feature = "only_i32"))] { @@ -185,6 +194,7 @@ impl Serializer for &mut DynamicSerializer { } } + #[inline] fn serialize_u64(self, v: u64) -> RhaiResultOf { #[cfg(not(feature = "only_i32"))] if v > i64::MAX as u64 { @@ -200,6 +210,7 @@ impl Serializer for &mut DynamicSerializer { } } + #[inline] fn serialize_u128(self, v: u128) -> RhaiResultOf { #[cfg(not(feature = "only_i32"))] if v > i64::MAX as u128 { @@ -215,6 +226,7 @@ impl Serializer for &mut DynamicSerializer { } } + #[inline] fn serialize_f32(self, v: f32) -> RhaiResultOf { #[cfg(any(not(feature = "no_float"), not(feature = "decimal")))] return Ok(Dynamic::from(v)); @@ -231,6 +243,7 @@ impl Serializer for &mut DynamicSerializer { } } + #[inline] fn serialize_f64(self, v: f64) -> RhaiResultOf { #[cfg(any(not(feature = "no_float"), not(feature = "decimal")))] return Ok(Dynamic::from(v)); @@ -247,14 +260,17 @@ impl Serializer for &mut DynamicSerializer { } } + #[inline(always)] fn serialize_char(self, v: char) -> RhaiResultOf { Ok(v.into()) } + #[inline(always)] fn serialize_str(self, v: &str) -> RhaiResultOf { Ok(v.into()) } + #[inline] fn serialize_bytes(self, _v: &[u8]) -> RhaiResultOf { #[cfg(not(feature = "no_index"))] return Ok(Dynamic::from_blob(_v.to_vec())); @@ -262,28 +278,33 @@ impl Serializer for &mut DynamicSerializer { #[cfg(feature = "no_index")] return Err(ERR::ErrorMismatchDataType( "".into(), - "BLOB's are not supported with 'no_index'".into(), + "BLOB's are not supported under 'no_index'".into(), Position::NONE, ) .into()); } + #[inline(always)] fn serialize_none(self) -> RhaiResultOf { Ok(Dynamic::UNIT) } + #[inline(always)] fn serialize_some(self, value: &T) -> RhaiResultOf { value.serialize(&mut *self) } + #[inline(always)] fn serialize_unit(self) -> RhaiResultOf { Ok(Dynamic::UNIT) } + #[inline(always)] fn serialize_unit_struct(self, _name: &'static str) -> RhaiResultOf { self.serialize_unit() } + #[inline(always)] fn serialize_unit_variant( self, _name: &'static str, @@ -293,6 +314,7 @@ impl Serializer for &mut DynamicSerializer { self.serialize_str(variant) } + #[inline(always)] fn serialize_newtype_struct( self, _name: &'static str, @@ -301,6 +323,7 @@ impl Serializer for &mut DynamicSerializer { value.serialize(&mut *self) } + #[inline] fn serialize_newtype_variant( self, _name: &'static str, @@ -316,28 +339,31 @@ impl Serializer for &mut DynamicSerializer { #[cfg(feature = "no_object")] return Err(ERR::ErrorMismatchDataType( "".into(), - "object maps are not supported with 'no_object'".into(), + "object maps are not supported under 'no_object'".into(), Position::NONE, ) .into()); } + #[inline] fn serialize_seq(self, _len: Option) -> RhaiResultOf { #[cfg(not(feature = "no_index"))] return Ok(DynamicSerializer::new(crate::Array::new().into())); #[cfg(feature = "no_index")] return Err(ERR::ErrorMismatchDataType( "".into(), - "arrays are not supported with 'no_index'".into(), + "arrays are not supported under 'no_index'".into(), Position::NONE, ) .into()); } + #[inline(always)] fn serialize_tuple(self, len: usize) -> RhaiResultOf { self.serialize_seq(Some(len)) } + #[inline(always)] fn serialize_tuple_struct( self, _name: &'static str, @@ -346,6 +372,7 @@ impl Serializer for &mut DynamicSerializer { self.serialize_seq(Some(len)) } + #[inline] fn serialize_tuple_variant( self, _name: &'static str, @@ -362,24 +389,26 @@ impl Serializer for &mut DynamicSerializer { #[cfg(any(feature = "no_object", feature = "no_index"))] return Err(ERR::ErrorMismatchDataType( "".into(), - "tuples are not supported with 'no_index' or 'no_object'".into(), + "tuples are not supported under 'no_index' or 'no_object'".into(), Position::NONE, ) .into()); } + #[inline] fn serialize_map(self, _len: Option) -> RhaiResultOf { #[cfg(not(feature = "no_object"))] return Ok(DynamicSerializer::new(crate::Map::new().into())); #[cfg(feature = "no_object")] return Err(ERR::ErrorMismatchDataType( "".into(), - "object maps are not supported with 'no_object'".into(), + "object maps are not supported under 'no_object'".into(), Position::NONE, ) .into()); } + #[inline(always)] fn serialize_struct( self, _name: &'static str, @@ -388,6 +417,7 @@ impl Serializer for &mut DynamicSerializer { self.serialize_map(Some(len)) } + #[inline] fn serialize_struct_variant( self, _name: &'static str, @@ -403,7 +433,7 @@ impl Serializer for &mut DynamicSerializer { #[cfg(feature = "no_object")] return Err(ERR::ErrorMismatchDataType( "".into(), - "object maps are not supported with 'no_object'".into(), + "object maps are not supported under 'no_object'".into(), Position::NONE, ) .into()); @@ -425,20 +455,21 @@ impl SerializeSeq for DynamicSerializer { #[cfg(feature = "no_index")] return Err(ERR::ErrorMismatchDataType( "".into(), - "arrays are not supported with 'no_index'".into(), + "arrays are not supported under 'no_index'".into(), Position::NONE, ) .into()); } // Close the sequence. + #[inline] fn end(self) -> RhaiResultOf { #[cfg(not(feature = "no_index"))] return Ok(self._value); #[cfg(feature = "no_index")] return Err(ERR::ErrorMismatchDataType( "".into(), - "arrays are not supported with 'no_index'".into(), + "arrays are not supported under 'no_index'".into(), Position::NONE, ) .into()); @@ -460,19 +491,20 @@ impl SerializeTuple for DynamicSerializer { #[cfg(feature = "no_index")] return Err(ERR::ErrorMismatchDataType( "".into(), - "tuples are not supported with 'no_index'".into(), + "tuples are not supported under 'no_index'".into(), Position::NONE, ) .into()); } + #[inline] fn end(self) -> RhaiResultOf { #[cfg(not(feature = "no_index"))] return Ok(self._value); #[cfg(feature = "no_index")] return Err(ERR::ErrorMismatchDataType( "".into(), - "tuples are not supported with 'no_index'".into(), + "tuples are not supported under 'no_index'".into(), Position::NONE, ) .into()); @@ -494,19 +526,20 @@ impl SerializeTupleStruct for DynamicSerializer { #[cfg(feature = "no_index")] return Err(ERR::ErrorMismatchDataType( "".into(), - "tuples are not supported with 'no_index'".into(), + "tuples are not supported under 'no_index'".into(), Position::NONE, ) .into()); } + #[inline] fn end(self) -> RhaiResultOf { #[cfg(not(feature = "no_index"))] return Ok(self._value); #[cfg(feature = "no_index")] return Err(ERR::ErrorMismatchDataType( "".into(), - "tuples are not supported with 'no_index'".into(), + "tuples are not supported under 'no_index'".into(), Position::NONE, ) .into()); @@ -520,13 +553,19 @@ impl SerializeMap for DynamicSerializer { fn serialize_key(&mut self, _key: &T) -> RhaiResultOf<()> { #[cfg(not(feature = "no_object"))] { - self._key = _key.serialize(&mut *self)?; + let key = _key.serialize(&mut *self)?; + self._key = key + .into_immutable_string() + .map_err(|typ| { + ERR::ErrorMismatchDataType("string".into(), typ.into(), Position::NONE) + })? + .into(); Ok(()) } #[cfg(feature = "no_object")] return Err(ERR::ErrorMismatchDataType( "".into(), - "object maps are not supported with 'no_object'".into(), + "object maps are not supported under 'no_object'".into(), Position::NONE, ) .into()); @@ -535,20 +574,16 @@ impl SerializeMap for DynamicSerializer { fn serialize_value(&mut self, _value: &T) -> RhaiResultOf<()> { #[cfg(not(feature = "no_object"))] { - let key = std::mem::take(&mut self._key) - .into_immutable_string() - .map_err(|typ| { - ERR::ErrorMismatchDataType("string".into(), typ.into(), Position::NONE) - })?; + let key = std::mem::take(&mut self._key); let value = _value.serialize(&mut *self)?; let map = self._value.downcast_mut::().unwrap(); - map.insert(key.into(), value); + map.insert(key, value); Ok(()) } #[cfg(feature = "no_object")] return Err(ERR::ErrorMismatchDataType( "".into(), - "object maps are not supported with 'no_object'".into(), + "object maps are not supported under 'no_object'".into(), Position::NONE, ) .into()); @@ -573,19 +608,20 @@ impl SerializeMap for DynamicSerializer { #[cfg(feature = "no_object")] return Err(ERR::ErrorMismatchDataType( "".into(), - "object maps are not supported with 'no_object'".into(), + "object maps are not supported under 'no_object'".into(), Position::NONE, ) .into()); } + #[inline] fn end(self) -> RhaiResultOf { #[cfg(not(feature = "no_object"))] return Ok(self._value); #[cfg(feature = "no_object")] return Err(ERR::ErrorMismatchDataType( "".into(), - "object maps are not supported with 'no_object'".into(), + "object maps are not supported under 'no_object'".into(), Position::NONE, ) .into()); @@ -611,19 +647,20 @@ impl SerializeStruct for DynamicSerializer { #[cfg(feature = "no_object")] return Err(ERR::ErrorMismatchDataType( "".into(), - "object maps are not supported with 'no_object'".into(), + "object maps are not supported under 'no_object'".into(), Position::NONE, ) .into()); } + #[inline] fn end(self) -> RhaiResultOf { #[cfg(not(feature = "no_object"))] return Ok(self._value); #[cfg(feature = "no_object")] return Err(ERR::ErrorMismatchDataType( "".into(), - "object maps are not supported with 'no_object'".into(), + "object maps are not supported under 'no_object'".into(), Position::NONE, ) .into()); @@ -632,7 +669,7 @@ impl SerializeStruct for DynamicSerializer { #[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_index"))] -struct TupleVariantSerializer { +pub struct TupleVariantSerializer { variant: &'static str, array: crate::Array, } @@ -649,13 +686,14 @@ impl serde::ser::SerializeTupleVariant for TupleVariantSerializer { Ok(()) } + #[inline] fn end(self) -> RhaiResultOf { make_variant(self.variant, self.array.into()) } } #[cfg(not(feature = "no_object"))] -struct StructVariantSerializer { +pub struct StructVariantSerializer { variant: &'static str, map: crate::Map, } @@ -665,6 +703,7 @@ impl serde::ser::SerializeStructVariant for StructVariantSerializer { type Ok = Dynamic; type Error = RhaiError; + #[inline] fn serialize_field( &mut self, key: &'static str, @@ -675,12 +714,14 @@ impl serde::ser::SerializeStructVariant for StructVariantSerializer { Ok(()) } + #[inline] fn end(self) -> RhaiResultOf { make_variant(self.variant, self.map.into()) } } #[cfg(not(feature = "no_object"))] +#[inline] fn make_variant(variant: &'static str, value: Dynamic) -> RhaiResult { let mut map = crate::Map::new(); map.insert(variant.into(), value); diff --git a/src/serde/serialize.rs b/src/serde/serialize.rs index 579f0d1e..0e9c481f 100644 --- a/src/serde/serialize.rs +++ b/src/serde/serialize.rs @@ -1,20 +1,22 @@ //! Implementations of [`serde::Serialize`]. use crate::types::dynamic::Union; -use crate::{Dynamic, ImmutableString}; -use serde::ser::{Serialize, Serializer}; +use crate::{Dynamic, ImmutableString, Scope}; +use serde::{ser::SerializeSeq, Serialize, Serializer}; #[cfg(feature = "no_std")] use std::prelude::v1::*; #[cfg(not(feature = "no_object"))] use serde::ser::SerializeMap; -#[cfg(not(feature = "no_std"))] +#[cfg(not(feature = "no_time"))] use crate::types::dynamic::Variant; impl Serialize for Dynamic { fn serialize(&self, ser: S) -> Result { match self.0 { + Union::Null => unreachable!(), + Union::Unit(..) => ser.serialize_unit(), Union::Bool(x, ..) => ser.serialize_bool(x), Union::Str(ref s, ..) => ser.serialize_str(s.as_str()), @@ -37,10 +39,9 @@ impl Serialize for Dynamic { Union::Decimal(ref x, ..) => { use rust_decimal::prelude::ToPrimitive; - if let Some(v) = x.to_f64() { - ser.serialize_f64(v) - } else { - ser.serialize_str(&x.to_string()) + match x.to_f64() { + Some(v) => ser.serialize_f64(v), + None => ser.serialize_str(&x.to_string()), } } #[cfg(feature = "decimal")] @@ -48,10 +49,9 @@ impl Serialize for Dynamic { Union::Decimal(ref x, ..) => { use rust_decimal::prelude::ToPrimitive; - if let Some(v) = x.to_f32() { - ser.serialize_f32(v) - } else { - ser.serialize_str(&x.to_string()) + match x.to_f32() { + Some(v) => ser.serialize_f32(v), + _ => ser.serialize_str(&x.to_string()), } } @@ -67,7 +67,7 @@ impl Serialize for Dynamic { map.end() } Union::FnPtr(ref f, ..) => ser.serialize_str(f.fn_name()), - #[cfg(not(feature = "no_std"))] + #[cfg(not(feature = "no_time"))] Union::TimeStamp(ref x, ..) => ser.serialize_str(x.as_ref().type_name()), Union::Variant(ref v, ..) => ser.serialize_str((***v).type_name()), @@ -83,7 +83,38 @@ impl Serialize for Dynamic { } impl Serialize for ImmutableString { + #[inline(always)] fn serialize(&self, ser: S) -> Result { ser.serialize_str(self.as_str()) } } + +impl Serialize for Scope<'_> { + #[inline(always)] + fn serialize(&self, ser: S) -> Result { + #[derive(Debug, Clone, Hash, Serialize)] + struct ScopeEntry<'a> { + pub name: &'a str, + pub value: &'a Dynamic, + #[serde(default, skip_serializing_if = "is_false")] + pub is_constant: bool, + } + + fn is_false(value: &bool) -> bool { + !value + } + + let mut ser = ser.serialize_seq(Some(self.len()))?; + + for (name, is_constant, value) in self.iter_raw() { + let entry = ScopeEntry { + name, + value, + is_constant, + }; + ser.serialize_element(&entry)?; + } + + ser.end() + } +} diff --git a/src/serde/str.rs b/src/serde/str.rs deleted file mode 100644 index df747a42..00000000 --- a/src/serde/str.rs +++ /dev/null @@ -1,143 +0,0 @@ -//! Implement deserialization support of [`ImmutableString`][crate::ImmutableString] for [`serde`]. - -use crate::{Position, RhaiError, RhaiResultOf, ERR}; -use serde::de::{Deserializer, Visitor}; -use std::any::type_name; -#[cfg(feature = "no_std")] -use std::prelude::v1::*; - -/// Deserializer for `ImmutableString`. -pub struct StringSliceDeserializer<'a> { - value: &'a str, -} - -impl<'a> StringSliceDeserializer<'a> { - /// Create an `ImmutableStringDeserializer` from an `&str` reference. - #[must_use] - pub const fn from_str(value: &'a str) -> Self { - Self { value } - } - /// Shortcut for a type conversion error. - fn type_error(&self) -> RhaiResultOf { - Err( - ERR::ErrorMismatchOutputType(type_name::().into(), "string".into(), Position::NONE) - .into(), - ) - } -} - -impl<'de> Deserializer<'de> for &mut StringSliceDeserializer<'de> { - type Error = RhaiError; - - fn deserialize_any>(self, v: V) -> RhaiResultOf { - self.deserialize_str(v) - } - fn deserialize_bool>(self, _: V) -> RhaiResultOf { - self.type_error() - } - fn deserialize_i8>(self, _: V) -> RhaiResultOf { - self.type_error() - } - fn deserialize_i16>(self, _: V) -> RhaiResultOf { - self.type_error() - } - fn deserialize_i32>(self, _: V) -> RhaiResultOf { - self.type_error() - } - fn deserialize_i64>(self, _: V) -> RhaiResultOf { - self.type_error() - } - fn deserialize_u8>(self, _: V) -> RhaiResultOf { - self.type_error() - } - fn deserialize_u16>(self, _: V) -> RhaiResultOf { - self.type_error() - } - fn deserialize_u32>(self, _: V) -> RhaiResultOf { - self.type_error() - } - fn deserialize_u64>(self, _: V) -> RhaiResultOf { - self.type_error() - } - fn deserialize_f32>(self, _: V) -> RhaiResultOf { - self.type_error() - } - fn deserialize_f64>(self, _: V) -> RhaiResultOf { - self.type_error() - } - fn deserialize_char>(self, _: V) -> RhaiResultOf { - self.type_error() - } - fn deserialize_str>(self, v: V) -> RhaiResultOf { - // Only allow deserialization into a string. - v.visit_borrowed_str(self.value) - } - fn deserialize_string>(self, visitor: V) -> RhaiResultOf { - self.deserialize_str(visitor) - } - fn deserialize_bytes>(self, _: V) -> RhaiResultOf { - self.type_error() - } - fn deserialize_byte_buf>(self, _: V) -> RhaiResultOf { - self.type_error() - } - fn deserialize_option>(self, _: V) -> RhaiResultOf { - self.type_error() - } - fn deserialize_unit>(self, _: V) -> RhaiResultOf { - self.type_error() - } - fn deserialize_unit_struct>( - self, - _name: &'static str, - v: V, - ) -> RhaiResultOf { - self.deserialize_unit(v) - } - fn deserialize_newtype_struct>( - self, - _name: &'static str, - v: V, - ) -> RhaiResultOf { - v.visit_newtype_struct(self) - } - fn deserialize_seq>(self, _: V) -> RhaiResultOf { - self.type_error() - } - fn deserialize_tuple>(self, _len: usize, v: V) -> RhaiResultOf { - self.deserialize_seq(v) - } - fn deserialize_tuple_struct>( - self, - _name: &'static str, - _len: usize, - v: V, - ) -> RhaiResultOf { - self.deserialize_seq(v) - } - fn deserialize_map>(self, _: V) -> RhaiResultOf { - self.type_error() - } - fn deserialize_struct>( - self, - _name: &'static str, - _fields: &'static [&'static str], - v: V, - ) -> RhaiResultOf { - self.deserialize_map(v) - } - fn deserialize_enum>( - self, - _name: &'static str, - _variants: &'static [&'static str], - _: V, - ) -> RhaiResultOf { - self.type_error() - } - fn deserialize_identifier>(self, v: V) -> RhaiResultOf { - self.deserialize_str(v) - } - fn deserialize_ignored_any>(self, v: V) -> RhaiResultOf { - self.deserialize_any(v) - } -} diff --git a/src/tests.rs b/src/tests.rs index 1d897871..6e36c977 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -22,15 +22,7 @@ fn check_struct_sizes() { ); assert_eq!( size_of::(), - if IS_32_BIT { - if cfg!(feature = "decimal") { - 24 - } else { - 16 - } - } else { - 32 - } + if IS_32_BIT { 8 } else { 16 } ); assert_eq!(size_of::(), if PACKED { 12 } else { 16 }); assert_eq!(size_of::>(), if PACKED { 12 } else { 16 }); @@ -43,19 +35,19 @@ fn check_struct_sizes() { #[cfg(target_pointer_width = "64")] { assert_eq!(size_of::(), 536); - assert_eq!(size_of::(), 80); + assert_eq!(size_of::(), 64); assert_eq!(size_of::(), 56); assert_eq!( size_of::(), if cfg!(feature = "no_position") { 8 } else { 16 } ); - assert_eq!(size_of::(), 72); + assert_eq!(size_of::(), 64); assert_eq!( size_of::(), if cfg!(feature = "no_position") { - 72 + 64 } else { - 80 + 72 } ); } diff --git a/src/tokenizer.rs b/src/tokenizer.rs index 71305dec..7dea89d9 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -32,7 +32,7 @@ pub struct TokenizerControlBlock { impl TokenizerControlBlock { /// Create a new `TokenizerControlBlock`. - #[inline(always)] + #[inline] #[must_use] pub const fn new() -> Self { Self { @@ -97,7 +97,7 @@ impl Position { /// # Panics /// /// Panics if `line` is zero. - #[inline(always)] + #[inline] #[must_use] pub const fn new(line: u16, position: u16) -> Self { assert!(line != 0, "line cannot be zero"); @@ -220,6 +220,7 @@ impl Position { impl Default for Position { #[inline(always)] + #[must_use] fn default() -> Self { Self::START } @@ -241,6 +242,8 @@ impl fmt::Display for Position { } impl fmt::Debug for Position { + #[cold] + #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if self.is_none() { f.write_str("none") @@ -297,6 +300,8 @@ pub struct Span { } impl Default for Span { + #[inline(always)] + #[must_use] fn default() -> Self { Self::NONE } @@ -313,7 +318,7 @@ impl Span { Self { start, end } } /// Is this [`Span`] non-existent? - #[inline(always)] + #[inline] #[must_use] pub const fn is_none(&self) -> bool { self.start.is_none() && self.end.is_none() @@ -333,7 +338,6 @@ impl Span { } impl fmt::Display for Span { - #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let _f = f; @@ -360,6 +364,8 @@ impl fmt::Display for Span { } impl fmt::Debug for Span { + #[cold] + #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(self, f) } @@ -381,15 +387,15 @@ pub enum Token { /// /// Requires the `decimal` feature. #[cfg(feature = "decimal")] - DecimalConstant(rust_decimal::Decimal), + DecimalConstant(Box), /// An identifier. - Identifier(Identifier), + Identifier(Box), /// A character constant. CharConstant(char), /// A string constant. - StringConstant(SmartString), + StringConstant(Box), /// An interpolated string. - InterpolatedString(SmartString), + InterpolatedString(Box), /// `{` LeftBrace, /// `}` @@ -570,14 +576,14 @@ pub enum Token { /// A lexer error. LexError(Box), /// A comment block. - Comment(SmartString), + Comment(Box), /// A reserved symbol. - Reserved(SmartString), + Reserved(Box), /// A custom keyword. /// /// Not available under `no_custom_syntax`. #[cfg(not(feature = "no_custom_syntax"))] - Custom(SmartString), + Custom(Box), /// End of the input stream. EOF, } @@ -699,7 +705,7 @@ impl Token { FloatConstant(f) => f.to_string().into(), #[cfg(feature = "decimal")] DecimalConstant(d) => d.to_string().into(), - StringConstant(..) => "string".into(), + StringConstant(s) => format!("\"{s}\"").into(), InterpolatedString(..) => "string".into(), CharConstant(c) => c.to_string().into(), Identifier(s) => s.to_string().into(), @@ -793,9 +799,9 @@ impl Token { }) } - /// Reverse lookup a token from a piece of syntax. + /// Reverse lookup a symbol token from a piece of syntax. #[must_use] - pub fn lookup_from_syntax(syntax: &str) -> Option { + pub fn lookup_symbol_from_syntax(syntax: &str) -> Option { use Token::*; Some(match syntax { @@ -873,19 +879,11 @@ impl Token { "**" => PowerOf, "**=" => PowerOfAssign, - #[cfg(feature = "no_object")] - "?." => Reserved(syntax.into()), - #[cfg(feature = "no_index")] - "?[" => Reserved(syntax.into()), - #[cfg(not(feature = "no_function"))] "fn" => Fn, #[cfg(not(feature = "no_function"))] "private" => Private, - #[cfg(feature = "no_function")] - "fn" | "private" => Reserved(syntax.into()), - #[cfg(not(feature = "no_module"))] "import" => Import, #[cfg(not(feature = "no_module"))] @@ -893,29 +891,43 @@ impl Token { #[cfg(not(feature = "no_module"))] "as" => As, + _ => return None, + }) + } + + /// Is a piece of syntax a reserved keyword? + #[must_use] + pub fn is_reserved_keyword(syntax: &str) -> bool { + match syntax { + #[cfg(feature = "no_object")] + "?." => true, + #[cfg(feature = "no_index")] + "?[" => true, + #[cfg(feature = "no_function")] + "fn" | "private" => true, #[cfg(feature = "no_module")] - "import" | "export" | "as" => Reserved(syntax.into()), + "import" | "export" | "as" => true, // List of reserved operators "===" | "!==" | "->" | "<-" | "?" | ":=" | ":;" | "~" | "!." | "::<" | "(*" | "*)" - | "#" | "#!" | "@" | "$" | "++" | "--" | "..." | "<|" | "|>" => Reserved(syntax.into()), + | "#" | "#!" | "@" | "$" | "++" | "--" | "..." | "<|" | "|>" => true, // List of reserved keywords "public" | "protected" | "super" | "new" | "use" | "module" | "package" | "var" | "static" | "shared" | "with" | "is" | "goto" | "exit" | "match" | "case" | "default" | "void" | "null" | "nil" | "spawn" | "thread" | "go" | "sync" - | "async" | "await" | "yield" => Reserved(syntax.into()), + | "async" | "await" | "yield" => true, KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR | KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_THIS | KEYWORD_IS_DEF_VAR => { - Reserved(syntax.into()) + true } #[cfg(not(feature = "no_function"))] - crate::engine::KEYWORD_IS_DEF_FN => Reserved(syntax.into()), + crate::engine::KEYWORD_IS_DEF_FN => true, - _ => return None, - }) + _ => false, + } } /// Is this token [`EOF`][Token::EOF]? @@ -1097,8 +1109,8 @@ impl Token { pub(crate) fn into_function_name_for_override(self) -> Result { match self { #[cfg(not(feature = "no_custom_syntax"))] - Self::Custom(s) if is_valid_function_name(&s) => Ok(s), - Self::Identifier(s) if is_valid_function_name(&s) => Ok(s), + Self::Custom(s) if is_valid_function_name(&s) => Ok(*s), + Self::Identifier(s) if is_valid_function_name(&s) => Ok(*s), _ => Err(self), } } @@ -1510,7 +1522,7 @@ fn get_next_token_inner( let return_comment = return_comment || is_doc_comment(comment.as_ref().expect("`Some`")); if return_comment { - return Some((Token::Comment(comment.expect("`Some`")), start_pos)); + return Some((Token::Comment(comment.expect("`Some`").into()), start_pos)); } if state.comment_level > 0 { // Reached EOF without ending comment block @@ -1524,9 +1536,9 @@ fn get_next_token_inner( |(err, err_pos)| Some((Token::LexError(err.into()), err_pos)), |(result, interpolated, start_pos)| { if interpolated { - Some((Token::InterpolatedString(result), start_pos)) + Some((Token::InterpolatedString(result.into()), start_pos)) } else { - Some((Token::StringConstant(result), start_pos)) + Some((Token::StringConstant(result.into()), start_pos)) } }, ); @@ -1676,13 +1688,16 @@ fn get_next_token_inner( // Then try decimal #[cfg(feature = "decimal")] let num = num.or_else(|_| { - rust_decimal::Decimal::from_str(&result).map(Token::DecimalConstant) + rust_decimal::Decimal::from_str(&result) + .map(Box::new) + .map(Token::DecimalConstant) }); // Then try decimal in scientific notation #[cfg(feature = "decimal")] let num = num.or_else(|_| { rust_decimal::Decimal::from_scientific(&result) + .map(Box::new) .map(Token::DecimalConstant) }); @@ -1697,11 +1712,11 @@ fn get_next_token_inner( // letter or underscore ... #[cfg(not(feature = "unicode-xid-ident"))] ('a'..='z' | '_' | 'A'..='Z', ..) => { - return Some(get_identifier(stream, pos, start_pos, c)); + return Some(get_token_as_identifier(stream, pos, start_pos, c)); } #[cfg(feature = "unicode-xid-ident")] (ch, ..) if unicode_xid::UnicodeXID::is_xid_start(ch) || ch == '_' => { - return Some(get_identifier(stream, pos, start_pos, c)); + return Some(get_token_as_identifier(stream, pos, start_pos, c)); } // " - string literal @@ -1709,7 +1724,7 @@ fn get_next_token_inner( return parse_string_literal(stream, state, pos, c, false, true, false) .map_or_else( |(err, err_pos)| Some((Token::LexError(err.into()), err_pos)), - |(result, ..)| Some((Token::StringConstant(result), start_pos)), + |(result, ..)| Some((Token::StringConstant(result.into()), start_pos)), ); } // ` - string literal @@ -1737,9 +1752,9 @@ fn get_next_token_inner( |(err, err_pos)| Some((Token::LexError(err.into()), err_pos)), |(result, interpolated, ..)| { if interpolated { - Some((Token::InterpolatedString(result), start_pos)) + Some((Token::InterpolatedString(result.into()), start_pos)) } else { - Some((Token::StringConstant(result), start_pos)) + Some((Token::StringConstant(result.into()), start_pos)) } }, ); @@ -1786,7 +1801,7 @@ fn get_next_token_inner( // Parentheses ('(', '*') => { eat_next(stream, pos); - return Some((Token::Reserved("(*".into()), start_pos)); + return Some((Token::Reserved(Box::new("(*".into())), start_pos)); } ('(', ..) => return Some((Token::LeftParen, start_pos)), (')', ..) => return Some((Token::RightParen, start_pos)), @@ -1802,7 +1817,7 @@ fn get_next_token_inner( return Some((Token::MapStart, start_pos)); } // Shebang - ('#', '!') => return Some((Token::Reserved("#!".into()), start_pos)), + ('#', '!') => return Some((Token::Reserved(Box::new("#!".into())), start_pos)), ('#', ' ') => { eat_next(stream, pos); @@ -1812,10 +1827,10 @@ fn get_next_token_inner( } else { "#" }; - return Some((Token::Reserved(token.into()), start_pos)); + return Some((Token::Reserved(Box::new(token.into())), start_pos)); } - ('#', ..) => return Some((Token::Reserved("#".into()), start_pos)), + ('#', ..) => return Some((Token::Reserved(Box::new("#".into())), start_pos)), // Operators ('+', '=') => { @@ -1824,7 +1839,7 @@ fn get_next_token_inner( } ('+', '+') => { eat_next(stream, pos); - return Some((Token::Reserved("++".into()), start_pos)); + return Some((Token::Reserved(Box::new("++".into())), start_pos)); } ('+', ..) if !state.next_token_cannot_be_unary => { return Some((Token::UnaryPlus, start_pos)) @@ -1839,11 +1854,11 @@ fn get_next_token_inner( } ('-', '>') => { eat_next(stream, pos); - return Some((Token::Reserved("->".into()), start_pos)); + return Some((Token::Reserved(Box::new("->".into())), start_pos)); } ('-', '-') => { eat_next(stream, pos); - return Some((Token::Reserved("--".into()), start_pos)); + return Some((Token::Reserved(Box::new("--".into())), start_pos)); } ('-', ..) if !state.next_token_cannot_be_unary => { return Some((Token::UnaryMinus, start_pos)) @@ -1852,7 +1867,7 @@ fn get_next_token_inner( ('*', ')') => { eat_next(stream, pos); - return Some((Token::Reserved("*)".into()), start_pos)); + return Some((Token::Reserved(Box::new("*)".into())), start_pos)); } ('*', '=') => { eat_next(stream, pos); @@ -1925,7 +1940,7 @@ fn get_next_token_inner( .borrow_mut() .global_comments .push(comment), - _ => return Some((Token::Comment(comment), start_pos)), + _ => return Some((Token::Comment(comment.into()), start_pos)), } } } @@ -1953,7 +1968,7 @@ fn get_next_token_inner( scan_block_comment(stream, state.comment_level, pos, comment.as_mut()); if let Some(comment) = comment { - return Some((Token::Comment(comment), start_pos)); + return Some((Token::Comment(comment.into()), start_pos)); } } @@ -1972,7 +1987,7 @@ fn get_next_token_inner( match stream.peek_next() { Some('.') => { eat_next(stream, pos); - Token::Reserved("...".into()) + Token::Reserved(Box::new("...".into())) } Some('=') => { eat_next(stream, pos); @@ -1990,7 +2005,7 @@ fn get_next_token_inner( if stream.peek_next() == Some('=') { eat_next(stream, pos); - return Some((Token::Reserved("===".into()), start_pos)); + return Some((Token::Reserved(Box::new("===".into())), start_pos)); } return Some((Token::EqualsTo, start_pos)); @@ -2007,18 +2022,18 @@ fn get_next_token_inner( if stream.peek_next() == Some('<') { eat_next(stream, pos); - return Some((Token::Reserved("::<".into()), start_pos)); + return Some((Token::Reserved(Box::new("::<".into())), start_pos)); } return Some((Token::DoubleColon, start_pos)); } (':', '=') => { eat_next(stream, pos); - return Some((Token::Reserved(":=".into()), start_pos)); + return Some((Token::Reserved(Box::new(":=".into())), start_pos)); } (':', ';') => { eat_next(stream, pos); - return Some((Token::Reserved(":;".into()), start_pos)); + return Some((Token::Reserved(Box::new(":;".into())), start_pos)); } (':', ..) => return Some((Token::Colon, start_pos)), @@ -2028,7 +2043,7 @@ fn get_next_token_inner( } ('<', '-') => { eat_next(stream, pos); - return Some((Token::Reserved("<-".into()), start_pos)); + return Some((Token::Reserved(Box::new("<-".into())), start_pos)); } ('<', '<') => { eat_next(stream, pos); @@ -2045,7 +2060,7 @@ fn get_next_token_inner( } ('<', '|') => { eat_next(stream, pos); - return Some((Token::Reserved("<|".into()), start_pos)); + return Some((Token::Reserved(Box::new("<|".into())), start_pos)); } ('<', ..) => return Some((Token::LessThan, start_pos)), @@ -2073,14 +2088,14 @@ fn get_next_token_inner( if stream.peek_next() == Some('=') { eat_next(stream, pos); - return Some((Token::Reserved("!==".into()), start_pos)); + return Some((Token::Reserved(Box::new("!==".into())), start_pos)); } return Some((Token::NotEqualsTo, start_pos)); } ('!', '.') => { eat_next(stream, pos); - return Some((Token::Reserved("!.".into()), start_pos)); + return Some((Token::Reserved(Box::new("!.".into())), start_pos)); } ('!', ..) => return Some((Token::Bang, start_pos)), @@ -2094,7 +2109,7 @@ fn get_next_token_inner( } ('|', '>') => { eat_next(stream, pos); - return Some((Token::Reserved("|>".into()), start_pos)); + return Some((Token::Reserved(Box::new("|>".into())), start_pos)); } ('|', ..) => return Some((Token::Pipe, start_pos)), @@ -2114,7 +2129,7 @@ fn get_next_token_inner( } ('^', ..) => return Some((Token::XOr, start_pos)), - ('~', ..) => return Some((Token::Reserved("~".into()), start_pos)), + ('~', ..) => return Some((Token::Reserved(Box::new("~".into())), start_pos)), ('%', '=') => { eat_next(stream, pos); @@ -2122,9 +2137,9 @@ fn get_next_token_inner( } ('%', ..) => return Some((Token::Modulo, start_pos)), - ('@', ..) => return Some((Token::Reserved("@".into()), start_pos)), + ('@', ..) => return Some((Token::Reserved(Box::new("@".into())), start_pos)), - ('$', ..) => return Some((Token::Reserved("$".into()), start_pos)), + ('$', ..) => return Some((Token::Reserved(Box::new("$".into())), start_pos)), ('?', '.') => { eat_next(stream, pos); @@ -2132,7 +2147,7 @@ fn get_next_token_inner( #[cfg(not(feature = "no_object"))] Token::Elvis, #[cfg(feature = "no_object")] - Token::Reserved("?.".into()), + Token::Reserved(Box::new("?.".into())), start_pos, )); } @@ -2146,11 +2161,11 @@ fn get_next_token_inner( #[cfg(not(feature = "no_index"))] Token::QuestionBracket, #[cfg(feature = "no_index")] - Token::Reserved("?[".into()), + Token::Reserved(Box::new("?[".into())), start_pos, )); } - ('?', ..) => return Some((Token::Reserved("?".into()), start_pos)), + ('?', ..) => return Some((Token::Reserved(Box::new("?".into())), start_pos)), (ch, ..) if ch.is_whitespace() => (), @@ -2168,8 +2183,8 @@ fn get_next_token_inner( Some((Token::EOF, *pos)) } -/// Get the next identifier. -fn get_identifier( +/// Get the next token, parsing it as an identifier. +fn get_token_as_identifier( stream: &mut impl InputStream, pos: &mut Position, start_pos: Position, @@ -2188,20 +2203,20 @@ fn get_identifier( } } - let is_valid_identifier = is_valid_identifier(identifier.chars()); - - if let Some(token) = Token::lookup_from_syntax(&identifier) { + if let Some(token) = Token::lookup_symbol_from_syntax(&identifier) { return (token, start_pos); + } else if Token::is_reserved_keyword(&identifier) { + return (Token::Reserved(Box::new(identifier)), start_pos); } - if !is_valid_identifier { + if !is_valid_identifier(&identifier) { return ( Token::LexError(LERR::MalformedIdentifier(identifier.to_string()).into()), start_pos, ); } - (Token::Identifier(identifier), start_pos) + (Token::Identifier(identifier.into()), start_pos) } /// Is a keyword allowed as a function? @@ -2222,10 +2237,10 @@ pub fn is_keyword_function(name: &str) -> bool { /// _(internals)_ Is a text string a valid identifier? /// Exported under the `internals` feature only. #[must_use] -pub fn is_valid_identifier(name: impl Iterator) -> bool { +pub fn is_valid_identifier(name: &str) -> bool { let mut first_alphabetic = false; - for ch in name { + for ch in name.chars() { match ch { '_' => (), _ if is_id_first_alphabetic(ch) => first_alphabetic = true, @@ -2243,7 +2258,7 @@ pub fn is_valid_identifier(name: impl Iterator) -> bool { #[inline(always)] #[must_use] pub fn is_valid_function_name(name: &str) -> bool { - is_valid_identifier(name.chars()) + is_valid_identifier(name) && !is_keyword_function(name) } /// Is a character valid to start an identifier? @@ -2382,7 +2397,7 @@ impl<'a> Iterator for TokenIterator<'a> { } // Reserved keyword/symbol Some((Token::Reserved(s), pos)) => (match - (&*s, + (s.as_str(), #[cfg(not(feature = "no_custom_syntax"))] (!self.engine.custom_keywords.is_empty() && self.engine.custom_keywords.contains_key(&*s)), #[cfg(feature = "no_custom_syntax")] @@ -2422,7 +2437,7 @@ impl<'a> Iterator for TokenIterator<'a> { (.., true) => unreachable!("no custom operators"), // Reserved keyword that is not custom and disabled. (token, false) if !self.engine.disabled_symbols.is_empty() && self.engine.disabled_symbols.contains(token) => { - let msg = format!("reserved {} '{token}' is disabled", if is_valid_identifier(token.chars()) { "keyword"} else {"symbol"}); + let msg = format!("reserved {} '{token}' is disabled", if is_valid_identifier(token) { "keyword"} else {"symbol"}); Token::LexError(LERR::ImproperSymbol(s.to_string(), msg).into()) }, // Reserved keyword/operator that is not custom. @@ -2438,7 +2453,7 @@ impl<'a> Iterator for TokenIterator<'a> { Some((token, pos)) if !self.engine.custom_keywords.is_empty() && self.engine.custom_keywords.contains_key(token.literal_syntax()) => { if !self.engine.disabled_symbols.is_empty() && self.engine.disabled_symbols.contains(token.literal_syntax()) { // Disabled standard keyword/symbol - (Token::Custom(token.literal_syntax().into()), pos) + (Token::Custom(Box::new(token.literal_syntax().into())), pos) } else { // Active standard keyword - should never be a custom keyword! unreachable!("{:?} is an active keyword", token) @@ -2446,7 +2461,7 @@ impl<'a> Iterator for TokenIterator<'a> { } // Disabled symbol Some((token, pos)) if !self.engine.disabled_symbols.is_empty() && self.engine.disabled_symbols.contains(token.literal_syntax()) => { - (Token::Reserved(token.literal_syntax().into()), pos) + (Token::Reserved(Box::new(token.literal_syntax().into())), pos) } // Normal symbol Some(r) => r, diff --git a/src/types/custom_types.rs b/src/types/custom_types.rs index 3b759edc..ca43e1e8 100644 --- a/src/types/custom_types.rs +++ b/src/types/custom_types.rs @@ -17,6 +17,8 @@ pub struct CustomTypeInfo { pub struct CustomTypesCollection(BTreeMap); impl fmt::Debug for CustomTypesCollection { + #[cold] + #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("CustomTypesCollection ")?; f.debug_map().entries(self.0.iter()).finish() diff --git a/src/types/dynamic.rs b/src/types/dynamic.rs index 6823dd74..eeee966a 100644 --- a/src/types/dynamic.rs +++ b/src/types/dynamic.rs @@ -1,7 +1,5 @@ -//! Helper module which defines the [`Dynamic`] data type and the -//! [`Any`] trait to to allow custom type handling. +//! Helper module which defines the [`Dynamic`] data type. -use crate::func::SendSync; use crate::{reify, ExclusiveRange, FnPtr, ImmutableString, InclusiveRange, INT}; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -14,11 +12,13 @@ use std::{ str::FromStr, }; -#[cfg(not(feature = "no_std"))] +pub use super::Variant; + +#[cfg(not(feature = "no_time"))] #[cfg(not(target_family = "wasm"))] pub use std::time::Instant; -#[cfg(not(feature = "no_std"))] +#[cfg(not(feature = "no_time"))] #[cfg(target_family = "wasm")] pub use instant::Instant; @@ -26,105 +26,6 @@ pub use instant::Instant; #[allow(dead_code)] const CHECKED: &str = "data type was checked"; -mod private { - use crate::func::SendSync; - use std::any::Any; - - /// A sealed trait that prevents other crates from implementing [`Variant`][super::Variant]. - pub trait Sealed {} - - impl Sealed for T {} -} - -/// _(internals)_ Trait to represent any type. -/// Exported under the `internals` feature only. -/// -/// This trait is sealed and cannot be implemented. -/// -/// Currently, [`Variant`] is not [`Send`] nor [`Sync`], so it can practically be any type. -/// Turn on the `sync` feature to restrict it to only types that implement [`Send`] `+` [`Sync`]. -#[cfg(not(feature = "sync"))] -pub trait Variant: Any + private::Sealed { - /// Convert this [`Variant`] trait object to [`&dyn Any`][Any]. - #[must_use] - fn as_any(&self) -> &dyn Any; - - /// Convert this [`Variant`] trait object to [`&mut dyn Any`][Any]. - #[must_use] - fn as_any_mut(&mut self) -> &mut dyn Any; - - /// Convert this [`Variant`] trait object to [`Box`][Any]. - #[must_use] - fn as_boxed_any(self: Box) -> Box; - - /// Get the name of this type. - #[must_use] - fn type_name(&self) -> &'static str; - - /// Clone this [`Variant`] trait object. - #[must_use] - fn clone_object(&self) -> Box; -} - -/// _(internals)_ Trait to represent any type. -/// Exported under the `internals` feature only. -/// -/// This trait is sealed and cannot be implemented. -#[cfg(feature = "sync")] -pub trait Variant: Any + Send + Sync + private::Sealed { - /// Convert this [`Variant`] trait object to [`&dyn Any`][Any]. - #[must_use] - fn as_any(&self) -> &dyn Any; - - /// Convert this [`Variant`] trait object to [`&mut dyn Any`][Any]. - #[must_use] - fn as_any_mut(&mut self) -> &mut dyn Any; - - /// Convert this [`Variant`] trait object to [`Box`][Any]. - #[must_use] - fn as_boxed_any(self: Box) -> Box; - - /// Get the name of this type. - #[must_use] - fn type_name(&self) -> &'static str; - - /// Clone this [`Variant`] trait object. - #[must_use] - fn clone_object(&self) -> Box; -} - -impl Variant for T { - #[inline(always)] - fn as_any(&self) -> &dyn Any { - self - } - #[inline(always)] - fn as_any_mut(&mut self) -> &mut dyn Any { - self - } - #[inline(always)] - fn as_boxed_any(self: Box) -> Box { - self - } - #[inline(always)] - fn type_name(&self) -> &'static str { - type_name::() - } - #[inline(always)] - fn clone_object(&self) -> Box { - Box::new(self.clone()) as Box - } -} - -impl dyn Variant { - /// Is this [`Variant`] a specific type? - #[inline(always)] - #[must_use] - pub fn is(&self) -> bool { - TypeId::of::() == self.type_id() - } -} - /// _(internals)_ Modes of access. /// Exported under the `internals` feature only. #[derive(Debug, Eq, PartialEq, Hash, Copy, Clone)] @@ -154,6 +55,9 @@ pub struct Dynamic(pub(crate) Union); /// /// Most variants are boxed to reduce the size. pub enum Union { + /// An error value which should not exist. + Null, + /// The Unit value - (). Unit((), Tag, AccessMode), /// A boolean value. @@ -183,7 +87,7 @@ pub enum Union { /// A function pointer. FnPtr(Box, Tag, AccessMode), /// A timestamp value. - #[cfg(not(feature = "no_std"))] + #[cfg(not(feature = "no_time"))] TimeStamp(Box, Tag, AccessMode), /// Any type as a trait object. @@ -277,6 +181,8 @@ impl Dynamic { #[must_use] pub const fn tag(&self) -> Tag { match self.0 { + Union::Null => unreachable!(), + Union::Unit(_, tag, _) | Union::Bool(_, tag, _) | Union::Str(_, tag, _) @@ -293,7 +199,7 @@ impl Dynamic { Union::Array(_, tag, _) | Union::Blob(_, tag, _) => tag, #[cfg(not(feature = "no_object"))] Union::Map(_, tag, _) => tag, - #[cfg(not(feature = "no_std"))] + #[cfg(not(feature = "no_time"))] Union::TimeStamp(_, tag, _) => tag, #[cfg(not(feature = "no_closure"))] Union::Shared(_, tag, _) => tag, @@ -302,6 +208,8 @@ impl Dynamic { /// Attach arbitrary data to this [`Dynamic`]. pub fn set_tag(&mut self, value: Tag) -> &mut Self { match self.0 { + Union::Null => unreachable!(), + Union::Unit(_, ref mut tag, _) | Union::Bool(_, ref mut tag, _) | Union::Str(_, ref mut tag, _) @@ -318,13 +226,19 @@ impl Dynamic { Union::Array(_, ref mut tag, _) | Union::Blob(_, ref mut tag, _) => *tag = value, #[cfg(not(feature = "no_object"))] Union::Map(_, ref mut tag, _) => *tag = value, - #[cfg(not(feature = "no_std"))] + #[cfg(not(feature = "no_time"))] Union::TimeStamp(_, ref mut tag, _) => *tag = value, #[cfg(not(feature = "no_closure"))] Union::Shared(_, ref mut tag, _) => *tag = value, } self } + /// Is this [`Dynamic`] null? + #[inline(always)] + #[must_use] + pub(crate) const fn is_null(&self) -> bool { + matches!(self.0, Union::Null) + } /// Does this [`Dynamic`] hold a variant data type instead of one of the supported system /// primitive types? #[inline(always)] @@ -390,7 +304,7 @@ impl Dynamic { if TypeId::of::() == TypeId::of::() { return matches!(self.0, Union::FnPtr(..)); } - #[cfg(not(feature = "no_std"))] + #[cfg(not(feature = "no_time"))] if TypeId::of::() == TypeId::of::() { return matches!(self.0, Union::TimeStamp(..)); } @@ -406,6 +320,8 @@ impl Dynamic { #[must_use] pub fn type_id(&self) -> TypeId { match self.0 { + Union::Null => unreachable!(), + Union::Unit(..) => TypeId::of::<()>(), Union::Bool(..) => TypeId::of::(), Union::Str(..) => TypeId::of::(), @@ -422,7 +338,7 @@ impl Dynamic { #[cfg(not(feature = "no_object"))] Union::Map(..) => TypeId::of::(), Union::FnPtr(..) => TypeId::of::(), - #[cfg(not(feature = "no_std"))] + #[cfg(not(feature = "no_time"))] Union::TimeStamp(..) => TypeId::of::(), Union::Variant(ref v, ..) => (***v).type_id(), @@ -440,6 +356,8 @@ impl Dynamic { #[must_use] pub fn type_name(&self) -> &'static str { match self.0 { + Union::Null => unreachable!(), + Union::Unit(..) => "()", Union::Bool(..) => "bool", Union::Str(..) => "string", @@ -456,7 +374,7 @@ impl Dynamic { #[cfg(not(feature = "no_object"))] Union::Map(..) => "map", Union::FnPtr(..) => "Fn", - #[cfg(not(feature = "no_std"))] + #[cfg(not(feature = "no_time"))] Union::TimeStamp(..) => "timestamp", Union::Variant(ref v, ..) => (***v).type_name(), @@ -484,6 +402,8 @@ impl Hash for Dynamic { mem::discriminant(&self.0).hash(state); match self.0 { + Union::Null => unreachable!(), + Union::Unit(..) => (), Union::Bool(ref b, ..) => b.hash(state), Union::Str(ref s, ..) => s.hash(state), @@ -506,7 +426,7 @@ impl Hash for Dynamic { Union::Variant(..) => unimplemented!("{} cannot be hashed", self.type_name()), - #[cfg(not(feature = "no_std"))] + #[cfg(not(feature = "no_time"))] Union::TimeStamp(..) => unimplemented!("{} cannot be hashed", self.type_name()), } } @@ -515,7 +435,9 @@ impl Hash for Dynamic { impl fmt::Display for Dynamic { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self.0 { - Union::Unit(..) => write!(f, ""), + Union::Null => unreachable!(), + + Union::Unit(..) => Ok(()), Union::Bool(ref v, ..) => fmt::Display::fmt(v, f), Union::Str(ref v, ..) => fmt::Display::fmt(v, f), Union::Char(ref v, ..) => fmt::Display::fmt(v, f), @@ -531,7 +453,7 @@ impl fmt::Display for Dynamic { #[cfg(not(feature = "no_object"))] Union::Map(..) => fmt::Debug::fmt(self, f), Union::FnPtr(ref v, ..) => fmt::Display::fmt(v, f), - #[cfg(not(feature = "no_std"))] + #[cfg(not(feature = "no_time"))] Union::TimeStamp(..) => f.write_str(""), Union::Variant(ref v, ..) => { @@ -604,8 +526,12 @@ impl fmt::Display for Dynamic { } impl fmt::Debug for Dynamic { + #[cold] + #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self.0 { + Union::Null => unreachable!(), + Union::Unit(ref v, ..) => fmt::Debug::fmt(v, f), Union::Bool(ref v, ..) => fmt::Debug::fmt(v, f), Union::Str(ref v, ..) => fmt::Debug::fmt(v, f), @@ -624,7 +550,7 @@ impl fmt::Debug for Dynamic { if i > 0 && i % 8 == 0 { f.write_str(" ")?; } - write!(f, "{:02x}", v) + write!(f, "{v:02x}") })?; f.write_str("]") } @@ -634,7 +560,7 @@ impl fmt::Debug for Dynamic { fmt::Debug::fmt(v, f) } Union::FnPtr(ref v, ..) => fmt::Debug::fmt(v, f), - #[cfg(not(feature = "no_std"))] + #[cfg(not(feature = "no_time"))] Union::TimeStamp(..) => write!(f, ""), Union::Variant(ref v, ..) => { @@ -716,6 +642,8 @@ impl Clone for Dynamic { /// The cloned copy is marked read-write even if the original is read-only. fn clone(&self) -> Self { match self.0 { + Union::Null => unreachable!(), + Union::Unit(v, tag, ..) => Self(Union::Unit(v, tag, ReadWrite)), Union::Bool(v, tag, ..) => Self(Union::Bool(v, tag, ReadWrite)), Union::Str(ref v, tag, ..) => Self(Union::Str(v.clone(), tag, ReadWrite)), @@ -732,7 +660,7 @@ impl Clone for Dynamic { #[cfg(not(feature = "no_object"))] Union::Map(ref v, tag, ..) => Self(Union::Map(v.clone(), tag, ReadWrite)), Union::FnPtr(ref v, tag, ..) => Self(Union::FnPtr(v.clone(), tag, ReadWrite)), - #[cfg(not(feature = "no_std"))] + #[cfg(not(feature = "no_time"))] Union::TimeStamp(ref v, tag, ..) => Self(Union::TimeStamp(v.clone(), tag, ReadWrite)), Union::Variant(ref v, tag, ..) => Self(Union::Variant( @@ -763,6 +691,9 @@ use std::f32::consts as FloatConstants; use std::f64::consts as FloatConstants; impl Dynamic { + /// A [`Dynamic`] containing a `null`. + pub(crate) const NULL: Self = Self(Union::Null); + /// A [`Dynamic`] containing a `()`. pub const UNIT: Self = Self(Union::Unit((), DEFAULT_TAG_VALUE, ReadWrite)); /// A [`Dynamic`] containing a `true`. @@ -973,8 +904,8 @@ impl Dynamic { } /// Create a new [`Dynamic`] from an [`Instant`]. /// - /// Not available under `no-std`. - #[cfg(not(feature = "no_std"))] + /// Not available under `no-std` or `no_time`. + #[cfg(not(feature = "no_time"))] #[inline(always)] #[must_use] pub fn from_timestamp(value: Instant) -> Self { @@ -985,6 +916,8 @@ impl Dynamic { #[must_use] pub(crate) const fn access_mode(&self) -> AccessMode { match self.0 { + Union::Null => unreachable!(), + Union::Unit(.., access) | Union::Bool(.., access) | Union::Str(.., access) @@ -1001,7 +934,7 @@ impl Dynamic { Union::Array(.., access) | Union::Blob(.., access) => access, #[cfg(not(feature = "no_object"))] Union::Map(.., access) => access, - #[cfg(not(feature = "no_std"))] + #[cfg(not(feature = "no_time"))] Union::TimeStamp(.., access) => access, #[cfg(not(feature = "no_closure"))] Union::Shared(.., access) => access, @@ -1010,6 +943,8 @@ impl Dynamic { /// Set the [`AccessMode`] for this [`Dynamic`]. pub(crate) fn set_access_mode(&mut self, typ: AccessMode) -> &mut Self { match self.0 { + Union::Null => unreachable!(), + Union::Unit(.., ref mut access) | Union::Bool(.., ref mut access) | Union::Str(.., ref mut access) @@ -1025,7 +960,7 @@ impl Dynamic { #[cfg(not(feature = "no_index"))] Union::Array(ref mut a, _, ref mut access) => { *access = typ; - for v in a.iter_mut() { + for v in a.as_mut() { v.set_access_mode(typ); } } @@ -1038,7 +973,7 @@ impl Dynamic { v.set_access_mode(typ); } } - #[cfg(not(feature = "no_std"))] + #[cfg(not(feature = "no_time"))] Union::TimeStamp(.., ref mut access) => *access = typ, #[cfg(not(feature = "no_closure"))] Union::Shared(.., ref mut access) => *access = typ, @@ -1172,7 +1107,7 @@ impl Dynamic { reify!(value, |v: crate::Map| return v.into()); reify!(value, |v: FnPtr| return v.into()); - #[cfg(not(feature = "no_std"))] + #[cfg(not(feature = "no_time"))] reify!(value, |v: Instant| return v.into()); #[cfg(not(feature = "no_closure"))] reify!(value, |v: crate::Shared>| return v @@ -1204,6 +1139,7 @@ impl Dynamic { let _access = self.access_mode(); match self.0 { + Union::Null => unreachable!(), Union::Shared(..) => self, _ => Self(Union::Shared( crate::Locked::new(self).into(), @@ -1248,6 +1184,8 @@ impl Dynamic { reify!(self, |v: T| return Some(v)); match self.0 { + Union::Null => unreachable!(), + Union::Int(v, ..) => reify!(v => Option), #[cfg(not(feature = "no_float"))] Union::Float(v, ..) => reify!(*v => Option), @@ -1265,7 +1203,7 @@ impl Dynamic { #[cfg(not(feature = "no_object"))] Union::Map(v, ..) => reify!(*v => Option), Union::FnPtr(v, ..) => reify!(*v => Option), - #[cfg(not(feature = "no_std"))] + #[cfg(not(feature = "no_time"))] Union::TimeStamp(v, ..) => reify!(*v => Option), Union::Unit(v, ..) => reify!(v => Option), Union::Variant(v, ..) => (*v).as_boxed_any().downcast().ok().map(|x| *x), @@ -1564,7 +1502,7 @@ impl Dynamic { _ => None, }; } - #[cfg(not(feature = "no_std"))] + #[cfg(not(feature = "no_time"))] if TypeId::of::() == TypeId::of::() { return match self.0 { Union::TimeStamp(ref v, ..) => v.as_ref().as_any().downcast_ref::(), @@ -1582,6 +1520,7 @@ impl Dynamic { } match self.0 { + Union::Null => unreachable!(), Union::Variant(ref v, ..) => (***v).as_any().downcast_ref::(), #[cfg(not(feature = "no_closure"))] Union::Shared(..) => None, @@ -1662,7 +1601,7 @@ impl Dynamic { _ => None, }; } - #[cfg(not(feature = "no_std"))] + #[cfg(not(feature = "no_time"))] if TypeId::of::() == TypeId::of::() { return match self.0 { Union::TimeStamp(ref mut v, ..) => v.as_mut().as_any_mut().downcast_mut::(), @@ -1680,6 +1619,7 @@ impl Dynamic { } match self.0 { + Union::Null => unreachable!(), Union::Variant(ref mut v, ..) => (***v).as_any_mut().downcast_mut::(), #[cfg(not(feature = "no_closure"))] Union::Shared(..) => None, @@ -2056,7 +1996,7 @@ impl From for Dynamic { Self(Union::FnPtr(value.into(), DEFAULT_TAG_VALUE, ReadWrite)) } } -#[cfg(not(feature = "no_std"))] +#[cfg(not(feature = "no_time"))] impl From for Dynamic { #[inline(always)] fn from(value: Instant) -> Self { diff --git a/src/types/error.rs b/src/types/error.rs index d426d5ed..906f32c9 100644 --- a/src/types/error.rs +++ b/src/types/error.rs @@ -23,6 +23,7 @@ use std::prelude::v1::*; /// Turn on the `sync` feature to make it [`Send`] `+` [`Sync`]. #[derive(Debug)] #[non_exhaustive] +#[must_use] pub enum EvalAltResult { /// System error. Wrapped values are the error message and the internal error. #[cfg(not(feature = "sync"))] @@ -83,6 +84,8 @@ pub enum EvalAltResult { /// Data race detected when accessing a variable. Wrapped value is the variable name. ErrorDataRace(String, Position), + /// Calling a non-pure method on a constant. Wrapped value is the function name. + ErrorNonPureMethodCallOnConstant(String, Position), /// Assignment to a constant variable. Wrapped value is the variable name. ErrorAssignmentToConstant(String, Position), /// Inappropriate property access. Wrapped value is the property name. @@ -114,7 +117,7 @@ pub enum EvalAltResult { /// Breaking out of loops - not an error if within a loop. /// The wrapped value, if true, means breaking clean out of the loop (i.e. a `break` statement). /// The wrapped value, if false, means breaking the current context (i.e. a `continue` statement). - LoopBreak(bool, Position), + LoopBreak(bool, Dynamic, Position), /// Not an error: Value returned from a script via the `return` keyword. /// Wrapped value is the result value. Return(Dynamic, Position), @@ -125,47 +128,45 @@ impl Error for EvalAltResult {} impl fmt::Display for EvalAltResult { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::ErrorSystem(s, err) => match s.as_str() { - "" => write!(f, "{}", err), - s => write!(f, "{}: {}", s, err), - }?, + Self::ErrorSystem(s, err) if s.is_empty() => write!(f, "{err}")?, + Self::ErrorSystem(s, err) => write!(f, "{s}: {err}")?, - Self::ErrorParsing(p, ..) => write!(f, "Syntax error: {}", p)?, + Self::ErrorParsing(p, ..) => write!(f, "Syntax error: {p}")?, #[cfg(not(feature = "no_function"))] Self::ErrorInFunctionCall(s, src, err, ..) if crate::parser::is_anonymous_fn(s) => { - write!(f, "{} in call to closure", err)?; + write!(f, "{err} in call to closure")?; if !src.is_empty() { - write!(f, " @ '{}'", src)?; + write!(f, " @ '{src}'")?; } } Self::ErrorInFunctionCall(s, src, err, ..) => { - write!(f, "{} in call to function {}", err, s)?; + write!(f, "{err} in call to function {s}")?; if !src.is_empty() { - write!(f, " @ '{}'", src)?; + write!(f, " @ '{src}'")?; } } Self::ErrorInModule(s, err, ..) if s.is_empty() => { - write!(f, "Error in module > {}", err)? + write!(f, "Error in module > {err}")? } - Self::ErrorInModule(s, err, ..) => write!(f, "Error in module '{}' > {}", s, err)?, + Self::ErrorInModule(s, err, ..) => write!(f, "Error in module '{s}' > {err}")?, - Self::ErrorVariableExists(s, ..) => write!(f, "Variable already defined: {}", s)?, - Self::ErrorForbiddenVariable(s, ..) => write!(f, "Forbidden variable name: {}", s)?, - Self::ErrorVariableNotFound(s, ..) => write!(f, "Variable not found: {}", s)?, - Self::ErrorPropertyNotFound(s, ..) => write!(f, "Property not found: {}", s)?, - Self::ErrorIndexNotFound(s, ..) => write!(f, "Invalid index: {}", s)?, - Self::ErrorFunctionNotFound(s, ..) => write!(f, "Function not found: {}", s)?, - Self::ErrorModuleNotFound(s, ..) => write!(f, "Module not found: {}", s)?, + Self::ErrorVariableExists(s, ..) => write!(f, "Variable already defined: {s}")?, + Self::ErrorForbiddenVariable(s, ..) => write!(f, "Forbidden variable name: {s}")?, + Self::ErrorVariableNotFound(s, ..) => write!(f, "Variable not found: {s}")?, + Self::ErrorPropertyNotFound(s, ..) => write!(f, "Property not found: {s}")?, + Self::ErrorIndexNotFound(s, ..) => write!(f, "Invalid index: {s}")?, + Self::ErrorFunctionNotFound(s, ..) => write!(f, "Function not found: {s}")?, + Self::ErrorModuleNotFound(s, ..) => write!(f, "Module not found: {s}")?, Self::ErrorDataRace(s, ..) => { - write!(f, "Data race detected when accessing variable: {}", s)? + write!(f, "Data race detected when accessing variable: {s}")? } - Self::ErrorDotExpr(s, ..) => match s.as_str() { - "" => f.write_str("Malformed dot expression"), - s => f.write_str(s), - }?, - Self::ErrorIndexingType(s, ..) => write!(f, "Indexer unavailable: {}", s)?, + + Self::ErrorDotExpr(s, ..) if s.is_empty() => f.write_str("Malformed dot expression")?, + Self::ErrorDotExpr(s, ..) => f.write_str(s)?, + + Self::ErrorIndexingType(s, ..) => write!(f, "Indexer unavailable: {s}")?, Self::ErrorUnboundThis(..) => f.write_str("'this' not bound")?, Self::ErrorFor(..) => f.write_str("For loop expects an iterable type")?, Self::ErrorTooManyOperations(..) => f.write_str("Too many operations")?, @@ -180,63 +181,89 @@ impl fmt::Display for EvalAltResult { { write!(f, "Runtime error")? } - Self::ErrorRuntime(d, ..) => write!(f, "Runtime error: {}", d)?, + Self::ErrorRuntime(d, ..) => write!(f, "Runtime error: {d}")?, - Self::ErrorAssignmentToConstant(s, ..) => write!(f, "Cannot modify constant: {}", s)?, + #[cfg(not(feature = "no_object"))] + Self::ErrorNonPureMethodCallOnConstant(s, ..) + if s.starts_with(crate::engine::FN_GET) => + { + let prop = &s[crate::engine::FN_GET.len()..]; + write!( + f, + "Property {prop} is not pure and cannot be accessed on a constant" + )? + } + #[cfg(not(feature = "no_object"))] + Self::ErrorNonPureMethodCallOnConstant(s, ..) + if s.starts_with(crate::engine::FN_SET) => + { + let prop = &s[crate::engine::FN_SET.len()..]; + write!(f, "Cannot modify property {prop} of constant")? + } + #[cfg(not(feature = "no_index"))] + Self::ErrorNonPureMethodCallOnConstant(s, ..) if s == crate::engine::FN_IDX_GET => { + write!( + f, + "Indexer is not pure and cannot be accessed on a constant" + )? + } + #[cfg(not(feature = "no_index"))] + Self::ErrorNonPureMethodCallOnConstant(s, ..) if s == crate::engine::FN_IDX_SET => { + write!(f, "Cannot assign to indexer of constant")? + } + Self::ErrorNonPureMethodCallOnConstant(s, ..) => { + write!(f, "Non-pure method {s} cannot be called on constant")? + } + + Self::ErrorAssignmentToConstant(s, ..) => write!(f, "Cannot modify constant {s}")?, Self::ErrorMismatchOutputType(e, a, ..) => match (a.as_str(), e.as_str()) { - ("", e) => write!(f, "Output type incorrect, expecting {}", e), - (a, "") => write!(f, "Output type incorrect: {}", a), - (a, e) => write!(f, "Output type incorrect: {} (expecting {})", a, e), + ("", e) => write!(f, "Output type incorrect, expecting {e}"), + (a, "") => write!(f, "Output type incorrect: {a}"), + (a, e) => write!(f, "Output type incorrect: {a} (expecting {e})"), }?, Self::ErrorMismatchDataType(e, a, ..) => match (a.as_str(), e.as_str()) { - ("", e) => write!(f, "Data type incorrect, expecting {}", e), - (a, "") => write!(f, "Data type incorrect: {}", a), - (a, e) => write!(f, "Data type incorrect: {} (expecting {})", a, e), - }?, - Self::ErrorArithmetic(s, ..) => match s.as_str() { - "" => f.write_str("Arithmetic error"), - s => f.write_str(s), + ("", e) => write!(f, "Data type incorrect, expecting {e}"), + (a, "") => write!(f, "Data type incorrect: {a}"), + (a, e) => write!(f, "Data type incorrect: {a} (expecting {e})"), }?, + Self::ErrorArithmetic(s, ..) if s.is_empty() => f.write_str("Arithmetic error")?, + Self::ErrorArithmetic(s, ..) => f.write_str(s)?, + Self::LoopBreak(true, ..) => f.write_str("'break' not inside a loop")?, Self::LoopBreak(false, ..) => f.write_str("'continue' not inside a loop")?, Self::Return(..) => f.write_str("NOT AN ERROR - function returns value")?, Self::ErrorArrayBounds(max, index, ..) => match max { - 0 => write!(f, "Array index {} out of bounds: array is empty", index), + 0 => write!(f, "Array index {index} out of bounds: array is empty"), 1 => write!( f, - "Array index {} out of bounds: only 1 element in array", - index + "Array index {index} out of bounds: only 1 element in array", ), _ => write!( f, - "Array index {} out of bounds: only {} elements in array", - index, max + "Array index {index} out of bounds: only {max} elements in array", ), }?, Self::ErrorStringBounds(max, index, ..) => match max { - 0 => write!(f, "String index {} out of bounds: string is empty", index), + 0 => write!(f, "String index {index} out of bounds: string is empty"), 1 => write!( f, - "String index {} out of bounds: only 1 character in string", - index + "String index {index} out of bounds: only 1 character in string", ), _ => write!( f, - "String index {} out of bounds: only {} characters in string", - index, max + "String index {index} out of bounds: only {max} characters in string", ), }?, Self::ErrorBitFieldBounds(max, index, ..) => write!( f, - "Bit-field index {} out of bounds: only {} bits in bit-field", - index, max + "Bit-field index {index} out of bounds: only {max} bits in bit-field", )?, - Self::ErrorDataTooLarge(typ, ..) => write!(f, "{} exceeds maximum limit", typ)?, + Self::ErrorDataTooLarge(typ, ..) => write!(f, "{typ} exceeds maximum limit")?, - Self::ErrorCustomSyntax(s, tokens, ..) => write!(f, "{}: {}", s, tokens.join(" "))?, + Self::ErrorCustomSyntax(s, tokens, ..) => write!(f, "{s}: {}", tokens.join(" "))?, } // Do not write any position if None @@ -258,9 +285,9 @@ impl> From for EvalAltResult { impl> From for Box { #[cold] - #[inline(never)] + #[inline(always)] fn from(err: T) -> Self { - EvalAltResult::ErrorRuntime(err.as_ref().to_string().into(), Position::NONE).into() + Into::::into(err).into() } } @@ -303,6 +330,7 @@ impl EvalAltResult { | Self::ErrorIndexNotFound(..) | Self::ErrorModuleNotFound(..) | Self::ErrorDataRace(..) + | Self::ErrorNonPureMethodCallOnConstant(..) | Self::ErrorAssignmentToConstant(..) | Self::ErrorMismatchOutputType(..) | Self::ErrorDotExpr(..) @@ -371,7 +399,7 @@ impl EvalAltResult { | Self::ErrorStackOverflow(..) | Self::ErrorRuntime(..) => (), - Self::ErrorFunctionNotFound(f, ..) => { + Self::ErrorFunctionNotFound(f, ..) | Self::ErrorNonPureMethodCallOnConstant(f, ..) => { map.insert("function".into(), f.into()); } Self::ErrorInFunctionCall(f, s, ..) => { @@ -466,6 +494,7 @@ impl EvalAltResult { | Self::ErrorIndexNotFound(.., pos) | Self::ErrorModuleNotFound(.., pos) | Self::ErrorDataRace(.., pos) + | Self::ErrorNonPureMethodCallOnConstant(.., pos) | Self::ErrorAssignmentToConstant(.., pos) | Self::ErrorMismatchOutputType(.., pos) | Self::ErrorDotExpr(.., pos) @@ -494,6 +523,7 @@ impl EvalAltResult { /// The [position][Position] of this error is set to [`NONE`][Position::NONE] afterwards. #[cold] #[inline(never)] + #[must_use] pub fn take_position(&mut self) -> Position { let pos = self.position(); self.set_position(Position::NONE); @@ -524,6 +554,7 @@ impl EvalAltResult { | Self::ErrorIndexNotFound(.., pos) | Self::ErrorModuleNotFound(.., pos) | Self::ErrorDataRace(.., pos) + | Self::ErrorNonPureMethodCallOnConstant(.., pos) | Self::ErrorAssignmentToConstant(.., pos) | Self::ErrorMismatchOutputType(.., pos) | Self::ErrorDotExpr(.., pos) diff --git a/src/types/fn_ptr.rs b/src/types/fn_ptr.rs index 3ac75077..5d087ef2 100644 --- a/src/types/fn_ptr.rs +++ b/src/types/fn_ptr.rs @@ -1,10 +1,11 @@ //! The `FnPtr` type. -use crate::tokenizer::is_valid_identifier; +use crate::eval::GlobalRuntimeState; +use crate::tokenizer::is_valid_function_name; use crate::types::dynamic::Variant; use crate::{ - Dynamic, Engine, FuncArgs, Identifier, Module, NativeCallContext, Position, RhaiError, - RhaiResult, RhaiResultOf, StaticVec, AST, ERR, + Dynamic, Engine, FuncArgs, ImmutableString, NativeCallContext, Position, RhaiError, RhaiResult, + RhaiResultOf, SharedModule, StaticVec, AST, ERR, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -18,11 +19,13 @@ use std::{ /// to be passed onto a function during a call. #[derive(Clone, Hash)] pub struct FnPtr { - name: Identifier, + name: ImmutableString, curry: StaticVec, } impl fmt::Debug for FnPtr { + #[cold] + #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if self.is_curried() { self.curry @@ -40,13 +43,16 @@ impl fmt::Debug for FnPtr { impl FnPtr { /// Create a new function pointer. #[inline(always)] - pub fn new(name: impl Into) -> RhaiResultOf { + pub fn new(name: impl Into) -> RhaiResultOf { name.into().try_into() } /// Create a new function pointer without checking its parameters. #[inline(always)] #[must_use] - pub(crate) fn new_unchecked(name: impl Into, curry: StaticVec) -> Self { + pub(crate) fn new_unchecked( + name: impl Into, + curry: StaticVec, + ) -> Self { Self { name: name.into(), curry, @@ -61,13 +67,13 @@ impl FnPtr { /// Get the name of the function. #[inline(always)] #[must_use] - pub(crate) const fn fn_name_raw(&self) -> &Identifier { + pub(crate) const fn fn_name_raw(&self) -> &ImmutableString { &self.name } /// Get the underlying data of the function pointer. #[inline(always)] #[must_use] - pub(crate) fn take_data(self) -> (Identifier, StaticVec) { + pub(crate) fn take_data(self) -> (ImmutableString, StaticVec) { (self.name, self.curry) } /// Get the curried arguments. @@ -101,7 +107,7 @@ impl FnPtr { #[inline(always)] #[must_use] pub fn is_anonymous(&self) -> bool { - self.name.starts_with(crate::engine::FN_ANONYMOUS) + crate::func::is_anonymous_fn(&self.name) } /// Call the function pointer with curried arguments (if any). /// The function may be script-defined (not available under `no_function`) or native Rust. @@ -145,17 +151,19 @@ impl FnPtr { let mut arg_values = crate::StaticVec::new_const(); args.parse(&mut arg_values); - let lib = [ + let lib: &[SharedModule] = &[ #[cfg(not(feature = "no_function"))] - _ast.as_ref(), + AsRef::::as_ref(ast).clone(), ]; - let lib = if lib.first().map_or(true, |m: &&Module| m.is_empty()) { - &lib[0..0] + let lib = if lib.first().map_or(true, |m| m.is_empty()) { + &[][..] } else { &lib }; - #[allow(deprecated)] - let ctx = NativeCallContext::new(engine, self.fn_name(), lib); + + let global = &GlobalRuntimeState::new(engine); + + let ctx = (engine, self.fn_name(), None, global, lib, Position::NONE).into(); let result = self.call_raw(&ctx, None, arg_values)?; @@ -244,12 +252,12 @@ impl fmt::Display for FnPtr { } } -impl TryFrom for FnPtr { +impl TryFrom for FnPtr { type Error = RhaiError; - #[inline] - fn try_from(value: Identifier) -> RhaiResultOf { - if is_valid_identifier(value.chars()) { + #[inline(always)] + fn try_from(value: ImmutableString) -> RhaiResultOf { + if is_valid_function_name(&value) { Ok(Self { name: value, curry: StaticVec::new_const(), @@ -259,43 +267,3 @@ impl TryFrom for FnPtr { } } } - -impl TryFrom for FnPtr { - type Error = RhaiError; - - #[inline(always)] - fn try_from(value: crate::ImmutableString) -> RhaiResultOf { - let s: Identifier = value.into(); - Self::try_from(s) - } -} - -impl TryFrom for FnPtr { - type Error = RhaiError; - - #[inline(always)] - fn try_from(value: String) -> RhaiResultOf { - let s: Identifier = value.into(); - Self::try_from(s) - } -} - -impl TryFrom> for FnPtr { - type Error = RhaiError; - - #[inline(always)] - fn try_from(value: Box) -> RhaiResultOf { - let s: Identifier = value.into(); - Self::try_from(s) - } -} - -impl TryFrom<&str> for FnPtr { - type Error = RhaiError; - - #[inline(always)] - fn try_from(value: &str) -> RhaiResultOf { - let s: Identifier = value.into(); - Self::try_from(s) - } -} diff --git a/src/types/immutable_string.rs b/src/types/immutable_string.rs index 6487efeb..f6dba61a 100644 --- a/src/types/immutable_string.rs +++ b/src/types/immutable_string.rs @@ -60,6 +60,7 @@ impl Deref for ImmutableString { impl AsRef for ImmutableString { #[inline(always)] + #[must_use] fn as_ref(&self) -> &SmartString { &self.0 } @@ -67,6 +68,7 @@ impl AsRef for ImmutableString { impl AsRef for ImmutableString { #[inline(always)] + #[must_use] fn as_ref(&self) -> &str { &self.0 } @@ -74,6 +76,7 @@ impl AsRef for ImmutableString { impl Borrow for ImmutableString { #[inline(always)] + #[must_use] fn borrow(&self) -> &SmartString { &self.0 } @@ -81,6 +84,7 @@ impl Borrow for ImmutableString { impl Borrow for ImmutableString { #[inline(always)] + #[must_use] fn borrow(&self) -> &str { self.as_str() } @@ -143,6 +147,7 @@ impl FromStr for ImmutableString { type Err = (); #[inline(always)] + #[must_use] fn from_str(s: &str) -> Result { let s: SmartString = s.into(); Ok(Self(s.into())) @@ -151,6 +156,7 @@ impl FromStr for ImmutableString { impl FromIterator for ImmutableString { #[inline] + #[must_use] fn from_iter>(iter: T) -> Self { Self(iter.into_iter().collect::().into()) } @@ -158,6 +164,7 @@ impl FromIterator for ImmutableString { impl<'a> FromIterator<&'a char> for ImmutableString { #[inline] + #[must_use] fn from_iter>(iter: T) -> Self { Self(iter.into_iter().copied().collect::().into()) } @@ -165,6 +172,7 @@ impl<'a> FromIterator<&'a char> for ImmutableString { impl<'a> FromIterator<&'a str> for ImmutableString { #[inline] + #[must_use] fn from_iter>(iter: T) -> Self { Self(iter.into_iter().collect::().into()) } @@ -172,6 +180,7 @@ impl<'a> FromIterator<&'a str> for ImmutableString { impl FromIterator for ImmutableString { #[inline] + #[must_use] fn from_iter>(iter: T) -> Self { Self(iter.into_iter().collect::().into()) } @@ -179,6 +188,7 @@ impl FromIterator for ImmutableString { impl FromIterator for ImmutableString { #[inline] + #[must_use] fn from_iter>(iter: T) -> Self { Self(iter.into_iter().collect::().into()) } @@ -192,7 +202,8 @@ impl fmt::Display for ImmutableString { } impl fmt::Debug for ImmutableString { - #[inline(always)] + #[cold] + #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(self.as_str(), f) } @@ -623,7 +634,7 @@ impl ImmutableString { #[inline] #[must_use] pub fn into_owned(mut self) -> String { - self.make_mut(); // Make sure it is unique reference + let _ = self.make_mut(); // Make sure it is unique reference shared_take(self.0).into() // Should succeed } /// Make sure that the [`ImmutableString`] is unique (i.e. no other outstanding references). @@ -631,6 +642,7 @@ impl ImmutableString { /// /// If there are other references to the same string, a cloned copy is used. #[inline(always)] + #[must_use] pub(crate) fn make_mut(&mut self) -> &mut SmartString { shared_make_mut(&mut self.0) } diff --git a/src/types/interner.rs b/src/types/interner.rs index 3dfa1048..43a2d395 100644 --- a/src/types/interner.rs +++ b/src/types/interner.rs @@ -1,3 +1,6 @@ +//! A strings interner type. + +use super::BloomFilterU64; use crate::func::{hashing::get_hasher, StraightHashMap}; use crate::ImmutableString; #[cfg(feature = "no_std")] @@ -14,37 +17,39 @@ use std::{ }; /// Maximum number of strings interned. -pub const MAX_INTERNED_STRINGS: usize = 256; +pub const MAX_INTERNED_STRINGS: usize = 1024; /// Maximum length of strings interned. pub const MAX_STRING_LEN: usize = 24; -/// _(internals)_ A factory of identifiers from text strings. +/// _(internals)_ A cache for interned strings. /// Exported under the `internals` feature only. -/// -/// Normal identifiers, property getters and setters are interned separately. pub struct StringsInterner<'a> { /// Maximum number of strings interned. pub capacity: usize, /// Maximum string length. pub max_string_len: usize, - /// Normal strings. - strings: StraightHashMap, + /// Cached strings. + cache: StraightHashMap, + /// Bloom filter to avoid caching "one-hit wonders". + filter: BloomFilterU64, /// Take care of the lifetime parameter. dummy: PhantomData<&'a ()>, } impl Default for StringsInterner<'_> { #[inline(always)] + #[must_use] fn default() -> Self { Self::new() } } impl fmt::Debug for StringsInterner<'_> { - #[inline] + #[cold] + #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_list().entries(self.strings.values()).finish() + f.debug_list().entries(self.cache.values()).finish() } } @@ -56,7 +61,8 @@ impl StringsInterner<'_> { Self { capacity: MAX_INTERNED_STRINGS, max_string_len: MAX_STRING_LEN, - strings: StraightHashMap::default(), + cache: StraightHashMap::default(), + filter: BloomFilterU64::new(), dummy: PhantomData, } } @@ -65,7 +71,7 @@ impl StringsInterner<'_> { #[inline(always)] #[must_use] pub fn get + Into>(&mut self, text: S) -> ImmutableString { - self.get_with_mapper(Into::into, text) + self.get_with_mapper("", Into::into, text) } /// Get an identifier from a text string, adding it to the interner if necessary. @@ -73,20 +79,23 @@ impl StringsInterner<'_> { #[must_use] pub fn get_with_mapper>( &mut self, + id: &str, mapper: impl Fn(S) -> ImmutableString, text: S, ) -> ImmutableString { let key = text.as_ref(); - if key.len() > MAX_STRING_LEN { + let hasher = &mut get_hasher(); + id.hash(hasher); + key.hash(hasher); + let hash = hasher.finish(); + + // Cache long strings only on the second try to avoid caching "one-hit wonders". + if key.len() > MAX_STRING_LEN && self.filter.is_absent_and_set(hash) { return mapper(text); } - let hasher = &mut get_hasher(); - key.hash(hasher); - let key = hasher.finish(); - - let result = match self.strings.entry(key) { + let result = match self.cache.entry(hash) { Entry::Occupied(e) => return e.get().clone(), Entry::Vacant(e) => { let value = mapper(text); @@ -94,49 +103,54 @@ impl StringsInterner<'_> { if value.strong_count() > 1 { return value; } - e.insert(value).clone() } }; - // If the interner is over capacity, remove the longest entry that has the lowest count - if self.strings.len() > self.capacity { - // Leave some buffer to grow when shrinking the cache. - // We leave at least two entries, one for the empty string, and one for the string - // that has just been inserted. - let max = if self.capacity < 5 { - 2 - } else { - self.capacity - 3 - }; - - while self.strings.len() > max { - let (_, _, n) = - self.strings - .iter() - .fold((0, usize::MAX, 0), |(x, c, n), (&k, v)| { - if k != key - && (v.strong_count() < c || (v.strong_count() == c && v.len() > x)) - { - (v.len(), v.strong_count(), k) - } else { - (x, c, n) - } - }); - - self.strings.remove(&n); - } - } + // Throttle the cache upon exit + self.throttle_cache(hash); result } + /// If the interner is over capacity, remove the longest entry that has the lowest count + fn throttle_cache(&mut self, hash: u64) { + if self.cache.len() <= self.capacity { + return; + } + + // Leave some buffer to grow when shrinking the cache. + // We leave at least two entries, one for the empty string, and one for the string + // that has just been inserted. + let max = if self.capacity < 5 { + 2 + } else { + self.capacity - 3 + }; + + while self.cache.len() > max { + let (_, _, n) = self + .cache + .iter() + .fold((0, usize::MAX, 0), |(x, c, n), (&k, v)| { + if k != hash && (v.strong_count() < c || (v.strong_count() == c && v.len() > x)) + { + (v.len(), v.strong_count(), k) + } else { + (x, c, n) + } + }); + + self.cache.remove(&n); + } + } + /// Number of strings interned. #[inline(always)] #[must_use] #[allow(dead_code)] pub fn len(&self) -> usize { - self.strings.len() + self.cache.len() } /// Returns `true` if there are no interned strings. @@ -144,28 +158,28 @@ impl StringsInterner<'_> { #[must_use] #[allow(dead_code)] pub fn is_empty(&self) -> bool { - self.strings.is_empty() + self.cache.is_empty() } /// Clear all interned strings. #[inline(always)] #[allow(dead_code)] pub fn clear(&mut self) { - self.strings.clear(); + self.cache.clear(); } } impl AddAssign for StringsInterner<'_> { #[inline(always)] fn add_assign(&mut self, rhs: Self) { - self.strings.extend(rhs.strings.into_iter()); + self.cache.extend(rhs.cache.into_iter()); } } impl AddAssign<&Self> for StringsInterner<'_> { #[inline(always)] fn add_assign(&mut self, rhs: &Self) { - self.strings - .extend(rhs.strings.iter().map(|(&k, v)| (k, v.clone()))); + self.cache + .extend(rhs.cache.iter().map(|(&k, v)| (k, v.clone()))); } } diff --git a/src/types/mod.rs b/src/types/mod.rs index 347bc85c..1fbf33d3 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -8,16 +8,20 @@ pub mod fn_ptr; pub mod immutable_string; pub mod interner; pub mod parse_error; +pub mod restore; pub mod scope; +pub mod variant; pub use bloom_filter::BloomFilterU64; pub use custom_types::{CustomTypeInfo, CustomTypesCollection}; pub use dynamic::Dynamic; -#[cfg(not(feature = "no_std"))] +#[cfg(not(feature = "no_time"))] pub use dynamic::Instant; pub use error::EvalAltResult; pub use fn_ptr::FnPtr; pub use immutable_string::ImmutableString; pub use interner::StringsInterner; pub use parse_error::{LexError, ParseError, ParseErrorType}; +pub use restore::RestoreOnDrop; pub use scope::Scope; +pub use variant::Variant; diff --git a/src/types/parse_error.rs b/src/types/parse_error.rs index 5cbdcb5d..86fedfdf 100644 --- a/src/types/parse_error.rs +++ b/src/types/parse_error.rs @@ -13,6 +13,7 @@ use std::prelude::v1::*; /// Error encountered when tokenizing the script text. #[derive(Debug, Eq, PartialEq, Clone, Hash)] #[non_exhaustive] +#[must_use] pub enum LexError { /// An unexpected symbol is encountered when tokenizing the script text. UnexpectedInput(String), @@ -37,11 +38,11 @@ impl Error for LexError {} impl fmt::Display for LexError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::UnexpectedInput(s) => write!(f, "Unexpected '{}'", s), - Self::MalformedEscapeSequence(s) => write!(f, "Invalid escape sequence: '{}'", s), - Self::MalformedNumber(s) => write!(f, "Invalid number: '{}'", s), - Self::MalformedChar(s) => write!(f, "Invalid character: '{}'", s), - Self::MalformedIdentifier(s) => write!(f, "Variable name is not proper: '{}'", s), + Self::UnexpectedInput(s) => write!(f, "Unexpected '{s}'"), + Self::MalformedEscapeSequence(s) => write!(f, "Invalid escape sequence: '{s}'"), + Self::MalformedNumber(s) => write!(f, "Invalid number: '{s}'"), + Self::MalformedChar(s) => write!(f, "Invalid character: '{s}'"), + Self::MalformedIdentifier(s) => write!(f, "Variable name is not proper: '{s}'"), Self::UnterminatedString => f.write_str("Open string is not terminated"), Self::StringTooLong(max) => write!( f, @@ -49,7 +50,7 @@ impl fmt::Display for LexError { max ), Self::ImproperSymbol(s, d) if d.is_empty() => { - write!(f, "Invalid symbol encountered: '{}'", s) + write!(f, "Invalid symbol encountered: '{s}'") } Self::ImproperSymbol(.., d) => f.write_str(d), } @@ -58,8 +59,8 @@ impl fmt::Display for LexError { impl LexError { /// Convert a [`LexError`] into a [`ParseError`]. - #[inline(always)] - #[must_use] + #[cold] + #[inline(never)] pub fn into_err(self, pos: Position) -> ParseError { ParseError(Box::new(self.into()), pos) } @@ -72,6 +73,7 @@ impl LexError { /// massive code changes to remove/add back enum variants in match statements. #[derive(Debug, Eq, PartialEq, Clone, Hash)] #[non_exhaustive] +#[must_use] pub enum ParseErrorType { /// The script ends prematurely. UnexpectedEOF, @@ -171,7 +173,8 @@ pub enum ParseErrorType { impl ParseErrorType { /// Make a [`ParseError`] using the current type and position. - #[inline(always)] + #[cold] + #[inline(never)] #[must_use] pub(crate) fn into_err(self, pos: Position) -> ParseError { ParseError(self.into(), pos) @@ -181,77 +184,69 @@ impl ParseErrorType { impl fmt::Display for ParseErrorType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::BadInput(err) => write!(f, "{}", err), + Self::BadInput(err) => write!(f, "{err}"), - Self::UnknownOperator(s) => write!(f, "Unknown operator: '{}'", s), + Self::UnknownOperator(s) => write!(f, "Unknown operator: '{s}'"), - Self::MalformedCallExpr(s) => match s.as_str() { - "" => f.write_str("Invalid expression in function call arguments"), - s => f.write_str(s) - }, - Self::MalformedIndexExpr(s) => match s.as_str() { - "" => f.write_str("Invalid index in indexing expression"), - s => f.write_str(s) - }, - Self::MalformedInExpr(s) => match s.as_str() { - "" => f.write_str("Invalid 'in' expression"), - s => f.write_str(s) - }, - Self::MalformedCapture(s) => match s.as_str() { - "" => f.write_str("Invalid capturing"), - s => f.write_str(s) - }, + Self::MalformedCallExpr(s) if s.is_empty() => f.write_str(s), + Self::MalformedCallExpr(..) => f.write_str("Invalid expression in function call arguments"), + + Self::MalformedIndexExpr(s) if s.is_empty() => f.write_str("Invalid index in indexing expression"), + Self::MalformedIndexExpr(s) => f.write_str(s), + + Self::MalformedInExpr(s) if s.is_empty() => f.write_str("Invalid 'in' expression"), + Self::MalformedInExpr(s) => f.write_str(s), + + Self::MalformedCapture(s) if s.is_empty() => f.write_str("Invalid capturing"), + Self::MalformedCapture(s) => f.write_str(s), Self::FnDuplicatedDefinition(s, n) => { - write!(f, "Function {} with ", s)?; + write!(f, "Function {s} with ")?; match n { 0 => f.write_str("no parameters already exists"), 1 => f.write_str("1 parameter already exists"), - _ => write!(f, "{} parameters already exists", n), + _ => write!(f, "{n} parameters already exists"), } } - Self::FnMissingBody(s) => match s.as_str() { - "" => f.write_str("Expecting body statement block for anonymous function"), - s => write!(f, "Expecting body statement block for function {}", s) - }, - Self::FnMissingParams(s) => write!(f, "Expecting parameters for function {}", s), - Self::FnDuplicatedParam(s, arg) => write!(f, "Duplicated parameter {} for function {}", arg, s), - Self::DuplicatedProperty(s) => write!(f, "Duplicated property for object map literal: {}", s), + Self::FnMissingBody(s) if s.is_empty() => f.write_str("Expecting body statement block for anonymous function"), + Self::FnMissingBody(s) => write!(f, "Expecting body statement block for function {s}"), + + Self::FnMissingParams(s) => write!(f, "Expecting parameters for function {s}"), + Self::FnDuplicatedParam(s, arg) => write!(f, "Duplicated parameter {arg} for function {s}"), + + Self::DuplicatedProperty(s) => write!(f, "Duplicated property for object map literal: {s}"), #[allow(deprecated)] Self::DuplicatedSwitchCase => f.write_str("Duplicated switch case"), - Self::DuplicatedVariable(s) => write!(f, "Duplicated variable name: {}", s), + Self::DuplicatedVariable(s) => write!(f, "Duplicated variable name: {s}"), - Self::VariableExists(s) => write!(f, "Variable already defined: {}", s), - Self::VariableUndefined(s) => write!(f, "Undefined variable: {}", s), - Self::ModuleUndefined(s) => write!(f, "Undefined module: {}", s), + Self::VariableExists(s) => write!(f, "Variable already defined: {s}"), + Self::VariableUndefined(s) => write!(f, "Undefined variable: {s}"), + Self::ModuleUndefined(s) => write!(f, "Undefined module: {s}"), - Self::MismatchedType(r, a) => write!(f, "Expecting {}, not {}", r, a), + Self::MismatchedType(r, a) => write!(f, "Expecting {r}, not {a}"), Self::ExprExpected(s) => write!(f, "Expecting {} expression", s), - Self::MissingToken(token, s) => write!(f, "Expecting '{}' {}", token, s), + Self::MissingToken(token, s) => write!(f, "Expecting '{token}' {s}"), Self::MissingSymbol(s) if s.is_empty() => f.write_str("Expecting a symbol"), Self::MissingSymbol(s) => f.write_str(s), - Self::AssignmentToConstant(s) => match s.as_str() { - "" => f.write_str("Cannot assign to a constant value"), - s => write!(f, "Cannot assign to constant {}", s) - }, - Self::AssignmentToInvalidLHS(s) => match s.as_str() { - "" => f.write_str("Expression cannot be assigned to"), - s => f.write_str(s) - }, + Self::AssignmentToConstant(s) if s.is_empty() => f.write_str("Cannot assign to a constant value"), + Self::AssignmentToConstant(s) => write!(f, "Cannot assign to constant {s}"), - Self::LiteralTooLarge(typ, max) => write!(f, "{} exceeds the maximum limit ({})", typ, max), - Self::Reserved(s) if is_valid_identifier(s.chars()) => write!(f, "'{}' is a reserved keyword", s), - Self::Reserved(s) => write!(f, "'{}' is a reserved symbol", s), + Self::AssignmentToInvalidLHS(s) if s.is_empty() => f.write_str("Expression cannot be assigned to"), + Self::AssignmentToInvalidLHS(s) => f.write_str(s), + + Self::LiteralTooLarge(typ, max) => write!(f, "{typ} exceeds the maximum limit ({max})"), + Self::Reserved(s) if is_valid_identifier(s.as_str()) => write!(f, "'{s}' is a reserved keyword"), + Self::Reserved(s) => write!(f, "'{s}' is a reserved symbol"), Self::UnexpectedEOF => f.write_str("Script is incomplete"), Self::WrongSwitchIntegerCase => f.write_str("Integer switch case cannot follow a range case"), Self::WrongSwitchDefaultCase => f.write_str("Default switch case must be the last"), Self::WrongSwitchCaseCondition => f.write_str("This switch case cannot have a condition"), Self::PropertyExpected => f.write_str("Expecting name of a property"), Self::VariableExpected => f.write_str("Expecting name of a variable"), - Self::ForbiddenVariable(s) => write!(f, "Forbidden variable name: {}", s), + Self::ForbiddenVariable(s) => write!(f, "Forbidden variable name: {s}"), Self::WrongFnDefinition => f.write_str("Function definitions must be at global level and cannot be inside a block or another function"), Self::FnMissingName => f.write_str("Expecting function name in function declaration"), Self::WrongDocComment => f.write_str("Doc-comment must be followed immediately by a function definition"), @@ -277,6 +272,7 @@ impl From for ParseErrorType { /// Error when parsing a script. #[derive(Debug, Eq, PartialEq, Clone, Hash)] +#[must_use] pub struct ParseError( /// Parse error type. pub Box, diff --git a/src/types/restore.rs b/src/types/restore.rs new file mode 100644 index 00000000..6d0ad595 --- /dev/null +++ b/src/types/restore.rs @@ -0,0 +1,65 @@ +//! Facility to run state restoration logic at the end of scope. + +use std::ops::{Deref, DerefMut}; +#[cfg(feature = "no_std")] +use std::prelude::v1::*; + +/// Run custom restoration logic upon the end of scope. +#[must_use] +pub struct RestoreOnDrop<'a, T, R: FnOnce(&mut T)> { + value: &'a mut T, + restore: Option, +} + +impl<'a, T, R: FnOnce(&mut T)> RestoreOnDrop<'a, T, R> { + /// Create a new [`RestoreOnDrop`] that locks a mutable reference and runs restoration logic at + /// the end of scope only when `need_restore` is `true`. + /// + /// Beware that the end of scope means the end of its lifetime, not necessarily waiting until + /// the current block scope is exited. + #[inline(always)] + pub fn lock_if(need_restore: bool, value: &'a mut T, restore: R) -> Self { + Self { + value, + restore: if need_restore { Some(restore) } else { None }, + } + } + + /// Create a new [`RestoreOnDrop`] that locks a mutable reference and runs restoration logic at + /// the end of scope. + /// + /// Beware that the end of scope means the end of its lifetime, not necessarily waiting until + /// the current block scope is exited. + #[inline(always)] + pub fn lock(value: &'a mut T, restore: R) -> Self { + Self { + value, + restore: Some(restore), + } + } +} + +impl<'a, T, R: FnOnce(&mut T)> Drop for RestoreOnDrop<'a, T, R> { + #[inline(always)] + fn drop(&mut self) { + if let Some(restore) = self.restore.take() { + restore(self.value); + } + } +} + +impl<'a, T, R: FnOnce(&mut T)> Deref for RestoreOnDrop<'a, T, R> { + type Target = T; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + self.value + } +} + +impl<'a, T, R: FnOnce(&mut T)> DerefMut for RestoreOnDrop<'a, T, R> { + #[inline(always)] + fn deref_mut(&mut self) -> &mut Self::Target { + self.value + } +} diff --git a/src/types/scope.rs b/src/types/scope.rs index 46dd1d1f..ba699c24 100644 --- a/src/types/scope.rs +++ b/src/types/scope.rs @@ -1,7 +1,7 @@ //! Module that defines the [`Scope`] type representing a function call-stack scope. use super::dynamic::{AccessMode, Variant}; -use crate::{Dynamic, Identifier}; +use crate::{Dynamic, Identifier, ImmutableString}; use smallvec::SmallVec; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -22,6 +22,14 @@ const SCOPE_ENTRIES_INLINED: usize = 8; /// Currently the lifetime parameter is not used, but it is not guaranteed to remain unused for /// future versions. Until then, `'static` can be used. /// +/// # Constant Generic Parameter +/// +/// There is a constant generic parameter that indicates how many entries to keep inline. +/// As long as the number of entries does not exceed this limit, no allocations occur. +/// The default is 8. +/// +/// A larger value makes [`Scope`] larger, but reduces the chance of allocations. +/// /// # Thread Safety /// /// Currently, [`Scope`] is neither [`Send`] nor [`Sync`]. Turn on the `sync` feature to make it @@ -61,19 +69,18 @@ const SCOPE_ENTRIES_INLINED: usize = 8; // // [`Dynamic`] is reasonably small so packing it tightly improves cache performance. #[derive(Debug, Hash, Default)] -pub struct Scope<'a> { +pub struct Scope<'a, const N: usize = SCOPE_ENTRIES_INLINED> { /// Current value of the entry. values: SmallVec<[Dynamic; SCOPE_ENTRIES_INLINED]>, /// Name of the entry. names: SmallVec<[Identifier; SCOPE_ENTRIES_INLINED]>, /// Aliases of the entry. - aliases: SmallVec<[Vec; SCOPE_ENTRIES_INLINED]>, + aliases: SmallVec<[Vec; SCOPE_ENTRIES_INLINED]>, /// Phantom to keep the lifetime parameter in order not to break existing code. dummy: PhantomData<&'a ()>, } impl fmt::Display for Scope<'_> { - #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { for (i, (name, constant, value)) in self.iter_raw().enumerate() { #[cfg(not(feature = "no_closure"))] @@ -118,10 +125,10 @@ impl Clone for Scope<'_> { } impl IntoIterator for Scope<'_> { - type Item = (String, Dynamic, Vec); + type Item = (String, Dynamic, Vec); type IntoIter = Box>; - #[inline] + #[must_use] fn into_iter(self) -> Self::IntoIter { Box::new( self.values @@ -133,10 +140,10 @@ impl IntoIterator for Scope<'_> { } impl<'a> IntoIterator for &'a Scope<'_> { - type Item = (&'a Identifier, &'a Dynamic, &'a Vec); + type Item = (&'a Identifier, &'a Dynamic, &'a Vec); type IntoIter = Box + 'a>; - #[inline] + #[must_use] fn into_iter(self) -> Self::IntoIter { Box::new( self.values @@ -170,6 +177,28 @@ impl Scope<'_> { dummy: PhantomData, } } + /// Create a new [`Scope`] with a particular capacity. + /// + /// # Example + /// + /// ``` + /// use rhai::Scope; + /// + /// let mut my_scope = Scope::with_capacity(10); + /// + /// my_scope.push("x", 42_i64); + /// assert_eq!(my_scope.get_value::("x").expect("x should exist"), 42); + /// ``` + #[inline(always)] + #[must_use] + pub fn with_capacity(capacity: usize) -> Self { + Self { + values: SmallVec::with_capacity(capacity), + names: SmallVec::with_capacity(capacity), + aliases: SmallVec::with_capacity(capacity), + dummy: PhantomData, + } + } /// Empty the [`Scope`]. /// /// # Example @@ -378,7 +407,7 @@ impl Scope<'_> { /// Find an entry in the [`Scope`], starting from the last. #[inline] #[must_use] - pub(crate) fn get_index(&self, name: &str) -> Option<(usize, AccessMode)> { + pub(crate) fn search(&self, name: &str) -> Option { let len = self.len(); self.names @@ -388,7 +417,7 @@ impl Scope<'_> { .find_map(|(i, key)| { if name == key { let index = len - 1 - i; - Some((index, self.values[index].access_mode())) + Some(index) } else { None } @@ -438,10 +467,11 @@ impl Scope<'_> { #[inline] #[must_use] pub fn is_constant(&self, name: &str) -> Option { - self.get_index(name).map(|(.., access)| match access { - AccessMode::ReadWrite => false, - AccessMode::ReadOnly => true, - }) + self.search(name) + .map(|n| match self.values[n].access_mode() { + AccessMode::ReadWrite => false, + AccessMode::ReadOnly => true, + }) } /// Update the value of the named entry in the [`Scope`] if it already exists and is not constant. /// Push a new entry with the value into the [`Scope`] if the name doesn't exist or if the @@ -474,7 +504,10 @@ impl Scope<'_> { name: impl AsRef + Into, value: impl Variant + Clone, ) -> &mut Self { - match self.get_index(name.as_ref()) { + match self + .search(name.as_ref()) + .map(|n| (n, self.values[n].access_mode())) + { None | Some((.., AccessMode::ReadOnly)) => { self.push(name, value); } @@ -513,7 +546,10 @@ impl Scope<'_> { name: impl AsRef + Into, value: impl Variant + Clone, ) -> &mut Self { - match self.get_index(name.as_ref()) { + match self + .search(name.as_ref()) + .map(|n| (n, self.values[n].access_mode())) + { None => { self.push(name, value); } @@ -547,7 +583,7 @@ impl Scope<'_> { #[inline(always)] #[must_use] pub fn get(&self, name: &str) -> Option<&Dynamic> { - self.get_index(name).map(|(index, _)| &self.values[index]) + self.search(name).map(|index| &self.values[index]) } /// Remove the last entry in the [`Scope`] by the specified name and return its value. /// @@ -578,7 +614,7 @@ impl Scope<'_> { #[inline(always)] #[must_use] pub fn remove(&mut self, name: &str) -> Option { - self.get_index(name).and_then(|(index, _)| { + self.search(name).and_then(|index| { self.names.remove(index); self.aliases.remove(index); self.values.remove(index).try_cast() @@ -610,9 +646,9 @@ impl Scope<'_> { #[inline] #[must_use] pub fn get_mut(&mut self, name: &str) -> Option<&mut Dynamic> { - self.get_index(name) - .and_then(move |(index, access)| match access { - AccessMode::ReadWrite => Some(self.get_mut_by_index(index)), + self.search(name) + .and_then(move |n| match self.values[n].access_mode() { + AccessMode::ReadWrite => Some(self.get_mut_by_index(n)), AccessMode::ReadOnly => None, }) } @@ -633,7 +669,7 @@ impl Scope<'_> { /// Panics if the index is out of bounds. #[cfg(not(feature = "no_module"))] #[inline] - pub(crate) fn add_alias_by_index(&mut self, index: usize, alias: Identifier) -> &mut Self { + pub(crate) fn add_alias_by_index(&mut self, index: usize, alias: ImmutableString) -> &mut Self { let aliases = self.aliases.get_mut(index).unwrap(); if aliases.is_empty() || !aliases.contains(&alias) { aliases.push(alias); @@ -654,11 +690,11 @@ impl Scope<'_> { pub fn set_alias( &mut self, name: impl AsRef + Into, - alias: impl Into, + alias: impl Into, ) { - if let Some((index, ..)) = self.get_index(name.as_ref()) { + if let Some(index) = self.search(name.as_ref()) { let alias = match alias.into() { - x if x.is_empty() => name.into(), + x if x.is_empty() => name.into().into(), x => x, }; self.add_alias_by_index(index, alias); @@ -690,9 +726,10 @@ impl Scope<'_> { scope } /// Get an iterator to entries in the [`Scope`]. - #[inline] #[allow(dead_code)] - pub(crate) fn into_iter(self) -> impl Iterator)> { + pub(crate) fn into_iter( + self, + ) -> impl Iterator)> { self.names .into_iter() .zip(self.values.into_iter().zip(self.aliases.into_iter())) diff --git a/src/types/variant.rs b/src/types/variant.rs new file mode 100644 index 00000000..0e1d4e6c --- /dev/null +++ b/src/types/variant.rs @@ -0,0 +1,105 @@ +//! [`Variant`] trait to to allow custom type handling. + +use crate::func::SendSync; +use std::any::{type_name, Any, TypeId}; +#[cfg(feature = "no_std")] +use std::prelude::v1::*; + +mod private { + use crate::func::SendSync; + use std::any::Any; + + /// A sealed trait that prevents other crates from implementing [`Variant`][super::Variant]. + pub trait Sealed {} + + impl Sealed for T {} +} + +/// _(internals)_ Trait to represent any type. +/// Exported under the `internals` feature only. +/// +/// This trait is sealed and cannot be implemented. +/// +/// Currently, [`Variant`] is not [`Send`] nor [`Sync`], so it can practically be any type. +/// Turn on the `sync` feature to restrict it to only types that implement [`Send`] `+` [`Sync`]. +#[cfg(not(feature = "sync"))] +pub trait Variant: Any + private::Sealed { + /// Convert this [`Variant`] trait object to [`&dyn Any`][Any]. + #[must_use] + fn as_any(&self) -> &dyn Any; + + /// Convert this [`Variant`] trait object to [`&mut dyn Any`][Any]. + #[must_use] + fn as_any_mut(&mut self) -> &mut dyn Any; + + /// Convert this [`Variant`] trait object to [`Box`][Any]. + #[must_use] + fn as_boxed_any(self: Box) -> Box; + + /// Get the name of this type. + #[must_use] + fn type_name(&self) -> &'static str; + + /// Clone this [`Variant`] trait object. + #[must_use] + fn clone_object(&self) -> Box; +} + +/// _(internals)_ Trait to represent any type. +/// Exported under the `internals` feature only. +/// +/// This trait is sealed and cannot be implemented. +#[cfg(feature = "sync")] +pub trait Variant: Any + Send + Sync + private::Sealed { + /// Convert this [`Variant`] trait object to [`&dyn Any`][Any]. + #[must_use] + fn as_any(&self) -> &dyn Any; + + /// Convert this [`Variant`] trait object to [`&mut dyn Any`][Any]. + #[must_use] + fn as_any_mut(&mut self) -> &mut dyn Any; + + /// Convert this [`Variant`] trait object to [`Box`][Any]. + #[must_use] + fn as_boxed_any(self: Box) -> Box; + + /// Get the name of this type. + #[must_use] + fn type_name(&self) -> &'static str; + + /// Clone this [`Variant`] trait object. + #[must_use] + fn clone_object(&self) -> Box; +} + +impl Variant for T { + #[inline(always)] + fn as_any(&self) -> &dyn Any { + self + } + #[inline(always)] + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } + #[inline(always)] + fn as_boxed_any(self: Box) -> Box { + self + } + #[inline(always)] + fn type_name(&self) -> &'static str { + type_name::() + } + #[inline(always)] + fn clone_object(&self) -> Box { + Box::new(self.clone()) as Box + } +} + +impl dyn Variant { + /// Is this [`Variant`] a specific type? + #[inline(always)] + #[must_use] + pub fn is(&self) -> bool { + TypeId::of::() == self.type_id() + } +} diff --git a/tests/build_type.rs b/tests/build_type.rs index 05b23bc2..4844a3b4 100644 --- a/tests/build_type.rs +++ b/tests/build_type.rs @@ -49,6 +49,8 @@ fn build_type() -> Result<(), Box> { type Item = INT; type IntoIter = std::vec::IntoIter; + #[inline] + #[must_use] fn into_iter(self) -> Self::IntoIter { vec![self.x, self.y, self.z].into_iter() } diff --git a/tests/constants.rs b/tests/constants.rs index b7df1b3a..bc49e8ee 100644 --- a/tests/constants.rs +++ b/tests/constants.rs @@ -87,7 +87,7 @@ fn test_constant_mut() -> Result<(), Box> { " ) .expect_err("should error"), - EvalAltResult::ErrorAssignmentToConstant(..) + EvalAltResult::ErrorNonPureMethodCallOnConstant(..) )); let mut scope = Scope::new(); @@ -120,7 +120,7 @@ fn test_constant_mut() -> Result<(), Box> { *engine .run_with_scope(&mut scope, "MY_NUMBER.value = 42;") .expect_err("should error"), - EvalAltResult::ErrorAssignmentToConstant(..) + EvalAltResult::ErrorNonPureMethodCallOnConstant(..) )); Ok(()) diff --git a/tests/custom_syntax.rs b/tests/custom_syntax.rs index 2c8db7f1..dacca728 100644 --- a/tests/custom_syntax.rs +++ b/tests/custom_syntax.rs @@ -256,10 +256,18 @@ fn test_custom_syntax_raw() -> Result<(), Box> { engine.register_custom_syntax_with_state_raw( "hello", - |stream, _, state| match stream.len() { + |stream, look_ahead, state| match stream.len() { 0 => unreachable!(), - 1 => Ok(Some("$ident$".into())), + 1 if look_ahead == "\"world\"" => { + *state = Dynamic::TRUE; + Ok(Some("$string$".into())) + } + 1 => { + *state = Dynamic::FALSE; + Ok(Some("$ident$".into())) + } 2 => match stream[1].as_str() { + "world" if state.as_bool().unwrap_or(false) => Ok(Some("$$world".into())), "world" => Ok(Some("$$hello".into())), "kitty" => { *state = (42 as INT).into(); @@ -276,16 +284,11 @@ fn test_custom_syntax_raw() -> Result<(), Box> { context.scope_mut().push("foo", 999 as INT); Ok(match inputs[0].get_string_value().unwrap() { - "world" - if inputs - .last() - .unwrap() - .get_string_value() - .map_or(false, |s| s == "$$hello") => - { - 0 as INT - } - "world" => 123 as INT, + "world" => match inputs.last().unwrap().get_string_value().unwrap_or("") { + "$$hello" => 0 as INT, + "$$world" => 123456 as INT, + _ => 123 as INT, + }, "kitty" if inputs.len() > 1 => 999 as INT, "kitty" => state.as_int().unwrap(), _ => unreachable!(), @@ -294,6 +297,7 @@ fn test_custom_syntax_raw() -> Result<(), Box> { }, ); + assert_eq!(engine.eval::(r#"hello "world""#)?, 123456); assert_eq!(engine.eval::("hello world")?, 0); assert_eq!(engine.eval::("hello kitty")?, 42); assert_eq!( diff --git a/tests/expressions.rs b/tests/expressions.rs index 4d0b4888..ed5e3a16 100644 --- a/tests/expressions.rs +++ b/tests/expressions.rs @@ -55,10 +55,13 @@ fn test_expressions() -> Result<(), Box> { ) .is_err()); - assert!(engine.eval_expression::<()>("40 + 2;").is_err()); - assert!(engine.eval_expression::<()>("40 + { 2 }").is_err()); - assert!(engine.eval_expression::<()>("x = 42").is_err()); + assert!(engine.compile_expression("40 + 2;").is_err()); + assert!(engine.compile_expression("40 + { 2 }").is_err()); + assert!(engine.compile_expression("x = 42").is_err()); assert!(engine.compile_expression("let x = 42").is_err()); + assert!(engine + .compile_expression("do { break 42; } while true") + .is_err()); engine.compile("40 + { let x = 2; x }")?; diff --git a/tests/for.rs b/tests/for.rs index 62112a12..48139e70 100644 --- a/tests/for.rs +++ b/tests/for.rs @@ -231,6 +231,25 @@ fn test_for_loop() -> Result<(), Box> { ); } + #[cfg(not(feature = "no_index"))] + #[cfg(not(feature = "no_object"))] + #[cfg(not(feature = "no_float"))] + assert_eq!( + engine.eval::( + r#" + let a = [123, 999, 42, 0, true, "hello", "world!", 987.654]; + + for (item, count) in a { + switch item.type_of() { + "i64" | "i32" if item.is_even => break count, + "f64" | "f32" if item.to_int().is_even => break count, + } + } + "# + )?, + 2 + ); + Ok(()) } @@ -337,6 +356,8 @@ impl IntoIterator for MyIterableType { type Item = char; type IntoIter = std::vec::IntoIter; + #[inline] + #[must_use] fn into_iter(self) -> Self::IntoIter { self.0.chars().collect::>().into_iter() } diff --git a/tests/maps.rs b/tests/maps.rs index 3cb414db..2620738d 100644 --- a/tests/maps.rs +++ b/tests/maps.rs @@ -276,6 +276,11 @@ fn test_map_json() -> Result<(), Box> { EvalAltResult::ErrorMismatchOutputType(..) )); + assert!(matches!( + *engine.parse_json("{a:42}", true).expect_err("should error"), + EvalAltResult::ErrorParsing(..) + )); + assert!(matches!( *engine .parse_json("#{a:123}", true) diff --git a/tests/modules.rs b/tests/modules.rs index 77e60b2b..babc862a 100644 --- a/tests/modules.rs +++ b/tests/modules.rs @@ -2,7 +2,7 @@ use rhai::{ module_resolvers::{DummyModuleResolver, StaticModuleResolver}, Dynamic, Engine, EvalAltResult, FnNamespace, FnPtr, ImmutableString, Module, NativeCallContext, - ParseError, ParseErrorType, Scope, Shared, INT, + ParseError, ParseErrorType, Scope, INT, }; #[test] @@ -546,44 +546,13 @@ fn test_module_context() -> Result<(), Box> { engine.register_fn( "calc", |context: NativeCallContext, fp: FnPtr| -> Result> { - // Store fields for later use let engine = context.engine(); - let fn_name = context.fn_name().to_string(); - let source = context.source().map(|s| s.to_string()); - let global = context.global_runtime_state().unwrap().clone(); - let pos = context.position(); - let call_level = context.call_level(); - // Store the paths of the stack of call modules up to this point - let modules_list: Vec = context - .iter_namespaces() - .map(|m| m.id().unwrap_or("testing")) - .filter(|id| !id.is_empty()) - .map(|id| id.to_string()) - .collect(); + // Store context for later use - requires the 'internals' feature + let context_data = context.store_data(); - // Recreate the 'NativeCallContext' - requires the 'internals' feature - let mut libraries = Vec::>::new(); - - for path in modules_list { - // Recreate the stack of call modules by resolving each path with - // the module resolver. - let module = engine.module_resolver().resolve(engine, None, &path, pos)?; - - libraries.push(module); - } - - let lib: Vec<&Module> = libraries.iter().map(|m| m.as_ref()).collect(); - - let new_context = NativeCallContext::new_with_all_fields( - engine, - &fn_name, - source.as_ref().map(|s| s.as_str()), - &global, - &lib, - pos, - call_level, - ); + // Recreate the 'NativeCallContext' + let new_context = context_data.create_context(engine); fp.call_within_context(&new_context, (41 as INT,)) }, diff --git a/tests/operations.rs b/tests/operations.rs index d6b7b69f..c4eb37ab 100644 --- a/tests/operations.rs +++ b/tests/operations.rs @@ -10,7 +10,7 @@ fn test_max_operations() -> Result<(), Box> { engine.on_progress(|count| { if count % 100 == 0 { - println!("{}", count); + println!("{count}"); } None }); @@ -68,7 +68,7 @@ fn test_max_operations_functions() -> Result<(), Box> { engine.on_progress(|count| { if count % 100 == 0 { - println!("{}", count); + println!("{count}"); } None }); @@ -124,7 +124,7 @@ fn test_max_operations_eval() -> Result<(), Box> { engine.on_progress(|count| { if count % 100 == 0 { - println!("{}", count); + println!("{count}"); } None }); diff --git a/tests/optimizer.rs b/tests/optimizer.rs index 248a15f0..b72cab02 100644 --- a/tests/optimizer.rs +++ b/tests/optimizer.rs @@ -89,21 +89,21 @@ fn test_optimizer_parse() -> Result<(), Box> { assert_eq!( format!("{ast:?}"), - r#"AST { source: "", doc: "", resolver: None, body: [Expr(123 @ 1:53)] }"# + r#"AST { source: None, doc: "", resolver: None, body: [Expr(123 @ 1:53)] }"# ); let ast = engine.compile("const DECISION = false; if DECISION { 42 } else { 123 }")?; assert_eq!( format!("{ast:?}"), - r#"AST { source: "", doc: "", resolver: None, body: [Var(("DECISION" @ 1:7, false @ 1:18, None), CONSTANT, 1:1), Expr(123 @ 1:51)] }"# + r#"AST { source: None, doc: "", resolver: None, body: [Var(("DECISION" @ 1:7, false @ 1:18, None), CONSTANT, 1:1), Expr(123 @ 1:51)] }"# ); let ast = engine.compile("if 1 == 2 { 42 }")?; assert_eq!( format!("{ast:?}"), - r#"AST { source: "", doc: "", resolver: None, body: [] }"# + r#"AST { source: None, doc: "", resolver: None, body: [] }"# ); engine.set_optimization_level(OptimizationLevel::Full); @@ -112,14 +112,14 @@ fn test_optimizer_parse() -> Result<(), Box> { assert_eq!( format!("{ast:?}"), - r#"AST { source: "", doc: "", resolver: None, body: [Expr(42 @ 1:1)] }"# + r#"AST { source: None, doc: "", resolver: None, body: [Expr(42 @ 1:1)] }"# ); let ast = engine.compile("NUMBER")?; assert_eq!( format!("{ast:?}"), - r#"AST { source: "", doc: "", resolver: None, body: [Expr(Variable(NUMBER) @ 1:1)] }"# + r#"AST { source: None, doc: "", resolver: None, body: [Expr(Variable(NUMBER) @ 1:1)] }"# ); let mut module = Module::new(); @@ -131,7 +131,7 @@ fn test_optimizer_parse() -> Result<(), Box> { assert_eq!( format!("{ast:?}"), - r#"AST { source: "", doc: "", resolver: None, body: [Expr(42 @ 1:1)] }"# + r#"AST { source: None, doc: "", resolver: None, body: [Expr(42 @ 1:1)] }"# ); Ok(()) diff --git a/tests/plugins.rs b/tests/plugins.rs index d5072eb9..ab752bc3 100644 --- a/tests/plugins.rs +++ b/tests/plugins.rs @@ -2,10 +2,10 @@ #![cfg(not(feature = "no_module"))] use rhai::plugin::*; -use rhai::{Engine, EvalAltResult, INT}; +use rhai::{Engine, EvalAltResult, Scope, INT}; mod test { - use rhai::plugin::*; + use super::*; #[export_module] pub mod special_array_package { @@ -42,7 +42,7 @@ mod test { #[rhai_fn(name = "no_effect", set = "no_effect", pure)] pub fn no_effect(array: &mut Array, value: INT) { // array is not modified - println!("Array = {:?}, Value = {}", array, value); + println!("Array = {array:?}, Value = {value}"); } } } @@ -119,7 +119,7 @@ fn test_plugins_package() -> Result<(), Box> { assert!( matches!(*engine.run("const A = [1, 2, 3]; A.test(42);").expect_err("should error"), - EvalAltResult::ErrorAssignmentToConstant(x, ..) if x == "array") + EvalAltResult::ErrorNonPureMethodCallOnConstant(x, ..) if x == "test") ) } @@ -170,3 +170,57 @@ fn test_plugins_parameters() -> Result<(), Box> { Ok(()) } + +#[cfg(target_pointer_width = "64")] +mod handle { + use super::*; + + #[derive(Debug, Eq, PartialEq, Clone, Copy, Hash)] + pub struct WorldHandle(usize); + pub type World = Vec; + + impl From<&mut World> for WorldHandle { + fn from(world: &mut World) -> Self { + Self::new(world) + } + } + + impl AsMut for WorldHandle { + fn as_mut(&mut self) -> &mut World { + unsafe { std::mem::transmute(self.0) } + } + } + + impl WorldHandle { + pub fn new(world: &mut World) -> Self { + Self(unsafe { std::mem::transmute(world) }) + } + } + + #[export_module] + pub mod handle_module { + pub type Handle = WorldHandle; + + #[rhai_fn(get = "len")] + pub fn len(world: &mut Handle) -> INT { + world.as_mut().len() as INT + } + } + + #[test] + fn test_module_handle() -> Result<(), Box> { + let mut engine = Engine::new(); + + engine.register_global_module(exported_module!(handle_module).into()); + + let mut scope = Scope::new(); + + let world: &mut World = &mut vec![42]; + scope.push("world", WorldHandle::from(world)); + + #[cfg(not(feature = "no_object"))] + assert_eq!(engine.eval_with_scope::(&mut scope, "world.len")?, 1); + + Ok(()) + } +} diff --git a/tests/print.rs b/tests/print.rs index 820cce58..982b2ff2 100644 --- a/tests/print.rs +++ b/tests/print.rs @@ -66,7 +66,7 @@ fn test_print_debug() -> Result<(), Box> { ); for entry in logbook.read().unwrap().iter() { - println!("{}", entry); + println!("{entry}"); } Ok(()) diff --git a/tests/serde.rs b/tests/serde.rs index 5df11139..4a08bb81 100644 --- a/tests/serde.rs +++ b/tests/serde.rs @@ -2,7 +2,7 @@ use rhai::{ serde::{from_dynamic, to_dynamic}, - Dynamic, Engine, EvalAltResult, ImmutableString, INT, + Dynamic, Engine, EvalAltResult, ImmutableString, Scope, INT, }; use serde::{Deserialize, Serialize}; use serde_json::json; @@ -820,6 +820,37 @@ fn test_serde_blob() -> Result<(), Box> { #[cfg(not(feature = "no_object"))] fn test_serde_json_borrowed_string() { let value = json!({ "a": "b" }); - println!("value: {:?}", value); + println!("value: {value:?}"); let _: Dynamic = serde_json::from_value(value).unwrap(); } + +#[test] +#[cfg(not(feature = "no_object"))] +fn test_serde_scope() { + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] + struct TestStruct { + foo: Option, + } + + let mut scope = Scope::new(); + scope.push("x", 42 as INT); + scope.push_constant("y", true); + scope.push("z", TestStruct { foo: None }); + + let json = serde_json::to_string(&scope).unwrap(); + + assert_eq!( + json, + r#"[{"name":"x","value":42},{"name":"y","value":true,"is_constant":true},{"name":"z","value":"serde::test_serde_scope::TestStruct"}]"# + ); + + scope = serde_json::from_str(&json).unwrap(); + + assert_eq!(scope.len(), 3); + assert_eq!(scope.get_value::("x").unwrap(), 42); + assert_eq!(scope.get_value::("y").unwrap(), true); + assert_eq!( + scope.get_value::("z").unwrap(), + "serde::test_serde_scope::TestStruct" + ); +} diff --git a/tests/switch.rs b/tests/switch.rs index 8008ccf2..2a4aed98 100644 --- a/tests/switch.rs +++ b/tests/switch.rs @@ -52,7 +52,7 @@ fn test_switch() -> Result<(), Box> { 123 ); assert_eq!( - engine.eval::("let x = timestamp(); switch x { 1 => 123, _ => 42 }")?, + engine.eval_with_scope::(&mut scope, "switch x { 424242 => 123, _ => 42 }")?, 42 ); assert_eq!( diff --git a/tests/time.rs b/tests/time.rs index 7a35da15..5434faa5 100644 --- a/tests/time.rs +++ b/tests/time.rs @@ -1,5 +1,4 @@ -#![cfg(not(feature = "no_std"))] -#![cfg(not(target_family = "wasm"))] +#![cfg(not(feature = "no_time"))] use rhai::{Engine, EvalAltResult}; diff --git a/tests/var_scope.rs b/tests/var_scope.rs index 73701746..e249b6de 100644 --- a/tests/var_scope.rs +++ b/tests/var_scope.rs @@ -140,7 +140,7 @@ fn test_scope_eval() -> Result<(), Box> { // Second invocation using the same state let result = engine.eval_with_scope::(&mut scope, "x")?; - println!("result: {}", result); // should print 966 + println!("result: {result}"); // should print 966 // Variable y is changed in the script assert_eq!( diff --git a/tests/while_loop.rs b/tests/while_loop.rs index 4e18a371..ebcdb8a0 100644 --- a/tests/while_loop.rs +++ b/tests/while_loop.rs @@ -22,6 +22,22 @@ fn test_while() -> Result<(), Box> { 6 ); + assert_eq!( + engine.eval::( + " + let x = 0; + + while x < 10 { + x += 1; + if x > 5 { break x * 2; } + if x > 3 { continue; } + x += 3; + } + ", + )?, + 12 + ); + Ok(()) } @@ -46,6 +62,25 @@ fn test_do() -> Result<(), Box> { )?, 6 ); + assert_eq!( + engine.eval::( + " + let x = 0; + + do { + x += 1; + if x > 5 { break x * 2; } + if x > 3 { continue; } + x += 3; + } while x < 10; + ", + )?, + 12 + ); + + engine.run("do {} while false")?; + + assert_eq!(engine.eval::("do { break 42; } while false")?, 42); Ok(()) }