diff --git a/CHANGELOG.md b/CHANGELOG.md index b5f152ee..f00032aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,36 @@ Rhai Release Notes ================== +Version 1.6.1 +============= + +Bug fixes +--------- + +* Functions with `Dynamic` parameters now work in qualified calls from `import`ed modules. +* `rhai-repl` now compiles with the new patch version of `rustyline`. + +Script-breaking changes +----------------------- + +* `split` now splits a string by whitespaces instead of splitting it into individual characters. This is more in line with common practices. +* A new function `to_chars` for strings is added to split the string into individual characters. + +Enhancements +------------ + +* Strings are now directly iterable (via `for .. in`) yielding individual characters. + + Version 1.6.0 ============= +This version, in particular, fixes a plugin macro hygiene error for the nightly compiler: + +```text +error[E0425]: cannot find value `args` in this scope +``` + Compiler version ---------------- @@ -12,6 +39,7 @@ Compiler version Bug fixes --------- +* Fixed macro hygiene error with nightly compiler. * Invalid property or method access such as `a.b::c.d` or `a.b::func()` no longer panics but properly returns a syntax error. * `Scope::is_constant` now returns the correct value. * Exporting a variable that contains a local function pointer (including anonymous function or closure) now raises a runtime error. diff --git a/Cargo.toml b/Cargo.toml index 03b3fe9a..47de2213 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ members = [".", "codegen"] [package] name = "rhai" -version = "1.6.0" +version = "1.6.1" rust-version = "1.57" edition = "2018" authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung", "jhwgh1968"] @@ -22,7 +22,7 @@ ahash = { version = "0.7", default-features = false } num-traits = { version = "0.2", default-features = false } bitflags = { version = "1", default-features = false } smartstring = { version = "1", default-features = false } -rhai_codegen = { version = "1.2", path = "codegen", default-features = false } +rhai_codegen = { version = "1.4", path = "codegen", default-features = false } no-std-compat = { version = "0.4", default-features = false, features = ["alloc"], optional = true } libm = { version = "0.2", default-features = false, optional = true } @@ -31,9 +31,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 } -# notice that a custom modified version of `rustyline` is used which supports bracketed paste on Windows -# this can be moved to the official version when bracketed paste is added -rustyline = { version = "9", optional = true, git = "https://github.com/schungx/rustyline" } +rustyline = { version = "9", optional = true } [dev-dependencies] serde_bytes = "0.11" @@ -91,3 +89,8 @@ instant = { version = "0.1.10" } # WASM implementation of std::time::Instant [package.metadata.docs.rs] features = ["metadata", "serde", "internals", "decimal", "debugging"] + +[patch.crates-io] +# Notice that a custom modified version of `rustyline` is used which supports bracketed paste on Windows. +# This can be moved to the official version when bracketed paste is added. +rustyline = { git = "https://github.com/schungx/rustyline" } diff --git a/README.md b/README.md index 23a28ba1..b070ca1e 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ Rhai - Embedded Scripting for Rust -================================= +================================== ![GitHub last commit](https://img.shields.io/github/last-commit/rhaiscript/rhai?logo=github) [![Build Status](https://github.com/rhaiscript/rhai/workflows/Build/badge.svg)](https://github.com/rhaiscript/rhai/actions) @@ -11,6 +11,7 @@ Rhai - Embedded Scripting for Rust [![VS Code plugin installs](https://img.shields.io/visual-studio-marketplace/i/rhaiscript.vscode-rhai?logo=visual-studio-code&label=vs%20code)](https://marketplace.visualstudio.com/items?itemName=rhaiscript.vscode-rhai) [![Sublime Text package downloads](https://img.shields.io/packagecontrol/dt/Rhai.svg?logo=sublime-text&label=sublime%20text)](https://packagecontrol.io/packages/Rhai) [![Discord Chat](https://img.shields.io/discord/767611025456889857.svg?logo=discord&label=discord)](https://discord.gg/HquqbYFcZ9) +[![Forum](https://img.shields.io/discourse/topics?server=https%3A%2F%2Frhai.discourse.group&logo=discourse&label=forum)](https://rhai.discourse.group/) [![Zulip Chat](https://img.shields.io/badge/zulip-join_chat-brightgreen.svg?logo=zulip)](https://rhaiscript.zulipchat.com) [![Reddit Channel](https://img.shields.io/reddit/subreddit-subscribers/Rhai?logo=reddit&label=reddit)](https://www.reddit.com/r/Rhai) @@ -65,7 +66,7 @@ Protected against attacks For those who actually want their own language ---------------------------------------------- +---------------------------------------------- * Use as a [DSL](https://rhai.rs/book/engine/dsl.html). * Disable certain [language features](https://rhai.rs/book/engine/options.html#language-features) such as [looping](https://rhai.rs/book/engine/disable-looping.html). diff --git a/codegen/ui_tests/non_clonable.stderr b/codegen/ui_tests/non_clonable.stderr index 8a8fc1f1..5e50c242 100644 --- a/codegen/ui_tests/non_clonable.stderr +++ b/codegen/ui_tests/non_clonable.stderr @@ -12,3 +12,7 @@ note: required by a bound in `rhai::Dynamic::cast` | pub fn cast(self) -> T { | ^^^^^ required by this bound in `rhai::Dynamic::cast` = note: this error originates in the attribute macro `export_fn` (in Nightly builds, run with -Z macro-backtrace for more info) +help: consider annotating `NonClonable` with `#[derive(Clone)]` + | +3 | #[derive(Clone)] + | diff --git a/codegen/ui_tests/non_clonable_second.stderr b/codegen/ui_tests/non_clonable_second.stderr index 413c3a70..a4c40461 100644 --- a/codegen/ui_tests/non_clonable_second.stderr +++ b/codegen/ui_tests/non_clonable_second.stderr @@ -12,3 +12,7 @@ note: required by a bound in `rhai::Dynamic::cast` | pub fn cast(self) -> T { | ^^^^^ required by this bound in `rhai::Dynamic::cast` = note: this error originates in the attribute macro `export_fn` (in Nightly builds, run with -Z macro-backtrace for more info) +help: consider annotating `NonClonable` with `#[derive(Clone)]` + | +3 | #[derive(Clone)] + | diff --git a/codegen/ui_tests/rhai_fn_non_clonable_return.stderr b/codegen/ui_tests/rhai_fn_non_clonable_return.stderr index efcd40de..78260a4a 100644 --- a/codegen/ui_tests/rhai_fn_non_clonable_return.stderr +++ b/codegen/ui_tests/rhai_fn_non_clonable_return.stderr @@ -12,3 +12,7 @@ note: required by a bound in `rhai::Dynamic::from` | pub fn from(value: T) -> Self { | ^^^^^ required by this bound in `rhai::Dynamic::from` = note: this error originates in the attribute macro `export_fn` (in Nightly builds, run with -Z macro-backtrace for more info) +help: consider annotating `NonClonable` with `#[derive(Clone)]` + | +3 | #[derive(Clone)] + | diff --git a/codegen/ui_tests/rhai_mod_non_clonable_return.stderr b/codegen/ui_tests/rhai_mod_non_clonable_return.stderr index d96fe2c8..7e646bcd 100644 --- a/codegen/ui_tests/rhai_mod_non_clonable_return.stderr +++ b/codegen/ui_tests/rhai_mod_non_clonable_return.stderr @@ -13,3 +13,7 @@ note: required by a bound in `rhai::Dynamic::from` | pub fn from(value: T) -> Self { | ^^^^^ required by this bound in `rhai::Dynamic::from` = note: this error originates in the attribute macro `export_module` (in Nightly builds, run with -Z macro-backtrace for more info) +help: consider annotating `NonClonable` with `#[derive(Clone)]` + | +3 | #[derive(Clone)] + | diff --git a/src/api/type_names.rs b/src/api/type_names.rs index 0b36603e..ea76ffa4 100644 --- a/src/api/type_names.rs +++ b/src/api/type_names.rs @@ -1,3 +1,4 @@ +use crate::packages::iter_basic::{BitRange, CharsStream, StepRange}; use crate::{ Engine, ExclusiveRange, FnPtr, ImmutableString, InclusiveRange, Position, RhaiError, ERR, }; @@ -65,6 +66,41 @@ fn map_std_type_name(name: &str, shorthands: bool) -> &str { "RangeInclusive" }; } + if name == type_name::() { + return if shorthands { "range" } else { "BitRange" }; + } + if name == type_name::() { + return if shorthands { "range" } else { "CharStream" }; + } + + let step_range_name = type_name::>(); + let step_range_name = &step_range_name[..step_range_name.len() - 3]; + + if name.starts_with(step_range_name) && name.ends_with('>') { + return if shorthands { + "range" + } else { + let step_range_name = step_range_name.split("::").last().unwrap(); + &step_range_name[..step_range_name.len() - 1] + }; + } + + #[cfg(not(feature = "no_float"))] + if name == type_name::() { + return if shorthands { + "range" + } else { + "StepFloatRange" + }; + } + #[cfg(feature = "decimal")] + if name == type_name::() { + return if shorthands { + "range" + } else { + "StepDecimalRange" + }; + } if name.starts_with("rhai::") { map_std_type_name(&name[6..], shorthands) diff --git a/src/ast/expr.rs b/src/ast/expr.rs index 52a11a29..6bad94a7 100644 --- a/src/ast/expr.rs +++ b/src/ast/expr.rs @@ -636,9 +636,7 @@ impl Expr { /// `non_qualified` is ignored under `no_module`. #[inline] #[must_use] - pub(crate) fn is_variable_access(&self, non_qualified: bool) -> bool { - let _non_qualified = non_qualified; - + pub(crate) fn is_variable_access(&self, _non_qualified: bool) -> bool { match self { #[cfg(not(feature = "no_module"))] Self::Variable(x, ..) if _non_qualified && !x.1.is_empty() => false, @@ -651,9 +649,7 @@ impl Expr { /// `non_qualified` is ignored under `no_module`. #[inline] #[must_use] - pub(crate) fn get_variable_name(&self, non_qualified: bool) -> Option<&str> { - let _non_qualified = non_qualified; - + pub(crate) fn get_variable_name(&self, _non_qualified: bool) -> Option<&str> { match self { #[cfg(not(feature = "no_module"))] Self::Variable(x, ..) if _non_qualified && !x.1.is_empty() => None, diff --git a/src/bin/rhai-dbg.rs b/src/bin/rhai-dbg.rs index 2fa211aa..7656c31a 100644 --- a/src/bin/rhai-dbg.rs +++ b/src/bin/rhai-dbg.rs @@ -321,7 +321,7 @@ fn debug_callback( let mut input = String::new(); loop { - print!("rhai-dbg> "); + print!("dbg> "); stdout().flush().expect("couldn't flush stdout"); diff --git a/src/bin/rhai-repl.rs b/src/bin/rhai-repl.rs index 87494edf..c0dfa093 100644 --- a/src/bin/rhai-repl.rs +++ b/src/bin/rhai-repl.rs @@ -3,7 +3,6 @@ use rhai::{Dynamic, Engine, EvalAltResult, Module, Scope, AST, INT}; use rustyline::config::Builder; use rustyline::error::ReadlineError; use rustyline::{Cmd, Editor, Event, EventHandler, KeyCode, KeyEvent, Modifiers, Movement}; -use smallvec::smallvec; use std::{env, fs::File, io::Read, path::Path, process::exit}; @@ -223,40 +222,40 @@ fn setup_editor() -> Editor<()> { // On Windows, Esc clears the input buffer #[cfg(target_family = "windows")] rl.bind_sequence( - Event::KeySeq(smallvec![KeyEvent(KeyCode::Esc, Modifiers::empty())]), + Event::KeySeq(vec![KeyEvent(KeyCode::Esc, Modifiers::empty())]), EventHandler::Simple(Cmd::Kill(Movement::WholeBuffer)), ); // On Windows, Ctrl-Z is undo #[cfg(target_family = "windows")] rl.bind_sequence( - Event::KeySeq(smallvec![KeyEvent::ctrl('z')]), + Event::KeySeq(vec![KeyEvent::ctrl('z')]), EventHandler::Simple(Cmd::Undo(1)), ); // Map Ctrl-Enter to insert a new line - bypass need for `\` continuation rl.bind_sequence( - Event::KeySeq(smallvec![KeyEvent(KeyCode::Char('J'), Modifiers::CTRL)]), + Event::KeySeq(vec![KeyEvent(KeyCode::Char('J'), Modifiers::CTRL)]), EventHandler::Simple(Cmd::Newline), ); rl.bind_sequence( - Event::KeySeq(smallvec![KeyEvent(KeyCode::Enter, Modifiers::CTRL)]), + Event::KeySeq(vec![KeyEvent(KeyCode::Enter, Modifiers::CTRL)]), EventHandler::Simple(Cmd::Newline), ); // Map Ctrl-Home and Ctrl-End for beginning/end of input rl.bind_sequence( - Event::KeySeq(smallvec![KeyEvent(KeyCode::Home, Modifiers::CTRL)]), + Event::KeySeq(vec![KeyEvent(KeyCode::Home, Modifiers::CTRL)]), EventHandler::Simple(Cmd::Move(Movement::BeginningOfBuffer)), ); rl.bind_sequence( - Event::KeySeq(smallvec![KeyEvent(KeyCode::End, Modifiers::CTRL)]), + Event::KeySeq(vec![KeyEvent(KeyCode::End, Modifiers::CTRL)]), EventHandler::Simple(Cmd::Move(Movement::EndOfBuffer)), ); // Map Ctrl-Up and Ctrl-Down to skip up/down the history, even through multi-line histories rl.bind_sequence( - Event::KeySeq(smallvec![KeyEvent(KeyCode::Down, Modifiers::CTRL)]), + Event::KeySeq(vec![KeyEvent(KeyCode::Down, Modifiers::CTRL)]), EventHandler::Simple(Cmd::NextHistory), ); rl.bind_sequence( - Event::KeySeq(smallvec![KeyEvent(KeyCode::Up, Modifiers::CTRL)]), + Event::KeySeq(vec![KeyEvent(KeyCode::Up, Modifiers::CTRL)]), EventHandler::Simple(Cmd::PreviousHistory), ); @@ -371,11 +370,7 @@ fn main() { input.clear(); loop { - let prompt = if input.is_empty() { - "rhai-repl> " - } else { - " > " - }; + let prompt = if input.is_empty() { "repl> " } else { " > " }; match rl.readline(prompt) { // Line continuation diff --git a/src/engine.rs b/src/engine.rs index 192416d0..8b0975d4 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -295,9 +295,7 @@ impl Engine { } /// Check a result to ensure that it is valid. - pub(crate) fn check_return_value(&self, mut result: RhaiResult, pos: Position) -> RhaiResult { - let _pos = pos; - + pub(crate) fn check_return_value(&self, mut result: RhaiResult, _pos: Position) -> RhaiResult { match result { Ok(ref mut r) => { // Concentrate all empty strings into one instance to save memory diff --git a/src/eval/chaining.rs b/src/eval/chaining.rs index 3cb2076a..bcedd846 100644 --- a/src/eval/chaining.rs +++ b/src/eval/chaining.rs @@ -127,16 +127,14 @@ impl Engine { this_ptr: &mut Option<&mut Dynamic>, target: &mut Target, root: (&str, Position), - parent: &Expr, + _parent: &Expr, rhs: &Expr, - parent_options: ASTFlags, + _parent_options: ASTFlags, idx_values: &mut StaticVec, chain_type: ChainType, level: usize, new_val: Option<((Dynamic, Position), (Option, Position))>, ) -> RhaiResultOf<(Dynamic, bool)> { - let _parent = parent; - let _parent_options = parent_options; let is_ref_mut = target.is_ref(); // Pop the last index value @@ -671,7 +669,7 @@ impl Engine { this_ptr: &mut Option<&mut Dynamic>, expr: &Expr, parent_options: ASTFlags, - parent_chain_type: ChainType, + _parent_chain_type: ChainType, idx_values: &mut StaticVec, size: usize, level: usize, @@ -679,8 +677,6 @@ impl Engine { #[cfg(not(feature = "unchecked"))] self.inc_operations(&mut global.num_operations, expr.position())?; - let _parent_chain_type = parent_chain_type; - match expr { #[cfg(not(feature = "no_object"))] Expr::MethodCall(x, ..) @@ -850,18 +846,15 @@ impl Engine { state: &mut EvalState, lib: &[&Module], target: &'t mut Dynamic, - idx: Dynamic, + mut idx: Dynamic, idx_pos: Position, - add_if_not_found: bool, + _add_if_not_found: bool, use_indexers: bool, level: usize, ) -> RhaiResultOf> { #[cfg(not(feature = "unchecked"))] self.inc_operations(&mut global.num_operations, Position::NONE)?; - let mut idx = idx; - let _add_if_not_found = add_if_not_found; - match target { #[cfg(not(feature = "no_index"))] Dynamic(Union::Array(arr, ..)) => { diff --git a/src/eval/data_check.rs b/src/eval/data_check.rs index 29d39406..a9cda2bc 100644 --- a/src/eval/data_check.rs +++ b/src/eval/data_check.rs @@ -16,9 +16,7 @@ impl Engine { /// /// 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) { - let _top = top; - + 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, ..) => { diff --git a/src/func/call.rs b/src/func/call.rs index 3b256349..190a29c7 100644 --- a/src/func/call.rs +++ b/src/func/call.rs @@ -195,7 +195,7 @@ impl Engine { #[must_use] fn resolve_fn<'s>( &self, - global: &GlobalRuntimeState, + _global: &GlobalRuntimeState, state: &'s mut EvalState, lib: &[&Module], fn_name: &str, @@ -204,8 +204,6 @@ impl Engine { allow_dynamic: bool, is_op_assignment: bool, ) -> Option<&'s FnResolutionCacheEntry> { - let _global = global; - if hash_script == 0 { return None; } @@ -576,7 +574,7 @@ impl Engine { /// all others are silently replaced by `()`! pub(crate) fn exec_fn_call( &self, - scope: Option<&mut Scope>, + _scope: Option<&mut Scope>, global: &mut GlobalRuntimeState, state: &mut EvalState, lib: &[&Module], @@ -584,7 +582,7 @@ impl Engine { hashes: FnCallHashes, args: &mut FnCallArgs, is_ref_mut: bool, - is_method_call: bool, + _is_method_call: bool, pos: Position, level: usize, ) -> RhaiResultOf<(Dynamic, bool)> { @@ -600,9 +598,6 @@ impl Engine { #[cfg(not(feature = "no_closure"))] ensure_no_data_race(fn_name, args, is_ref_mut)?; - let _scope = scope; - let _is_method_call = is_method_call; - // These may be redirected from method style calls. match fn_name { // Handle type_of() @@ -1301,13 +1296,14 @@ impl Engine { } } + // Search for the root namespace let module = self .search_imports(global, state, namespace) .ok_or_else(|| ERR::ErrorModuleNotFound(namespace.to_string(), namespace.position()))?; - // First search in script-defined functions (can override built-in) - let func = match module.get_qualified_fn(hash) { - // Then search in Rust functions + // First search script-defined functions in namespace (can override built-in) + 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)?; @@ -1320,6 +1316,41 @@ impl Engine { r => r, }; + // Check for `Dynamic` parameters. + // + // Note - This is done during every function call mismatch without cache, + // so hopefully the number of arguments should not be too many + // (expected because closures cannot be qualified). + if func.is_none() && !args.is_empty() { + let num_args = args.len(); + let max_bitmask = 1usize << usize::min(num_args, MAX_DYNAMIC_PARAMETERS); + let mut bitmask = 1usize; // Bitmask of which parameter to replace with `Dynamic` + + // 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 { + // Replace with `Dynamic` + TypeId::of::() + } else { + a.type_id() + } + })); + let hash_qualified_fn = combine_hashes(hash, hash_params); + + #[cfg(not(feature = "unchecked"))] + self.inc_operations(&mut global.num_operations, pos)?; + + if let Some(f) = module.get_qualified_fn(hash_qualified_fn) { + func = Some(f); + break; + } + + bitmask += 1; + } + } + // Clone first argument if the function is not a method after-all if !func.map(|f| f.is_method()).unwrap_or(true) { if let Some(first) = first_arg_value { @@ -1382,11 +1413,9 @@ impl Engine { state: &mut EvalState, lib: &[&Module], script: &str, - pos: Position, + _pos: Position, level: usize, ) -> RhaiResult { - let _pos = pos; - #[cfg(not(feature = "unchecked"))] self.inc_operations(&mut global.num_operations, _pos)?; diff --git a/src/func/script.rs b/src/func/script.rs index 97721df5..97bcdaba 100644 --- a/src/func/script.rs +++ b/src/func/script.rs @@ -38,13 +38,11 @@ impl Engine { #[inline(never)] fn make_error( name: String, - fn_def: &ScriptFnDef, + _fn_def: &ScriptFnDef, global: &GlobalRuntimeState, err: RhaiError, pos: Position, ) -> RhaiResult { - let _fn_def = fn_def; - #[cfg(not(feature = "no_module"))] let source = _fn_def .environ @@ -232,13 +230,11 @@ impl Engine { #[must_use] pub(crate) fn has_script_fn( &self, - global: Option<&GlobalRuntimeState>, + _global: Option<&GlobalRuntimeState>, state: &mut EvalState, lib: &[&Module], hash_script: u64, ) -> bool { - let _global = global; - let cache = state.fn_resolution_cache_mut(); if let Some(result) = cache.get(&hash_script).map(|v| v.is_some()) { diff --git a/src/module/mod.rs b/src/module/mod.rs index 34c2887b..1b1051af 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -417,20 +417,68 @@ impl Module { } /// Map a custom type to a friendly display name. + /// + /// # Example + /// + /// ``` + /// # use rhai::Module; + /// #[derive(Clone)] + /// struct TestStruct; + /// + /// let name = std::any::type_name::(); + /// + /// let mut module = Module::new(); + /// + /// module.set_custom_type::("MyType"); + /// + /// assert_eq!(module.get_custom_type(name), Some("MyType")); + /// ``` #[inline(always)] - pub fn set_custom_type(&mut self, name: &str) { - self.custom_types.add_type::(name) + pub fn set_custom_type(&mut self, name: &str) -> &mut Self { + self.custom_types.add_type::(name); + self } /// Map a custom type to a friendly display name. + /// + /// ``` + /// # use rhai::Module; + /// #[derive(Clone)] + /// struct TestStruct; + /// + /// let name = std::any::type_name::(); + /// + /// let mut module = Module::new(); + /// + /// module.set_custom_type_raw(name, "MyType"); + /// + /// assert_eq!(module.get_custom_type(name), Some("MyType")); + /// ``` #[inline(always)] pub fn set_custom_type_raw( &mut self, type_name: impl Into, name: impl Into, - ) { - self.custom_types.add(type_name, name) + ) -> &mut Self { + self.custom_types.add(type_name, name); + self } /// Get the display name of a registered custom type. + /// + /// # Example + /// + /// ``` + /// # use rhai::Module; + /// #[derive(Clone)] + /// struct TestStruct; + /// + /// let name = std::any::type_name::(); + /// + /// let mut module = Module::new(); + /// + /// module.set_custom_type::("MyType"); + /// + /// assert_eq!(module.get_custom_type(name), Some("MyType")); + /// ``` #[inline(always)] pub fn get_custom_type(&self, key: &str) -> Option<&str> { self.custom_types.get(key) diff --git a/src/optimizer.rs b/src/optimizer.rs index 2fcd06bb..fe13e94f 100644 --- a/src/optimizer.rs +++ b/src/optimizer.rs @@ -863,7 +863,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b } /// Optimize an [expression][Expr]. -fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) { +fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) { // These keywords are handled specially const DONT_EVAL_KEYWORDS: &[&str] = &[ KEYWORD_PRINT, // side effects @@ -871,8 +871,6 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) { KEYWORD_EVAL, // arbitrary scripts ]; - let _chaining = chaining; - match expr { // {} Expr::Stmt(x) if x.is_empty() => { state.set_dirty(); *expr = Expr::Unit(x.position()) } diff --git a/src/packages/iter_basic.rs b/src/packages/iter_basic.rs index 95ccdf7a..7e8b6af8 100644 --- a/src/packages/iter_basic.rs +++ b/src/packages/iter_basic.rs @@ -17,7 +17,7 @@ use std::ops::{Add, Sub}; // Range iterator with step #[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] -struct StepRange(T, T, T) +pub struct StepRange(T, T, T) where T: Variant + Copy + PartialOrd + Add + Sub; @@ -115,7 +115,7 @@ impl FusedIterator for StepRange where // Bit-field iterator with step #[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] -struct BitRange(INT, INT, usize); +pub struct BitRange(INT, INT, usize); impl BitRange { pub fn new(value: INT, from: INT, len: INT) -> RhaiResultOf { @@ -166,7 +166,7 @@ impl ExactSizeIterator for BitRange { // String iterator over characters #[derive(Debug, Clone, Hash, Eq, PartialEq)] -struct CharsStream(Vec, usize); +pub struct CharsStream(Vec, usize); impl CharsStream { pub fn new(string: &str, from: INT, len: INT) -> Self { @@ -237,6 +237,134 @@ impl ExactSizeIterator for CharsStream { } } +#[cfg(not(feature = "no_float"))] +pub mod float { + use super::*; + use crate::FLOAT; + + #[derive(Debug, Clone, Copy, PartialEq)] + pub struct StepFloatRange(FLOAT, FLOAT, FLOAT); + + impl StepFloatRange { + pub fn new(from: FLOAT, to: FLOAT, step: FLOAT) -> RhaiResultOf { + #[cfg(not(feature = "unchecked"))] + if step == 0.0 { + return Err(crate::ERR::ErrorInFunctionCall( + "range".to_string(), + "".to_string(), + crate::ERR::ErrorArithmetic( + "step value cannot be zero".to_string(), + Position::NONE, + ) + .into(), + Position::NONE, + ) + .into()); + } + + Ok(Self(from, to, step)) + } + } + + impl Iterator for StepFloatRange { + type Item = FLOAT; + + fn next(&mut self) -> Option { + if self.0 == self.1 { + None + } else if self.0 < self.1 { + #[cfg(not(feature = "unchecked"))] + if self.2 < 0.0 { + return None; + } + + let v = self.0; + let n = self.0 + self.2; + + self.0 = if n >= self.1 { self.1 } else { n }; + Some(v) + } else { + #[cfg(not(feature = "unchecked"))] + if self.2 > 0.0 { + return None; + } + + let v = self.0; + let n = self.0 + self.2; + + self.0 = if n <= self.1 { self.1 } else { n }; + Some(v) + } + } + } + + impl FusedIterator for StepFloatRange {} +} + +#[cfg(feature = "decimal")] +pub mod decimal { + use super::*; + use rust_decimal::Decimal; + + #[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] + pub struct StepDecimalRange(Decimal, Decimal, Decimal); + + impl StepDecimalRange { + pub fn new(from: Decimal, to: Decimal, step: Decimal) -> RhaiResultOf { + #[cfg(not(feature = "unchecked"))] + if step.is_zero() { + return Err(crate::ERR::ErrorInFunctionCall( + "range".to_string(), + "".to_string(), + crate::ERR::ErrorArithmetic( + "step value cannot be zero".to_string(), + Position::NONE, + ) + .into(), + Position::NONE, + ) + .into()); + } + + Ok(Self(from, to, step)) + } + } + + impl Iterator for StepDecimalRange { + type Item = Decimal; + + fn next(&mut self) -> Option { + if self.0 == self.1 { + None + } else if self.0 < self.1 { + #[cfg(not(feature = "unchecked"))] + if self.2.is_sign_negative() { + return None; + } + + let v = self.0; + let n = self.0 + self.2; + + self.0 = if n >= self.1 { self.1 } else { n }; + Some(v) + } else { + #[cfg(not(feature = "unchecked"))] + if self.2.is_sign_positive() { + return None; + } + + let v = self.0; + let n = self.0 + self.2; + + self.0 = if n <= self.1 { self.1 } else { n }; + Some(v) + } + } + } + + impl FusedIterator for StepDecimalRange {} +} + macro_rules! reg_range { ($lib:ident | $x:expr => $( $y:ty ),*) => { $( @@ -326,62 +454,9 @@ def_package! { #[cfg(not(feature = "no_float"))] { - use crate::FLOAT; + lib.set_iterator::(); - #[derive(Debug, Clone, Copy, PartialEq)] - struct StepFloatRange(FLOAT, FLOAT, FLOAT); - - impl StepFloatRange { - pub fn new(from: FLOAT, to: FLOAT, step: FLOAT) -> RhaiResultOf { - #[cfg(not(feature = "unchecked"))] - if step == 0.0 { - return Err(crate::ERR::ErrorInFunctionCall("range".to_string(), "".to_string(), - crate::ERR::ErrorArithmetic("step value cannot be zero".to_string(), Position::NONE).into(), - Position::NONE, - ).into()); - } - - Ok(Self(from, to, step)) - } - } - - impl Iterator for StepFloatRange { - type Item = FLOAT; - - fn next(&mut self) -> Option { - if self.0 == self.1 { - None - } else if self.0 < self.1 { - #[cfg(not(feature = "unchecked"))] - if self.2 < 0.0 { - return None; - } - - let v = self.0; - let n = self.0 + self.2; - - self.0 = if n >= self.1 { self.1 } else { n }; - Some(v) - } else { - #[cfg(not(feature = "unchecked"))] - if self.2 > 0.0 { - return None; - } - - let v = self.0; - let n = self.0 + self.2; - - self.0 = if n <= self.1 { self.1 } else { n }; - Some(v) - } - } - } - - impl FusedIterator for StepFloatRange {} - - lib.set_iterator::(); - - let _hash = lib.set_native_fn("range", StepFloatRange::new); + let _hash = lib.set_native_fn("range", float::StepFloatRange::new); #[cfg(feature = "metadata")] lib.update_fn_metadata_with_comments( _hash, @@ -408,62 +483,9 @@ def_package! { #[cfg(feature = "decimal")] { - use rust_decimal::Decimal; + lib.set_iterator::(); - #[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] - struct StepDecimalRange(Decimal, Decimal, Decimal); - - impl StepDecimalRange { - pub fn new(from: Decimal, to: Decimal, step: Decimal) -> RhaiResultOf { - #[cfg(not(feature = "unchecked"))] - if step.is_zero() { - return Err(crate::ERR::ErrorInFunctionCall("range".to_string(), "".to_string(), - crate::ERR::ErrorArithmetic("step value cannot be zero".to_string(), Position::NONE).into(), - Position::NONE, - ).into()); - } - - Ok(Self(from, to, step)) - } - } - - impl Iterator for StepDecimalRange { - type Item = Decimal; - - fn next(&mut self) -> Option { - if self.0 == self.1 { - None - } else if self.0 < self.1 { - #[cfg(not(feature = "unchecked"))] - if self.2.is_sign_negative() { - return None; - } - - let v = self.0; - let n = self.0 + self.2; - - self.0 = if n >= self.1 { self.1 } else { n }; - Some(v) - } else { - #[cfg(not(feature = "unchecked"))] - if self.2.is_sign_positive() { - return None; - } - - let v = self.0; - let n = self.0 + self.2; - - self.0 = if n <= self.1 { self.1 } else { n }; - Some(v) - } - } - } - - impl FusedIterator for StepDecimalRange {} - - lib.set_iterator::(); - - let _hash = lib.set_native_fn("range", StepDecimalRange::new); + let _hash = lib.set_native_fn("range", decimal::StepDecimalRange::new); #[cfg(feature = "metadata")] lib.update_fn_metadata_with_comments( _hash, @@ -773,13 +795,13 @@ mod range_functions { /// Return `true` if the range is inclusive. #[rhai_fn(get = "is_inclusive", name = "is_inclusive", pure)] pub fn is_inclusive(range: &mut ExclusiveRange) -> bool { - let _range = range; + let _ = range; false } /// Return `true` if the range is exclusive. #[rhai_fn(get = "is_exclusive", name = "is_exclusive", pure)] pub fn is_exclusive(range: &mut ExclusiveRange) -> bool { - let _range = range; + let _ = range; true } /// Return the start of the inclusive range. @@ -795,13 +817,13 @@ mod range_functions { /// Return `true` if the range is inclusive. #[rhai_fn(get = "is_inclusive", name = "is_inclusive", pure)] pub fn is_inclusive_inclusive(range: &mut InclusiveRange) -> bool { - let _range = range; + let _ = range; true } /// Return `true` if the range is exclusive. #[rhai_fn(get = "is_exclusive", name = "is_exclusive", pure)] pub fn is_exclusive_inclusive(range: &mut InclusiveRange) -> bool { - let _range = range; + let _ = range; false } } diff --git a/src/packages/mod.rs b/src/packages/mod.rs index 343dd651..046b9cde 100644 --- a/src/packages/mod.rs +++ b/src/packages/mod.rs @@ -8,7 +8,7 @@ mod bit_field; pub(crate) mod blob_basic; mod debugging; mod fn_basic; -mod iter_basic; +pub(crate) mod iter_basic; mod lang_core; mod logic; mod map_basic; diff --git a/src/packages/string_basic.rs b/src/packages/string_basic.rs index ae3342ea..c817a24c 100644 --- a/src/packages/string_basic.rs +++ b/src/packages/string_basic.rs @@ -1,5 +1,6 @@ use crate::plugin::*; use crate::{def_package, FnPtr, INT}; +use std::any::TypeId; use std::fmt::{Binary, LowerHex, Octal}; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -20,6 +21,12 @@ def_package! { combine_with_exported_module!(lib, "print_debug", print_debug_functions); combine_with_exported_module!(lib, "number_formatting", number_formatting); + + // Register characters iterator + #[cfg(not(feature = "no_index"))] + lib.set_iter(TypeId::of::(), |value| Box::new( + value.cast::().chars().map(Into::into).collect::().into_iter() + )); } } diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index 6112b5a2..6a0c2cad 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -76,7 +76,7 @@ mod string_functions { #[rhai_fn(name = "+")] pub fn add_append_unit(string: ImmutableString, item: ()) -> ImmutableString { - let _item = item; + let _ = item; string } #[rhai_fn(name = "+")] @@ -1210,22 +1210,6 @@ mod string_functions { pub mod arrays { use crate::{Array, ImmutableString}; - /// Return an array containing all the characters of the string. - /// - /// # Example - /// - /// ```rhai - /// let text = "hello"; - /// - /// print(text.split()); // prints "['h', 'e', 'l', 'l', 'o']" - #[rhai_fn(name = "split")] - pub fn chars(string: &str) -> Array { - if string.is_empty() { - Array::new() - } else { - string.chars().map(Into::into).collect() - } - } /// Split the string into two at the specified `index` position and return it both strings /// as an array. /// @@ -1275,6 +1259,40 @@ mod string_functions { vec![prefix.into(), string[prefix_len..].into()] } } + /// Return an array containing all the characters of the string. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello"; + /// + /// print(text.to_chars()); // prints "['h', 'e', 'l', 'l', 'o']" + /// ``` + #[rhai_fn(name = "to_chars")] + pub fn to_chars(string: &str) -> Array { + if string.is_empty() { + Array::new() + } else { + string.chars().map(Into::into).collect() + } + } + /// Split the string into segments based on whitespaces, returning an array of the segments. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world! hello, foo!"; + /// + /// print(text.split()); // prints ["hello,", "world!", "hello,", "foo!"] + /// ``` + #[rhai_fn(name = "split")] + pub fn split_whitespace(string: &str) -> Array { + if string.is_empty() { + Array::new() + } else { + string.split_whitespace().map(Into::into).collect() + } + } /// Split the string into segments based on a `delimiter` string, returning an array of the segments. /// /// # Example diff --git a/src/parser.rs b/src/parser.rs index f7e7b9b0..dd6659d4 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -316,8 +316,6 @@ impl Expr { #[cfg(not(feature = "no_float"))] Expr::FloatConstant(..) => "a floating-point number", Expr::CharConstant(..) => "a character", - Expr::StringConstant(..) => "a string", - Expr::InterpolatedString(..) => "a string", Expr::Map(..) => "an object map", _ => return Ok(self), }; @@ -3506,11 +3504,9 @@ impl Engine { &self, input: &mut TokenStream, state: &mut ParseState, - scope: &Scope, - optimization_level: OptimizationLevel, + _scope: &Scope, + _optimization_level: OptimizationLevel, ) -> ParseResult { - let _scope = scope; - let _optimization_level = optimization_level; let mut functions = BTreeMap::new(); let settings = ParseSettings { @@ -3554,7 +3550,7 @@ impl Engine { statements, #[cfg(not(feature = "no_function"))] StaticVec::new_const(), - optimization_level, + _optimization_level, )); #[cfg(feature = "no_optimize")] @@ -3632,12 +3628,9 @@ impl Engine { &self, input: &mut TokenStream, state: &mut ParseState, - scope: &Scope, - optimization_level: OptimizationLevel, + _scope: &Scope, + _optimization_level: OptimizationLevel, ) -> ParseResult { - let _scope = scope; - let _optimization_level = optimization_level; - let (statements, _lib) = self.parse_global_level(input, state)?; #[cfg(not(feature = "no_optimize"))] @@ -3647,7 +3640,7 @@ impl Engine { statements, #[cfg(not(feature = "no_function"))] _lib, - optimization_level, + _optimization_level, )); #[cfg(feature = "no_optimize")] diff --git a/src/serde/ser.rs b/src/serde/ser.rs index d85cea90..b7e2d915 100644 --- a/src/serde/ser.rs +++ b/src/serde/ser.rs @@ -417,9 +417,9 @@ impl SerializeSeq for DynamicSerializer { fn serialize_element(&mut self, _value: &T) -> RhaiResultOf<()> { #[cfg(not(feature = "no_index"))] { - let _value = _value.serialize(&mut *self)?; + let value = _value.serialize(&mut *self)?; let arr = self._value.downcast_mut::().unwrap(); - arr.push(_value); + arr.push(value); Ok(()) } #[cfg(feature = "no_index")] @@ -452,9 +452,9 @@ impl SerializeTuple for DynamicSerializer { fn serialize_element(&mut self, _value: &T) -> RhaiResultOf<()> { #[cfg(not(feature = "no_index"))] { - let _value = _value.serialize(&mut *self)?; + let value = _value.serialize(&mut *self)?; let arr = self._value.downcast_mut::().unwrap(); - arr.push(_value); + arr.push(value); Ok(()) } #[cfg(feature = "no_index")] @@ -486,9 +486,9 @@ impl SerializeTupleStruct for DynamicSerializer { fn serialize_field(&mut self, _value: &T) -> RhaiResultOf<()> { #[cfg(not(feature = "no_index"))] { - let _value = _value.serialize(&mut *self)?; + let value = _value.serialize(&mut *self)?; let arr = self._value.downcast_mut::().unwrap(); - arr.push(_value); + arr.push(value); Ok(()) } #[cfg(feature = "no_index")] @@ -540,9 +540,9 @@ impl SerializeMap for DynamicSerializer { .map_err(|typ| { ERR::ErrorMismatchDataType("string".into(), typ.into(), Position::NONE) })?; - let _value = _value.serialize(&mut *self)?; + let value = _value.serialize(&mut *self)?; let map = self._value.downcast_mut::().unwrap(); - map.insert(key.into(), _value); + map.insert(key.into(), value); Ok(()) } #[cfg(feature = "no_object")] @@ -561,13 +561,13 @@ impl SerializeMap for DynamicSerializer { ) -> RhaiResultOf<()> { #[cfg(not(feature = "no_object"))] { - let _key: Dynamic = _key.serialize(&mut *self)?; - let _key = _key.into_immutable_string().map_err(|typ| { + let key: Dynamic = _key.serialize(&mut *self)?; + let key = key.into_immutable_string().map_err(|typ| { ERR::ErrorMismatchDataType("string".into(), typ.into(), Position::NONE) })?; - let _value = _value.serialize(&mut *self)?; + let value = _value.serialize(&mut *self)?; let map = self._value.downcast_mut::().unwrap(); - map.insert(_key.into(), _value); + map.insert(key.into(), value); Ok(()) } #[cfg(feature = "no_object")] @@ -603,9 +603,9 @@ impl SerializeStruct for DynamicSerializer { ) -> RhaiResultOf<()> { #[cfg(not(feature = "no_object"))] { - let _value = _value.serialize(&mut *self)?; + let value = _value.serialize(&mut *self)?; let map = self._value.downcast_mut::().unwrap(); - map.insert(_key.into(), _value); + map.insert(_key.into(), value); Ok(()) } #[cfg(feature = "no_object")] diff --git a/src/types/dynamic.rs b/src/types/dynamic.rs index e8a8a07a..792d008b 100644 --- a/src/types/dynamic.rs +++ b/src/types/dynamic.rs @@ -469,9 +469,7 @@ impl Hash for Dynamic { #[cfg(feature = "sync")] Union::Shared(ref cell, ..) => (*cell.read().unwrap()).hash(state), - Union::Variant(ref v, ..) => { - let _v = v; - + Union::Variant(ref _v, ..) => { #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] { diff --git a/tests/modules.rs b/tests/modules.rs index 2b952a8d..4394d893 100644 --- a/tests/modules.rs +++ b/tests/modules.rs @@ -562,3 +562,29 @@ fn test_module_environ() -> Result<(), Box> { Ok(()) } + +#[test] +fn test_module_dynamic() -> Result<(), Box> { + fn test_fn(input: Dynamic, x: INT) -> Result> { + let s = input.into_string().unwrap(); + Ok(s.len() as INT + x) + } + + let mut engine = rhai::Engine::new(); + let mut module = Module::new(); + module.set_native_fn("test", test_fn); + + let mut static_modules = rhai::module_resolvers::StaticModuleResolver::new(); + static_modules.insert("test", module); + engine.set_module_resolver(static_modules); + engine.register_result_fn("test2", test_fn); + + assert_eq!(engine.eval::(r#"test2("test", 38);"#)?, 42); + + assert_eq!( + engine.eval::(r#"import "test" as test; test::test("test", 38);"#)?, + 42 + ); + + Ok(()) +}