diff --git a/CHANGELOG.md b/CHANGELOG.md index 20d377cf..cfc3b096 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,8 @@ New features * An integer value can now be indexed to get/set a single bit. * The `bits` method of an integer can be used to iterate through its bits. * New `$bool$`, `$int$`, `$float$` and `$string$` expression types for custom syntax. +* New methods `to_hex`, `to_octal` and `to_binary` for integer numbers. +* New methods `to_upper`, `to_lower`, `make_upper`, `make_lower` for strings/characters. Version 0.20.2 diff --git a/src/engine.rs b/src/engine.rs index 205a8c37..7b0faa6b 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -248,7 +248,6 @@ pub const FN_ANONYMOUS: &str = "anon$"; pub const OP_EQUALS: &str = "=="; /// Standard method function for containment testing. -/// /// The `in` operator is implemented as a call to this method. pub const OP_CONTAINS: &str = "contains"; @@ -1233,7 +1232,7 @@ impl Engine { } } - let is_ref = target.is_ref(); + let is_ref_mut = target.is_ref(); // Pop the last index value let idx_val = idx_values @@ -1293,10 +1292,11 @@ impl Engine { let hash_set = FnCallHashes::from_native(crate::calc_fn_hash(FN_IDX_SET, 3)); let args = &mut [target, &mut idx_val_for_setter, &mut new_val]; + let pos = Position::NONE; self.exec_fn_call( - mods, state, lib, FN_IDX_SET, hash_set, args, is_ref, true, - new_pos, None, level, + mods, state, lib, FN_IDX_SET, hash_set, args, is_ref_mut, true, + pos, None, level, )?; } @@ -1374,8 +1374,8 @@ impl Engine { let args = &mut [target.as_mut()]; let (mut orig_val, _) = self .exec_fn_call( - mods, state, lib, getter, hash, args, is_ref, true, *pos, None, - level, + mods, state, lib, getter, hash, args, is_ref_mut, true, *pos, + None, level, ) .or_else(|err| match *err { // Try an indexer if property does not exist @@ -1417,7 +1417,8 @@ impl Engine { let hash = FnCallHashes::from_native(*hash_set); let args = &mut [target.as_mut(), &mut new_val]; self.exec_fn_call( - mods, state, lib, setter, hash, args, is_ref, true, *pos, None, level, + mods, state, lib, setter, hash, args, is_ref_mut, true, *pos, None, + level, ) .or_else(|err| match *err { // Try an indexer if property does not exist @@ -1425,10 +1426,11 @@ impl Engine { let args = &mut [target, &mut name.into(), &mut new_val]; let hash_set = FnCallHashes::from_native(crate::calc_fn_hash(FN_IDX_SET, 3)); + let pos = Position::NONE; self.exec_fn_call( - mods, state, lib, FN_IDX_SET, hash_set, args, is_ref, true, - *pos, None, level, + mods, state, lib, FN_IDX_SET, hash_set, args, is_ref_mut, true, + pos, None, level, ) .map_err( |idx_err| match *idx_err { @@ -1446,7 +1448,8 @@ impl Engine { let hash = FnCallHashes::from_native(*hash_get); let args = &mut [target.as_mut()]; self.exec_fn_call( - mods, state, lib, getter, hash, args, is_ref, true, *pos, None, level, + mods, state, lib, getter, hash, args, is_ref_mut, true, *pos, None, + level, ) .map_or_else( |err| match *err { @@ -1517,7 +1520,7 @@ impl Engine { let args = &mut arg_values[..1]; let (mut val, updated) = self .exec_fn_call( - mods, state, lib, getter, hash_get, args, is_ref, true, + mods, state, lib, getter, hash_get, args, is_ref_mut, true, *pos, None, level, ) .or_else(|err| match *err { @@ -1563,7 +1566,7 @@ impl Engine { let mut arg_values = [target.as_mut(), val]; let args = &mut arg_values; self.exec_fn_call( - mods, state, lib, setter, hash_set, args, is_ref, true, + mods, state, lib, setter, hash_set, args, is_ref_mut, true, *pos, None, level, ) .or_else( @@ -1577,7 +1580,7 @@ impl Engine { ); self.exec_fn_call( mods, state, lib, FN_IDX_SET, hash_set, args, - is_ref, true, *pos, None, level, + is_ref_mut, true, *pos, None, level, ) .or_else(|idx_err| match *idx_err { EvalAltResult::ErrorIndexingType(_, _) => { @@ -1972,6 +1975,7 @@ impl Engine { _ if indexers => { let args = &mut [target, &mut idx]; let hash_get = FnCallHashes::from_native(crate::calc_fn_hash(FN_IDX_GET, 2)); + let idx_pos = Position::NONE; self.exec_fn_call( mods, state, lib, FN_IDX_GET, hash_get, args, true, true, idx_pos, None, level, @@ -1980,7 +1984,11 @@ impl Engine { } _ => EvalAltResult::ErrorIndexingType( - self.map_type_name(target.type_name()).into(), + format!( + "{} [{}]", + self.map_type_name(target.type_name()), + self.map_type_name(idx.type_name()) + ), Position::NONE, ) .into(), diff --git a/src/engine_api.rs b/src/engine_api.rs index 9aba5f05..c0052d75 100644 --- a/src/engine_api.rs +++ b/src/engine_api.rs @@ -2,7 +2,8 @@ use crate::dynamic::Variant; use crate::engine::{EvalContext, Imports, State}; -use crate::fn_native::{FnCallArgs, SendSync}; +use crate::fn_call::FnCallArgs; +use crate::fn_native::SendSync; use crate::fn_register::RegisterNativeFunction; use crate::optimize::OptimizationLevel; use crate::parse::ParseState; diff --git a/src/error.rs b/src/error.rs index ea518647..ea3ad170 100644 --- a/src/error.rs +++ b/src/error.rs @@ -59,8 +59,7 @@ pub enum EvalAltResult { /// Bit-field indexing out-of-bounds. /// Wrapped values are the current number of bits in the bit-field and the index number. ErrorBitFieldBounds(usize, INT, Position), - /// Trying to index into a type that is not an array, an object map, or a string, and has no - /// indexer function defined. Wrapped value is the type name. + /// Trying to index into a type that has no indexer function defined. Wrapped value is the type name. ErrorIndexingType(String, Position), /// The `for` statement encounters a type that is not an iterator. ErrorFor(Position), @@ -101,14 +100,12 @@ impl EvalAltResult { #[allow(deprecated)] Self::ErrorSystem(_, s) => s.description(), Self::ErrorParsing(p, _) => p.desc(), - Self::ErrorInFunctionCall(_,_, _, _) => "Error in called function", + Self::ErrorInFunctionCall(_, _, _, _) => "Error in called function", Self::ErrorInModule(_, _, _) => "Error in module", Self::ErrorFunctionNotFound(_, _) => "Function not found", Self::ErrorUnboundThis(_) => "'this' is not bound", Self::ErrorMismatchDataType(_, _, _) => "Data type is incorrect", - Self::ErrorIndexingType(_, _) => { - "Indexing can only be performed on an array, an object map, a string, or a type with an indexer function defined" - } + Self::ErrorIndexingType(_, _) => "No indexer of the appropriate types defined", Self::ErrorArrayBounds(0, _, _) => "Empty array has nothing to access", Self::ErrorArrayBounds(_, _, _) => "Array index out of bounds", Self::ErrorStringBounds(0, _, _) => "Empty string has nothing to index", @@ -126,7 +123,7 @@ impl EvalAltResult { Self::ErrorTooManyModules(_) => "Too many modules imported", Self::ErrorStackOverflow(_) => "Stack overflow", Self::ErrorDataTooLarge(_, _) => "Data size exceeds maximum limit", - Self::ErrorTerminated(_,_) => "Script terminated.", + Self::ErrorTerminated(_, _) => "Script terminated.", Self::ErrorRuntime(_, _) => "Runtime error", Self::LoopBreak(true, _) => "Break statement not inside a loop", Self::LoopBreak(false, _) => "Continue statement not inside a loop", @@ -175,7 +172,7 @@ impl fmt::Display for EvalAltResult { Self::ErrorDotExpr(s, _) if !s.is_empty() => f.write_str(s)?, - Self::ErrorIndexingType(s, _) => write!(f, "Indexer not registered for type '{}'", s)?, + Self::ErrorIndexingType(s, _) => write!(f, "Indexer not registered for '{}'", s)?, Self::ErrorUnboundThis(_) | Self::ErrorFor(_) diff --git a/src/fn_builtin.rs b/src/fn_builtin.rs index ed5a5bda..727da5f4 100644 --- a/src/fn_builtin.rs +++ b/src/fn_builtin.rs @@ -1,7 +1,8 @@ //! Built-in implementations for common operators. use crate::engine::OP_CONTAINS; -use crate::fn_native::{FnCallArgs, NativeCallContext}; +use crate::fn_call::FnCallArgs; +use crate::fn_native::NativeCallContext; use crate::{Dynamic, ImmutableString, RhaiResult, INT}; use std::any::TypeId; #[cfg(feature = "no_std")] diff --git a/src/fn_call.rs b/src/fn_call.rs index 78c32f09..932d0019 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -7,7 +7,7 @@ use crate::engine::{ MAX_DYNAMIC_PARAMETERS, }; use crate::fn_builtin::{get_builtin_binary_op_fn, get_builtin_op_assignment_fn}; -use crate::fn_native::{FnAny, FnCallArgs}; +use crate::fn_native::FnAny; use crate::module::NamespaceRef; use crate::optimize::OptimizationLevel; use crate::{ @@ -30,6 +30,9 @@ use std::{ #[cfg(not(feature = "no_object"))] use crate::Map; +/// Arguments to a function call, which is a list of [`&mut Dynamic`][Dynamic]. +pub type FnCallArgs<'a> = [&'a mut Dynamic]; + /// A type that temporarily stores a mutable reference to a `Dynamic`, /// replacing it with a cloned copy. #[derive(Debug, Default)] @@ -102,13 +105,13 @@ impl Drop for ArgBackup<'_> { pub fn ensure_no_data_race( fn_name: &str, args: &FnCallArgs, - is_ref: bool, + is_method_call: bool, ) -> Result<(), Box> { #[cfg(not(feature = "no_closure"))] if let Some((n, _)) = args .iter() .enumerate() - .skip(if is_ref { 1 } else { 0 }) + .skip(if is_method_call { 1 } else { 0 }) .find(|(_, a)| a.is_locked()) { return EvalAltResult::ErrorDataRace( @@ -298,7 +301,7 @@ impl Engine { name: &str, hash: u64, args: &mut FnCallArgs, - is_ref: bool, + is_method_call: bool, is_op_assign: bool, pos: Position, ) -> Result<(Dynamic, bool), Box> { @@ -316,7 +319,7 @@ impl Engine { // Calling pure function but the first argument is a reference? let mut backup: Option = None; - if is_ref && func.is_pure() && !args.is_empty() { + if is_method_call && func.is_pure() && !args.is_empty() { backup = Some(Default::default()); backup.as_mut().map(|bk| bk.change_first_arg_to_copy(args)); } @@ -373,7 +376,11 @@ impl Engine { assert!(args.len() == 2); EvalAltResult::ErrorIndexingType( - self.map_type_name(args[0].type_name()).to_string(), + format!( + "{} [{}]", + self.map_type_name(args[0].type_name()), + self.map_type_name(args[1].type_name()) + ), pos, ) .into() @@ -385,7 +392,12 @@ impl Engine { assert!(args.len() == 3); EvalAltResult::ErrorIndexingType( - self.map_type_name(args[0].type_name()).to_string(), + format!( + "{} [{}] = {}", + self.map_type_name(args[0].type_name()), + self.map_type_name(args[1].type_name()), + self.map_type_name(args[2].type_name()) + ), pos, ) .into() @@ -620,8 +632,8 @@ impl Engine { fn_name: &str, hashes: FnCallHashes, args: &mut FnCallArgs, - is_ref: bool, - _is_method: bool, + is_ref_mut: bool, + _is_method_call: bool, pos: Position, _capture_scope: Option, _level: usize, @@ -634,7 +646,7 @@ impl Engine { // Check for data race. #[cfg(not(feature = "no_closure"))] - ensure_no_data_race(fn_name, args, is_ref)?; + ensure_no_data_race(fn_name, args, is_ref_mut)?; // These may be redirected from method style calls. match fn_name { @@ -722,7 +734,7 @@ impl Engine { }); } - let result = if _is_method { + let result = if _is_method_call { // Method call of script function - map first argument to `this` let (first, rest) = args .split_first_mut() @@ -753,7 +765,7 @@ impl Engine { // Normal call of script function // The first argument is a reference? let mut backup: Option = None; - if is_ref && !args.is_empty() { + if is_ref_mut && !args.is_empty() { backup = Some(Default::default()); backup.as_mut().map(|bk| bk.change_first_arg_to_copy(args)); } @@ -780,7 +792,9 @@ impl Engine { // Native function call let hash = hashes.native; - self.call_native_fn(mods, state, lib, fn_name, hash, args, is_ref, false, pos) + self.call_native_fn( + mods, state, lib, fn_name, hash, args, is_ref_mut, false, pos, + ) } /// Evaluate a list of statements with no `this` pointer. @@ -872,7 +886,7 @@ impl Engine { pos: Position, level: usize, ) -> Result<(Dynamic, bool), Box> { - let is_ref = target.is_ref(); + let is_ref_mut = target.is_ref(); let (result, updated) = match fn_name { KEYWORD_FN_PTR_CALL if target.is::() => { @@ -932,7 +946,8 @@ impl Engine { // Map it to name(args) in function-call style self.exec_fn_call( - mods, state, lib, fn_name, new_hash, &mut args, is_ref, true, pos, None, level, + mods, state, lib, fn_name, new_hash, &mut args, is_ref_mut, true, pos, None, + level, ) } KEYWORD_FN_PTR_CURRY => { @@ -1004,7 +1019,7 @@ impl Engine { args.extend(call_args.iter_mut()); self.exec_fn_call( - mods, state, lib, fn_name, hash, &mut args, is_ref, true, pos, None, level, + mods, state, lib, fn_name, hash, &mut args, is_ref_mut, true, pos, None, level, ) } }?; @@ -1227,7 +1242,7 @@ impl Engine { // Normal function call - except for Fn, curry, call and eval (handled above) let mut arg_values = StaticVec::with_capacity(args_expr.len()); let mut args = StaticVec::with_capacity(args_expr.len() + curry.len()); - let mut is_ref = false; + let mut is_ref_mut = false; let capture = if capture_scope && !scope.is_empty() { Some(scope.clone_visible()) } else { @@ -1269,7 +1284,7 @@ impl Engine { args.extend(arg_values.iter_mut()) } else { // Turn it into a method call only if the object is not shared and not a simple value - is_ref = true; + is_ref_mut = true; let obj_ref = target.take_ref().expect("never fails because `target` is a reference if it is not a value and not shared"); args.push(obj_ref); args.extend(arg_values.iter_mut()); @@ -1288,7 +1303,7 @@ impl Engine { } self.exec_fn_call( - mods, state, lib, name, hashes, &mut args, is_ref, false, pos, capture, level, + mods, state, lib, name, hashes, &mut args, is_ref_mut, false, pos, capture, level, ) .map(|(v, _)| v) } diff --git a/src/fn_native.rs b/src/fn_native.rs index 9798ebf5..95fa9f94 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -2,18 +2,14 @@ use crate::ast::{FnAccess, FnCallHashes}; use crate::engine::Imports; +use crate::fn_call::FnCallArgs; use crate::plugin::PluginFunction; -use crate::token::is_valid_identifier; use crate::{ - calc_fn_hash, Dynamic, Engine, EvalAltResult, EvalContext, Identifier, Module, Position, - RhaiResult, StaticVec, + calc_fn_hash, Dynamic, Engine, EvalAltResult, EvalContext, Module, Position, RhaiResult, }; +use std::fmt; #[cfg(feature = "no_std")] use std::prelude::v1::*; -use std::{ - convert::{TryFrom, TryInto}, - fmt, mem, -}; /// Trait that maps to `Send + Sync` only under the `sync` feature. #[cfg(feature = "sync")] @@ -204,12 +200,12 @@ impl<'a> NativeCallContext<'a> { pub fn call_fn_dynamic_raw( &self, fn_name: impl AsRef, - is_method: bool, + is_method_call: bool, args: &mut [&mut Dynamic], ) -> RhaiResult { let fn_name = fn_name.as_ref(); - let hash = if is_method { + let hash = if is_method_call { FnCallHashes::from_script_and_native( calc_fn_hash(fn_name, args.len() - 1), calc_fn_hash(fn_name, args.len()), @@ -226,8 +222,8 @@ impl<'a> NativeCallContext<'a> { fn_name, hash, args, - is_method, - is_method, + is_method_call, + is_method_call, Position::NONE, None, 0, @@ -271,175 +267,6 @@ pub fn shared_take(value: Shared) -> T { .expect("resource should have no outstanding references") } -/// Arguments to a function call, which is a list of [`&mut Dynamic`][Dynamic]. -pub type FnCallArgs<'a> = [&'a mut Dynamic]; - -/// A general function pointer, which may carry additional (i.e. curried) argument values -/// to be passed onto a function during a call. -#[derive(Debug, Clone, Hash)] -pub struct FnPtr(Identifier, StaticVec); - -impl FnPtr { - /// Create a new function pointer. - #[inline(always)] - #[must_use] - pub fn new(name: impl Into) -> Result> { - name.into().try_into() - } - /// Create a new function pointer without checking its parameters. - #[inline(always)] - #[must_use] - pub(crate) fn new_unchecked(name: Identifier, curry: StaticVec) -> Self { - Self(name.into(), curry) - } - /// Get the name of the function. - #[inline(always)] - #[must_use] - pub fn fn_name(&self) -> &str { - self.get_fn_name().as_ref() - } - /// Get the name of the function. - #[inline(always)] - #[must_use] - pub(crate) fn get_fn_name(&self) -> &Identifier { - &self.0 - } - /// Get the underlying data of the function pointer. - #[inline(always)] - #[must_use] - pub(crate) fn take_data(self) -> (Identifier, StaticVec) { - (self.0, self.1) - } - /// Get the curried arguments. - #[inline(always)] - #[must_use] - pub fn curry(&self) -> &[Dynamic] { - self.1.as_ref() - } - /// Add a new curried argument. - #[inline(always)] - pub fn add_curry(&mut self, value: Dynamic) -> &mut Self { - self.1.push(value); - self - } - /// Set curried arguments to the function pointer. - #[inline(always)] - pub fn set_curry(&mut self, values: impl IntoIterator) -> &mut Self { - self.1 = values.into_iter().collect(); - self - } - /// Is the function pointer curried? - #[inline(always)] - #[must_use] - pub fn is_curried(&self) -> bool { - !self.1.is_empty() - } - /// Get the number of curried arguments. - #[inline(always)] - #[must_use] - pub fn num_curried(&self) -> usize { - self.1.len() - } - /// Does the function pointer refer to an anonymous function? - /// - /// Not available under `no_function`. - #[cfg(not(feature = "no_function"))] - #[inline(always)] - #[must_use] - pub fn is_anonymous(&self) -> bool { - self.0.starts_with(crate::engine::FN_ANONYMOUS) - } - /// Call the function pointer with curried arguments (if any). - /// - /// If this function is a script-defined function, it must not be marked private. - /// - /// # WARNING - /// - /// All the arguments are _consumed_, meaning that they're replaced by `()`. - /// This is to avoid unnecessarily cloning the arguments. - /// Do not use the arguments after this call. If they are needed afterwards, - /// clone them _before_ calling this function. - #[inline(always)] - #[must_use] - pub fn call_dynamic( - &self, - ctx: &NativeCallContext, - this_ptr: Option<&mut Dynamic>, - mut arg_values: impl AsMut<[Dynamic]>, - ) -> RhaiResult { - let mut arg_values = arg_values.as_mut(); - let mut args_data; - - if self.num_curried() > 0 { - args_data = StaticVec::with_capacity(self.num_curried() + arg_values.len()); - args_data.extend(self.curry().iter().cloned()); - args_data.extend(arg_values.iter_mut().map(mem::take)); - arg_values = args_data.as_mut(); - }; - - let is_method = this_ptr.is_some(); - - let mut args = StaticVec::with_capacity(arg_values.len() + 1); - if let Some(obj) = this_ptr { - args.push(obj); - } - args.extend(arg_values.iter_mut()); - - ctx.call_fn_dynamic_raw(self.fn_name(), is_method, &mut args) - } -} - -impl fmt::Display for FnPtr { - #[inline(always)] - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Fn({})", self.0) - } -} - -impl TryFrom for FnPtr { - type Error = Box; - - #[inline(always)] - fn try_from(value: Identifier) -> Result { - if is_valid_identifier(value.chars()) { - Ok(Self(value, Default::default())) - } else { - EvalAltResult::ErrorFunctionNotFound(value.to_string(), Position::NONE).into() - } - } -} - -#[cfg(not(feature = "no_smartstring"))] -impl TryFrom for FnPtr { - type Error = Box; - - #[inline(always)] - fn try_from(value: crate::ImmutableString) -> Result { - let s: Identifier = value.into(); - Self::try_from(s) - } -} - -impl TryFrom for FnPtr { - type Error = Box; - - #[inline(always)] - fn try_from(value: String) -> Result { - let s: Identifier = value.into(); - Self::try_from(s) - } -} - -impl TryFrom<&str> for FnPtr { - type Error = Box; - - #[inline(always)] - fn try_from(value: &str) -> Result { - let s: Identifier = value.into(); - Self::try_from(s) - } -} - /// A general function trail object. #[cfg(not(feature = "sync"))] pub type FnAny = dyn Fn(NativeCallContext, &mut FnCallArgs) -> RhaiResult; diff --git a/src/fn_ptr.rs b/src/fn_ptr.rs new file mode 100644 index 00000000..fefe90ba --- /dev/null +++ b/src/fn_ptr.rs @@ -0,0 +1,178 @@ +//! The `FnPtr` type. + +use crate::token::is_valid_identifier; +use crate::{ + Dynamic, EvalAltResult, Identifier, NativeCallContext, Position, RhaiResult, StaticVec, +}; +#[cfg(feature = "no_std")] +use std::prelude::v1::*; +use std::{ + convert::{TryFrom, TryInto}, + fmt, mem, +}; + +/// A general function pointer, which may carry additional (i.e. curried) argument values +/// to be passed onto a function during a call. +#[derive(Debug, Clone, Hash)] +pub struct FnPtr(Identifier, StaticVec); + +impl FnPtr { + /// Create a new function pointer. + #[inline(always)] + #[must_use] + pub fn new(name: impl Into) -> Result> { + name.into().try_into() + } + /// Create a new function pointer without checking its parameters. + #[inline(always)] + #[must_use] + pub(crate) fn new_unchecked(name: Identifier, curry: StaticVec) -> Self { + Self(name.into(), curry) + } + /// Get the name of the function. + #[inline(always)] + #[must_use] + pub fn fn_name(&self) -> &str { + self.get_fn_name().as_ref() + } + /// Get the name of the function. + #[inline(always)] + #[must_use] + pub(crate) fn get_fn_name(&self) -> &Identifier { + &self.0 + } + /// Get the underlying data of the function pointer. + #[inline(always)] + #[must_use] + pub(crate) fn take_data(self) -> (Identifier, StaticVec) { + (self.0, self.1) + } + /// Get the curried arguments. + #[inline(always)] + #[must_use] + pub fn curry(&self) -> &[Dynamic] { + self.1.as_ref() + } + /// Add a new curried argument. + #[inline(always)] + pub fn add_curry(&mut self, value: Dynamic) -> &mut Self { + self.1.push(value); + self + } + /// Set curried arguments to the function pointer. + #[inline(always)] + pub fn set_curry(&mut self, values: impl IntoIterator) -> &mut Self { + self.1 = values.into_iter().collect(); + self + } + /// Is the function pointer curried? + #[inline(always)] + #[must_use] + pub fn is_curried(&self) -> bool { + !self.1.is_empty() + } + /// Get the number of curried arguments. + #[inline(always)] + #[must_use] + pub fn num_curried(&self) -> usize { + self.1.len() + } + /// Does the function pointer refer to an anonymous function? + /// + /// Not available under `no_function`. + #[cfg(not(feature = "no_function"))] + #[inline(always)] + #[must_use] + pub fn is_anonymous(&self) -> bool { + self.0.starts_with(crate::engine::FN_ANONYMOUS) + } + /// Call the function pointer with curried arguments (if any). + /// + /// If this function is a script-defined function, it must not be marked private. + /// + /// # WARNING + /// + /// All the arguments are _consumed_, meaning that they're replaced by `()`. + /// This is to avoid unnecessarily cloning the arguments. + /// Do not use the arguments after this call. If they are needed afterwards, + /// clone them _before_ calling this function. + #[inline(always)] + #[must_use] + pub fn call_dynamic( + &self, + ctx: &NativeCallContext, + this_ptr: Option<&mut Dynamic>, + mut arg_values: impl AsMut<[Dynamic]>, + ) -> RhaiResult { + let mut arg_values = arg_values.as_mut(); + let mut args_data; + + if self.num_curried() > 0 { + args_data = StaticVec::with_capacity(self.num_curried() + arg_values.len()); + args_data.extend(self.curry().iter().cloned()); + args_data.extend(arg_values.iter_mut().map(mem::take)); + arg_values = args_data.as_mut(); + }; + + let is_method = this_ptr.is_some(); + + let mut args = StaticVec::with_capacity(arg_values.len() + 1); + if let Some(obj) = this_ptr { + args.push(obj); + } + args.extend(arg_values.iter_mut()); + + ctx.call_fn_dynamic_raw(self.fn_name(), is_method, &mut args) + } +} + +impl fmt::Display for FnPtr { + #[inline(always)] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Fn({})", self.0) + } +} + +impl TryFrom for FnPtr { + type Error = Box; + + #[inline(always)] + fn try_from(value: Identifier) -> Result { + if is_valid_identifier(value.chars()) { + Ok(Self(value, Default::default())) + } else { + EvalAltResult::ErrorFunctionNotFound(value.to_string(), Position::NONE).into() + } + } +} + +#[cfg(not(feature = "no_smartstring"))] +impl TryFrom for FnPtr { + type Error = Box; + + #[inline(always)] + fn try_from(value: crate::ImmutableString) -> Result { + let s: Identifier = value.into(); + Self::try_from(s) + } +} + +impl TryFrom for FnPtr { + type Error = Box; + + #[inline(always)] + fn try_from(value: String) -> Result { + let s: Identifier = value.into(); + Self::try_from(s) + } +} + +impl TryFrom<&str> for FnPtr { + type Error = Box; + + #[inline(always)] + fn try_from(value: &str) -> Result { + let s: Identifier = value.into(); + Self::try_from(s) + } +} diff --git a/src/fn_register.rs b/src/fn_register.rs index 2178e56b..0e978142 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -3,7 +3,8 @@ #![allow(non_snake_case)] use crate::dynamic::{DynamicWriteLock, Variant}; -use crate::fn_native::{CallableFunction, FnAny, FnCallArgs, SendSync}; +use crate::fn_call::FnCallArgs; +use crate::fn_native::{CallableFunction, FnAny, SendSync}; use crate::r#unsafe::unsafe_try_cast; use crate::token::Position; use crate::{Dynamic, EvalAltResult, NativeCallContext}; diff --git a/src/lib.rs b/src/lib.rs index a193bf30..669ff9ac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -83,6 +83,7 @@ mod fn_call; mod fn_func; mod fn_hash; mod fn_native; +mod fn_ptr; mod fn_register; mod immutable_string; mod module; @@ -132,22 +133,14 @@ pub use dynamic::Dynamic; pub use engine::{Engine, EvalContext, OP_CONTAINS, OP_EQUALS}; pub use error::EvalAltResult; pub use error_parsing::{LexError, ParseError, ParseErrorType}; -pub use fn_native::{FnPtr, NativeCallContext}; +pub use fn_native::NativeCallContext; +pub use fn_ptr::FnPtr; pub use fn_register::RegisterNativeFunction; pub use immutable_string::ImmutableString; pub use module::{FnNamespace, Module}; pub use scope::Scope; pub use token::Position; -/// 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 = "no_smartstring"))] -pub type Identifier = SmartString; - -/// An identifier in Rhai. -#[cfg(feature = "no_smartstring")] -pub type Identifier = ImmutableString; - /// A trait to enable registering Rust functions. /// This trait is no longer needed and will be removed in the future. #[deprecated( @@ -164,6 +157,30 @@ pub trait RegisterFn {} )] pub trait RegisterResultFn {} +/// 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"))] +#[cfg(not(feature = "no_smartstring"))] +pub(crate) type Identifier = SmartString; + +/// An identifier in Rhai. +#[cfg(not(feature = "internals"))] +#[cfg(feature = "no_smartstring")] +pub(crate) type Identifier = ImmutableString; + +/// 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(feature = "internals")] +#[cfg(not(feature = "no_smartstring"))] +#[deprecated = "this type is volatile and may change"] +pub type Identifier = SmartString; + +/// An identifier in Rhai. +#[cfg(feature = "internals")] +#[cfg(feature = "no_smartstring")] +#[deprecated = "this type is volatile and may change"] +pub type Identifier = ImmutableString; + /// Alias to [`Rc`][std::rc::Rc] or [`Arc`][std::sync::Arc] depending on the `sync` feature flag. pub use fn_native::Shared; @@ -310,17 +327,12 @@ type StaticVec = smallvec::SmallVec<[T; 4]>; #[cfg(feature = "internals")] pub type StaticVec = smallvec::SmallVec<[T; 4]>; -#[cfg(not(feature = "internals"))] #[cfg(not(feature = "no_smartstring"))] pub(crate) type SmartString = smartstring::SmartString; #[cfg(feature = "no_smartstring")] pub(crate) type SmartString = String; -#[cfg(feature = "internals")] -#[cfg(not(feature = "no_smartstring"))] -pub type SmartString = smartstring::SmartString; - // Compiler guards against mutually-exclusive feature flags #[cfg(feature = "no_float")] diff --git a/src/module/mod.rs b/src/module/mod.rs index c29fd06f..9479e6c1 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -2,7 +2,8 @@ use crate::ast::{FnAccess, Ident}; use crate::dynamic::Variant; -use crate::fn_native::{shared_take_or_clone, CallableFunction, FnCallArgs, IteratorFn, SendSync}; +use crate::fn_call::FnCallArgs; +use crate::fn_native::{shared_take_or_clone, CallableFunction, IteratorFn, SendSync}; use crate::fn_register::RegisterNativeFunction; use crate::parse::IdentifierBuilder; use crate::token::Token; diff --git a/src/packages/string_basic.rs b/src/packages/string_basic.rs index d577fe2e..348e602c 100644 --- a/src/packages/string_basic.rs +++ b/src/packages/string_basic.rs @@ -1,7 +1,8 @@ #![allow(non_snake_case)] use crate::plugin::*; -use crate::{def_package, FnPtr}; +use crate::{def_package, FnPtr, INT}; +use std::fmt::{Binary, LowerHex, Octal}; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -16,6 +17,7 @@ pub const FUNC_TO_DEBUG: &'static str = "to_debug"; def_package!(crate:BasicStringPackage:"Basic string utilities, including printing.", lib, { combine_with_exported_module!(lib, "print_debug", print_debug_functions); + combine_with_exported_module!(lib, "number_formatting", number_formatting); }); // Register print and debug @@ -147,3 +149,145 @@ mod print_debug_functions { } } } + +#[export_module] +mod number_formatting { + #[rhai_fn(skip)] + pub fn to_hex(value: T) -> ImmutableString { + format!("{:x}", value).into() + } + #[rhai_fn(skip)] + pub fn to_octal(value: T) -> ImmutableString { + format!("{:o}", value).into() + } + #[rhai_fn(skip)] + pub fn to_binary(value: T) -> ImmutableString { + format!("{:b}", value).into() + } + + #[rhai_fn(name = "to_hex")] + pub fn int_to_hex(value: INT) -> ImmutableString { + to_hex(value) + } + #[rhai_fn(name = "to_octal")] + pub fn int_to_octal(value: INT) -> ImmutableString { + to_octal(value) + } + + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + pub mod numbers { + #[rhai_fn(name = "to_hex")] + pub fn u8_to_hex(value: u8) -> ImmutableString { + to_hex(value) + } + #[rhai_fn(name = "to_hex")] + pub fn u16_to_hex(value: u16) -> ImmutableString { + to_hex(value) + } + #[rhai_fn(name = "to_hex")] + pub fn u32_to_hex(value: u32) -> ImmutableString { + to_hex(value) + } + #[rhai_fn(name = "to_hex")] + pub fn u64_to_hex(value: u64) -> ImmutableString { + to_hex(value) + } + #[rhai_fn(name = "to_hex")] + pub fn i8_to_hex(value: i8) -> ImmutableString { + to_hex(value) + } + #[rhai_fn(name = "to_hex")] + pub fn i16_to_hex(value: i16) -> ImmutableString { + to_hex(value) + } + #[rhai_fn(name = "to_hex")] + pub fn i32_to_hex(value: i32) -> ImmutableString { + to_hex(value) + } + #[rhai_fn(name = "to_hex")] + pub fn i64_to_hex(value: i64) -> ImmutableString { + to_hex(value) + } + #[rhai_fn(name = "to_octal")] + pub fn u8_to_octal(value: u8) -> ImmutableString { + to_octal(value) + } + #[rhai_fn(name = "to_octal")] + pub fn u16_to_octal(value: u16) -> ImmutableString { + to_octal(value) + } + #[rhai_fn(name = "to_octal")] + pub fn u32_to_octal(value: u32) -> ImmutableString { + to_octal(value) + } + #[rhai_fn(name = "to_octal")] + pub fn u64_to_octal(value: u64) -> ImmutableString { + to_octal(value) + } + #[rhai_fn(name = "to_octal")] + pub fn i8_to_octal(value: i8) -> ImmutableString { + to_octal(value) + } + #[rhai_fn(name = "to_octal")] + pub fn i16_to_octal(value: i16) -> ImmutableString { + to_octal(value) + } + #[rhai_fn(name = "to_octal")] + pub fn i32_to_octal(value: i32) -> ImmutableString { + to_octal(value) + } + #[rhai_fn(name = "to_octal")] + pub fn i64_to_octal(value: i64) -> ImmutableString { + to_octal(value) + } + #[rhai_fn(name = "to_binary")] + pub fn u8_to_binary(value: u8) -> ImmutableString { + to_binary(value) + } + #[rhai_fn(name = "to_binary")] + pub fn u16_to_binary(value: u16) -> ImmutableString { + to_binary(value) + } + #[rhai_fn(name = "to_binary")] + pub fn u32_to_binary(value: u32) -> ImmutableString { + to_binary(value) + } + #[rhai_fn(name = "to_binary")] + pub fn u64_to_binary(value: u64) -> ImmutableString { + to_binary(value) + } + #[rhai_fn(name = "to_binary")] + pub fn i8_to_binary(value: i8) -> ImmutableString { + to_binary(value) + } + #[rhai_fn(name = "to_binary")] + pub fn i16_to_binary(value: i16) -> ImmutableString { + to_binary(value) + } + #[rhai_fn(name = "to_binary")] + pub fn i32_to_binary(value: i32) -> ImmutableString { + to_binary(value) + } + #[rhai_fn(name = "to_binary")] + pub fn i64_to_binary(value: i64) -> ImmutableString { + to_binary(value) + } + + #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] + pub mod num_128 { + #[rhai_fn(name = "to_hex")] + pub fn u128_to_hex(value: u128) -> ImmutableString { + to_hex(value) + } + #[rhai_fn(name = "to_octal")] + pub fn i128_to_octal(value: i128) -> ImmutableString { + to_octal(value) + } + #[rhai_fn(name = "to_binary")] + pub fn i128_to_binary(value: i128) -> ImmutableString { + to_binary(value) + } + } + } +} diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index 86694e84..2dc9cfa3 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -100,6 +100,52 @@ mod string_functions { } } + pub fn to_upper(string: &str) -> ImmutableString { + string.to_uppercase().into() + } + pub fn make_upper(string: &mut ImmutableString) { + *string = to_upper(string); + } + pub fn to_lower(string: &str) -> ImmutableString { + string.to_lowercase().into() + } + pub fn make_lower(string: &mut ImmutableString) { + *string = to_lower(string); + } + + #[rhai_fn(name = "to_upper")] + pub fn to_upper_char(character: char) -> char { + let mut stream = character.to_uppercase(); + let ch = stream + .next() + .expect("never fails because there should be at least one character"); + if stream.next().is_some() { + character + } else { + ch + } + } + #[rhai_fn(name = "make_upper")] + pub fn make_upper_char(character: &mut char) { + *character = to_upper_char(*character) + } + #[rhai_fn(name = "to_lower")] + pub fn to_lower_char(character: char) -> char { + let mut stream = character.to_lowercase(); + let ch = stream + .next() + .expect("never fails because there should be at least one character"); + if stream.next().is_some() { + character + } else { + ch + } + } + #[rhai_fn(name = "make_lower")] + pub fn make_lower_char(character: &mut char) { + *character = to_lower_char(*character) + } + #[rhai_fn(name = "index_of")] pub fn index_of_char_starting_from(string: &str, character: char, start: INT) -> INT { let start = if start < 0 { diff --git a/src/parse.rs b/src/parse.rs index 6b443fa4..890db0cf 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -318,7 +318,7 @@ fn parse_var_name(input: &mut TokenStream) -> Result<(String, Position), ParseEr } } -/// Parse ( expr ) +/// Parse `(` expr `)` fn parse_paren_expr( input: &mut TokenStream, state: &mut ParseState, diff --git a/src/plugin.rs b/src/plugin.rs index 6e9e88ec..24bf9e3a 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -1,6 +1,7 @@ //! Module defining macros for developing _plugins_. -pub use crate::fn_native::{CallableFunction, FnCallArgs}; +use crate::fn_call::FnCallArgs; +pub use crate::fn_native::CallableFunction; pub use crate::{ Dynamic, Engine, EvalAltResult, FnAccess, FnNamespace, ImmutableString, Module, NativeCallContext, Position, diff --git a/src/scope.rs b/src/scope.rs index b1d7c749..8f6138eb 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -122,7 +122,6 @@ impl<'a> Scope<'a> { /// assert!(my_scope.is_empty()); /// ``` #[inline(always)] - #[must_use] pub fn clear(&mut self) -> &mut Self { self.names.clear(); self.values.clear(); diff --git a/src/serde/serialize.rs b/src/serde/serialize.rs index ee0f3996..1bf93383 100644 --- a/src/serde/serialize.rs +++ b/src/serde/serialize.rs @@ -31,7 +31,7 @@ impl Serialize for Dynamic { #[cfg(feature = "decimal")] #[cfg(not(feature = "f32_float"))] - Union::Decimal(x, _, _) => { + Union::Decimal(ref x, _, _) => { use rust_decimal::prelude::ToPrimitive; if let Some(v) = x.to_f64() { @@ -42,7 +42,7 @@ impl Serialize for Dynamic { } #[cfg(feature = "decimal")] #[cfg(feature = "f32_float")] - Union::Decimal(x, _, _) => { + Union::Decimal(ref x, _, _) => { use rust_decimal::prelude::ToPrimitive; if let Some(v) = x.to_f32() {