Merge pull request #550 from schungx/master

Release 1.6.1
This commit is contained in:
Stephen Chung 2022-04-11 16:51:21 +08:00 committed by GitHub
commit 1616541df4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 437 additions and 238 deletions

View File

@ -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.

View File

@ -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" }

View File

@ -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).

View File

@ -12,3 +12,7 @@ note: required by a bound in `rhai::Dynamic::cast`
| pub fn cast<T: Any + Clone>(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)]
|

View File

@ -12,3 +12,7 @@ note: required by a bound in `rhai::Dynamic::cast`
| pub fn cast<T: Any + Clone>(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)]
|

View File

@ -12,3 +12,7 @@ note: required by a bound in `rhai::Dynamic::from`
| pub fn from<T: Variant + Clone>(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)]
|

View File

@ -13,3 +13,7 @@ note: required by a bound in `rhai::Dynamic::from`
| pub fn from<T: Variant + Clone>(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)]
|

View File

@ -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<i64>"
};
}
if name == type_name::<BitRange>() {
return if shorthands { "range" } else { "BitRange" };
}
if name == type_name::<CharsStream>() {
return if shorthands { "range" } else { "CharStream" };
}
let step_range_name = type_name::<StepRange<u8>>();
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::<crate::packages::iter_basic::float::StepFloatRange>() {
return if shorthands {
"range"
} else {
"StepFloatRange"
};
}
#[cfg(feature = "decimal")]
if name == type_name::<crate::packages::iter_basic::decimal::StepDecimalRange>() {
return if shorthands {
"range"
} else {
"StepDecimalRange"
};
}
if name.starts_with("rhai::") {
map_std_type_name(&name[6..], shorthands)

View File

@ -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,

View File

@ -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");

View File

@ -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

View File

@ -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

View File

@ -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<super::ChainArgument>,
chain_type: ChainType,
level: usize,
new_val: Option<((Dynamic, Position), (Option<OpAssignment>, 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<super::ChainArgument>,
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<Target<'t>> {
#[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, ..)) => {

View File

@ -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, ..) => {

View File

@ -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::<Dynamic>()
} 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)?;

View File

@ -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()) {

View File

@ -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::<TestStruct>();
///
/// let mut module = Module::new();
///
/// module.set_custom_type::<TestStruct>("MyType");
///
/// assert_eq!(module.get_custom_type(name), Some("MyType"));
/// ```
#[inline(always)]
pub fn set_custom_type<T>(&mut self, name: &str) {
self.custom_types.add_type::<T>(name)
pub fn set_custom_type<T>(&mut self, name: &str) -> &mut Self {
self.custom_types.add_type::<T>(name);
self
}
/// Map a custom type to a friendly display name.
///
/// ```
/// # use rhai::Module;
/// #[derive(Clone)]
/// struct TestStruct;
///
/// let name = std::any::type_name::<TestStruct>();
///
/// 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<Identifier>,
name: impl Into<Identifier>,
) {
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::<TestStruct>();
///
/// let mut module = Module::new();
///
/// module.set_custom_type::<TestStruct>("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)

View File

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

View File

@ -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, T)
pub struct StepRange<T>(T, T, T)
where
T: Variant + Copy + PartialOrd + Add<Output = T> + Sub<Output = T>;
@ -115,7 +115,7 @@ impl<T> FusedIterator for StepRange<T> 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<Self> {
@ -166,7 +166,7 @@ impl ExactSizeIterator for BitRange {
// String iterator over characters
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
struct CharsStream(Vec<char>, usize);
pub struct CharsStream(Vec<char>, 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<Self> {
#[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<FLOAT> {
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<Self> {
#[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<Decimal> {
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::<float::StepFloatRange>();
#[derive(Debug, Clone, Copy, PartialEq)]
struct StepFloatRange(FLOAT, FLOAT, FLOAT);
impl StepFloatRange {
pub fn new(from: FLOAT, to: FLOAT, step: FLOAT) -> RhaiResultOf<Self> {
#[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<FLOAT> {
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::<StepFloatRange>();
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::<decimal::StepDecimalRange>();
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)]
struct StepDecimalRange(Decimal, Decimal, Decimal);
impl StepDecimalRange {
pub fn new(from: Decimal, to: Decimal, step: Decimal) -> RhaiResultOf<Self> {
#[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<Decimal> {
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::<StepDecimalRange>();
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
}
}

View File

@ -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;

View File

@ -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::<ImmutableString>(), |value| Box::new(
value.cast::<ImmutableString>().chars().map(Into::into).collect::<crate::Array>().into_iter()
));
}
}

View File

@ -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

View File

@ -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<AST> {
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<AST> {
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")]

View File

@ -417,9 +417,9 @@ impl SerializeSeq for DynamicSerializer {
fn serialize_element<T: ?Sized + Serialize>(&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::<crate::Array>().unwrap();
arr.push(_value);
arr.push(value);
Ok(())
}
#[cfg(feature = "no_index")]
@ -452,9 +452,9 @@ impl SerializeTuple for DynamicSerializer {
fn serialize_element<T: ?Sized + Serialize>(&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::<crate::Array>().unwrap();
arr.push(_value);
arr.push(value);
Ok(())
}
#[cfg(feature = "no_index")]
@ -486,9 +486,9 @@ impl SerializeTupleStruct for DynamicSerializer {
fn serialize_field<T: ?Sized + Serialize>(&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::<crate::Array>().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::<crate::Map>().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::<crate::Map>().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::<crate::Map>().unwrap();
map.insert(_key.into(), _value);
map.insert(_key.into(), value);
Ok(())
}
#[cfg(feature = "no_object")]

View File

@ -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"))]
{

View File

@ -562,3 +562,29 @@ fn test_module_environ() -> Result<(), Box<EvalAltResult>> {
Ok(())
}
#[test]
fn test_module_dynamic() -> Result<(), Box<EvalAltResult>> {
fn test_fn(input: Dynamic, x: INT) -> Result<INT, Box<EvalAltResult>> {
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::<INT>(r#"test2("test", 38);"#)?, 42);
assert_eq!(
engine.eval::<INT>(r#"import "test" as test; test::test("test", 38);"#)?,
42
);
Ok(())
}