diff --git a/CHANGELOG.md b/CHANGELOG.md index d8f6f26e..0d73c19f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,36 @@ Rhai Release Notes ================== +Version 0.20.2 +============== + +Bug fixes +--------- + +* Constant propagation during optimization for constants held in a custom scope now works properly instead of always replacing by `()`. + +Breaking changes +---------------- + +* `Engine::disable_doc_comments` is removed because doc-comments are now placed under the `metadata` feature flag. +* Registering a custom syntax now only requires specifying whether the `Scope` is adjusted (i.e. whether variables are added or removed). There is no need to specify the number of variables added/removed. +* Assigning to a property of a constant is now allowed and no longer raise an `EvalAltResult::ErrorAssignmentToConstant` error. This is to facilitate the Singleton pattern. Registered setter functions are automatically guarded against setters calling on constants and will continue to raise errors unless the `pure` attribute is present (for plugins). + +New features +------------ + +* Each `Dynamic` value can now contain arbitrary data (type `i16`) in the form of a _tag_. This is to use up otherwise wasted space in the `Dynamic` type. +* A new internal feature `no_smartstring` to turn off `SmartString` for those rare cases that it is needed. +* `DynamicReadLock` and `DynamicWriteLoc` are exposed under `internals`. +* `From>>` is added for `Dynamic` mapping directly to a shared value, together with support for `Dynamic::from`. + +Enhancements +------------ + +* Registering a custom syntax now only requires specifying whether the `Scope` is adjusted (i.e. whether variables are added or removed). This allows more flexibility for cases where the number of new variables declared depends on internal logic. +* Putting a `pure` attribute on a plugin property/index setter now enables it to be used on constants. + + Version 0.20.1 ============== diff --git a/Cargo.toml b/Cargo.toml index 7e6d2d03..dd2d32de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ members = [".", "codegen"] [package] name = "rhai" -version = "0.20.1" +version = "0.20.2" edition = "2018" authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung", "jhwgh1968"] description = "Embedded scripting for Rust" @@ -32,7 +32,7 @@ no_float = [] # no floating-point f32_float = [] # set FLOAT=f32 only_i32 = [] # set INT=i32 (useful for 32-bit systems) only_i64 = [] # set INT=i64 (default) and disable support for all other integer types -decimal = ["rust_decimal"] # add the Decimal number type +decimal = ["rust_decimal/std"] # add the Decimal number type no_index = [] # no arrays and indexing no_object = [] # no custom objects no_function = ["no_closure"] # no script-defined functions (meaning no closures) @@ -48,6 +48,9 @@ no_std = ["no-std-compat", "num-traits/libm", "core-error", "libm", "ahash/compi wasm-bindgen = ["instant/wasm-bindgen"] stdweb = ["instant/stdweb"] +# internal feature flags - volatile +no_smartstring = [] # Do not use SmartString + [profile.release] lto = "fat" codegen-units = 1 @@ -89,8 +92,9 @@ default_features = false optional = true [dependencies.rust_decimal] -version = "1.11" +version = "1.13" default_features = false +features = ["maths"] optional = true [target.'cfg(target_arch = "wasm32")'.dependencies] diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml index 778a70fe..284644a8 100644 --- a/codegen/Cargo.toml +++ b/codegen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rhai_codegen" -version = "0.3.5" +version = "0.3.6" edition = "2018" authors = ["jhwgh1968", "Stephen Chung"] description = "Procedural macros support package for Rhai, a scripting language and engine for Rust" diff --git a/codegen/src/function.rs b/codegen/src/function.rs index c8c336e6..ccc97afa 100644 --- a/codegen/src/function.rs +++ b/codegen/src/function.rs @@ -541,8 +541,10 @@ impl ExportedFn { "property setter requires exactly 2 parameters", )) } - // 3b. Property setters must return nothing. - FnSpecialAccess::Property(Property::Set(_)) if self.return_type().is_some() => { + // 3b. Non-raw property setters must return nothing. + FnSpecialAccess::Property(Property::Set(_)) + if params.return_raw.is_none() && self.return_type().is_some() => + { return Err(syn::Error::new( self.signature.output.span(), "property setter cannot return any value", @@ -569,8 +571,10 @@ impl ExportedFn { "index setter requires exactly 3 parameters", )) } - // 5b. Index setters must return nothing. - FnSpecialAccess::Index(Index::Set) if self.return_type().is_some() => { + // 5b. Non-raw index setters must return nothing. + FnSpecialAccess::Index(Index::Set) + if params.return_raw.is_none() && self.return_type().is_some() => + { return Err(syn::Error::new( self.signature.output.span(), "index setter cannot return any value", @@ -693,9 +697,7 @@ impl ExportedFn { unpack_statements.push( syn::parse2::(quote! { if args[0usize].is_read_only() { - return Err(Box::new( - EvalAltResult::ErrorAssignmentToConstant(#arg_lit_str.to_string(), Position::NONE) - )); + return EvalAltResult::ErrorAssignmentToConstant(#arg_lit_str.to_string(), Position::NONE).into(); } }) .unwrap(), diff --git a/codegen/src/test/function.rs b/codegen/src/test/function.rs index a8a56d83..2013a120 100644 --- a/codegen/src/test/function.rs +++ b/codegen/src/test/function.rs @@ -492,9 +492,7 @@ mod generate_tests { #[inline(always)] fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { if args[0usize].is_read_only() { - return Err(Box::new( - EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE) - )); + return EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into(); } let arg1 = mem::take(args[1usize]).cast::(); let arg0 = &mut args[0usize].write_lock::().unwrap(); diff --git a/codegen/src/test/module.rs b/codegen/src/test/module.rs index 410f25ed..959d2700 100644 --- a/codegen/src/test/module.rs +++ b/codegen/src/test/module.rs @@ -1107,9 +1107,7 @@ mod generate_tests { #[inline(always)] fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { if args[0usize].is_read_only() { - return Err(Box::new( - EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE) - )); + return EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into(); } let arg0 = &mut args[0usize].write_lock::().unwrap(); Ok(Dynamic::from(increment(arg0))) @@ -1169,9 +1167,7 @@ mod generate_tests { #[inline(always)] fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { if args[0usize].is_read_only() { - return Err(Box::new( - EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE) - )); + return EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into(); } let arg0 = &mut args[0usize].write_lock::().unwrap(); Ok(Dynamic::from(increment(arg0))) @@ -1252,9 +1248,7 @@ mod generate_tests { #[inline(always)] fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { if args[0usize].is_read_only() { - return Err(Box::new( - EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE) - )); + return EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into(); } let arg0 = &mut args[0usize].write_lock::().unwrap(); Ok(Dynamic::from(increment(arg0))) @@ -1336,9 +1330,7 @@ mod generate_tests { #[inline(always)] fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { if args[0usize].is_read_only() { - return Err(Box::new( - EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE) - )); + return EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into(); } let arg0 = &mut args[0usize].write_lock::().unwrap(); Ok(Dynamic::from(int_foo(arg0))) @@ -1399,9 +1391,7 @@ mod generate_tests { #[inline(always)] fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { if args[0usize].is_read_only() { - return Err(Box::new( - EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE) - )); + return EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into(); } let arg0 = &mut args[0usize].write_lock::().unwrap(); Ok(Dynamic::from(int_foo(arg0))) @@ -1459,9 +1449,7 @@ mod generate_tests { #[inline(always)] fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { if args[0usize].is_read_only() { - return Err(Box::new( - EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE) - )); + return EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into(); } let arg1 = mem::take(args[1usize]).cast::(); let arg0 = &mut args[0usize].write_lock::().unwrap(); @@ -1523,9 +1511,7 @@ mod generate_tests { #[inline(always)] fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { if args[0usize].is_read_only() { - return Err(Box::new( - EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE) - )); + return EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into(); } let arg1 = mem::take(args[1usize]).cast::(); let arg0 = &mut args[0usize].write_lock::().unwrap(); @@ -1584,9 +1570,7 @@ mod generate_tests { #[inline(always)] fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { if args[0usize].is_read_only() { - return Err(Box::new( - EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE) - )); + return EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into(); } let arg1 = mem::take(args[1usize]).cast::(); let arg0 = &mut args[0usize].write_lock::().unwrap(); @@ -1648,9 +1632,7 @@ mod generate_tests { #[inline(always)] fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { if args[0usize].is_read_only() { - return Err(Box::new( - EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE) - )); + return EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into(); } let arg1 = mem::take(args[1usize]).cast::(); let arg0 = &mut args[0usize].write_lock::().unwrap(); @@ -1709,9 +1691,7 @@ mod generate_tests { #[inline(always)] fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { if args[0usize].is_read_only() { - return Err(Box::new( - EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE) - )); + return EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into(); } let arg1 = mem::take(args[1usize]).cast::(); let arg2 = mem::take(args[2usize]).cast::(); @@ -1774,9 +1754,7 @@ mod generate_tests { #[inline(always)] fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { if args[0usize].is_read_only() { - return Err(Box::new( - EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE) - )); + return EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into(); } let arg1 = mem::take(args[1usize]).cast::(); let arg2 = mem::take(args[2usize]).cast::(); diff --git a/no_std/no_std_test/Cargo.toml b/no_std/no_std_test/Cargo.toml index 5ae73ab8..94087a70 100644 --- a/no_std/no_std_test/Cargo.toml +++ b/no_std/no_std_test/Cargo.toml @@ -39,4 +39,4 @@ lto = "fat" [patch.crates-io] # Patch smartstring wth a PR fix because it doesn't properly handle no-std builds. -smartstring = { git = "https://github.com/okready/smartstring", branch = "fix-no_std-builds" } +smartstring = { git = "https://github.com/rhaiscript/smartstring" } diff --git a/src/ast.rs b/src/ast.rs index 3d16bf68..21841502 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -14,6 +14,7 @@ use std::{ fmt, hash::Hash, iter::empty, + mem, num::{NonZeroU8, NonZeroUsize}, ops::{Add, AddAssign, Deref, DerefMut}, }; @@ -52,6 +53,8 @@ pub struct ScriptFnDef { /// Encapsulated running environment, if any. pub lib: Option>, /// Encapsulated imported modules. + /// + /// Not available under `no_module`. #[cfg(not(feature = "no_module"))] pub mods: crate::engine::Imports, /// Function name. @@ -61,9 +64,14 @@ pub struct ScriptFnDef { /// Names of function parameters. pub params: StaticVec, /// Access to external variables. + /// + /// Not available under `no_closure`. #[cfg(not(feature = "no_closure"))] pub externals: std::collections::BTreeSet, - /// Function doc-comments (if any). + /// _(METADATA)_ Function doc-comments (if any). + /// Exported under the `metadata` feature only. + /// + /// Not available under `no_function`. #[cfg(not(feature = "no_function"))] #[cfg(feature = "metadata")] pub comments: StaticVec, @@ -97,7 +105,10 @@ impl fmt::Display for ScriptFnDef { #[cfg(not(feature = "no_function"))] #[derive(Debug, Eq, PartialEq, Clone, Hash)] pub struct ScriptFnMetadata<'a> { - /// Function doc-comments (if any). + /// _(METADATA)_ Function doc-comments (if any). + /// Exported under the `metadata` feature only. + /// + /// Not available under `no_function`. /// /// Block doc-comments are kept in a single string slice with line-breaks within. /// @@ -148,6 +159,23 @@ impl<'a> Into> for &'a ScriptFnDef { } } +#[cfg(not(feature = "no_function"))] +impl std::cmp::PartialOrd for ScriptFnMetadata<'_> { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +#[cfg(not(feature = "no_function"))] +impl std::cmp::Ord for ScriptFnMetadata<'_> { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + match self.name.cmp(other.name) { + std::cmp::Ordering::Equal => self.params.len().cmp(&other.params.len()), + cmp => cmp, + } + } +} + /// Compiled AST (abstract syntax tree) of a Rhai script. /// /// # Thread Safety @@ -162,6 +190,8 @@ pub struct AST { /// Script-defined functions. functions: Shared, /// Embedded module resolver, if any. + /// + /// Not available under `no_module`. #[cfg(not(feature = "no_module"))] resolver: Option>, } @@ -186,9 +216,10 @@ impl AST { statements: impl IntoIterator, functions: impl Into>, ) -> Self { + let statements: StaticVec<_> = statements.into_iter().collect(); Self { source: None, - body: StmtBlock(statements.into_iter().collect(), Position::NONE), + body: StmtBlock::new(statements, Position::NONE), functions: functions.into(), #[cfg(not(feature = "no_module"))] resolver: None, @@ -201,9 +232,10 @@ impl AST { functions: impl Into>, source: impl Into, ) -> Self { + let statements: StaticVec<_> = statements.into_iter().collect(); Self { source: Some(source.into()), - body: StmtBlock(statements.into_iter().collect(), Position::NONE), + body: StmtBlock::new(statements, Position::NONE), functions: functions.into(), #[cfg(not(feature = "no_module"))] resolver: None, @@ -267,7 +299,7 @@ impl AST { /// _(INTERNALS)_ Get the internal shared [`Module`] containing all script-defined functions. /// Exported under the `internals` feature only. /// - /// Not available under `no_function`. + /// Not available under `no_function` or `no_module`. #[cfg(feature = "internals")] #[deprecated = "this method is volatile and may change"] #[cfg(not(feature = "no_module"))] @@ -303,6 +335,8 @@ impl AST { } /// _(INTERNALS)_ Get the embedded [module resolver][crate::ModuleResolver]. /// Exported under the `internals` feature only. + /// + /// Not available under `no_module`. #[cfg(not(feature = "no_module"))] #[cfg(feature = "internals")] #[inline(always)] @@ -835,7 +869,9 @@ pub struct StmtBlock(StaticVec, Position); impl StmtBlock { /// Create a new [`StmtBlock`]. pub fn new(statements: impl Into>, pos: Position) -> Self { - Self(statements.into(), pos) + let mut statements = statements.into(); + statements.shrink_to_fit(); + Self(statements, pos) } /// Is this statements block empty? #[inline(always)] @@ -882,7 +918,7 @@ impl fmt::Debug for StmtBlock { impl From for Stmt { fn from(block: StmtBlock) -> Self { let block_pos = block.position(); - Self::Block(block.0.into_vec(), block_pos) + Self::Block(block.0.into_boxed_slice(), block_pos) } } @@ -922,7 +958,7 @@ pub enum Stmt { /// function call forming one statement. FnCall(Box, Position), /// `{` stmt`;` ... `}` - Block(Vec, Position), + Block(Box<[Stmt]>, Position), /// `try` `{` stmt; ... `}` `catch` `(` var `)` `{` stmt; ... `}` TryCatch( Box<(StmtBlock, Option, StmtBlock)>, @@ -938,12 +974,18 @@ pub enum Stmt { /// `return`/`throw` Return(ReturnType, Option, Position), /// `import` expr `as` var + /// + /// Not available under `no_module`. #[cfg(not(feature = "no_module"))] Import(Expr, Option>, Position), /// `export` var `as` var `,` ... + /// + /// Not available under `no_module`. #[cfg(not(feature = "no_module"))] - Export(Vec<(Ident, Option)>, Position), + Export(Box<[(Ident, Option)]>, Position), /// Convert a variable to shared. + /// + /// Not available under `no_closure`. #[cfg(not(feature = "no_closure"))] Share(Identifier), } @@ -959,7 +1001,9 @@ impl From for StmtBlock { #[inline(always)] fn from(stmt: Stmt) -> Self { match stmt { - Stmt::Block(block, pos) => Self(block.into(), pos), + Stmt::Block(mut block, pos) => { + Self(block.iter_mut().map(|v| mem::take(v)).collect(), pos) + } Stmt::Noop(pos) => Self(Default::default(), pos), _ => { let pos = stmt.position(); @@ -1265,7 +1309,7 @@ impl Stmt { } } Self::Block(x, _) => { - for s in x { + for s in x.iter() { if !s.walk(path, on_node) { return false; } @@ -1313,10 +1357,10 @@ impl Stmt { pub struct CustomExpr { /// List of keywords. pub keywords: StaticVec, + /// Is the current [`Scope`][crate::Scope] modified? + pub scope_changed: bool, /// List of tokens actually parsed. - pub tokens: Vec, - /// Delta number of variables in the scope. - pub scope_delta: isize, + pub tokens: StaticVec, } /// _(INTERNALS)_ A binary expression. @@ -1419,7 +1463,7 @@ impl fmt::Debug for FnCallHashes { } impl FnCallHashes { - /// Create a [`FnCallHash`] with only the native Rust hash. + /// Create a [`FnCallHashes`] with only the native Rust hash. #[inline(always)] pub fn from_native(hash: u64) -> Self { Self { @@ -1427,7 +1471,7 @@ impl FnCallHashes { native: hash, } } - /// Create a [`FnCallHash`] with both native Rust and script function hashes set to the same value. + /// Create a [`FnCallHashes`] with both native Rust and script function hashes set to the same value. #[inline(always)] pub fn from_script(hash: u64) -> Self { Self { @@ -1435,7 +1479,7 @@ impl FnCallHashes { native: hash, } } - /// Create a [`FnCallHash`] with both native Rust and script function hashes. + /// Create a [`FnCallHashes`] with both native Rust and script function hashes. #[inline(always)] pub fn from_script_and_native(script: u64, native: u64) -> Self { Self { @@ -1443,21 +1487,21 @@ impl FnCallHashes { native, } } - /// Is this [`FnCallHash`] native Rust only? + /// Is this [`FnCallHashes`] native Rust only? #[inline(always)] pub fn is_native_only(&self) -> bool { self.script.is_none() } - /// Get the script function hash from this [`FnCallHash`]. + /// Get the script function hash from this [`FnCallHashes`]. /// /// # Panics /// - /// Panics if the [`FnCallHash`] is native Rust only. + /// Panics if the [`FnCallHashes`] is native Rust only. #[inline(always)] pub fn script_hash(&self) -> u64 { self.script.unwrap() } - /// Get the naive Rust function hash from this [`FnCallHash`]. + /// Get the naive Rust function hash from this [`FnCallHashes`]. #[inline(always)] pub fn native_hash(&self) -> u64 { self.native @@ -1479,7 +1523,7 @@ pub struct FnCallExpr { /// List of function call argument expressions. pub args: StaticVec, /// List of function call arguments that are constants. - pub constant_args: smallvec::SmallVec<[(Dynamic, Position); 2]>, + pub literal_args: smallvec::SmallVec<[(Dynamic, Position); 2]>, /// Function name. pub name: Identifier, /// Does this function call capture the parent scope? @@ -1495,16 +1539,18 @@ impl FnCallExpr { /// Are there no arguments to this function call? #[inline(always)] pub fn is_args_empty(&self) -> bool { - self.args.is_empty() && self.constant_args.is_empty() + self.args.is_empty() && self.literal_args.is_empty() } /// Get the number of arguments to this function call. #[inline(always)] pub fn args_count(&self) -> usize { - self.args.len() + self.constant_args.len() + self.args.len() + self.literal_args.len() } } /// A type that wraps a floating-point number and implements [`Hash`]. +/// +/// Not available under `no_float`. #[cfg(not(feature = "no_float"))] #[derive(Clone, Copy, PartialEq, PartialOrd)] pub struct FloatWrapper(F); @@ -1635,6 +1681,8 @@ pub enum Expr { /// Integer constant. IntegerConstant(INT, Position), /// Floating-point constant. + /// + /// Not available under `no_float`. #[cfg(not(feature = "no_float"))] FloatConstant(FloatWrapper, Position), /// Character constant. @@ -1745,8 +1793,8 @@ impl fmt::Debug for Expr { ff.field("name", &x.name) .field("hash", &x.hashes) .field("args", &x.args); - if !x.constant_args.is_empty() { - ff.field("constant_args", &x.constant_args); + if !x.literal_args.is_empty() { + ff.field("literal_args", &x.literal_args); } if x.capture { ff.field("capture", &x.capture); @@ -2072,7 +2120,14 @@ mod tests { assert_eq!(size_of::>(), 16); assert_eq!(size_of::(), 32); assert_eq!(size_of::>(), 32); - assert_eq!(size_of::(), 96); + assert_eq!( + size_of::(), + if cfg!(feature = "no_smartstring") { + 80 + } else { + 96 + } + ); assert_eq!(size_of::(), 288); assert_eq!(size_of::(), 56); assert_eq!( diff --git a/src/bin/rhai-repl.rs b/src/bin/rhai-repl.rs index 7f4f87d0..a4af261f 100644 --- a/src/bin/rhai-repl.rs +++ b/src/bin/rhai-repl.rs @@ -106,7 +106,7 @@ fn main() { .compile(&contents) .map_err(|err| err.into()) .and_then(|mut ast| { - ast.set_source(filename.to_string_lossy()); + ast.set_source(filename.to_string_lossy().to_string()); Module::eval_ast_as_new(Default::default(), &ast, &engine) }) { Err(err) => { diff --git a/src/bin/rhai-run.rs b/src/bin/rhai-run.rs index bdef6b64..887cffc4 100644 --- a/src/bin/rhai-run.rs +++ b/src/bin/rhai-run.rs @@ -85,7 +85,7 @@ fn main() { .compile(contents) .map_err(|err| Box::new(err.into()) as Box) .and_then(|mut ast| { - ast.set_source(filename.to_string_lossy()); + ast.set_source(filename.to_string_lossy().to_string()); engine.consume_ast(&ast) }) { diff --git a/src/dynamic.rs b/src/dynamic.rs index 2414d31f..d90b532d 100644 --- a/src/dynamic.rs +++ b/src/dynamic.rs @@ -2,7 +2,7 @@ use crate::fn_native::SendSync; use crate::r#unsafe::{unsafe_cast_box, unsafe_try_cast}; -use crate::{FnPtr, ImmutableString, SmartString, INT}; +use crate::{FnPtr, ImmutableString, INT}; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{ @@ -140,6 +140,12 @@ pub enum AccessMode { ReadOnly, } +/// Arbitrary data attached to a [`Dynamic`] value. +pub type Tag = i16; + +/// Default tag value for [`Dynamic`]. +const DEFAULT_TAG: Tag = 0; + /// Dynamic type containing any value. pub struct Dynamic(pub(crate) Union); @@ -148,45 +154,61 @@ pub struct Dynamic(pub(crate) Union); /// Most variants are boxed to reduce the size. pub enum Union { /// The Unit value - (). - Unit((), AccessMode), + Unit((), Tag, AccessMode), /// A boolean value. - Bool(bool, AccessMode), + Bool(bool, Tag, AccessMode), /// An [`ImmutableString`] value. - Str(ImmutableString, AccessMode), + Str(ImmutableString, Tag, AccessMode), /// A character value. - Char(char, AccessMode), + Char(char, Tag, AccessMode), /// An integer value. - Int(INT, AccessMode), + Int(INT, Tag, AccessMode), /// A floating-point value. + /// + /// Not available under `no_float`. #[cfg(not(feature = "no_float"))] - Float(FloatWrapper, AccessMode), - /// A fixed-precision decimal value. + Float(FloatWrapper, Tag, AccessMode), + /// _(DECIMAL)_ A fixed-precision decimal value. + /// Exported under the `decimal` feature only. #[cfg(feature = "decimal")] - Decimal(Box, AccessMode), + Decimal(Box, Tag, AccessMode), /// An array value. + /// + /// Not available under `no_index`. #[cfg(not(feature = "no_index"))] - Array(Box, AccessMode), + Array(Box, Tag, AccessMode), /// An object map value. + /// + /// Not available under `no_object`. #[cfg(not(feature = "no_object"))] - Map(Box, AccessMode), + Map(Box, Tag, AccessMode), /// A function pointer. - FnPtr(Box, AccessMode), + FnPtr(Box, Tag, AccessMode), /// A timestamp value. + /// + /// Not available under `no-std`. #[cfg(not(feature = "no_std"))] - TimeStamp(Box, AccessMode), + TimeStamp(Box, Tag, AccessMode), /// Any type as a trait object. - Variant(Box>, AccessMode), + Variant(Box>, Tag, AccessMode), /// A _shared_ value of any type. + /// + /// Not available under `no_closure`. #[cfg(not(feature = "no_closure"))] - Shared(crate::Shared>, AccessMode), + Shared(crate::Shared>, Tag, AccessMode), } -/// Underlying [`Variant`] read guard for [`Dynamic`]. +/// _(INTERNALS)_ Lock guard for reading a [`Dynamic`]. +/// Exported under the `internals` feature only. /// -/// This data structure provides transparent interoperability between -/// normal [`Dynamic`] and shared [`Dynamic`] values. +/// This type provides transparent interoperability between normal [`Dynamic`] and shared +/// [`Dynamic`] values. +/// +/// # Volatile Data Structure +/// +/// This type is volatile and may change. #[derive(Debug)] pub struct DynamicReadLock<'d, T: Clone>(DynamicReadLockInner<'d, T>); @@ -220,10 +242,15 @@ impl<'d, T: Any + Clone> Deref for DynamicReadLock<'d, T> { } } -/// Underlying [`Variant`] write guard for [`Dynamic`]. +/// _(INTERNALS)_ Lock guard for writing a [`Dynamic`]. +/// Exported under the `internals` feature only. /// -/// This data structure provides transparent interoperability between -/// normal [`Dynamic`] and shared [`Dynamic`] values. +/// This type provides transparent interoperability between normal [`Dynamic`] and shared +/// [`Dynamic`] values. +/// +/// # Volatile Data Structure +/// +/// This type is volatile and may change. #[derive(Debug)] pub struct DynamicWriteLock<'d, T: Clone>(DynamicWriteLockInner<'d, T>); @@ -270,12 +297,62 @@ impl<'d, T: Any + Clone> DerefMut for DynamicWriteLock<'d, T> { } impl Dynamic { + /// Get the arbitrary data attached to this [`Dynamic`]. + pub fn tag(&self) -> Tag { + match self.0 { + Union::Unit(_, tag, _) + | Union::Bool(_, tag, _) + | Union::Str(_, tag, _) + | Union::Char(_, tag, _) + | Union::Int(_, tag, _) + | Union::FnPtr(_, tag, _) + | Union::Variant(_, tag, _) => tag, + + #[cfg(not(feature = "no_float"))] + Union::Float(_, tag, _) => tag, + #[cfg(feature = "decimal")] + Union::Decimal(_, tag, _) => tag, + #[cfg(not(feature = "no_index"))] + Union::Array(_, tag, _) => tag, + #[cfg(not(feature = "no_object"))] + Union::Map(_, tag, _) => tag, + #[cfg(not(feature = "no_std"))] + Union::TimeStamp(_, tag, _) => tag, + #[cfg(not(feature = "no_closure"))] + Union::Shared(_, tag, _) => tag, + } + } + /// Attach arbitrary data to this [`Dynamic`]. + pub fn set_tag(&mut self, value: Tag) { + match &mut self.0 { + Union::Unit(_, tag, _) + | Union::Bool(_, tag, _) + | Union::Str(_, tag, _) + | Union::Char(_, tag, _) + | Union::Int(_, tag, _) + | Union::FnPtr(_, tag, _) + | Union::Variant(_, tag, _) => *tag = value, + + #[cfg(not(feature = "no_float"))] + Union::Float(_, tag, _) => *tag = value, + #[cfg(feature = "decimal")] + Union::Decimal(_, tag, _) => *tag = value, + #[cfg(not(feature = "no_index"))] + Union::Array(_, tag, _) => *tag = value, + #[cfg(not(feature = "no_object"))] + Union::Map(_, tag, _) => *tag = value, + #[cfg(not(feature = "no_std"))] + Union::TimeStamp(_, tag, _) => *tag = value, + #[cfg(not(feature = "no_closure"))] + Union::Shared(_, tag, _) => *tag = value, + } + } /// Does this [`Dynamic`] hold a variant data type /// instead of one of the supported system primitive types? #[inline(always)] pub fn is_variant(&self) -> bool { match self.0 { - Union::Variant(_, _) => true, + Union::Variant(_, _, _) => true, _ => false, } } @@ -287,7 +364,7 @@ impl Dynamic { pub fn is_shared(&self) -> bool { #[cfg(not(feature = "no_closure"))] match self.0 { - Union::Shared(_, _) => return true, + Union::Shared(_, _, _) => return true, _ => (), } @@ -315,27 +392,27 @@ impl Dynamic { /// Otherwise, this call panics if the data is currently borrowed for write. pub fn type_id(&self) -> TypeId { match &self.0 { - Union::Unit(_, _) => TypeId::of::<()>(), - Union::Bool(_, _) => TypeId::of::(), - Union::Str(_, _) => TypeId::of::(), - Union::Char(_, _) => TypeId::of::(), - Union::Int(_, _) => TypeId::of::(), + Union::Unit(_, _, _) => TypeId::of::<()>(), + Union::Bool(_, _, _) => TypeId::of::(), + Union::Str(_, _, _) => TypeId::of::(), + Union::Char(_, _, _) => TypeId::of::(), + Union::Int(_, _, _) => TypeId::of::(), #[cfg(not(feature = "no_float"))] - Union::Float(_, _) => TypeId::of::(), + Union::Float(_, _, _) => TypeId::of::(), #[cfg(feature = "decimal")] - Union::Decimal(_, _) => TypeId::of::(), + Union::Decimal(_, _, _) => TypeId::of::(), #[cfg(not(feature = "no_index"))] - Union::Array(_, _) => TypeId::of::(), + Union::Array(_, _, _) => TypeId::of::(), #[cfg(not(feature = "no_object"))] - Union::Map(_, _) => TypeId::of::(), - Union::FnPtr(_, _) => TypeId::of::(), + Union::Map(_, _, _) => TypeId::of::(), + Union::FnPtr(_, _, _) => TypeId::of::(), #[cfg(not(feature = "no_std"))] - Union::TimeStamp(_, _) => TypeId::of::(), + Union::TimeStamp(_, _, _) => TypeId::of::(), - Union::Variant(value, _) => (***value).type_id(), + Union::Variant(value, _, _) => (***value).type_id(), #[cfg(not(feature = "no_closure"))] - Union::Shared(cell, _) => { + Union::Shared(cell, _, _) => { #[cfg(not(feature = "sync"))] let value = cell.borrow(); #[cfg(feature = "sync")] @@ -353,34 +430,34 @@ impl Dynamic { /// Otherwise, this call panics if the data is currently borrowed for write. pub fn type_name(&self) -> &'static str { match &self.0 { - Union::Unit(_, _) => "()", - Union::Bool(_, _) => "bool", - Union::Str(_, _) => "string", - Union::Char(_, _) => "char", - Union::Int(_, _) => type_name::(), + Union::Unit(_, _, _) => "()", + Union::Bool(_, _, _) => "bool", + Union::Str(_, _, _) => "string", + Union::Char(_, _, _) => "char", + Union::Int(_, _, _) => type_name::(), #[cfg(not(feature = "no_float"))] - Union::Float(_, _) => type_name::(), + Union::Float(_, _, _) => type_name::(), #[cfg(feature = "decimal")] - Union::Decimal(_, _) => "decimal", + Union::Decimal(_, _, _) => "decimal", #[cfg(not(feature = "no_index"))] - Union::Array(_, _) => "array", + Union::Array(_, _, _) => "array", #[cfg(not(feature = "no_object"))] - Union::Map(_, _) => "map", - Union::FnPtr(_, _) => "Fn", + Union::Map(_, _, _) => "map", + Union::FnPtr(_, _, _) => "Fn", #[cfg(not(feature = "no_std"))] - Union::TimeStamp(_, _) => "timestamp", + Union::TimeStamp(_, _, _) => "timestamp", - Union::Variant(value, _) => (***value).type_name(), + Union::Variant(value, _, _) => (***value).type_name(), #[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "sync"))] - Union::Shared(cell, _) => cell + Union::Shared(cell, _, _) => cell .try_borrow() .map(|v| (*v).type_name()) .unwrap_or(""), #[cfg(not(feature = "no_closure"))] #[cfg(feature = "sync")] - Union::Shared(cell, _) => (*cell.read().unwrap()).type_name(), + Union::Shared(cell, _, _) => (*cell.read().unwrap()).type_name(), } } } @@ -388,17 +465,17 @@ impl Dynamic { impl Hash for Dynamic { fn hash(&self, state: &mut H) { match &self.0 { - Union::Unit(_, _) => ().hash(state), - Union::Bool(value, _) => value.hash(state), - Union::Str(s, _) => s.hash(state), - Union::Char(ch, _) => ch.hash(state), - Union::Int(i, _) => i.hash(state), + Union::Unit(_, _, _) => ().hash(state), + Union::Bool(value, _, _) => value.hash(state), + Union::Str(s, _, _) => s.hash(state), + Union::Char(ch, _, _) => ch.hash(state), + Union::Int(i, _, _) => i.hash(state), #[cfg(not(feature = "no_float"))] - Union::Float(f, _) => f.hash(state), + Union::Float(f, _, _) => f.hash(state), #[cfg(not(feature = "no_index"))] - Union::Array(a, _) => (**a).hash(state), + Union::Array(a, _, _) => (**a).hash(state), #[cfg(not(feature = "no_object"))] - Union::Map(m, _) => { + Union::Map(m, _, _) => { let mut buf: crate::StaticVec<_> = m.iter().collect(); buf.sort_by(|(a, _), (b, _)| a.cmp(b)); @@ -407,16 +484,16 @@ impl Hash for Dynamic { value.hash(state); }) } - Union::FnPtr(f, _) if f.is_curried() => { + Union::FnPtr(f, _, _) if f.is_curried() => { unimplemented!( "{} with curried arguments cannot be hashed", self.type_name() ) } - Union::FnPtr(f, _) => f.fn_name().hash(state), + Union::FnPtr(f, _, _) => f.fn_name().hash(state), #[cfg(not(feature = "no_closure"))] - Union::Shared(cell, _) => { + Union::Shared(cell, _, _) => { #[cfg(not(feature = "sync"))] let value = cell.borrow(); #[cfg(feature = "sync")] @@ -468,27 +545,27 @@ pub(crate) fn map_std_type_name(name: &str) -> &str { impl fmt::Display for Dynamic { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match &self.0 { - Union::Unit(_, _) => write!(f, ""), - Union::Bool(value, _) => fmt::Display::fmt(value, f), - Union::Str(value, _) => fmt::Display::fmt(value, f), - Union::Char(value, _) => fmt::Display::fmt(value, f), - Union::Int(value, _) => fmt::Display::fmt(value, f), + Union::Unit(_, _, _) => write!(f, ""), + Union::Bool(value, _, _) => fmt::Display::fmt(value, f), + Union::Str(value, _, _) => fmt::Display::fmt(value, f), + Union::Char(value, _, _) => fmt::Display::fmt(value, f), + Union::Int(value, _, _) => fmt::Display::fmt(value, f), #[cfg(not(feature = "no_float"))] - Union::Float(value, _) => fmt::Display::fmt(value, f), + Union::Float(value, _, _) => fmt::Display::fmt(value, f), #[cfg(feature = "decimal")] - Union::Decimal(value, _) => fmt::Display::fmt(value, f), + Union::Decimal(value, _, _) => fmt::Display::fmt(value, f), #[cfg(not(feature = "no_index"))] - Union::Array(value, _) => fmt::Debug::fmt(value, f), + Union::Array(value, _, _) => fmt::Debug::fmt(value, f), #[cfg(not(feature = "no_object"))] - Union::Map(value, _) => { + Union::Map(value, _, _) => { f.write_str("#")?; fmt::Debug::fmt(value, f) } - Union::FnPtr(value, _) => fmt::Display::fmt(value, f), + Union::FnPtr(value, _, _) => fmt::Display::fmt(value, f), #[cfg(not(feature = "no_std"))] - Union::TimeStamp(_, _) => f.write_str(""), + Union::TimeStamp(_, _, _) => f.write_str(""), - Union::Variant(value, _) => { + Union::Variant(value, _, _) => { let _type_id = (***value).type_id(); #[cfg(not(feature = "only_i32"))] @@ -530,7 +607,7 @@ impl fmt::Display for Dynamic { #[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "sync"))] - Union::Shared(cell, _) => { + Union::Shared(cell, _, _) => { if let Ok(v) = cell.try_borrow() { fmt::Display::fmt(&*v, f) } else { @@ -539,7 +616,7 @@ impl fmt::Display for Dynamic { } #[cfg(not(feature = "no_closure"))] #[cfg(feature = "sync")] - Union::Shared(cell, _) => fmt::Display::fmt(&*cell.read().unwrap(), f), + Union::Shared(cell, _, _) => fmt::Display::fmt(&*cell.read().unwrap(), f), } } } @@ -547,27 +624,27 @@ impl fmt::Display for Dynamic { impl fmt::Debug for Dynamic { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match &self.0 { - Union::Unit(value, _) => fmt::Debug::fmt(value, f), - Union::Bool(value, _) => fmt::Debug::fmt(value, f), - Union::Str(value, _) => fmt::Debug::fmt(value, f), - Union::Char(value, _) => fmt::Debug::fmt(value, f), - Union::Int(value, _) => fmt::Debug::fmt(value, f), + Union::Unit(value, _, _) => fmt::Debug::fmt(value, f), + Union::Bool(value, _, _) => fmt::Debug::fmt(value, f), + Union::Str(value, _, _) => fmt::Debug::fmt(value, f), + Union::Char(value, _, _) => fmt::Debug::fmt(value, f), + Union::Int(value, _, _) => fmt::Debug::fmt(value, f), #[cfg(not(feature = "no_float"))] - Union::Float(value, _) => fmt::Debug::fmt(value, f), + Union::Float(value, _, _) => fmt::Debug::fmt(value, f), #[cfg(feature = "decimal")] - Union::Decimal(value, _) => fmt::Debug::fmt(value, f), + Union::Decimal(value, _, _) => fmt::Debug::fmt(value, f), #[cfg(not(feature = "no_index"))] - Union::Array(value, _) => fmt::Debug::fmt(value, f), + Union::Array(value, _, _) => fmt::Debug::fmt(value, f), #[cfg(not(feature = "no_object"))] - Union::Map(value, _) => { + Union::Map(value, _, _) => { f.write_str("#")?; fmt::Debug::fmt(value, f) } - Union::FnPtr(value, _) => fmt::Debug::fmt(value, f), + Union::FnPtr(value, _, _) => fmt::Debug::fmt(value, f), #[cfg(not(feature = "no_std"))] - Union::TimeStamp(_, _) => write!(f, ""), + Union::TimeStamp(_, _, _) => write!(f, ""), - Union::Variant(value, _) => { + Union::Variant(value, _, _) => { let _type_id = (***value).type_id(); #[cfg(not(feature = "only_i32"))] @@ -617,7 +694,7 @@ impl fmt::Debug for Dynamic { #[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "sync"))] - Union::Shared(cell, _) => { + Union::Shared(cell, _, _) => { if let Ok(v) = cell.try_borrow() { write!(f, "{:?} (shared)", *v) } else { @@ -626,7 +703,7 @@ impl fmt::Debug for Dynamic { } #[cfg(not(feature = "no_closure"))] #[cfg(feature = "sync")] - Union::Shared(cell, _) => fmt::Debug::fmt(&*cell.read().unwrap(), f), + Union::Shared(cell, _, _) => fmt::Debug::fmt(&*cell.read().unwrap(), f), } } } @@ -639,31 +716,45 @@ impl Clone for Dynamic { /// The cloned copy is marked read-write even if the original is read-only. fn clone(&self) -> Self { match self.0 { - Union::Unit(value, _) => Self(Union::Unit(value, AccessMode::ReadWrite)), - Union::Bool(value, _) => Self(Union::Bool(value, AccessMode::ReadWrite)), - Union::Str(ref value, _) => Self(Union::Str(value.clone(), AccessMode::ReadWrite)), - Union::Char(value, _) => Self(Union::Char(value, AccessMode::ReadWrite)), - Union::Int(value, _) => Self(Union::Int(value, AccessMode::ReadWrite)), + Union::Unit(value, tag, _) => Self(Union::Unit(value, tag, AccessMode::ReadWrite)), + Union::Bool(value, tag, _) => Self(Union::Bool(value, tag, AccessMode::ReadWrite)), + Union::Str(ref value, tag, _) => { + Self(Union::Str(value.clone(), tag, AccessMode::ReadWrite)) + } + Union::Char(value, tag, _) => Self(Union::Char(value, tag, AccessMode::ReadWrite)), + Union::Int(value, tag, _) => Self(Union::Int(value, tag, AccessMode::ReadWrite)), #[cfg(not(feature = "no_float"))] - Union::Float(value, _) => Self(Union::Float(value, AccessMode::ReadWrite)), + Union::Float(value, tag, _) => Self(Union::Float(value, tag, AccessMode::ReadWrite)), #[cfg(feature = "decimal")] - Union::Decimal(ref value, _) => { - Self(Union::Decimal(value.clone(), AccessMode::ReadWrite)) + Union::Decimal(ref value, tag, _) => { + Self(Union::Decimal(value.clone(), tag, AccessMode::ReadWrite)) } #[cfg(not(feature = "no_index"))] - Union::Array(ref value, _) => Self(Union::Array(value.clone(), AccessMode::ReadWrite)), + Union::Array(ref value, tag, _) => { + Self(Union::Array(value.clone(), tag, AccessMode::ReadWrite)) + } #[cfg(not(feature = "no_object"))] - Union::Map(ref value, _) => Self(Union::Map(value.clone(), AccessMode::ReadWrite)), - Union::FnPtr(ref value, _) => Self(Union::FnPtr(value.clone(), AccessMode::ReadWrite)), + Union::Map(ref value, tag, _) => { + Self(Union::Map(value.clone(), tag, AccessMode::ReadWrite)) + } + Union::FnPtr(ref value, tag, _) => { + Self(Union::FnPtr(value.clone(), tag, AccessMode::ReadWrite)) + } #[cfg(not(feature = "no_std"))] - Union::TimeStamp(ref value, _) => { - Self(Union::TimeStamp(value.clone(), AccessMode::ReadWrite)) + Union::TimeStamp(ref value, tag, _) => { + Self(Union::TimeStamp(value.clone(), tag, AccessMode::ReadWrite)) } - Union::Variant(ref value, _) => (***value).clone_into_dynamic(), + Union::Variant(ref value, tag, _) => { + let mut x = (***value).clone_into_dynamic(); + x.set_tag(tag); + x + } #[cfg(not(feature = "no_closure"))] - Union::Shared(ref cell, _) => Self(Union::Shared(cell.clone(), AccessMode::ReadWrite)), + Union::Shared(ref cell, tag, _) => { + Self(Union::Shared(cell.clone(), tag, AccessMode::ReadWrite)) + } } } } @@ -677,84 +768,121 @@ impl Default for Dynamic { impl Dynamic { /// A [`Dynamic`] containing a `()`. - pub const UNIT: Dynamic = Self(Union::Unit((), AccessMode::ReadWrite)); + pub const UNIT: Dynamic = Self(Union::Unit((), DEFAULT_TAG, AccessMode::ReadWrite)); /// A [`Dynamic`] containing a `true`. - pub const TRUE: Dynamic = Self(Union::Bool(true, AccessMode::ReadWrite)); + pub const TRUE: Dynamic = Self(Union::Bool(true, DEFAULT_TAG, AccessMode::ReadWrite)); /// A [`Dynamic`] containing a [`false`]. - pub const FALSE: Dynamic = Self(Union::Bool(false, AccessMode::ReadWrite)); + pub const FALSE: Dynamic = Self(Union::Bool(false, DEFAULT_TAG, AccessMode::ReadWrite)); /// A [`Dynamic`] containing the integer zero. - pub const ZERO: Dynamic = Self(Union::Int(0, AccessMode::ReadWrite)); + pub const ZERO: Dynamic = Self(Union::Int(0, DEFAULT_TAG, AccessMode::ReadWrite)); /// A [`Dynamic`] containing the integer one. - pub const ONE: Dynamic = Self(Union::Int(1, AccessMode::ReadWrite)); + pub const ONE: Dynamic = Self(Union::Int(1, DEFAULT_TAG, AccessMode::ReadWrite)); + /// A [`Dynamic`] containing the integer two. + pub const TWO: Dynamic = Self(Union::Int(2, DEFAULT_TAG, AccessMode::ReadWrite)); + /// A [`Dynamic`] containing the integer ten. + pub const TEN: Dynamic = Self(Union::Int(10, DEFAULT_TAG, AccessMode::ReadWrite)); /// A [`Dynamic`] containing the integer negative one. - pub const NEGATIVE_ONE: Dynamic = Self(Union::Int(-1, AccessMode::ReadWrite)); - /// A [`Dynamic`] containing the floating-point zero. + pub const NEGATIVE_ONE: Dynamic = Self(Union::Int(-1, DEFAULT_TAG, AccessMode::ReadWrite)); + /// A [`Dynamic`] containing `0.0`. + /// + /// Not available under `no_float`. #[cfg(not(feature = "no_float"))] pub const FLOAT_ZERO: Dynamic = Self(Union::Float( FloatWrapper::const_new(0.0), + DEFAULT_TAG, AccessMode::ReadWrite, )); - /// A [`Dynamic`] containing the floating-point one. + /// A [`Dynamic`] containing `1.0`. + /// + /// Not available under `no_float`. #[cfg(not(feature = "no_float"))] pub const FLOAT_ONE: Dynamic = Self(Union::Float( FloatWrapper::const_new(1.0), + DEFAULT_TAG, AccessMode::ReadWrite, )); - /// A [`Dynamic`] containing the floating-point negative one. + /// A [`Dynamic`] containing `2.0`. + /// + /// Not available under `no_float`. + #[cfg(not(feature = "no_float"))] + pub const FLOAT_TWO: Dynamic = Self(Union::Float( + FloatWrapper::const_new(2.0), + DEFAULT_TAG, + AccessMode::ReadWrite, + )); + /// A [`Dynamic`] containing `10.0`. + /// + /// Not available under `no_float`. + #[cfg(not(feature = "no_float"))] + pub const FLOAT_TEN: Dynamic = Self(Union::Float( + FloatWrapper::const_new(10.0), + DEFAULT_TAG, + AccessMode::ReadWrite, + )); + /// A [`Dynamic`] containing the `-1.0`. + /// + /// Not available under `no_float`. #[cfg(not(feature = "no_float"))] pub const FLOAT_NEGATIVE_ONE: Dynamic = Self(Union::Float( FloatWrapper::const_new(-1.0), + DEFAULT_TAG, AccessMode::ReadWrite, )); /// Get the [`AccessMode`] for this [`Dynamic`]. pub(crate) fn access_mode(&self) -> AccessMode { match self.0 { - Union::Unit(_, access) - | Union::Bool(_, access) - | Union::Str(_, access) - | Union::Char(_, access) - | Union::Int(_, access) - | Union::FnPtr(_, access) - | Union::Variant(_, access) => access, + Union::Unit(_, _, access) + | Union::Bool(_, _, access) + | Union::Str(_, _, access) + | Union::Char(_, _, access) + | Union::Int(_, _, access) + | Union::FnPtr(_, _, access) + | Union::Variant(_, _, access) => access, #[cfg(not(feature = "no_float"))] - Union::Float(_, access) => access, + Union::Float(_, _, access) => access, #[cfg(feature = "decimal")] - Union::Decimal(_, access) => access, + Union::Decimal(_, _, access) => access, #[cfg(not(feature = "no_index"))] - Union::Array(_, access) => access, + Union::Array(_, _, access) => access, #[cfg(not(feature = "no_object"))] - Union::Map(_, access) => access, + Union::Map(_, _, access) => access, #[cfg(not(feature = "no_std"))] - Union::TimeStamp(_, access) => access, + Union::TimeStamp(_, _, access) => access, #[cfg(not(feature = "no_closure"))] - Union::Shared(_, access) => access, + Union::Shared(_, _, access) => access, } } /// Set the [`AccessMode`] for this [`Dynamic`]. pub(crate) fn set_access_mode(&mut self, typ: AccessMode) { match &mut self.0 { - Union::Unit(_, access) - | Union::Bool(_, access) - | Union::Str(_, access) - | Union::Char(_, access) - | Union::Int(_, access) - | Union::FnPtr(_, access) - | Union::Variant(_, access) => *access = typ, + Union::Unit(_, _, access) + | Union::Bool(_, _, access) + | Union::Str(_, _, access) + | Union::Char(_, _, access) + | Union::Int(_, _, access) + | Union::FnPtr(_, _, access) + | Union::Variant(_, _, access) => *access = typ, #[cfg(not(feature = "no_float"))] - Union::Float(_, access) => *access = typ, + Union::Float(_, _, access) => *access = typ, #[cfg(feature = "decimal")] - Union::Decimal(_, access) => *access = typ, + Union::Decimal(_, _, access) => *access = typ, #[cfg(not(feature = "no_index"))] - Union::Array(_, access) => *access = typ, + Union::Array(a, _, access) => { + *access = typ; + a.iter_mut().for_each(|v| v.set_access_mode(typ)); + } #[cfg(not(feature = "no_object"))] - Union::Map(_, access) => *access = typ, + Union::Map(m, _, access) => { + *access = typ; + m.values_mut().for_each(|v| v.set_access_mode(typ)); + } #[cfg(not(feature = "no_std"))] - Union::TimeStamp(_, access) => *access = typ, + Union::TimeStamp(_, _, access) => *access = typ, #[cfg(not(feature = "no_closure"))] - Union::Shared(_, access) => *access = typ, + Union::Shared(_, _, access) => *access = typ, } } /// Is this [`Dynamic`] read-only? @@ -767,8 +895,8 @@ impl Dynamic { pub fn is_read_only(&self) -> bool { #[cfg(not(feature = "no_closure"))] match self.0 { - Union::Shared(_, AccessMode::ReadOnly) => return true, - Union::Shared(ref cell, _) => { + Union::Shared(_, _, AccessMode::ReadOnly) => return true, + Union::Shared(ref cell, _, _) => { #[cfg(not(feature = "sync"))] let value = cell.borrow(); #[cfg(feature = "sync")] @@ -790,21 +918,21 @@ impl Dynamic { /// Can this [`Dynamic`] be hashed? pub(crate) fn is_hashable(&self) -> bool { match &self.0 { - Union::Unit(_, _) - | Union::Bool(_, _) - | Union::Str(_, _) - | Union::Char(_, _) - | Union::Int(_, _) => true, + Union::Unit(_, _, _) + | Union::Bool(_, _, _) + | Union::Str(_, _, _) + | Union::Char(_, _, _) + | Union::Int(_, _, _) => true, #[cfg(not(feature = "no_float"))] - Union::Float(_, _) => true, + Union::Float(_, _, _) => true, #[cfg(not(feature = "no_index"))] - Union::Array(_, _) => true, + Union::Array(_, _, _) => true, #[cfg(not(feature = "no_object"))] - Union::Map(_, _) => true, + Union::Map(_, _, _) => true, #[cfg(not(feature = "no_closure"))] - Union::Shared(cell, _) => { + Union::Shared(cell, _, _) => { #[cfg(not(feature = "sync"))] let value = cell.borrow(); #[cfg(feature = "sync")] @@ -900,51 +1028,54 @@ impl Dynamic { .deref() .into(); } - if TypeId::of::() == TypeId::of::() { - return ::downcast_ref::(&value) - .unwrap() - .clone() - .into(); - } if TypeId::of::() == TypeId::of::<()>() { return ().into(); } value = match unsafe_try_cast::<_, String>(value) { - Ok(s) => return (s).into(), - Err(val) => val, + Ok(s) => return s.into(), + Err(value) => value, }; #[cfg(not(feature = "no_index"))] { value = match unsafe_try_cast::<_, Array>(value) { - Ok(array) => return (array).into(), - Err(val) => val, + Ok(array) => return array.into(), + Err(value) => value, }; } #[cfg(not(feature = "no_object"))] { value = match unsafe_try_cast::<_, Map>(value) { - Ok(map) => return (map).into(), - Err(val) => val, + Ok(map) => return map.into(), + Err(value) => value, }; } value = match unsafe_try_cast::<_, FnPtr>(value) { - Ok(fn_ptr) => return (fn_ptr).into(), - Err(val) => val, + Ok(fn_ptr) => return fn_ptr.into(), + Err(value) => value, }; #[cfg(not(feature = "no_std"))] { value = match unsafe_try_cast::<_, Instant>(value) { - Ok(timestamp) => return (timestamp).into(), - Err(val) => val, + Ok(timestamp) => return timestamp.into(), + Err(value) => value, + }; + } + + #[cfg(not(feature = "no_closure"))] + { + value = match unsafe_try_cast::<_, crate::Shared>>(value) { + Ok(value) => return value.into(), + Err(value) => value, }; } Self(Union::Variant( Box::new(Box::new(value)), + DEFAULT_TAG, AccessMode::ReadWrite, )) } @@ -968,8 +1099,12 @@ impl Dynamic { let _access = self.access_mode(); match self.0 { - Union::Shared(_, _) => self, - _ => Self(Union::Shared(crate::Locked::new(self).into(), _access)), + Union::Shared(_, _, _) => self, + _ => Self(Union::Shared( + crate::Locked::new(self).into(), + DEFAULT_TAG, + _access, + )), } } /// Convert the [`Dynamic`] value into specific type. @@ -1000,7 +1135,7 @@ impl Dynamic { // Coded this way in order to maximally leverage potentials for dead-code removal. #[cfg(not(feature = "no_closure"))] - if let Union::Shared(_, _) = self.0 { + if let Union::Shared(_, _, _) = self.0 { return self.flatten().try_cast::(); } @@ -1010,7 +1145,7 @@ impl Dynamic { if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Int(value, _) => unsafe_try_cast(value).ok(), + Union::Int(value, _, _) => unsafe_try_cast(value).ok(), _ => None, }; } @@ -1018,7 +1153,7 @@ impl Dynamic { #[cfg(not(feature = "no_float"))] if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Float(value, _) => unsafe_try_cast(*value).ok(), + Union::Float(value, _, _) => unsafe_try_cast(*value).ok(), _ => None, }; } @@ -1026,35 +1161,35 @@ impl Dynamic { #[cfg(feature = "decimal")] if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Decimal(value, _) => unsafe_try_cast(*value).ok(), + Union::Decimal(value, _, _) => unsafe_try_cast(*value).ok(), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Bool(value, _) => unsafe_try_cast(value).ok(), + Union::Bool(value, _, _) => unsafe_try_cast(value).ok(), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Str(value, _) => unsafe_try_cast(value).ok(), + Union::Str(value, _, _) => unsafe_try_cast(value).ok(), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Str(value, _) => unsafe_try_cast(value.into_owned()).ok(), + Union::Str(value, _, _) => unsafe_try_cast(value.into_owned()).ok(), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Char(value, _) => unsafe_try_cast(value).ok(), + Union::Char(value, _, _) => unsafe_try_cast(value).ok(), _ => None, }; } @@ -1062,7 +1197,7 @@ impl Dynamic { #[cfg(not(feature = "no_index"))] if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Array(value, _) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v), + Union::Array(value, _, _) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v), _ => None, }; } @@ -1070,14 +1205,14 @@ impl Dynamic { #[cfg(not(feature = "no_object"))] if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Map(value, _) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v), + Union::Map(value, _, _) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::FnPtr(value, _) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v), + Union::FnPtr(value, _, _) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v), _ => None, }; } @@ -1085,22 +1220,22 @@ impl Dynamic { #[cfg(not(feature = "no_std"))] if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::TimeStamp(value, _) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v), + Union::TimeStamp(value, _, _) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v), _ => None, }; } if TypeId::of::() == TypeId::of::<()>() { return match self.0 { - Union::Unit(value, _) => unsafe_try_cast(value).ok(), + Union::Unit(value, _, _) => unsafe_try_cast(value).ok(), _ => None, }; } match self.0 { - Union::Variant(value, _) => (*value).as_box_any().downcast().map(|x| *x).ok(), + Union::Variant(value, _, _) => (*value).as_box_any().downcast().map(|x| *x).ok(), #[cfg(not(feature = "no_closure"))] - Union::Shared(_, _) => unreachable!("Union::Shared case should be already handled"), + Union::Shared(_, _, _) => unreachable!("Union::Shared case should be already handled"), _ => None, } } @@ -1190,7 +1325,7 @@ impl Dynamic { pub fn flatten_clone(&self) -> Self { #[cfg(not(feature = "no_closure"))] match &self.0 { - Union::Shared(cell, _) => { + Union::Shared(cell, _, _) => { #[cfg(not(feature = "sync"))] let value = cell.borrow(); #[cfg(feature = "sync")] @@ -1213,7 +1348,7 @@ impl Dynamic { pub fn flatten(self) -> Self { #[cfg(not(feature = "no_closure"))] match self.0 { - Union::Shared(cell, _) => { + Union::Shared(cell, _, _) => { return crate::fn_native::shared_try_take(cell).map_or_else( |cell| { #[cfg(not(feature = "sync"))] @@ -1246,8 +1381,8 @@ impl Dynamic { pub(crate) fn flatten_in_place(&mut self) { #[cfg(not(feature = "no_closure"))] match self.0 { - Union::Shared(_, _) => match std::mem::take(self).0 { - Union::Shared(cell, _) => { + Union::Shared(_, _, _) => match std::mem::take(self).0 { + Union::Shared(cell, _, _) => { *self = crate::fn_native::shared_try_take(cell).map_or_else( |cell| { #[cfg(not(feature = "sync"))] @@ -1284,7 +1419,7 @@ impl Dynamic { pub fn is_locked(&self) -> bool { #[cfg(not(feature = "no_closure"))] match self.0 { - Union::Shared(ref _cell, _) => { + Union::Shared(ref _cell, _, _) => { #[cfg(not(feature = "sync"))] return _cell.try_borrow().is_err(); @@ -1309,7 +1444,7 @@ impl Dynamic { pub fn read_lock(&self) -> Option> { match self.0 { #[cfg(not(feature = "no_closure"))] - Union::Shared(ref cell, _) => { + Union::Shared(ref cell, _, _) => { #[cfg(not(feature = "sync"))] let value = cell.borrow(); #[cfg(feature = "sync")] @@ -1342,7 +1477,7 @@ impl Dynamic { pub fn write_lock(&mut self) -> Option> { match self.0 { #[cfg(not(feature = "no_closure"))] - Union::Shared(ref cell, _) => { + Union::Shared(ref cell, _, _) => { #[cfg(not(feature = "sync"))] let value = cell.borrow_mut(); #[cfg(feature = "sync")] @@ -1372,72 +1507,72 @@ impl Dynamic { if TypeId::of::() == TypeId::of::() { return match &self.0 { - Union::Int(value, _) => ::downcast_ref::(value), + Union::Int(value, _, _) => ::downcast_ref::(value), _ => None, }; } #[cfg(not(feature = "no_float"))] if TypeId::of::() == TypeId::of::() { return match &self.0 { - Union::Float(value, _) => ::downcast_ref::(value.as_ref()), + Union::Float(value, _, _) => ::downcast_ref::(value.as_ref()), _ => None, }; } #[cfg(feature = "decimal")] if TypeId::of::() == TypeId::of::() { return match &self.0 { - Union::Decimal(value, _) => ::downcast_ref::(value.as_ref()), + Union::Decimal(value, _, _) => ::downcast_ref::(value.as_ref()), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match &self.0 { - Union::Bool(value, _) => ::downcast_ref::(value), + Union::Bool(value, _, _) => ::downcast_ref::(value), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match &self.0 { - Union::Str(value, _) => ::downcast_ref::(value), + Union::Str(value, _, _) => ::downcast_ref::(value), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match &self.0 { - Union::Char(value, _) => ::downcast_ref::(value), + Union::Char(value, _, _) => ::downcast_ref::(value), _ => None, }; } #[cfg(not(feature = "no_index"))] if TypeId::of::() == TypeId::of::() { return match &self.0 { - Union::Array(value, _) => ::downcast_ref::(value.as_ref()), + Union::Array(value, _, _) => ::downcast_ref::(value.as_ref()), _ => None, }; } #[cfg(not(feature = "no_object"))] if TypeId::of::() == TypeId::of::() { return match &self.0 { - Union::Map(value, _) => ::downcast_ref::(value.as_ref()), + Union::Map(value, _, _) => ::downcast_ref::(value.as_ref()), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match &self.0 { - Union::FnPtr(value, _) => ::downcast_ref::(value.as_ref()), + Union::FnPtr(value, _, _) => ::downcast_ref::(value.as_ref()), _ => None, }; } #[cfg(not(feature = "no_std"))] if TypeId::of::() == TypeId::of::() { return match &self.0 { - Union::TimeStamp(value, _) => ::downcast_ref::(value.as_ref()), + Union::TimeStamp(value, _, _) => ::downcast_ref::(value.as_ref()), _ => None, }; } if TypeId::of::() == TypeId::of::<()>() { return match &self.0 { - Union::Unit(value, _) => ::downcast_ref::(value), + Union::Unit(value, _, _) => ::downcast_ref::(value), _ => None, }; } @@ -1446,9 +1581,9 @@ impl Dynamic { } match &self.0 { - Union::Variant(value, _) => value.as_ref().as_ref().as_any().downcast_ref::(), + Union::Variant(value, _, _) => value.as_ref().as_ref().as_any().downcast_ref::(), #[cfg(not(feature = "no_closure"))] - Union::Shared(_, _) => None, + Union::Shared(_, _, _) => None, _ => None, } } @@ -1462,72 +1597,72 @@ impl Dynamic { if TypeId::of::() == TypeId::of::() { return match &mut self.0 { - Union::Int(value, _) => ::downcast_mut::(value), + Union::Int(value, _, _) => ::downcast_mut::(value), _ => None, }; } #[cfg(not(feature = "no_float"))] if TypeId::of::() == TypeId::of::() { return match &mut self.0 { - Union::Float(value, _) => ::downcast_mut::(value.as_mut()), + Union::Float(value, _, _) => ::downcast_mut::(value.as_mut()), _ => None, }; } #[cfg(feature = "decimal")] if TypeId::of::() == TypeId::of::() { return match &mut self.0 { - Union::Decimal(value, _) => ::downcast_mut::(value.as_mut()), + Union::Decimal(value, _, _) => ::downcast_mut::(value.as_mut()), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match &mut self.0 { - Union::Bool(value, _) => ::downcast_mut::(value), + Union::Bool(value, _, _) => ::downcast_mut::(value), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match &mut self.0 { - Union::Str(value, _) => ::downcast_mut::(value), + Union::Str(value, _, _) => ::downcast_mut::(value), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match &mut self.0 { - Union::Char(value, _) => ::downcast_mut::(value), + Union::Char(value, _, _) => ::downcast_mut::(value), _ => None, }; } #[cfg(not(feature = "no_index"))] if TypeId::of::() == TypeId::of::() { return match &mut self.0 { - Union::Array(value, _) => ::downcast_mut::(value.as_mut()), + Union::Array(value, _, _) => ::downcast_mut::(value.as_mut()), _ => None, }; } #[cfg(not(feature = "no_object"))] if TypeId::of::() == TypeId::of::() { return match &mut self.0 { - Union::Map(value, _) => ::downcast_mut::(value.as_mut()), + Union::Map(value, _, _) => ::downcast_mut::(value.as_mut()), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match &mut self.0 { - Union::FnPtr(value, _) => ::downcast_mut::(value.as_mut()), + Union::FnPtr(value, _, _) => ::downcast_mut::(value.as_mut()), _ => None, }; } #[cfg(not(feature = "no_std"))] if TypeId::of::() == TypeId::of::() { return match &mut self.0 { - Union::TimeStamp(value, _) => ::downcast_mut::(value.as_mut()), + Union::TimeStamp(value, _, _) => ::downcast_mut::(value.as_mut()), _ => None, }; } if TypeId::of::() == TypeId::of::<()>() { return match &mut self.0 { - Union::Unit(value, _) => ::downcast_mut::(value), + Union::Unit(value, _, _) => ::downcast_mut::(value), _ => None, }; } @@ -1536,9 +1671,9 @@ impl Dynamic { } match &mut self.0 { - Union::Variant(value, _) => value.as_mut().as_mut_any().downcast_mut::(), + Union::Variant(value, _, _) => value.as_mut().as_mut_any().downcast_mut::(), #[cfg(not(feature = "no_closure"))] - Union::Shared(_, _) => None, + Union::Shared(_, _, _) => None, _ => None, } } @@ -1547,9 +1682,9 @@ impl Dynamic { #[inline(always)] pub fn as_unit(&self) -> Result<(), &'static str> { match self.0 { - Union::Unit(value, _) => Ok(value), + Union::Unit(value, _, _) => Ok(value), #[cfg(not(feature = "no_closure"))] - Union::Shared(_, _) => self.read_lock().map(|v| *v).ok_or_else(|| self.type_name()), + Union::Shared(_, _, _) => self.read_lock().map(|v| *v).ok_or_else(|| self.type_name()), _ => Err(self.type_name()), } } @@ -1558,9 +1693,9 @@ impl Dynamic { #[inline(always)] pub fn as_int(&self) -> Result { match self.0 { - Union::Int(n, _) => Ok(n), + Union::Int(n, _, _) => Ok(n), #[cfg(not(feature = "no_closure"))] - Union::Shared(_, _) => self.read_lock().map(|v| *v).ok_or_else(|| self.type_name()), + Union::Shared(_, _, _) => self.read_lock().map(|v| *v).ok_or_else(|| self.type_name()), _ => Err(self.type_name()), } } @@ -1572,9 +1707,9 @@ impl Dynamic { #[inline(always)] pub fn as_float(&self) -> Result { match self.0 { - Union::Float(n, _) => Ok(*n), + Union::Float(n, _, _) => Ok(*n), #[cfg(not(feature = "no_closure"))] - Union::Shared(_, _) => self.read_lock().map(|v| *v).ok_or_else(|| self.type_name()), + Union::Shared(_, _, _) => self.read_lock().map(|v| *v).ok_or_else(|| self.type_name()), _ => Err(self.type_name()), } } @@ -1586,9 +1721,9 @@ impl Dynamic { #[inline(always)] pub fn as_decimal(&self) -> Result { match &self.0 { - Union::Decimal(n, _) => Ok(**n), + Union::Decimal(n, _, _) => Ok(**n), #[cfg(not(feature = "no_closure"))] - Union::Shared(_, _) => self.read_lock().map(|v| *v).ok_or_else(|| self.type_name()), + Union::Shared(_, _, _) => self.read_lock().map(|v| *v).ok_or_else(|| self.type_name()), _ => Err(self.type_name()), } } @@ -1597,9 +1732,9 @@ impl Dynamic { #[inline(always)] pub fn as_bool(&self) -> Result { match self.0 { - Union::Bool(b, _) => Ok(b), + Union::Bool(b, _, _) => Ok(b), #[cfg(not(feature = "no_closure"))] - Union::Shared(_, _) => self.read_lock().map(|v| *v).ok_or_else(|| self.type_name()), + Union::Shared(_, _, _) => self.read_lock().map(|v| *v).ok_or_else(|| self.type_name()), _ => Err(self.type_name()), } } @@ -1608,9 +1743,9 @@ impl Dynamic { #[inline(always)] pub fn as_char(&self) -> Result { match self.0 { - Union::Char(n, _) => Ok(n), + Union::Char(n, _, _) => Ok(n), #[cfg(not(feature = "no_closure"))] - Union::Shared(_, _) => self.read_lock().map(|v| *v).ok_or_else(|| self.type_name()), + Union::Shared(_, _, _) => self.read_lock().map(|v| *v).ok_or_else(|| self.type_name()), _ => Err(self.type_name()), } } @@ -1623,9 +1758,9 @@ impl Dynamic { #[inline(always)] pub(crate) fn as_str_ref(&self) -> Result<&str, &'static str> { match &self.0 { - Union::Str(s, _) => Ok(s), + Union::Str(s, _, _) => Ok(s), #[cfg(not(feature = "no_closure"))] - Union::Shared(_, _) => panic!("as_str() cannot be called on shared values"), + Union::Shared(_, _, _) => panic!("as_str() cannot be called on shared values"), _ => Err(self.type_name()), } } @@ -1642,16 +1777,16 @@ impl Dynamic { #[inline(always)] pub fn take_immutable_string(self) -> Result { match self.0 { - Union::Str(s, _) => Ok(s), + Union::Str(s, _, _) => Ok(s), #[cfg(not(feature = "no_closure"))] - Union::Shared(cell, _) => { + Union::Shared(cell, _, _) => { #[cfg(not(feature = "sync"))] let value = cell.borrow(); #[cfg(feature = "sync")] let value = cell.read().unwrap(); match &value.0 { - Union::Str(s, _) => Ok(s.clone()), + Union::Str(s, _, _) => Ok(s.clone()), _ => Err((*value).type_name()), } } @@ -1663,33 +1798,37 @@ impl Dynamic { impl From<()> for Dynamic { #[inline(always)] fn from(value: ()) -> Self { - Self(Union::Unit(value, AccessMode::ReadWrite)) + Self(Union::Unit(value, DEFAULT_TAG, AccessMode::ReadWrite)) } } impl From for Dynamic { #[inline(always)] fn from(value: bool) -> Self { - Self(Union::Bool(value, AccessMode::ReadWrite)) + Self(Union::Bool(value, DEFAULT_TAG, AccessMode::ReadWrite)) } } impl From for Dynamic { #[inline(always)] fn from(value: INT) -> Self { - Self(Union::Int(value, AccessMode::ReadWrite)) + Self(Union::Int(value, DEFAULT_TAG, AccessMode::ReadWrite)) } } #[cfg(not(feature = "no_float"))] impl From for Dynamic { #[inline(always)] fn from(value: FLOAT) -> Self { - Self(Union::Float(value.into(), AccessMode::ReadWrite)) + Self(Union::Float( + value.into(), + DEFAULT_TAG, + AccessMode::ReadWrite, + )) } } #[cfg(not(feature = "no_float"))] impl From> for Dynamic { #[inline(always)] fn from(value: FloatWrapper) -> Self { - Self(Union::Float(value, AccessMode::ReadWrite)) + Self(Union::Float(value, DEFAULT_TAG, AccessMode::ReadWrite)) } } #[cfg(feature = "decimal")] @@ -1698,6 +1837,7 @@ impl From for Dynamic { fn from(value: Decimal) -> Self { Self(Union::Decimal( Box::new(value.into()), + DEFAULT_TAG, AccessMode::ReadWrite, )) } @@ -1705,13 +1845,13 @@ impl From for Dynamic { impl From for Dynamic { #[inline(always)] fn from(value: char) -> Self { - Self(Union::Char(value, AccessMode::ReadWrite)) + Self(Union::Char(value, DEFAULT_TAG, AccessMode::ReadWrite)) } } impl> From for Dynamic { #[inline(always)] fn from(value: S) -> Self { - Self(Union::Str(value.into(), AccessMode::ReadWrite)) + Self(Union::Str(value.into(), DEFAULT_TAG, AccessMode::ReadWrite)) } } impl From<&ImmutableString> for Dynamic { @@ -1720,7 +1860,7 @@ impl From<&ImmutableString> for Dynamic { value.clone().into() } } -#[cfg(not(feature = "no_smartstring_for_identifier"))] +#[cfg(not(feature = "no_smartstring"))] impl From<&crate::Identifier> for Dynamic { #[inline(always)] fn from(value: &crate::Identifier) -> Self { @@ -1729,10 +1869,14 @@ impl From<&crate::Identifier> for Dynamic { } #[cfg(not(feature = "no_index"))] impl Dynamic { - /// Create a [`Dynamc`] from an [`Array`]. + /// Create a [`Dynamic`] from an [`Array`]. #[inline(always)] pub(crate) fn from_array(array: Array) -> Self { - Self(Union::Array(Box::new(array), AccessMode::ReadWrite)) + Self(Union::Array( + Box::new(array), + DEFAULT_TAG, + AccessMode::ReadWrite, + )) } } #[cfg(not(feature = "no_index"))] @@ -1741,6 +1885,7 @@ impl From> for Dynamic { fn from(value: Vec) -> Self { Self(Union::Array( Box::new(value.into_iter().map(Dynamic::from).collect()), + DEFAULT_TAG, AccessMode::ReadWrite, )) } @@ -1751,6 +1896,7 @@ impl From<&[T]> for Dynamic { fn from(value: &[T]) -> Self { Self(Union::Array( Box::new(value.iter().cloned().map(Dynamic::from).collect()), + DEFAULT_TAG, AccessMode::ReadWrite, )) } @@ -1761,16 +1907,21 @@ impl std::iter::FromIterator for Dynamic { fn from_iter>(iter: X) -> Self { Self(Union::Array( Box::new(iter.into_iter().map(Dynamic::from).collect()), + DEFAULT_TAG, AccessMode::ReadWrite, )) } } #[cfg(not(feature = "no_object"))] impl Dynamic { - /// Create a [`Dynamc`] from a [`Map`]. + /// Create a [`Dynamic`] from a [`Map`]. #[inline(always)] pub(crate) fn from_map(map: Map) -> Self { - Self(Union::Map(Box::new(map), AccessMode::ReadWrite)) + Self(Union::Map( + Box::new(map), + DEFAULT_TAG, + AccessMode::ReadWrite, + )) } } #[cfg(not(feature = "no_object"))] @@ -1787,6 +1938,7 @@ impl, T: Variant + Clone> From, T: Variant + Clone> From, T: Variant + Clone> From for Dynamic { #[inline(always)] fn from(value: FnPtr) -> Self { - Self(Union::FnPtr(Box::new(value), AccessMode::ReadWrite)) + Self(Union::FnPtr( + Box::new(value), + DEFAULT_TAG, + AccessMode::ReadWrite, + )) } } impl From> for Dynamic { #[inline(always)] fn from(value: Box) -> Self { - Self(Union::FnPtr(value, AccessMode::ReadWrite)) + Self(Union::FnPtr(value, DEFAULT_TAG, AccessMode::ReadWrite)) } } #[cfg(not(feature = "no_std"))] impl From for Dynamic { #[inline(always)] fn from(value: Instant) -> Self { - Self(Union::TimeStamp(Box::new(value), AccessMode::ReadWrite)) + Self(Union::TimeStamp( + Box::new(value), + DEFAULT_TAG, + AccessMode::ReadWrite, + )) + } +} +#[cfg(not(feature = "no_closure"))] +impl From>> for Dynamic { + #[inline(always)] + fn from(value: crate::Shared>) -> Self { + Self(Union::Shared( + value.into(), + DEFAULT_TAG, + AccessMode::ReadWrite, + )) } } diff --git a/src/engine.rs b/src/engine.rs index 1c696e02..fe2bcc7a 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -558,7 +558,7 @@ pub struct FnResolutionCacheEntry { } /// A function resolution cache. -pub type FnResolutionCache = BTreeMap>; +pub type FnResolutionCache = BTreeMap>>; /// _(INTERNALS)_ A type that holds all the current states of the [`Engine`]. /// Exported under the `internals` feature only. @@ -776,7 +776,7 @@ pub struct Engine { pub(crate) module_resolver: Box, /// A map mapping type names to pretty-print names. - pub(crate) type_names: BTreeMap, + pub(crate) type_names: BTreeMap>, /// An empty [`ImmutableString`] for cloning purposes. pub(crate) empty_string: ImmutableString, @@ -786,7 +786,7 @@ pub struct Engine { /// A map containing custom keywords and precedence to recognize. pub(crate) custom_keywords: BTreeMap>, /// Custom syntax. - pub(crate) custom_syntax: BTreeMap, + pub(crate) custom_syntax: BTreeMap>, /// Callback closure for resolving variable access. pub(crate) resolve_var: Option, @@ -804,11 +804,6 @@ pub struct Engine { /// Max limits. #[cfg(not(feature = "unchecked"))] pub(crate) limits: Limits, - - /// Disable doc-comments? - #[cfg(not(feature = "no_function"))] - #[cfg(feature = "metadata")] - pub(crate) disable_doc_comments: bool, } impl fmt::Debug for Engine { @@ -926,13 +921,9 @@ impl Engine { #[cfg(not(feature = "no_object"))] max_map_size: None, }, - - #[cfg(not(feature = "no_function"))] - #[cfg(feature = "metadata")] - disable_doc_comments: false, }; - engine.global_namespace.set_internal(true); + engine.global_namespace.internal = true; engine.register_global_module(StandardPackage::new().as_shared_module()); engine @@ -987,13 +978,9 @@ impl Engine { #[cfg(not(feature = "no_object"))] max_map_size: None, }, - - #[cfg(not(feature = "no_function"))] - #[cfg(feature = "metadata")] - disable_doc_comments: false, }; - engine.global_namespace.set_internal(true); + engine.global_namespace.internal = true; engine } @@ -1144,6 +1131,7 @@ impl Engine { lib: &[&Module], this_ptr: &mut Option<&mut Dynamic>, target: &mut Target, + root: (&str, Position), rhs: &Expr, idx_values: &mut StaticVec, chain_type: ChainType, @@ -1179,8 +1167,8 @@ impl Engine { let rhs_chain = rhs_chain.unwrap(); self.eval_dot_index_chain_helper( - mods, state, lib, this_ptr, obj_ptr, &x.rhs, idx_values, rhs_chain, - level, new_val, + mods, state, lib, this_ptr, obj_ptr, root, &x.rhs, idx_values, + rhs_chain, level, new_val, ) .map_err(|err| err.fill_position(*x_pos)) } @@ -1199,7 +1187,8 @@ impl Engine { Ok(obj_ptr) => { let ((new_val, new_pos), (op_info, op_pos)) = new_val.unwrap(); self.eval_op_assignment( - mods, state, lib, op_info, op_pos, obj_ptr, new_val, new_pos, + mods, state, lib, op_info, op_pos, obj_ptr, root, new_val, + new_pos, )?; None } @@ -1282,7 +1271,7 @@ impl Engine { )?; let ((new_val, new_pos), (op_info, op_pos)) = new_val.unwrap(); self.eval_op_assignment( - mods, state, lib, op_info, op_pos, val, new_val, new_pos, + mods, state, lib, op_info, op_pos, val, root, new_val, new_pos, )?; Ok((Dynamic::UNIT, true)) } @@ -1311,7 +1300,7 @@ impl Engine { )?; let obj_ptr = (&mut orig_val).into(); self.eval_op_assignment( - mods, state, lib, op_info, op_pos, obj_ptr, new_val, new_pos, + mods, state, lib, op_info, op_pos, obj_ptr, root, new_val, new_pos, )?; new_val = orig_val; } @@ -1365,8 +1354,8 @@ impl Engine { let rhs_chain = rhs_chain.unwrap(); self.eval_dot_index_chain_helper( - mods, state, lib, this_ptr, &mut val, &x.rhs, idx_values, rhs_chain, - level, new_val, + mods, state, lib, this_ptr, &mut val, root, &x.rhs, idx_values, + rhs_chain, level, new_val, ) .map_err(|err| err.fill_position(*x_pos)) } @@ -1396,6 +1385,7 @@ impl Engine { lib, this_ptr, &mut val.into(), + root, &x.rhs, idx_values, rhs_chain, @@ -1438,7 +1428,7 @@ impl Engine { let target = &mut val.into(); self.eval_dot_index_chain_helper( - mods, state, lib, this_ptr, target, &x.rhs, idx_values, + mods, state, lib, this_ptr, target, root, &x.rhs, idx_values, rhs_chain, level, new_val, ) .map_err(|err| err.fill_position(*pos)) @@ -1487,21 +1477,16 @@ impl Engine { match lhs { // id.??? or id[???] - Expr::Variable(_, _var_pos, x) => { + Expr::Variable(_, var_pos, x) => { #[cfg(not(feature = "unchecked"))] - self.inc_operations(state, *_var_pos)?; + self.inc_operations(state, *var_pos)?; - let (target, pos) = - self.search_namespace(scope, mods, state, lib, this_ptr, lhs)?; - - // Constants cannot be modified - if target.is_read_only() && new_val.is_some() { - return EvalAltResult::ErrorAssignmentToConstant(x.2.to_string(), pos).into(); - } + let (target, _) = self.search_namespace(scope, mods, state, lib, this_ptr, lhs)?; let obj_ptr = &mut target.into(); + let root = (x.2.as_str(), *var_pos); self.eval_dot_index_chain_helper( - mods, state, lib, &mut None, obj_ptr, rhs, idx_values, chain_type, level, + mods, state, lib, &mut None, obj_ptr, root, rhs, idx_values, chain_type, level, new_val, ) .map(|(v, _)| v) @@ -1513,8 +1498,9 @@ impl Engine { expr => { let value = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; let obj_ptr = &mut value.into(); + let root = ("", expr.position()); self.eval_dot_index_chain_helper( - mods, state, lib, this_ptr, obj_ptr, rhs, idx_values, chain_type, level, + mods, state, lib, this_ptr, obj_ptr, root, rhs, idx_values, chain_type, level, new_val, ) .map(|(v, _)| v) @@ -1558,7 +1544,7 @@ impl Engine { }) .collect::, _>>()?; - x.constant_args + x.literal_args .iter() .inspect(|(_, pos)| arg_positions.push(*pos)) .for_each(|(v, _)| arg_values.push(v.clone())); @@ -1603,7 +1589,7 @@ impl Engine { }) .collect::, _>>()?; - x.constant_args + x.literal_args .iter() .inspect(|(_, pos)| arg_positions.push(*pos)) .for_each(|(v, _)| arg_values.push(v.clone())); @@ -1676,7 +1662,7 @@ impl Engine { match target { #[cfg(not(feature = "no_index"))] - Dynamic(Union::Array(arr, _)) => { + Dynamic(Union::Array(arr, _, _)) => { // val_array[idx] let index = _idx .as_int() @@ -1718,7 +1704,7 @@ impl Engine { } #[cfg(not(feature = "no_object"))] - Dynamic(Union::Map(map, _)) => { + Dynamic(Union::Map(map, _, _)) => { // val_map[idx] let index = &*_idx.read_lock::().ok_or_else(|| { self.make_type_mismatch_err::(_idx.type_name(), idx_pos) @@ -1735,7 +1721,7 @@ impl Engine { } #[cfg(not(feature = "no_index"))] - Dynamic(Union::Str(s, _)) => { + Dynamic(Union::Str(s, _, _)) => { // val_string[idx] let index = _idx .as_int() @@ -1861,6 +1847,7 @@ impl Engine { Some(OpAssignment::new(TOKEN_OP_CONCAT)), pos, (&mut result).into(), + ("", Position::NONE), item, expr.position(), )?; @@ -1905,7 +1892,7 @@ impl Engine { namespace, hashes, args, - constant_args: c_args, + literal_args: c_args, .. } = x.as_ref(); let namespace = namespace.as_ref(); @@ -1923,7 +1910,7 @@ impl Engine { capture, hashes, args, - constant_args: c_args, + literal_args: c_args, .. } = x.as_ref(); self.make_function_call( @@ -2077,11 +2064,13 @@ impl Engine { op_info: Option, op_pos: Position, mut target: Target, + root: (&str, Position), mut new_value: Dynamic, new_value_pos: Position, ) -> Result<(), Box> { if target.is_read_only() { - unreachable!("LHS should not be read-only"); + // Assignment to constant variable + return EvalAltResult::ErrorAssignmentToConstant(root.0.to_string(), root.1).into(); } if let Some(OpAssignment { @@ -2180,26 +2169,18 @@ impl Engine { #[cfg(not(feature = "unchecked"))] self.inc_operations(state, pos)?; - if lhs_ptr.is_read_only() { - // Assignment to constant variable - EvalAltResult::ErrorAssignmentToConstant( - lhs_expr.get_variable_name(false).unwrap().to_string(), - pos, - ) - .into() - } else { - self.eval_op_assignment( - mods, - state, - lib, - op_info.clone(), - *op_pos, - lhs_ptr, - rhs_val, - rhs_expr.position(), - )?; - Ok(Dynamic::UNIT) - } + self.eval_op_assignment( + mods, + state, + lib, + op_info.clone(), + *op_pos, + lhs_ptr, + (lhs_expr.get_variable_name(false).unwrap(), pos), + rhs_val, + rhs_expr.position(), + )?; + Ok(Dynamic::UNIT) } // lhs op= rhs @@ -2467,7 +2448,7 @@ impl Engine { namespace, hashes, args, - constant_args: c_args, + literal_args: c_args, .. } = x.as_ref(); let namespace = namespace.as_ref(); @@ -2485,7 +2466,7 @@ impl Engine { capture, hashes, args, - constant_args: c_args, + literal_args: c_args, .. } = x.as_ref(); self.make_function_call( @@ -2569,7 +2550,7 @@ impl Engine { Ok(_) => Ok(Dynamic::UNIT), Err(result_err) => match *result_err { // Re-throw exception - EvalAltResult::ErrorRuntime(Dynamic(Union::Unit(_, _)), pos) => { + EvalAltResult::ErrorRuntime(Dynamic(Union::Unit(_, _, _)), pos) => { err.set_position(pos); Err(err) } @@ -2623,18 +2604,15 @@ impl Engine { #[cfg(not(feature = "no_function"))] if entry_type == AccessMode::ReadOnly && lib.iter().any(|&m| !m.is_empty()) { let global = if let Some(index) = mods.find(KEYWORD_GLOBAL) { - let global = mods.get_mut(index).unwrap(); - - if !global.is_internal() { - None - } else { - Some(global) + match mods.get_mut(index).unwrap() { + m if m.internal => Some(m), + _ => None, } } else { // Create automatic global module let mut global = Module::new(); - global.set_internal(true); - mods.push(crate::engine::KEYWORD_GLOBAL, global); + global.internal = true; + mods.push(KEYWORD_GLOBAL, global); Some(mods.get_mut(mods.len() - 1).unwrap()) }; @@ -2777,18 +2755,18 @@ impl Engine { fn calc_size(value: &Dynamic) -> (usize, usize, usize) { match value { #[cfg(not(feature = "no_index"))] - Dynamic(Union::Array(arr, _)) => { + Dynamic(Union::Array(arr, _, _)) => { let mut arrays = 0; let mut maps = 0; arr.iter().for_each(|value| match value { - Dynamic(Union::Array(_, _)) => { + Dynamic(Union::Array(_, _, _)) => { let (a, m, _) = calc_size(value); arrays += a; maps += m; } #[cfg(not(feature = "no_object"))] - Dynamic(Union::Map(_, _)) => { + Dynamic(Union::Map(_, _, _)) => { let (a, m, _) = calc_size(value); arrays += a; maps += m; @@ -2799,18 +2777,18 @@ impl Engine { (arrays, maps, 0) } #[cfg(not(feature = "no_object"))] - Dynamic(Union::Map(map, _)) => { + Dynamic(Union::Map(map, _, _)) => { let mut arrays = 0; let mut maps = 0; map.values().for_each(|value| match value { #[cfg(not(feature = "no_index"))] - Dynamic(Union::Array(_, _)) => { + Dynamic(Union::Array(_, _, _)) => { let (a, m, _) = calc_size(value); arrays += a; maps += m; } - Dynamic(Union::Map(_, _)) => { + Dynamic(Union::Map(_, _, _)) => { let (a, m, _) = calc_size(value); arrays += a; maps += m; @@ -2820,7 +2798,7 @@ impl Engine { (arrays, maps, 0) } - Dynamic(Union::Str(s, _)) => (0, 0, s.len()), + Dynamic(Union::Str(s, _, _)) => (0, 0, s.len()), _ => (0, 0, 0), } } diff --git a/src/engine_api.rs b/src/engine_api.rs index 1722dd76..152a5338 100644 --- a/src/engine_api.rs +++ b/src/engine_api.rs @@ -183,6 +183,8 @@ impl Engine { /// Register a custom type for use with the [`Engine`]. /// The type must implement [`Clone`]. /// + /// Not available under `no_object`. + /// /// # Example /// /// ``` @@ -224,6 +226,8 @@ impl Engine { /// Register a custom type for use with the [`Engine`], with a pretty-print name /// for the `type_of` function. The type must implement [`Clone`]. /// + /// Not available under `no_object`. + /// /// # Example /// /// ``` @@ -265,7 +269,8 @@ impl Engine { #[inline(always)] pub fn register_type_with_name(&mut self, name: &str) -> &mut Self { // Add the pretty-print type name into the map - self.type_names.insert(type_name::().into(), name.into()); + self.type_names + .insert(type_name::().into(), Box::new(name.into())); self } /// Register an type iterator for an iterable type with the [`Engine`]. @@ -283,6 +288,8 @@ impl Engine { /// /// The function signature must start with `&mut self` and not `&self`. /// + /// Not available under `no_object`. + /// /// # Example /// /// ``` @@ -328,6 +335,8 @@ impl Engine { /// /// The function signature must start with `&mut self` and not `&self`. /// + /// Not available under `no_object`. + /// /// # Example /// /// ``` @@ -373,6 +382,8 @@ impl Engine { } /// Register a setter function for a member of a registered type with the [`Engine`]. /// + /// Not available under `no_object`. + /// /// # Example /// /// ``` @@ -419,6 +430,8 @@ impl Engine { } /// Register a setter function for a member of a registered type with the [`Engine`]. /// + /// Not available under `no_object`. + /// /// # Example /// /// ``` @@ -473,6 +486,8 @@ impl Engine { /// /// All function signatures must start with `&mut self` and not `&self`. /// + /// Not available under `no_object`. + /// /// # Example /// /// ``` @@ -521,6 +536,8 @@ impl Engine { /// /// The function signature must start with `&mut self` and not `&self`. /// + /// Not available under `no_index`. + /// /// # Panics /// /// Panics if the type is [`Array`], [`Map`], [`String`], [`ImmutableString`][crate::ImmutableString] or `&str`. @@ -585,6 +602,8 @@ impl Engine { /// /// The function signature must start with `&mut self` and not `&self`. /// + /// Not available under `no_index`. + /// /// # Panics /// /// Panics if the type is [`Array`], [`Map`], [`String`], [`ImmutableString`][crate::ImmutableString] or `&str`. @@ -653,6 +672,8 @@ impl Engine { } /// Register an index setter for a custom type with the [`Engine`]. /// + /// Not available under `no_index`. + /// /// # Panics /// /// Panics if the type is [`Array`], [`Map`], [`String`], [`ImmutableString`][crate::ImmutableString] or `&str`. @@ -717,6 +738,8 @@ impl Engine { } /// Register an index setter for a custom type with the [`Engine`]. /// + /// Not available under `no_index`. + /// /// # Panics /// /// Panics if the type is [`Array`], [`Map`], [`String`], [`ImmutableString`][crate::ImmutableString] or `&str`. @@ -791,6 +814,8 @@ impl Engine { } /// Short-hand for register both index getter and setter functions for a custom type with the [`Engine`]. /// + /// Not available under `no_index`. + /// /// # Panics /// /// Panics if the type is [`Array`], [`Map`], [`String`], [`ImmutableString`][crate::ImmutableString] or `&str`. @@ -869,6 +894,8 @@ impl Engine { /// Functions marked [`FnNamespace::Global`] and type iterators are exposed to scripts without /// namespace qualifications. /// + /// Not available under `no_module`. + /// /// # Example /// /// ``` @@ -1023,6 +1050,8 @@ impl Engine { /// Compile a string into an [`AST`] using own scope, which can be used later for evaluation, /// embedding all imported modules. /// + /// Not available under `no_module`. + /// /// Modules referred by `import` statements containing literal string paths are eagerly resolved /// via the current [module resolver][crate::ModuleResolver] and embedded into the resultant /// [`AST`]. When it is evaluated later, `import` statement directly recall pre-resolved @@ -1271,6 +1300,8 @@ impl Engine { /// Parse a JSON string into an [object map][`Map`]. /// This is a light-weight alternative to using, say, [`serde_json`] to deserialize the JSON. /// + /// Not available under `no_object`. + /// /// The JSON string must be an object hash. It cannot be a simple scalar value. /// /// Set `has_null` to `true` in order to map `null` values to `()`. @@ -1787,6 +1818,8 @@ impl Engine { /// Call a script function defined in an [`AST`] with multiple arguments. /// Arguments are passed as a tuple. /// + /// Not available under `no_function`. + /// /// The [`AST`] is evaluated before calling the function. /// This allows a script to load the necessary modules. /// This is usually desired. If not, a specialized [`AST`] can be prepared that contains only @@ -1854,6 +1887,8 @@ impl Engine { /// Call a script function defined in an [`AST`] with multiple [`Dynamic`] arguments /// and optionally a value for binding to the `this` pointer. /// + /// Not available under `no_function`. + /// /// There is an option to evaluate the [`AST`] to load necessary modules before calling the function. /// /// # WARNING @@ -1970,6 +2005,8 @@ impl Engine { /// Optimize the [`AST`] with constants defined in an external Scope. /// An optimized copy of the [`AST`] is returned while the original [`AST`] is consumed. /// + /// Not available under `no_optimize`. + /// /// Although optimization is performed by default during compilation, sometimes it is necessary to /// _re_-optimize an [`AST`]. For example, when working with constants that are passed in via an /// external scope, it will be more efficient to optimize the [`AST`] once again to take advantage @@ -2002,7 +2039,7 @@ impl Engine { let stmt = std::mem::take(ast.statements_mut()); crate::optimize::optimize_into_ast(self, scope, stmt.into_vec(), lib, optimization_level) } - /// Generate a list of all registered functions. + /// _(METADATA)_ Generate a list of all registered functions. /// Exported under the `metadata` feature only. /// /// Functions from the following sources are included, in order: @@ -2073,6 +2110,8 @@ impl Engine { } /// Register a callback for script evaluation progress. /// + /// Not available under `unchecked`. + /// /// # Example /// /// ``` diff --git a/src/engine_settings.rs b/src/engine_settings.rs index ce6aa1e3..a995c71b 100644 --- a/src/engine_settings.rs +++ b/src/engine_settings.rs @@ -31,16 +31,6 @@ impl Engine { pub fn optimization_level(&self) -> crate::OptimizationLevel { self.optimization_level } - /// _(METADATA)_ Enable/disable doc-comments for functions. - /// Exported under the `metadata` feature only. - /// Not available under `no_function`. - #[cfg(not(feature = "no_function"))] - #[cfg(feature = "metadata")] - #[inline(always)] - pub fn enable_doc_comments(&mut self, enable: bool) -> &mut Self { - self.disable_doc_comments = !enable; - self - } /// Set the maximum levels of function calls allowed for a script in order to avoid /// infinite recursion and stack overflows. /// diff --git a/src/fn_builtin.rs b/src/fn_builtin.rs index 87f7bcba..b95b0fcf 100644 --- a/src/fn_builtin.rs +++ b/src/fn_builtin.rs @@ -184,15 +184,19 @@ pub fn get_builtin_binary_op_fn( "*" => impl_op!(from Decimal => multiply($xx, $yy)), "/" => impl_op!(from Decimal => divide($xx, $yy)), "%" => impl_op!(from Decimal => modulo($xx, $yy)), + "**" => impl_op!(from Decimal => power($xx, $yy)), _ => () } } else { + use rust_decimal::MathematicalOps; + match op { "+" => impl_op!(from Decimal => $xx + $yy), "-" => impl_op!(from Decimal => $xx - $yy), "*" => impl_op!(from Decimal => $xx * $yy), "/" => impl_op!(from Decimal => $xx / $yy), "%" => impl_op!(from Decimal => $xx % $yy), + "**" => impl_op!(from Decimal => $xx.powd($yy)), _ => () } } @@ -522,15 +526,19 @@ pub fn get_builtin_op_assignment_fn( "*=" => impl_op!(from $x => multiply($xx, $yy)), "/=" => impl_op!(from $x => divide($xx, $yy)), "%=" => impl_op!(from $x => modulo($xx, $yy)), + "**=" => impl_op!(from $x => power($xx, $yy)), _ => return None, } } else { + use rust_decimal::MathematicalOps; + match op { "+=" => impl_op!(from $x += $yy), "-=" => impl_op!(from $x -= $yy), "*=" => impl_op!(from $x *= $yy), "/=" => impl_op!(from $x /= $yy), "%=" => impl_op!(from $x %= $yy), + "**=" => impl_op!(from $x => $xx.powd($yy)), _ => return None, } } diff --git a/src/fn_call.rs b/src/fn_call.rs index fff4dfb6..0c087b2e 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -161,7 +161,7 @@ impl Engine { args: Option<&mut FnCallArgs>, allow_dynamic: bool, is_op_assignment: bool, - ) -> &'s Option { + ) -> &'s Option> { let mut hash = if let Some(ref args) = args { let hash_params = calc_fn_params_hash(args.iter().map(|a| a.type_id())); combine_hashes(hash_script, hash_params) @@ -222,7 +222,7 @@ impl Engine { match func { // Specific version found - Some(f) => return Some(f), + Some(f) => return Some(Box::new(f)), // Stop when all permutations are exhausted None if bitmask >= max_bitmask => { @@ -250,6 +250,7 @@ impl Engine { }, ) } + .map(Box::new) }); } @@ -311,7 +312,8 @@ impl Engine { is_op_assignment, ); - if let Some(FnResolutionCacheEntry { func, source }) = func { + if let Some(f) = func { + let FnResolutionCacheEntry { func, source } = f.as_ref(); assert!(func.is_native()); // Calling pure function but the first argument is a reference? @@ -717,10 +719,11 @@ impl Engine { }; #[cfg(not(feature = "no_function"))] - if let Some(FnResolutionCacheEntry { func, source }) = hash_script.and_then(|hash| { + if let Some(f) = hash_script.and_then(|hash| { self.resolve_function(mods, state, lib, fn_name, hash, None, false, false) .clone() }) { + let FnResolutionCacheEntry { func, source } = *f; // Script function call assert!(func.is_script()); @@ -1062,7 +1065,7 @@ impl Engine { this_ptr: &mut Option<&mut Dynamic>, fn_name: &str, args_expr: &[Expr], - constant_args: &[(Dynamic, Position)], + literal_args: &[(Dynamic, Position)], mut hashes: FnCallHashes, pos: Position, capture_scope: bool, @@ -1071,8 +1074,8 @@ impl Engine { // Handle call() - Redirect function call let redirected; let mut args_expr = args_expr; - let mut constant_args = constant_args; - let mut total_args = args_expr.len() + constant_args.len(); + let mut literal_args = literal_args; + let mut total_args = args_expr.len() + literal_args.len(); let mut curry = StaticVec::new(); let mut name = fn_name; @@ -1080,7 +1083,7 @@ impl Engine { // Handle call() KEYWORD_FN_PTR_CALL if total_args >= 1 => { let (arg, arg_pos) = args_expr.get(0).map_or_else( - || Ok(constant_args[0].clone()), + || Ok(literal_args[0].clone()), |arg| { self.eval_expr(scope, mods, state, lib, this_ptr, arg, level) .map(|v| (v, arg.position())) @@ -1105,7 +1108,7 @@ impl Engine { if !args_expr.is_empty() { args_expr = &args_expr[1..]; } else { - constant_args = &constant_args[1..]; + literal_args = &literal_args[1..]; } total_args -= 1; @@ -1120,7 +1123,7 @@ impl Engine { // Handle Fn() KEYWORD_FN_PTR if total_args == 1 => { let (arg, arg_pos) = args_expr.get(0).map_or_else( - || Ok(constant_args[0].clone()), + || Ok(literal_args[0].clone()), |arg| { self.eval_expr(scope, mods, state, lib, this_ptr, arg, level) .map(|v| (v, arg.position())) @@ -1139,7 +1142,7 @@ impl Engine { // Handle curry() KEYWORD_FN_PTR_CURRY if total_args > 1 => { let (arg, arg_pos) = args_expr.get(0).map_or_else( - || Ok(constant_args[0].clone()), + || Ok(literal_args[0].clone()), |arg| { self.eval_expr(scope, mods, state, lib, this_ptr, arg, level) .map(|v| (v, arg.position())) @@ -1161,9 +1164,9 @@ impl Engine { self.eval_expr(scope, mods, state, lib, this_ptr, expr, level) .map(|value| fn_curry.push(value)) })?; - fn_curry.extend(constant_args.iter().map(|(v, _)| v.clone())); + fn_curry.extend(literal_args.iter().map(|(v, _)| v.clone())); } else { - fn_curry.extend(constant_args.iter().skip(1).map(|(v, _)| v.clone())); + fn_curry.extend(literal_args.iter().skip(1).map(|(v, _)| v.clone())); } return Ok(FnPtr::new_unchecked(name, fn_curry).into()); @@ -1173,7 +1176,7 @@ impl Engine { #[cfg(not(feature = "no_closure"))] crate::engine::KEYWORD_IS_SHARED if total_args == 1 => { let arg = args_expr.get(0).map_or_else( - || Ok(constant_args[0].0.clone()), + || Ok(literal_args[0].0.clone()), |arg| self.eval_expr(scope, mods, state, lib, this_ptr, arg, level), )?; return Ok(arg.is_shared().into()); @@ -1188,7 +1191,7 @@ impl Engine { args_expr[0].position(), ) } else { - constant_args[0].clone() + literal_args[0].clone() }; let fn_name = arg @@ -1201,7 +1204,7 @@ impl Engine { args_expr[1].position(), ) } else { - constant_args[if args_expr.is_empty() { 1 } else { 0 }].clone() + literal_args[if args_expr.is_empty() { 1 } else { 0 }].clone() }; let num_params = arg @@ -1220,7 +1223,7 @@ impl Engine { // Handle is_def_var() KEYWORD_IS_DEF_VAR if total_args == 1 => { let (arg, arg_pos) = args_expr.get(0).map_or_else( - || Ok(constant_args[0].clone()), + || Ok(literal_args[0].clone()), |arg| { self.eval_expr(scope, mods, state, lib, this_ptr, arg, level) .map(|v| (v, arg.position())) @@ -1237,7 +1240,7 @@ impl Engine { // eval - only in function call style let prev_len = scope.len(); let (script, script_pos) = args_expr.get(0).map_or_else( - || Ok(constant_args[0].clone()), + || Ok(literal_args[0].clone()), |script_expr| { self.eval_expr(scope, mods, state, lib, this_ptr, script_expr, level) .map(|v| (v, script_expr.position())) @@ -1289,7 +1292,7 @@ impl Engine { None }; - if args_expr.is_empty() && constant_args.is_empty() && curry.is_empty() { + if args_expr.is_empty() && literal_args.is_empty() && curry.is_empty() { // No arguments args = Default::default(); } else { @@ -1305,7 +1308,7 @@ impl Engine { self.eval_expr(scope, mods, state, lib, this_ptr, expr, level) .map(Dynamic::flatten) }) - .chain(constant_args.iter().map(|(v, _)| Ok(v.clone()))) + .chain(literal_args.iter().map(|(v, _)| Ok(v.clone()))) .collect::>()?; let (mut target, _pos) = @@ -1341,7 +1344,7 @@ impl Engine { self.eval_expr(scope, mods, state, lib, this_ptr, expr, level) .map(Dynamic::flatten) }) - .chain(constant_args.iter().map(|(v, _)| Ok(v.clone()))) + .chain(literal_args.iter().map(|(v, _)| Ok(v.clone()))) .collect::>()?; args = curry.iter_mut().chain(arg_values.iter_mut()).collect(); @@ -1365,7 +1368,7 @@ impl Engine { namespace: Option<&NamespaceRef>, fn_name: &str, args_expr: &[Expr], - constant_args: &[(Dynamic, Position)], + literal_args: &[(Dynamic, Position)], hash: u64, pos: Position, level: usize, @@ -1375,7 +1378,7 @@ impl Engine { let mut first_arg_value = None; let mut args: StaticVec<_>; - if args_expr.is_empty() && constant_args.is_empty() { + if args_expr.is_empty() && literal_args.is_empty() { // No arguments args = Default::default(); } else { @@ -1396,7 +1399,7 @@ impl Engine { .map(Dynamic::flatten) } }) - .chain(constant_args.iter().map(|(v, _)| Ok(v.clone()))) + .chain(literal_args.iter().map(|(v, _)| Ok(v.clone()))) .collect::>()?; // Get target reference to first argument @@ -1429,7 +1432,7 @@ impl Engine { self.eval_expr(scope, mods, state, lib, this_ptr, expr, level) .map(Dynamic::flatten) }) - .chain(constant_args.iter().map(|(v, _)| Ok(v.clone()))) + .chain(literal_args.iter().map(|(v, _)| Ok(v.clone()))) .collect::>()?; args = arg_values.iter_mut().collect(); diff --git a/src/fn_native.rs b/src/fn_native.rs index a72d79b0..5996d6db 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -5,8 +5,8 @@ use crate::engine::Imports; use crate::plugin::PluginFunction; use crate::token::is_valid_identifier; use crate::{ - calc_fn_hash, Dynamic, Engine, EvalAltResult, EvalContext, Identifier, ImmutableString, Module, - Position, RhaiResult, StaticVec, + calc_fn_hash, Dynamic, Engine, EvalAltResult, EvalContext, Identifier, Module, Position, + RhaiResult, StaticVec, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -39,10 +39,14 @@ pub use std::rc::Rc as Shared; pub use std::sync::Arc as Shared; /// Synchronized shared object. +/// +/// Not available under `no_closure`. #[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "sync"))] pub use std::cell::RefCell as Locked; /// Synchronized shared object. +/// +/// Not available under `no_closure`. #[cfg(not(feature = "no_closure"))] #[cfg(feature = "sync")] pub use std::sync::RwLock as Locked; @@ -101,6 +105,8 @@ impl<'a> NativeCallContext<'a> { } /// _(INTERNALS)_ Create a new [`NativeCallContext`]. /// Exported under the `internals` feature only. + /// + /// Not available under `no_module`. #[cfg(feature = "internals")] #[cfg(not(feature = "no_module"))] #[inline(always)] @@ -135,6 +141,8 @@ impl<'a> NativeCallContext<'a> { self.source } /// Get an iterator over the current set of modules imported via `import` statements. + /// + /// Not available under `no_module`. #[cfg(not(feature = "no_module"))] #[inline(always)] pub fn iter_imports(&self) -> impl Iterator { @@ -151,6 +159,8 @@ impl<'a> NativeCallContext<'a> { } /// _(INTERNALS)_ The current set of modules imported via `import` statements. /// Exported under the `internals` feature only. + /// + /// Not available under `no_module`. #[cfg(feature = "internals")] #[cfg(not(feature = "no_module"))] #[inline(always)] @@ -303,6 +313,8 @@ impl FnPtr { !self.1.is_empty() } /// Does the function pointer refer to an anonymous function? + /// + /// Not available under `no_function`. #[cfg(not(feature = "no_function"))] #[inline(always)] pub fn is_anonymous(&self) -> bool { @@ -367,16 +379,17 @@ impl TryFrom for FnPtr { if is_valid_identifier(value.chars()) { Ok(Self(value, Default::default())) } else { - EvalAltResult::ErrorFunctionNotFound(value.into(), Position::NONE).into() + EvalAltResult::ErrorFunctionNotFound(value.to_string(), Position::NONE).into() } } } -impl TryFrom for FnPtr { +#[cfg(not(feature = "no_smartstring"))] +impl TryFrom for FnPtr { type Error = Box; #[inline(always)] - fn try_from(value: ImmutableString) -> Result { + fn try_from(value: crate::ImmutableString) -> Result { let s: Identifier = value.into(); Self::try_from(s) } @@ -466,6 +479,8 @@ pub enum CallableFunction { /// A plugin function, Plugin(Shared), /// A script-defined function. + /// + /// Not available under `no_function`. #[cfg(not(feature = "no_function"))] Script(Shared), } @@ -600,6 +615,8 @@ impl CallableFunction { } /// Get a shared reference to a script-defined function definition. /// + /// Not available under `no_function`. + /// /// # Panics /// /// Panics if the [`CallableFunction`] is not [`Script`][CallableFunction::Script]. diff --git a/src/fn_register.rs b/src/fn_register.rs index a4535ead..e7a4c6b7 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -5,6 +5,7 @@ use crate::dynamic::{DynamicWriteLock, Variant}; use crate::fn_native::{CallableFunction, FnAny, FnCallArgs, SendSync}; use crate::r#unsafe::unsafe_try_cast; +use crate::token::Position; use crate::{Dynamic, EvalAltResult, NativeCallContext}; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -61,20 +62,33 @@ pub trait RegisterNativeFunction { fn into_callable_function(self) -> CallableFunction; /// Get the type ID's of this function's parameters. fn param_types() -> Box<[TypeId]>; - /// Get the type names of this function's parameters. + /// _(METADATA)_ Get the type names of this function's parameters. /// Exported under the `metadata` feature only. #[cfg(feature = "metadata")] fn param_names() -> Box<[&'static str]>; - /// Get the type ID of this function's return value. + /// _(METADATA)_ Get the type ID of this function's return value. /// Exported under the `metadata` feature only. #[cfg(feature = "metadata")] fn return_type() -> TypeId; - /// Get the type name of this function's return value. + /// _(METADATA)_ Get the type name of this function's return value. /// Exported under the `metadata` feature only. #[cfg(feature = "metadata")] fn return_type_name() -> &'static str; } +#[inline(always)] +fn is_setter(_fn_name: &str) -> bool { + #[cfg(not(feature = "no_object"))] + if _fn_name.starts_with(crate::engine::FN_SET) { + return true; + } + #[cfg(not(feature = "no_index"))] + if _fn_name.starts_with(crate::engine::FN_IDX_SET) { + return true; + } + false +} + macro_rules! def_register { () => { def_register!(imp from_pure :); @@ -97,7 +111,11 @@ macro_rules! def_register { #[cfg(feature = "metadata")] #[inline(always)] fn return_type() -> TypeId { TypeId::of::() } #[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { std::any::type_name::() } #[inline(always)] fn into_callable_function(self) -> CallableFunction { - CallableFunction::$abi(Box::new(move |_: NativeCallContext, args: &mut FnCallArgs| { + CallableFunction::$abi(Box::new(move |ctx: NativeCallContext, args: &mut FnCallArgs| { + if args.len() == 2 && args[0].is_read_only() && is_setter(ctx.fn_name()) { + return EvalAltResult::ErrorAssignmentToConstant(Default::default(), Position::NONE).into(); + } + // The arguments are assumed to be of the correct number and types! let mut _drain = args.iter_mut(); $($let $par = ($clone)(_drain.next().unwrap()); )* @@ -122,6 +140,10 @@ macro_rules! def_register { #[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { std::any::type_name::() } #[inline(always)] fn into_callable_function(self) -> CallableFunction { CallableFunction::$abi(Box::new(move |ctx: NativeCallContext, args: &mut FnCallArgs| { + if args.len() == 2 && args[0].is_read_only() && is_setter(ctx.fn_name()) { + return EvalAltResult::ErrorAssignmentToConstant(Default::default(), Position::NONE).into(); + } + // The arguments are assumed to be of the correct number and types! let mut _drain = args.iter_mut(); $($let $par = ($clone)(_drain.next().unwrap()); )* @@ -145,7 +167,11 @@ macro_rules! def_register { #[cfg(feature = "metadata")] #[inline(always)] fn return_type() -> TypeId { TypeId::of::>>() } #[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { std::any::type_name::>>() } #[inline(always)] fn into_callable_function(self) -> CallableFunction { - CallableFunction::$abi(Box::new(move |_: NativeCallContext, args: &mut FnCallArgs| { + CallableFunction::$abi(Box::new(move |ctx: NativeCallContext, args: &mut FnCallArgs| { + if args.len() == 2 && args[0].is_read_only() && is_setter(ctx.fn_name()) { + return EvalAltResult::ErrorAssignmentToConstant(Default::default(), Position::NONE).into(); + } + // The arguments are assumed to be of the correct number and types! let mut _drain = args.iter_mut(); $($let $par = ($clone)(_drain.next().unwrap()); )* @@ -167,6 +193,10 @@ macro_rules! def_register { #[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { std::any::type_name::>>() } #[inline(always)] fn into_callable_function(self) -> CallableFunction { CallableFunction::$abi(Box::new(move |ctx: NativeCallContext, args: &mut FnCallArgs| { + if args.len() == 2 && args[0].is_read_only() && is_setter(ctx.fn_name()) { + return EvalAltResult::ErrorAssignmentToConstant(Default::default(), Position::NONE).into(); + } + // The arguments are assumed to be of the correct number and types! let mut _drain = args.iter_mut(); $($let $par = ($clone)(_drain.next().unwrap()); )* diff --git a/src/lib.rs b/src/lib.rs index 4b8633bd..30b9faa9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -140,11 +140,11 @@ pub use utils::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(not(feature = "no_smartstring_for_identifier"))] +#[cfg(not(feature = "no_smartstring"))] pub type Identifier = SmartString; /// An identifier in Rhai. -#[cfg(feature = "no_smartstring_for_identifier")] +#[cfg(feature = "no_smartstring")] pub type Identifier = ImmutableString; /// A trait to enable registering Rust functions. @@ -207,15 +207,17 @@ pub use optimize::OptimizationLevel; #[cfg(feature = "internals")] #[deprecated = "this type is volatile and may change"] -pub use dynamic::Variant; +pub use dynamic::{DynamicReadLock, DynamicWriteLock, Variant}; + +// Expose internal data structures. +#[cfg(feature = "internals")] +#[deprecated = "this function is volatile and may change"] +pub use token::{get_next_token, parse_string_literal}; // Expose internal data structures. #[cfg(feature = "internals")] #[deprecated = "this type is volatile and may change"] -pub use token::{ - get_next_token, parse_string_literal, InputStream, Token, TokenizeState, TokenizerControl, - TokenizerControlBlock, -}; +pub use token::{InputStream, Token, TokenizeState, TokenizerControl, TokenizerControlBlock}; #[cfg(feature = "internals")] #[deprecated = "this type is volatile and may change"] @@ -306,9 +308,14 @@ type StaticVec = smallvec::SmallVec<[T; 4]>; 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 diff --git a/src/module/mod.rs b/src/module/mod.rs index 5f2868f8..16c93374 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -48,7 +48,7 @@ impl Default for FnNamespace { #[derive(Debug, Clone)] pub struct FuncInfo { /// Function instance. - pub func: CallableFunction, + pub func: Shared, /// Function namespace. pub namespace: FnNamespace, /// Function access mode. @@ -129,7 +129,7 @@ pub struct Module { /// ID identifying the module. id: Option, /// Is this module internal? - internal: bool, + pub(crate) internal: bool, /// Sub-modules. modules: BTreeMap>, /// [`Module`] variables. @@ -140,7 +140,7 @@ pub struct Module { functions: BTreeMap>, /// Flattened collection of all external Rust functions, native or scripted. /// including those in sub-modules. - all_functions: BTreeMap, + all_functions: BTreeMap>, /// Iterator functions, keyed by the type producing the iterator. type_iterators: BTreeMap, /// Flattened collection of iterator functions, including those in sub-modules. @@ -309,20 +309,6 @@ impl Module { self } - /// Is the [`Module`] internal? - #[allow(dead_code)] - #[inline(always)] - pub(crate) fn is_internal(&self) -> bool { - self.internal - } - - /// Set the internal status of the [`Module`]. - #[inline(always)] - pub(crate) fn set_internal(&mut self, value: bool) -> &mut Self { - self.internal = value; - self - } - /// Is the [`Module`] empty? /// /// # Example @@ -476,10 +462,7 @@ impl Module { /// If there is an existing function of the same name and number of arguments, it is replaced. #[cfg(not(feature = "no_function"))] #[inline] - pub(crate) fn set_script_fn( - &mut self, - fn_def: impl Into>, - ) -> u64 { + pub fn set_script_fn(&mut self, fn_def: impl Into>) -> u64 { let fn_def = fn_def.into(); // None + function name + number of arguments. @@ -497,7 +480,7 @@ impl Module { param_types: Default::default(), #[cfg(feature = "metadata")] param_names, - func: fn_def.into(), + func: Into::::into(fn_def).into(), }), ); self.indexed = false; @@ -695,19 +678,22 @@ impl Module { ) -> u64 { let is_method = func.is_method(); - let param_types: StaticVec<_> = arg_types + let mut param_types: StaticVec<_> = arg_types .iter() .cloned() .enumerate() .map(|(i, type_id)| Self::map_type(!is_method || i > 0, type_id)) .collect(); + param_types.shrink_to_fit(); #[cfg(feature = "metadata")] - let param_names = _arg_names + let mut param_names: StaticVec<_> = _arg_names .iter() .flat_map(|p| p.iter()) .map(|&arg| self.identifiers.get(arg)) .collect(); + #[cfg(feature = "metadata")] + param_names.shrink_to_fit(); let hash_fn = calc_native_fn_hash(empty(), &name, ¶m_types); @@ -721,7 +707,7 @@ impl Module { param_types, #[cfg(feature = "metadata")] param_names, - func, + func: func.into(), }), ); @@ -1119,7 +1105,7 @@ impl Module { /// The [`u64`] hash is returned by the [`set_native_fn`][Module::set_native_fn] call. #[inline(always)] pub(crate) fn get_fn(&self, hash_fn: u64) -> Option<&CallableFunction> { - self.functions.get(&hash_fn).map(|f| &f.func) + self.functions.get(&hash_fn).map(|f| f.func.as_ref()) } /// Does the particular namespace-qualified function exist in the [`Module`]? @@ -1135,7 +1121,9 @@ impl Module { /// The [`u64`] hash is calculated by [`build_index`][Module::build_index]. #[inline(always)] pub(crate) fn get_qualified_fn(&self, hash_qualified_fn: u64) -> Option<&CallableFunction> { - self.all_functions.get(&hash_qualified_fn) + self.all_functions + .get(&hash_qualified_fn) + .map(|f| f.as_ref()) } /// Combine another [`Module`] into this [`Module`]. @@ -1454,7 +1442,7 @@ impl Module { .filter(|f| f.func.is_script()) .for_each(|f| { // Encapsulate AST environment - let mut func = crate::fn_native::shared_take_or_clone(f.func.get_fn_def().clone()); + let mut func = f.func.get_fn_def().as_ref().clone(); func.lib = Some(ast.shared_lib()); func.mods = func_mods.clone(); module.set_script_fn(func); @@ -1486,7 +1474,7 @@ impl Module { module: &'a Module, path: &mut Vec<&'a str>, variables: &mut BTreeMap, - functions: &mut BTreeMap, + functions: &mut BTreeMap>, type_iterators: &mut BTreeMap, ) -> bool { let mut contains_indexed_global_functions = false; @@ -1690,6 +1678,8 @@ impl DerefMut for NamespaceRef { impl From> for NamespaceRef { #[inline(always)] fn from(path: StaticVec) -> Self { + let mut path = path; + path.shrink_to_fit(); Self { index: None, path } } } diff --git a/src/optimize.rs b/src/optimize.rs index 77243bbf..be02071c 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -390,7 +390,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) { x.2 = if x2.args.len() > 1 { mem::take(&mut x2.args[1]) } else { - let (value, pos) = mem::take(&mut x2.constant_args[0]); + let (value, pos) = mem::take(&mut x2.literal_args[0]); Expr::DynamicConstant(Box::new(value), pos) }; } @@ -417,7 +417,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) { *stmt = if preserve_result { // -> { expr, Noop } - Stmt::Block(vec![Stmt::Expr(expr), Stmt::Noop(pos)], pos) + Stmt::Block(Box::new([Stmt::Expr(expr), Stmt::Noop(pos)]), pos) } else { // -> expr Stmt::Expr(expr) @@ -434,7 +434,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) { let else_block = mem::take(&mut *x.1).into_vec(); *stmt = match optimize_stmt_block(else_block, state, preserve_result, true, false) { statements if statements.is_empty() => Stmt::Noop(x.1.position()), - statements => Stmt::Block(statements, x.1.position()), + statements => Stmt::Block(statements.into_boxed_slice(), x.1.position()), } } // if true { if_block } else { else_block } -> if_block @@ -443,7 +443,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) { let if_block = mem::take(&mut *x.0).into_vec(); *stmt = match optimize_stmt_block(if_block, state, preserve_result, true, false) { statements if statements.is_empty() => Stmt::Noop(x.0.position()), - statements => Stmt::Block(statements, x.0.position()), + statements => Stmt::Block(statements.into_boxed_slice(), x.0.position()), } } // if expr { if_block } else { else_block } @@ -484,7 +484,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) { condition, Box::new(( mem::take(&mut block.1), - Stmt::Block(def_stmt, def_pos).into(), + Stmt::Block(def_stmt.into_boxed_slice(), def_pos).into(), )), match_expr.position(), ); @@ -494,7 +494,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) { let statements = mem::take(&mut *block.1); let statements = optimize_stmt_block(statements.into_vec(), state, true, true, false); - *stmt = Stmt::Block(statements, new_pos); + *stmt = Stmt::Block(statements.into_boxed_slice(), new_pos); } } else { // Promote the default case @@ -505,7 +505,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) { } else { x.1.position() }; - *stmt = Stmt::Block(def_stmt, def_pos); + *stmt = Stmt::Block(def_stmt.into_boxed_slice(), def_pos); } } // switch @@ -572,7 +572,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) { if preserve_result { statements.push(Stmt::Noop(pos)) } - *stmt = Stmt::Block(statements, pos); + *stmt = Stmt::Block(statements.into_boxed_slice(), pos); } else { *stmt = Stmt::Noop(pos); }; @@ -588,7 +588,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) { let block_pos = body.position(); let block = mem::take(body.statements()).into_vec(); *stmt = Stmt::Block( - optimize_stmt_block(block, state, false, true, false), + optimize_stmt_block(block, state, false, true, false).into_boxed_slice(), block_pos, ); } @@ -611,8 +611,8 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) { Stmt::Import(expr, _, _) => optimize_expr(expr, state), // { block } Stmt::Block(statements, pos) => { - let mut block = - optimize_stmt_block(mem::take(statements), state, preserve_result, true, false); + let statements = mem::take(statements).into_vec(); + let mut block = optimize_stmt_block(statements, state, preserve_result, true, false); match block.as_mut_slice() { [] => { @@ -624,7 +624,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) { state.set_dirty(); *stmt = mem::take(s); } - _ => *stmt = Stmt::Block(block, *pos), + _ => *stmt = Stmt::Block(block.into_boxed_slice(), *pos), } } // try { pure try_block } catch ( var ) { catch_block } -> try_block @@ -634,7 +634,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) { let try_pos = x.0.position(); let try_block = mem::take(&mut *x.0).into_vec(); *stmt = Stmt::Block( - optimize_stmt_block(try_block, state, false, true, false), + optimize_stmt_block(try_block, state, false, true, false).into_boxed_slice(), try_pos, ); } @@ -820,6 +820,8 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) { } } } + + x.shrink_to_fit(); } // [ constant .. ] #[cfg(not(feature = "no_index"))] @@ -893,12 +895,12 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) { if !x.is_qualified() // Non-qualified && state.optimization_level == OptimizationLevel::Simple // simple optimizations && x.args_count() == 1 - && x.constant_args.len() == 1 - && x.constant_args[0].0.is::() + && x.literal_args.len() == 1 + && x.literal_args[0].0.is::() && x.name == KEYWORD_FN_PTR => { state.set_dirty(); - let fn_ptr = FnPtr::new_unchecked(mem::take(&mut x.constant_args[0].0).as_str_ref().unwrap().into(), Default::default()); + let fn_ptr = FnPtr::new_unchecked(mem::take(&mut x.literal_args[0].0).as_str_ref().unwrap().into(), Default::default()); *expr = Expr::DynamicConstant(Box::new(fn_ptr.into()), *pos); } @@ -916,7 +918,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) { //&& !is_valid_identifier(x.name.chars()) // cannot be scripted => { let mut arg_values: StaticVec<_> = x.args.iter().map(|e| e.get_constant_value().unwrap()) - .chain(x.constant_args.iter().map(|(v, _)| v).cloned()) + .chain(x.literal_args.iter().map(|(v, _)| v).cloned()) .collect(); let arg_types: StaticVec<_> = arg_values.iter().map(Dynamic::type_id).collect(); @@ -943,10 +945,11 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) { while x.args.last().map(Expr::is_constant).unwrap_or(false) { let arg = x.args.pop().unwrap(); let arg_pos = arg.position(); - x.constant_args.insert(0, (arg.get_constant_value().unwrap(), arg_pos)); + x.literal_args.insert(0, (arg.get_constant_value().unwrap(), arg_pos)); } x.args.shrink_to_fit(); + x.literal_args.shrink_to_fit(); } // Eagerly call functions @@ -963,7 +966,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) { if !has_script_fn { let mut arg_values: StaticVec<_> = x.args.iter().map(|e| e.get_constant_value().unwrap()) - .chain(x.constant_args.iter().map(|(v, _)| v).cloned()) + .chain(x.literal_args.iter().map(|(v, _)| v).cloned()) .collect(); // Save the typename of the first argument if it is `type_of()` @@ -1002,10 +1005,11 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) { while x.args.last().map(Expr::is_constant).unwrap_or(false) { let arg = x.args.pop().unwrap(); let arg_pos = arg.position(); - x.constant_args.insert(0, (arg.get_constant_value().unwrap(), arg_pos)); + x.literal_args.insert(0, (arg.get_constant_value().unwrap(), arg_pos)); } x.args.shrink_to_fit(); + x.literal_args.shrink_to_fit(); } // constant-name @@ -1020,7 +1024,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) { // Custom syntax Expr::Custom(x, _) => { - if x.scope_delta != 0 { + if x.scope_changed { state.propagate_constants = false; } x.keywords.iter_mut().for_each(|expr| optimize_expr(expr, state)); @@ -1052,10 +1056,12 @@ fn optimize_top_level( scope.iter().for_each(|(name, constant, value)| { if !constant { state.push_var(name, AccessMode::ReadWrite, Expr::Unit(Position::NONE)); - } else if let Some(val) = map_dynamic_to_expr(value, Position::NONE) { - state.push_var(name, AccessMode::ReadOnly, val); } else { - state.push_var(name, AccessMode::ReadOnly, Expr::Unit(Position::NONE)); + state.push_var( + name, + AccessMode::ReadOnly, + Expr::DynamicConstant(Box::new(value), Position::NONE), + ); } }); diff --git a/src/packages/arithmetic.rs b/src/packages/arithmetic.rs index e70ef146..27d639c1 100644 --- a/src/packages/arithmetic.rs +++ b/src/packages/arithmetic.rs @@ -5,9 +5,6 @@ use crate::{def_package, EvalAltResult, Position, INT}; #[cfg(feature = "no_std")] use std::prelude::v1::*; -#[cfg(not(feature = "no_float"))] -use crate::FLOAT; - #[cfg(feature = "no_std")] #[cfg(not(feature = "no_float"))] use num_traits::Float; @@ -77,7 +74,7 @@ macro_rules! gen_arithmetic_functions { } else if y < 0 { Err(make_err(format!("Integer raised to a negative index: {} ~ {}", x, y))) } else { - x.checked_pow(y as u32).ok_or_else(|| make_err(format!("Power overflow: {} ~ {}", x, y))) + x.checked_pow(y as u32).ok_or_else(|| make_err(format!("Exponential overflow: {} ~ {}", x, y))) } } else { Ok(x.pow(y as u32)) @@ -423,23 +420,13 @@ mod f64_functions { 1 } } - #[rhai_fn(name = "**", return_raw)] - pub fn pow_f_i(x: FLOAT, y: INT) -> Result> { - if cfg!(not(feature = "unchecked")) && y > (i32::MAX as INT) { - Err(make_err(format!( - "Number raised to too large an index: {} ~ {}", - x, y - ))) - } else { - Ok(x.powi(y as i32)) - } - } } #[cfg(feature = "decimal")] #[export_module] pub mod decimal_functions { - use rust_decimal::{prelude::Zero, Decimal}; + use num_traits::Pow; + use rust_decimal::{prelude::Zero, Decimal, MathematicalOps}; #[rhai_fn(skip, return_raw)] pub fn add(x: Decimal, y: Decimal) -> Result> { @@ -495,6 +482,15 @@ pub mod decimal_functions { Ok(x % y) } } + #[rhai_fn(skip, return_raw)] + pub fn power(x: Decimal, y: Decimal) -> Result> { + if cfg!(not(feature = "unchecked")) { + x.checked_powd(y) + .ok_or_else(|| make_err(format!("Exponential overflow: {} + {}", x, y))) + } else { + Ok(x.pow(y)) + } + } #[rhai_fn(name = "-")] pub fn neg(x: Decimal) -> Decimal { -x diff --git a/src/packages/iter_basic.rs b/src/packages/iter_basic.rs index e09d8923..30280c42 100644 --- a/src/packages/iter_basic.rs +++ b/src/packages/iter_basic.rs @@ -26,7 +26,7 @@ where if r == from { return EvalAltResult::ErrorInFunctionCall( "range".to_string(), - "".to_string(), + Default::default(), Box::new(EvalAltResult::ErrorArithmetic( "step value cannot be zero".to_string(), crate::Position::NONE, @@ -246,7 +246,6 @@ def_package!(crate:BasicIteratorPackage:"Basic range iterators.", lib, { #[cfg(feature = "decimal")] { use rust_decimal::Decimal; - use num_traits::Zero; #[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] struct StepDecimalRange(Decimal, Decimal, Decimal); diff --git a/src/packages/lang_core.rs b/src/packages/lang_core.rs new file mode 100644 index 00000000..e20f76db --- /dev/null +++ b/src/packages/lang_core.rs @@ -0,0 +1,45 @@ +use crate::def_package; +use crate::dynamic::Tag; +use crate::plugin::*; +use crate::{Dynamic, EvalAltResult, INT}; +#[cfg(feature = "no_std")] +use std::prelude::v1::*; + +#[export_module] +mod core_functions { + #[rhai_fn(name = "tag", get = "tag", pure)] + pub fn get_tag(value: &mut Dynamic) -> INT { + value.tag() as INT + } + #[rhai_fn(name = "set_tag", set = "tag", return_raw)] + pub fn set_tag(value: &mut Dynamic, tag: INT) -> Result<(), Box> { + if tag < Tag::MIN as INT { + Err(Box::new(EvalAltResult::ErrorArithmetic( + format!( + "{} is too small to fit into a tag (must be between {} and {})", + tag, + Tag::MIN, + Tag::MAX + ), + Position::NONE, + ))) + } else if tag > Tag::MAX as INT { + Err(Box::new(EvalAltResult::ErrorArithmetic( + format!( + "{} is too large to fit into a tag (must be between {} and {})", + tag, + Tag::MIN, + Tag::MAX + ), + Position::NONE, + ))) + } else { + value.set_tag(tag as Tag); + Ok(()) + } + } +} + +def_package!(crate:LanguageCorePackage:"Language core functions.", lib, { + combine_with_exported_module!(lib, "language_core", core_functions); +}); diff --git a/src/packages/math_basic.rs b/src/packages/math_basic.rs index c3e992e0..e4d8b0b4 100644 --- a/src/packages/math_basic.rs +++ b/src/packages/math_basic.rs @@ -222,6 +222,7 @@ mod float_functions { pub fn log(x: FLOAT, base: FLOAT) -> FLOAT { x.log(base) } + #[rhai_fn(name = "log")] pub fn log10(x: FLOAT) -> FLOAT { x.log10() } @@ -305,10 +306,33 @@ mod float_functions { mod decimal_functions { use rust_decimal::{ prelude::{FromStr, RoundingStrategy}, - Decimal, + Decimal, MathematicalOps, }; - use std::convert::TryFrom; + #[rhai_fn(return_raw)] + pub fn sqrt(x: Decimal) -> Result> { + if cfg!(not(feature = "unchecked")) { + x.sqrt() + .ok_or_else(|| make_err(format!("Error taking the square root of {}", x,))) + } else { + Ok(x.sqrt().unwrap()) + } + } + #[rhai_fn(return_raw)] + pub fn exp(x: Decimal) -> Result> { + if cfg!(not(feature = "unchecked")) { + if x > Decimal::from_parts(117578, 0, 0, false, 4) { + Err(make_err(format!("Exponential overflow: e ** {}", x,))) + } else { + Ok(x.exp()) + } + } else { + Ok(x.exp()) + } + } + pub fn ln(x: Decimal) -> Decimal { + x.ln() + } #[rhai_fn(name = "floor", get = "floor")] pub fn floor(x: Decimal) -> Decimal { x.floor() @@ -424,6 +448,8 @@ mod decimal_functions { #[cfg(not(feature = "no_float"))] pub mod float { + use std::convert::TryFrom; + #[rhai_fn(name = "to_decimal", return_raw)] pub fn f32_to_decimal(x: f32) -> Result> { Decimal::try_from(x).map_err(|_| { diff --git a/src/packages/mod.rs b/src/packages/mod.rs index 145eabf0..3e3975e3 100644 --- a/src/packages/mod.rs +++ b/src/packages/mod.rs @@ -6,6 +6,7 @@ pub(crate) mod arithmetic; mod array_basic; mod fn_basic; mod iter_basic; +mod lang_core; mod logic; mod map_basic; mod math_basic; @@ -86,6 +87,7 @@ macro_rules! def_package { } impl $package { + #[allow(dead_code)] pub fn new() -> Self { let mut module = $root::Module::new(); ::init(&mut module); diff --git a/src/packages/pkg_core.rs b/src/packages/pkg_core.rs index 54dff2ca..7f79b3f4 100644 --- a/src/packages/pkg_core.rs +++ b/src/packages/pkg_core.rs @@ -1,6 +1,7 @@ use super::arithmetic::ArithmeticPackage; use super::fn_basic::BasicFnPackage; use super::iter_basic::BasicIteratorPackage; +use super::lang_core::LanguageCorePackage; use super::logic::LogicPackage; use super::string_basic::BasicStringPackage; #[cfg(feature = "no_std")] @@ -9,6 +10,7 @@ use std::prelude::v1::*; use crate::def_package; def_package!(crate:CorePackage:"_Core_ package containing basic facilities.", lib, { + LanguageCorePackage::init(lib); ArithmeticPackage::init(lib); LogicPackage::init(lib); BasicStringPackage::init(lib); diff --git a/src/parser.rs b/src/parser.rs index b33ae36e..c8a82deb 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -348,6 +348,8 @@ fn parse_fn_call( FnCallHashes::from_native(hash) }; + args.shrink_to_fit(); + return Ok(Expr::FnCall( Box::new(FnCallExpr { name: state.get_identifier(id), @@ -393,6 +395,8 @@ fn parse_fn_call( FnCallHashes::from_native(hash) }; + args.shrink_to_fit(); + return Ok(Expr::FnCall( Box::new(FnCallExpr { name: state.get_identifier(id), @@ -1066,6 +1070,7 @@ fn parse_primary( } } + segments.shrink_to_fit(); Expr::InterpolatedString(Box::new(segments)) } @@ -1344,6 +1349,7 @@ fn parse_unary( expr => { let mut args = StaticVec::new(); args.push(expr); + args.shrink_to_fit(); Ok(Expr::FnCall( Box::new(FnCallExpr { @@ -1370,6 +1376,7 @@ fn parse_unary( expr => { let mut args = StaticVec::new(); args.push(expr); + args.shrink_to_fit(); Ok(Expr::FnCall( Box::new(FnCallExpr { @@ -1387,8 +1394,8 @@ fn parse_unary( Token::Bang => { let pos = eat_token(input, Token::Bang); let mut args = StaticVec::new(); - let expr = parse_unary(input, state, lib, settings.level_up())?; - args.push(expr); + args.push(parse_unary(input, state, lib, settings.level_up())?); + args.shrink_to_fit(); Ok(Expr::FnCall( Box::new(FnCallExpr { @@ -1461,24 +1468,10 @@ fn make_assignment_stmt<'a>( Expr::Index(x, _) | Expr::Dot(x, _) => { match check_lvalue(&x.rhs, matches!(lhs, Expr::Dot(_, _))) { None => match &x.lhs { - // var[???] (non-indexed) = rhs, var.??? (non-indexed) = rhs - Expr::Variable(None, _, x) if x.0.is_none() => { + // var[???] = rhs, var.??? = rhs + Expr::Variable(_, _, _) => { Ok(Stmt::Assignment(Box::new((lhs, op_info, rhs)), op_pos)) } - // var[???] (indexed) = rhs, var.??? (indexed) = rhs - Expr::Variable(i, var_pos, x) => { - let (index, _, name) = x.as_ref(); - let index = i.map_or_else(|| index.unwrap().get(), |n| n.get() as usize); - match state.stack[state.stack.len() - index].1 { - AccessMode::ReadWrite => { - Ok(Stmt::Assignment(Box::new((lhs, op_info, rhs)), op_pos)) - } - // Constant values cannot be assigned to - AccessMode::ReadOnly => { - Err(PERR::AssignmentToConstant(name.to_string()).into_err(*var_pos)) - } - } - } // expr[???] = rhs, expr.??? = rhs expr => { Err(PERR::AssignmentToInvalidLHS("".to_string()).into_err(expr.position())) @@ -1730,6 +1723,7 @@ fn parse_binary_op( let mut args = StaticVec::new(); args.push(root); args.push(rhs); + args.shrink_to_fit(); root = match op_token { Token::Plus @@ -1782,6 +1776,7 @@ fn parse_binary_op( // Swap the arguments let current_lhs = args.remove(0); args.push(current_lhs); + args.shrink_to_fit(); // Convert into a call to `contains` let hash = calc_fn_hash(empty(), OP_CONTAINS, 2); @@ -1836,25 +1831,15 @@ fn parse_custom_syntax( ) -> Result { let mut keywords: StaticVec = Default::default(); let mut segments: StaticVec<_> = Default::default(); - let mut tokens: Vec<_> = Default::default(); + let mut tokens: StaticVec<_> = Default::default(); // Adjust the variables stack - match syntax.scope_delta { - delta if delta > 0 => { - // Add enough empty variable names to the stack. - // Empty variable names act as a barrier so earlier variables will not be matched. - // Variable searches stop at the first empty variable name. - let empty = state.get_identifier(""); - state.stack.resize( - state.stack.len() + delta as usize, - (empty, AccessMode::ReadWrite), - ); - } - delta if delta < 0 && state.stack.len() <= delta.abs() as usize => state.stack.clear(), - delta if delta < 0 => state - .stack - .truncate(state.stack.len() - delta.abs() as usize), - _ => (), + if syntax.scope_changed { + // Add an empty variable name to the stack. + // Empty variable names act as a barrier so earlier variables will not be matched. + // Variable searches stop at the first empty variable name. + let empty = state.get_identifier(""); + state.stack.push((empty, AccessMode::ReadWrite)); } let parse_func = &syntax.parse; @@ -1920,11 +1905,14 @@ fn parse_custom_syntax( } } + keywords.shrink_to_fit(); + tokens.shrink_to_fit(); + Ok(Expr::Custom( Box::new(CustomExpr { keywords, tokens, - scope_delta: syntax.scope_delta, + scope_changed: syntax.scope_changed, }), pos, )) @@ -2366,7 +2354,7 @@ fn parse_export( } } - Ok(Stmt::Export(exports, settings.pos)) + Ok(Stmt::Export(exports.into_boxed_slice(), settings.pos)) } /// Parse a statement block. @@ -2467,7 +2455,7 @@ fn parse_block( #[cfg(not(feature = "no_module"))] state.modules.truncate(prev_mods_len); - Ok(Stmt::Block(statements, settings.pos)) + Ok(Stmt::Block(statements.into_boxed_slice(), settings.pos)) } /// Parse an expression as a statement. @@ -2820,7 +2808,8 @@ fn parse_fn( } .into(); - let params: StaticVec<_> = params.into_iter().map(|(p, _)| p).collect(); + let mut params: StaticVec<_> = params.into_iter().map(|(p, _)| p).collect(); + params.shrink_to_fit(); #[cfg(not(feature = "no_closure"))] let externals = state @@ -2874,6 +2863,8 @@ fn make_curry_from_externals( )); }); + args.shrink_to_fit(); + let expr = Expr::FnCall( Box::new(FnCallExpr { name: state.get_identifier(crate::engine::KEYWORD_FN_PTR_CURRY), @@ -2967,7 +2958,7 @@ fn parse_anon_fn( Default::default() }; - let params: StaticVec<_> = if cfg!(not(feature = "no_closure")) { + let mut params: StaticVec<_> = if cfg!(not(feature = "no_closure")) { externals .iter() .cloned() @@ -2976,6 +2967,7 @@ fn parse_anon_fn( } else { params.into_iter().map(|(v, _)| v).collect() }; + params.shrink_to_fit(); // Create unique function name by hashing the script body plus the parameters. let hasher = &mut get_hasher(); @@ -3135,22 +3127,22 @@ impl Engine { pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> Option { match value.0 { #[cfg(not(feature = "no_float"))] - Union::Float(value, _) => Some(Expr::FloatConstant(value, pos)), + Union::Float(value, _, _) => Some(Expr::FloatConstant(value, pos)), #[cfg(feature = "decimal")] - Union::Decimal(value, _) => Some(Expr::DynamicConstant(Box::new((*value).into()), pos)), + Union::Decimal(value, _, _) => Some(Expr::DynamicConstant(Box::new((*value).into()), pos)), - Union::Unit(_, _) => Some(Expr::Unit(pos)), - Union::Int(value, _) => Some(Expr::IntegerConstant(value, pos)), - Union::Char(value, _) => Some(Expr::CharConstant(value, pos)), - Union::Str(value, _) => Some(Expr::StringConstant(value, pos)), - Union::Bool(value, _) => Some(Expr::BoolConstant(value, pos)), + Union::Unit(_, _, _) => Some(Expr::Unit(pos)), + Union::Int(value, _, _) => Some(Expr::IntegerConstant(value, pos)), + Union::Char(value, _, _) => Some(Expr::CharConstant(value, pos)), + Union::Str(value, _, _) => Some(Expr::StringConstant(value, pos)), + Union::Bool(value, _, _) => Some(Expr::BoolConstant(value, pos)), #[cfg(not(feature = "no_index"))] - Union::Array(array, _) => Some(Expr::DynamicConstant(Box::new((*array).into()), pos)), + Union::Array(array, _, _) => Some(Expr::DynamicConstant(Box::new((*array).into()), pos)), #[cfg(not(feature = "no_object"))] - Union::Map(map, _) => Some(Expr::DynamicConstant(Box::new((*map).into()), pos)), + Union::Map(map, _, _) => Some(Expr::DynamicConstant(Box::new((*map).into()), pos)), _ => None, } diff --git a/src/serde/de.rs b/src/serde/de.rs index 2d81cff9..d6e93d20 100644 --- a/src/serde/de.rs +++ b/src/serde/de.rs @@ -115,7 +115,7 @@ pub fn from_dynamic<'de, T: Deserialize<'de>>( impl Error for Box { fn custom(err: T) -> Self { - LexError::ImproperSymbol("".to_string(), err.to_string()) + LexError::ImproperSymbol(Default::default(), err.to_string()) .into_err(Position::NONE) .into() } @@ -126,53 +126,53 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { fn deserialize_any>(self, visitor: V) -> Result> { match &self.value.0 { - Union::Unit(_, _) => self.deserialize_unit(visitor), - Union::Bool(_, _) => self.deserialize_bool(visitor), - Union::Str(_, _) => self.deserialize_str(visitor), - Union::Char(_, _) => self.deserialize_char(visitor), + Union::Unit(_, _, _) => self.deserialize_unit(visitor), + Union::Bool(_, _, _) => self.deserialize_bool(visitor), + Union::Str(_, _, _) => self.deserialize_str(visitor), + Union::Char(_, _, _) => self.deserialize_char(visitor), #[cfg(not(feature = "only_i32"))] - Union::Int(_, _) => self.deserialize_i64(visitor), + Union::Int(_, _, _) => self.deserialize_i64(visitor), #[cfg(feature = "only_i32")] - Union::Int(_, _) => self.deserialize_i32(visitor), + Union::Int(_, _, _) => self.deserialize_i32(visitor), #[cfg(not(feature = "no_float"))] #[cfg(not(feature = "f32_float"))] - Union::Float(_, _) => self.deserialize_f64(visitor), + Union::Float(_, _, _) => self.deserialize_f64(visitor), #[cfg(not(feature = "no_float"))] #[cfg(feature = "f32_float")] - Union::Float(_, _) => self.deserialize_f32(visitor), + Union::Float(_, _, _) => self.deserialize_f32(visitor), #[cfg(feature = "decimal")] #[cfg(not(feature = "f32_float"))] - Union::Decimal(_, _) => self.deserialize_f64(visitor), + Union::Decimal(_, _, _) => self.deserialize_f64(visitor), #[cfg(feature = "decimal")] #[cfg(feature = "f32_float")] - Union::Decimal(_, _) => self.deserialize_f32(visitor), + Union::Decimal(_, _, _) => self.deserialize_f32(visitor), #[cfg(not(feature = "no_index"))] - Union::Array(_, _) => self.deserialize_seq(visitor), + Union::Array(_, _, _) => self.deserialize_seq(visitor), #[cfg(not(feature = "no_object"))] - Union::Map(_, _) => self.deserialize_map(visitor), - Union::FnPtr(_, _) => self.type_error(), + Union::Map(_, _, _) => self.deserialize_map(visitor), + Union::FnPtr(_, _, _) => self.type_error(), #[cfg(not(feature = "no_std"))] - Union::TimeStamp(_, _) => self.type_error(), + Union::TimeStamp(_, _, _) => self.type_error(), - Union::Variant(value, _) if value.is::() => self.deserialize_i8(visitor), - Union::Variant(value, _) if value.is::() => self.deserialize_i16(visitor), - Union::Variant(value, _) if value.is::() => self.deserialize_i32(visitor), - Union::Variant(value, _) if value.is::() => self.deserialize_i64(visitor), - Union::Variant(value, _) if value.is::() => self.deserialize_i128(visitor), - Union::Variant(value, _) if value.is::() => self.deserialize_u8(visitor), - Union::Variant(value, _) if value.is::() => self.deserialize_u16(visitor), - Union::Variant(value, _) if value.is::() => self.deserialize_u32(visitor), - Union::Variant(value, _) if value.is::() => self.deserialize_u64(visitor), - Union::Variant(value, _) if value.is::() => self.deserialize_u128(visitor), + Union::Variant(value, _, _) if value.is::() => self.deserialize_i8(visitor), + Union::Variant(value, _, _) if value.is::() => self.deserialize_i16(visitor), + Union::Variant(value, _, _) if value.is::() => self.deserialize_i32(visitor), + Union::Variant(value, _, _) if value.is::() => self.deserialize_i64(visitor), + Union::Variant(value, _, _) if value.is::() => self.deserialize_i128(visitor), + Union::Variant(value, _, _) if value.is::() => self.deserialize_u8(visitor), + Union::Variant(value, _, _) if value.is::() => self.deserialize_u16(visitor), + Union::Variant(value, _, _) if value.is::() => self.deserialize_u32(visitor), + Union::Variant(value, _, _) if value.is::() => self.deserialize_u64(visitor), + Union::Variant(value, _, _) if value.is::() => self.deserialize_u128(visitor), - Union::Variant(_, _) => self.type_error(), + Union::Variant(_, _, _) => self.type_error(), #[cfg(not(feature = "no_closure"))] - Union::Shared(_, _) => self.type_error(), + Union::Shared(_, _, _) => self.type_error(), } } diff --git a/src/serde/serialize.rs b/src/serde/serialize.rs index 43d5189a..be3625ac 100644 --- a/src/serde/serialize.rs +++ b/src/serde/serialize.rs @@ -12,26 +12,26 @@ use serde::ser::SerializeMap; impl Serialize for Dynamic { fn serialize(&self, ser: S) -> Result { match &self.0 { - Union::Unit(_, _) => ser.serialize_unit(), - Union::Bool(x, _) => ser.serialize_bool(*x), - Union::Str(s, _) => ser.serialize_str(s.as_str()), - Union::Char(c, _) => ser.serialize_str(&c.to_string()), + Union::Unit(_, _, _) => ser.serialize_unit(), + Union::Bool(x, _, _) => ser.serialize_bool(*x), + Union::Str(s, _, _) => ser.serialize_str(s.as_str()), + Union::Char(c, _, _) => ser.serialize_str(&c.to_string()), #[cfg(not(feature = "only_i32"))] - Union::Int(x, _) => ser.serialize_i64(*x), + Union::Int(x, _, _) => ser.serialize_i64(*x), #[cfg(feature = "only_i32")] - Union::Int(x, _) => ser.serialize_i32(*x), + Union::Int(x, _, _) => ser.serialize_i32(*x), #[cfg(not(feature = "no_float"))] #[cfg(not(feature = "f32_float"))] - Union::Float(x, _) => ser.serialize_f64(**x), + Union::Float(x, _, _) => ser.serialize_f64(**x), #[cfg(not(feature = "no_float"))] #[cfg(feature = "f32_float")] - Union::Float(x, _) => ser.serialize_f32(**x), + Union::Float(x, _, _) => ser.serialize_f32(**x), #[cfg(feature = "decimal")] #[cfg(not(feature = "f32_float"))] - Union::Decimal(x, _) => { + Union::Decimal(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(x, _, _) => { use rust_decimal::prelude::ToPrimitive; if let Some(v) = x.to_f32() { @@ -53,27 +53,27 @@ impl Serialize for Dynamic { } #[cfg(not(feature = "no_index"))] - Union::Array(a, _) => (**a).serialize(ser), + Union::Array(a, _, _) => (**a).serialize(ser), #[cfg(not(feature = "no_object"))] - Union::Map(m, _) => { + Union::Map(m, _, _) => { let mut map = ser.serialize_map(Some(m.len()))?; for (k, v) in m.iter() { map.serialize_entry(k.as_str(), v)?; } map.end() } - Union::FnPtr(f, _) => ser.serialize_str(f.fn_name()), + Union::FnPtr(f, _, _) => ser.serialize_str(f.fn_name()), #[cfg(not(feature = "no_std"))] - Union::TimeStamp(x, _) => ser.serialize_str(x.as_ref().type_name()), + Union::TimeStamp(x, _, _) => ser.serialize_str(x.as_ref().type_name()), - Union::Variant(v, _) => ser.serialize_str((***v).type_name()), + Union::Variant(v, _, _) => ser.serialize_str((***v).type_name()), #[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "sync"))] - Union::Shared(cell, _) => cell.borrow().serialize(ser), + Union::Shared(cell, _, _) => cell.borrow().serialize(ser), #[cfg(not(feature = "no_closure"))] #[cfg(feature = "sync")] - Union::Shared(cell, _) => cell.read().unwrap().serialize(ser), + Union::Shared(cell, _, _) => cell.read().unwrap().serialize(ser), } } } diff --git a/src/syntax.rs b/src/syntax.rs index 7d951cea..34632f9e 100644 --- a/src/syntax.rs +++ b/src/syntax.rs @@ -87,28 +87,33 @@ pub struct CustomSyntax { pub parse: Box, /// Custom syntax implementation function. pub func: Shared, - /// Delta number of variables in the scope. - pub scope_delta: isize, + /// Any variables added/removed in the scope? + pub scope_changed: bool, } impl Engine { /// Register a custom syntax with the [`Engine`]. /// /// * `keywords` holds a slice of strings that define the custom syntax. - /// * `new_vars` is the number of new variables declared by this custom syntax, or the number of variables removed (if negative). + /// * `scope_changed` specifies variables have been added/removed by this custom syntax. /// * `func` is the implementation function. /// - /// # Notes + /// # Caveat - Do not change beyond block scope /// - /// If `new_vars` is positive, then a number of new variables are expected to be pushed into the - /// current [`Scope`][crate::Scope]. + /// If `scope_changed` is `true`, then the current [`Scope`][crate::Scope] is assumed to be + /// modified by this custom syntax. /// - /// If `new_vars` is negative, then it is expected that only the top `new_vars` variables in the - /// current [`Scope`][crate::Scope] will be _popped_. Do not randomly remove variables. + /// Adding new variables and/or removing variables are allowed. Simply modifying the values of + /// variables does NOT count, so `false` should be passed in this case. + /// + /// However, only variables declared within the current _block scope_ should be touched, + /// since they all go away at the end of the block. + /// + /// Variables in parent blocks should be left untouched as they persist beyond the current block. pub fn register_custom_syntax + Into>( &mut self, keywords: &[S], - new_vars: isize, + scope_changed: bool, func: impl Fn(&mut EvalContext, &[Expression]) -> RhaiResult + SendSync + 'static, ) -> Result<&mut Self, ParseError> { let keywords = keywords.as_ref(); @@ -203,7 +208,7 @@ impl Engine { Ok(Some(segments[stream.len()].clone())) } }, - new_vars, + scope_changed, func, ); @@ -215,7 +220,7 @@ impl Engine { /// /// This function is very low level. /// - /// * `new_vars` is the number of new variables declared by this custom syntax, or the number of variables removed (if negative). + /// * `scope_changed` specifies variables have been added/removed by this custom syntax. /// * `parse` is the parsing function. /// * `func` is the implementation function. /// @@ -227,16 +232,17 @@ impl Engine { parse: impl Fn(&[ImmutableString], &str) -> Result, ParseError> + SendSync + 'static, - new_vars: isize, + scope_changed: bool, func: impl Fn(&mut EvalContext, &[Expression]) -> RhaiResult + SendSync + 'static, ) -> &mut Self { - let syntax = CustomSyntax { - parse: Box::new(parse), - func: (Box::new(func) as Box).into(), - scope_delta: new_vars, - }; - - self.custom_syntax.insert(key.into(), syntax); + self.custom_syntax.insert( + key.into(), + Box::new(CustomSyntax { + parse: Box::new(parse), + func: (Box::new(func) as Box).into(), + scope_changed, + }), + ); self } } diff --git a/src/token.rs b/src/token.rs index ad4d6a44..44826444 100644 --- a/src/token.rs +++ b/src/token.rs @@ -973,10 +973,6 @@ pub struct TokenizeState { pub comment_level: usize, /// Include comments? pub include_comments: bool, - /// Disable doc-comments? - #[cfg(not(feature = "no_function"))] - #[cfg(feature = "metadata")] - pub disable_doc_comments: bool, /// Is the current tokenizer position within the text stream of an interpolated string? pub is_within_text_terminated_by: Option, } @@ -1357,12 +1353,11 @@ fn get_next_token_inner( #[cfg(not(feature = "no_function"))] #[cfg(feature = "metadata")] - let include_comments = - if !state.disable_doc_comments && is_doc_comment(comment.as_ref().unwrap()) { - true - } else { - include_comments - }; + let include_comments = if is_doc_comment(comment.as_ref().unwrap()) { + true + } else { + include_comments + }; if include_comments { return Some((Token::Comment(comment.unwrap()), start_pos)); @@ -1705,7 +1700,7 @@ fn get_next_token_inner( let mut comment = match stream.peek_next() { #[cfg(not(feature = "no_function"))] #[cfg(feature = "metadata")] - Some('/') if !state.disable_doc_comments => { + Some('/') => { eat_next(stream, pos); // Long streams of `///...` are not doc-comments @@ -1740,7 +1735,7 @@ fn get_next_token_inner( let mut comment = match stream.peek_next() { #[cfg(not(feature = "no_function"))] #[cfg(feature = "metadata")] - Some('*') if !state.disable_doc_comments => { + Some('*') => { eat_next(stream, pos); // Long streams of `/****...` are not doc-comments @@ -2239,9 +2234,6 @@ impl Engine { non_unary: false, comment_level: 0, include_comments: false, - #[cfg(not(feature = "no_function"))] - #[cfg(feature = "metadata")] - disable_doc_comments: self.disable_doc_comments, is_within_text_terminated_by: None, }, pos: Position::new(1, 0), diff --git a/src/utils.rs b/src/utils.rs index 234ad303..ab998c5d 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -198,6 +198,7 @@ impl From for ImmutableString { Self(Into::::into(value).into()) } } +#[cfg(not(feature = "no_smartstring"))] impl From for ImmutableString { #[inline(always)] fn from(value: SmartString) -> Self { @@ -248,6 +249,14 @@ impl<'a> FromIterator for ImmutableString { } } +#[cfg(not(feature = "no_smartstring"))] +impl<'a> FromIterator for ImmutableString { + #[inline(always)] + fn from_iter>(iter: T) -> Self { + Self(iter.into_iter().collect::().into()) + } +} + impl fmt::Display for ImmutableString { #[inline(always)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -618,17 +627,17 @@ impl ImmutableString { /// yet interned. #[derive(Debug, Clone, Default, Hash)] pub struct IdentifierBuilder( - #[cfg(feature = "no_smartstring_for_identifier")] std::collections::BTreeSet, + #[cfg(feature = "no_smartstring")] std::collections::BTreeSet, ); impl IdentifierBuilder { /// Get an identifier from a text string. #[inline(always)] pub fn get(&mut self, text: impl AsRef + Into) -> Identifier { - #[cfg(not(feature = "no_smartstring_for_identifier"))] + #[cfg(not(feature = "no_smartstring"))] return text.as_ref().into(); - #[cfg(feature = "no_smartstring_for_identifier")] + #[cfg(feature = "no_smartstring")] return self.0.get(text.as_ref()).cloned().unwrap_or_else(|| { let s: Identifier = text.into(); self.0.insert(s.clone()); diff --git a/tests/comments.rs b/tests/comments.rs index 91d7b200..695c47a4 100644 --- a/tests/comments.rs +++ b/tests/comments.rs @@ -30,7 +30,7 @@ fn test_comments() -> Result<(), Box> { #[cfg(feature = "metadata")] #[test] fn test_comments_doc() -> Result<(), Box> { - let mut engine = Engine::new(); + let engine = Engine::new(); let ast = engine.compile( " @@ -89,17 +89,5 @@ fn test_comments_doc() -> Result<(), Box> { ) .is_err()); - engine.enable_doc_comments(false); - - engine.compile( - " - /// Hello world! - let x = 42; - - /** Hello world! */ - let x = 42; - ", - )?; - Ok(()) } diff --git a/tests/constants.rs b/tests/constants.rs index 8c3c95af..c64ecb1b 100644 --- a/tests/constants.rs +++ b/tests/constants.rs @@ -16,7 +16,7 @@ fn test_constant() -> Result<(), Box> { #[cfg(not(feature = "no_index"))] assert!(matches!( *engine.consume("const x = [1, 2, 3, 4, 5]; x[2] = 42;").expect_err("expects error"), - EvalAltResult::ErrorParsing(ParseErrorType::AssignmentToConstant(x), _) if x == "x" + EvalAltResult::ErrorAssignmentToConstant(x, _) if x == "x" )); Ok(()) @@ -45,17 +45,66 @@ fn test_constant_mut() -> Result<(), Box> { let mut engine = Engine::new(); + fn set_value(obj: &mut TestStruct, value: INT) { + obj.0 = value; + } + engine .register_type_with_name::("TestStruct") + .register_fn("new_ts", || TestStruct(123)) .register_get("value", |obj: &mut TestStruct| obj.0) - .register_fn("update_value", |obj: &mut TestStruct, value: INT| { - obj.0 = value - }); + .register_set("value", set_value) + .register_fn("update_value", set_value); + + assert_eq!( + engine.eval::( + " + const MY_NUMBER = new_ts(); + MY_NUMBER.update_value(42); + MY_NUMBER.value + ", + )?, + 42 + ); + + assert_eq!( + engine.eval::( + " + const MY_NUMBER = new_ts(); + update_value(MY_NUMBER, 42); + MY_NUMBER.value + ", + )?, + 123 + ); + + assert!(matches!( + *engine + .consume( + " + const MY_NUMBER = new_ts(); + MY_NUMBER.value = 42; + " + ) + .expect_err("should error"), + EvalAltResult::ErrorAssignmentToConstant(_, _) + )); let mut scope = Scope::new(); scope.push_constant("MY_NUMBER", TestStruct(123)); + assert_eq!( + engine.eval_with_scope::( + &mut scope, + " + update_value(MY_NUMBER, 42); + MY_NUMBER.value + ", + )?, + 123 + ); + assert_eq!( engine.eval_with_scope::( &mut scope, @@ -67,5 +116,12 @@ fn test_constant_mut() -> Result<(), Box> { 42 ); + assert!(matches!( + *engine + .consume_with_scope(&mut scope, "MY_NUMBER.value = 42;") + .expect_err("should error"), + EvalAltResult::ErrorAssignmentToConstant(_, _) + )); + Ok(()) } diff --git a/tests/plugins.rs b/tests/plugins.rs index e42c4c73..9e5548f6 100644 --- a/tests/plugins.rs +++ b/tests/plugins.rs @@ -31,15 +31,18 @@ mod test { } #[rhai_fn(name = "test", name = "hi")] - #[inline(always)] pub fn len(array: &mut Array, mul: INT) -> INT { (array.len() as INT) * mul } #[rhai_fn(name = "+")] - #[inline(always)] pub fn funky_add(x: INT, y: INT) -> INT { x / 2 + y * 2 } + #[rhai_fn(name = "no_effect", set = "no_effect", pure)] + pub fn no_effect(array: &mut Array, value: INT) { + // array is not modified + println!("Array = {:?}, Value = {}", array, value); + } } } @@ -82,7 +85,16 @@ fn test_plugins_package() -> Result<(), Box> { reg_functions!(engine += greet::single(INT, bool, char)); #[cfg(not(feature = "no_object"))] - assert_eq!(engine.eval::("let a = [1, 2, 3]; a.foo")?, 1); + { + assert_eq!(engine.eval::("let a = [1, 2, 3]; a.foo")?, 1); + engine.consume("const A = [1, 2, 3]; A.no_effect(42);")?; + engine.consume("const A = [1, 2, 3]; A.no_effect = 42;")?; + + assert!( + matches!(*engine.consume("const A = [1, 2, 3]; A.test(42);").expect_err("should error"), + EvalAltResult::ErrorAssignmentToConstant(x, _) if x == "array") + ) + } assert_eq!(engine.eval::(r#"hash("hello")"#)?, 42); assert_eq!(engine.eval::(r#"hash2("hello")"#)?, 42); diff --git a/tests/syntax.rs b/tests/syntax.rs index 5dd03d9a..1b8857e9 100644 --- a/tests/syntax.rs +++ b/tests/syntax.rs @@ -19,15 +19,15 @@ fn test_custom_syntax() -> Result<(), Box> { engine.register_custom_syntax( &[ - "exec", "|", "$ident$", "|", "->", "$block$", "while", "$expr$", + "exec", "[", "$ident$", "]", "->", "$block$", "while", "$expr$", ], - 1, + true, |context, inputs| { let var_name = inputs[0].get_variable_name().unwrap().to_string(); let stmt = inputs.get(1).unwrap(); let condition = inputs.get(2).unwrap(); - context.scope_mut().push(var_name, 0 as INT); + context.scope_mut().push(var_name.clone(), 0 as INT); let mut count: INT = 0; @@ -35,6 +35,10 @@ fn test_custom_syntax() -> Result<(), Box> { context.eval_expression_tree(stmt)?; count += 1; + context + .scope_mut() + .push(format!("{}{}", var_name, count), count); + let stop = !context .eval_expression_tree(condition)? .as_bool() @@ -59,7 +63,7 @@ fn test_custom_syntax() -> Result<(), Box> { engine.eval::( " let x = 0; - let foo = (exec |x| -> { x += 2 } while x < 42) * 10; + let foo = (exec [x] -> { x += 2 } while x < 42) * 10; foo " )?, @@ -69,7 +73,7 @@ fn test_custom_syntax() -> Result<(), Box> { engine.eval::( " let x = 0; - exec |x| -> { x += 1 } while x < 42; + exec [x] -> { x += 1 } while x < 42; x " )?, @@ -78,17 +82,27 @@ fn test_custom_syntax() -> Result<(), Box> { assert_eq!( engine.eval::( " - exec |x| -> { x += 1 } while x < 42; + exec [x] -> { x += 1 } while x < 42; x " )?, 42 ); + assert_eq!( + engine.eval::( + " + let foo = 123; + exec [x] -> { x += 1 } while x < 42; + foo + x + x1 + x2 + x3 + " + )?, + 171 + ); // The first symbol must be an identifier assert_eq!( *engine - .register_custom_syntax(&["!"], 0, |_, _| Ok(Dynamic::UNIT)) + .register_custom_syntax(&["!"], false, |_, _| Ok(Dynamic::UNIT)) .expect_err("should error") .0, ParseErrorType::BadInput(LexError::ImproperSymbol( @@ -114,14 +128,14 @@ fn test_custom_syntax_raw() -> Result<(), Box> { s => Err(ParseError( Box::new(ParseErrorType::BadInput(LexError::ImproperSymbol( s.to_string(), - "".to_string(), + Default::default(), ))), Position::NONE, )), }, _ => unreachable!(), }, - 1, + true, |context, inputs| { context.scope_mut().push("foo", 999 as INT);