From f2b1184805d839ec660b43f99d6c5e291404239f Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 22 Sep 2022 23:11:38 +0800 Subject: [PATCH 01/10] Remove need for smartstring/std. --- CHANGELOG.md | 2 +- src/api/files.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index adab88b9..98281abf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ Bug fixes --------- * Compiling on 32-bit architectures no longer cause a compilation error. -* Fix type-size test fo 32-bit architectures without the `decimal` feature. +* Fix type-size test for 32-bit architectures without the `decimal` feature. Custom syntax with state ------------------------ diff --git a/src/api/files.rs b/src/api/files.rs index ad2bbeae..b2a2ffd1 100644 --- a/src/api/files.rs +++ b/src/api/files.rs @@ -108,7 +108,7 @@ impl Engine { pub fn compile_file_with_scope(&self, scope: &Scope, path: PathBuf) -> RhaiResultOf { Self::read_file(&path).and_then(|contents| { let mut ast = self.compile_with_scope(scope, &contents)?; - ast.set_source(path.to_string_lossy()); + ast.set_source(path.to_string_lossy().as_ref()); Ok(ast) }) } From def1a683efa1274a1d106ffef46790dd54f69f41 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 24 Sep 2022 16:59:01 +0800 Subject: [PATCH 02/10] Distinguish string literals from identifiers in custom syntax parser. --- CHANGELOG.md | 8 ++++++++ src/parser.rs | 2 +- src/tokenizer.rs | 2 +- tests/custom_syntax.rs | 28 ++++++++++++++++------------ 4 files changed, 26 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 98281abf..a057eebe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,14 @@ Rhai Release Notes ================== +Version 1.11.0 +============== + +Enhancements + +* The look-ahead symbol for custom syntax now renders a string literal in quotes (instead of the generic term `string`). This facilitates more accurate parsing by separating strings and identifiers. + + Version 1.10.1 ============== diff --git a/src/parser.rs b/src/parser.rs index 20600e0b..621a1b47 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -2461,7 +2461,7 @@ impl Engine { use crate::api::custom_syntax::markers::*; let mut settings = settings; - let mut inputs = StaticVec::::new(); + let mut inputs = StaticVec::new_const(); let mut segments = StaticVec::new_const(); let mut tokens = StaticVec::new_const(); diff --git a/src/tokenizer.rs b/src/tokenizer.rs index 71305dec..6cc7eb57 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -699,7 +699,7 @@ impl Token { FloatConstant(f) => f.to_string().into(), #[cfg(feature = "decimal")] DecimalConstant(d) => d.to_string().into(), - StringConstant(..) => "string".into(), + StringConstant(s) => format!("\"{s}\"").into(), InterpolatedString(..) => "string".into(), CharConstant(c) => c.to_string().into(), Identifier(s) => s.to_string().into(), diff --git a/tests/custom_syntax.rs b/tests/custom_syntax.rs index 2c8db7f1..dacca728 100644 --- a/tests/custom_syntax.rs +++ b/tests/custom_syntax.rs @@ -256,10 +256,18 @@ fn test_custom_syntax_raw() -> Result<(), Box> { engine.register_custom_syntax_with_state_raw( "hello", - |stream, _, state| match stream.len() { + |stream, look_ahead, state| match stream.len() { 0 => unreachable!(), - 1 => Ok(Some("$ident$".into())), + 1 if look_ahead == "\"world\"" => { + *state = Dynamic::TRUE; + Ok(Some("$string$".into())) + } + 1 => { + *state = Dynamic::FALSE; + Ok(Some("$ident$".into())) + } 2 => match stream[1].as_str() { + "world" if state.as_bool().unwrap_or(false) => Ok(Some("$$world".into())), "world" => Ok(Some("$$hello".into())), "kitty" => { *state = (42 as INT).into(); @@ -276,16 +284,11 @@ fn test_custom_syntax_raw() -> Result<(), Box> { context.scope_mut().push("foo", 999 as INT); Ok(match inputs[0].get_string_value().unwrap() { - "world" - if inputs - .last() - .unwrap() - .get_string_value() - .map_or(false, |s| s == "$$hello") => - { - 0 as INT - } - "world" => 123 as INT, + "world" => match inputs.last().unwrap().get_string_value().unwrap_or("") { + "$$hello" => 0 as INT, + "$$world" => 123456 as INT, + _ => 123 as INT, + }, "kitty" if inputs.len() > 1 => 999 as INT, "kitty" => state.as_int().unwrap(), _ => unreachable!(), @@ -294,6 +297,7 @@ fn test_custom_syntax_raw() -> Result<(), Box> { }, ); + assert_eq!(engine.eval::(r#"hello "world""#)?, 123456); assert_eq!(engine.eval::("hello world")?, 0); assert_eq!(engine.eval::("hello kitty")?, 42); assert_eq!( From b56a9c22f3eb90ca89d788ba013a3a41bb4fb19c Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 25 Sep 2022 12:24:03 +0800 Subject: [PATCH 03/10] Refactor. --- src/api/custom_syntax.rs | 2 ++ src/api/mod.rs | 1 + src/api/register.rs | 2 ++ src/ast/ast.rs | 3 +++ src/ast/expr.rs | 4 ++++ src/ast/ident.rs | 3 +++ src/ast/namespace.rs | 2 ++ src/ast/stmt.rs | 4 ++++ src/engine.rs | 6 +++--- src/eval/target.rs | 4 ++++ src/module/resolvers/file.rs | 4 ++-- src/types/dynamic.rs | 2 ++ src/types/error.rs | 2 ++ src/types/immutable_string.rs | 8 +++++++- src/types/parse_error.rs | 10 +++++++--- tests/operations.rs | 6 +++--- tests/plugins.rs | 2 +- tests/print.rs | 2 +- tests/serde.rs | 2 +- tests/var_scope.rs | 2 +- 20 files changed, 55 insertions(+), 16 deletions(-) diff --git a/src/api/custom_syntax.rs b/src/api/custom_syntax.rs index ad7e5b40..080d4477 100644 --- a/src/api/custom_syntax.rs +++ b/src/api/custom_syntax.rs @@ -147,6 +147,7 @@ impl Expression<'_> { impl AsRef for Expression<'_> { #[inline(always)] + #[must_use] fn as_ref(&self) -> &Expr { self.0 } @@ -156,6 +157,7 @@ impl Deref for Expression<'_> { type Target = Expr; #[inline(always)] + #[must_use] fn deref(&self) -> &Self::Target { self.0 } diff --git a/src/api/mod.rs b/src/api/mod.rs index e9e03854..6ddf090b 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -75,6 +75,7 @@ impl Engine { /// Not available under `no_module`. #[cfg(not(feature = "no_module"))] #[inline(always)] + #[must_use] pub fn module_resolver(&self) -> &dyn crate::ModuleResolver { &*self.module_resolver } diff --git a/src/api/register.rs b/src/api/register.rs index 2b4e20ed..9095a31f 100644 --- a/src/api/register.rs +++ b/src/api/register.rs @@ -16,12 +16,14 @@ impl Engine { /// Get the global namespace module (which is the fist module in `global_modules`). #[inline(always)] #[allow(dead_code)] + #[must_use] pub(crate) fn global_namespace(&self) -> &Module { self.global_modules.first().unwrap() } /// Get a mutable reference to the global namespace module /// (which is the first module in `global_modules`). #[inline(always)] + #[must_use] pub(crate) fn global_namespace_mut(&mut self) -> &mut Module { let module = self.global_modules.first_mut().unwrap(); Shared::get_mut(module).expect("not shared") diff --git a/src/ast/ast.rs b/src/ast/ast.rs index da0454fe..902a6856 100644 --- a/src/ast/ast.rs +++ b/src/ast/ast.rs @@ -919,6 +919,7 @@ impl> AddAssign for AST { impl AsRef<[Stmt]> for AST { #[inline(always)] + #[must_use] fn as_ref(&self) -> &[Stmt] { self.statements() } @@ -927,6 +928,7 @@ impl AsRef<[Stmt]> for AST { #[cfg(not(feature = "no_function"))] impl AsRef for AST { #[inline(always)] + #[must_use] fn as_ref(&self) -> &crate::Module { self.shared_lib().as_ref() } @@ -935,6 +937,7 @@ impl AsRef for AST { #[cfg(not(feature = "no_function"))] impl AsRef> for AST { #[inline(always)] + #[must_use] fn as_ref(&self) -> &crate::Shared { self.shared_lib() } diff --git a/src/ast/expr.rs b/src/ast/expr.rs index 3b07ecfd..389ac137 100644 --- a/src/ast/expr.rs +++ b/src/ast/expr.rs @@ -257,6 +257,7 @@ impl Hash for FloatWrapper { #[cfg(not(feature = "no_float"))] impl AsRef for FloatWrapper { #[inline(always)] + #[must_use] fn as_ref(&self) -> &F { &self.0 } @@ -265,6 +266,7 @@ impl AsRef for FloatWrapper { #[cfg(not(feature = "no_float"))] impl AsMut for FloatWrapper { #[inline(always)] + #[must_use] fn as_mut(&mut self) -> &mut F { &mut self.0 } @@ -275,6 +277,7 @@ impl Deref for FloatWrapper { type Target = F; #[inline(always)] + #[must_use] fn deref(&self) -> &Self::Target { &self.0 } @@ -283,6 +286,7 @@ impl Deref for FloatWrapper { #[cfg(not(feature = "no_float"))] impl DerefMut for FloatWrapper { #[inline(always)] + #[must_use] fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } diff --git a/src/ast/ident.rs b/src/ast/ident.rs index 13ab4ef9..8a6d98aa 100644 --- a/src/ast/ident.rs +++ b/src/ast/ident.rs @@ -28,6 +28,7 @@ impl fmt::Debug for Ident { impl AsRef for Ident { #[inline(always)] + #[must_use] fn as_ref(&self) -> &str { self.name.as_ref() } @@ -37,6 +38,7 @@ impl Deref for Ident { type Target = ImmutableString; #[inline(always)] + #[must_use] fn deref(&self) -> &Self::Target { &self.name } @@ -44,6 +46,7 @@ impl Deref for Ident { impl DerefMut for Ident { #[inline(always)] + #[must_use] fn deref_mut(&mut self) -> &mut Self::Target { &mut self.name } diff --git a/src/ast/namespace.rs b/src/ast/namespace.rs index 800dba52..9dba5b18 100644 --- a/src/ast/namespace.rs +++ b/src/ast/namespace.rs @@ -70,6 +70,7 @@ impl Deref for Namespace { type Target = StaticVec; #[inline(always)] + #[must_use] fn deref(&self) -> &Self::Target { &self.path } @@ -77,6 +78,7 @@ impl Deref for Namespace { impl DerefMut for Namespace { #[inline(always)] + #[must_use] fn deref_mut(&mut self) -> &mut Self::Target { &mut self.path } diff --git a/src/ast/stmt.rs b/src/ast/stmt.rs index 68b63ecd..3b830100 100644 --- a/src/ast/stmt.rs +++ b/src/ast/stmt.rs @@ -424,6 +424,7 @@ impl Deref for StmtBlock { type Target = StmtBlockContainer; #[inline(always)] + #[must_use] fn deref(&self) -> &Self::Target { &self.block } @@ -431,6 +432,7 @@ impl Deref for StmtBlock { impl DerefMut for StmtBlock { #[inline(always)] + #[must_use] fn deref_mut(&mut self) -> &mut Self::Target { &mut self.block } @@ -438,6 +440,7 @@ impl DerefMut for StmtBlock { impl AsRef<[Stmt]> for StmtBlock { #[inline(always)] + #[must_use] fn as_ref(&self) -> &[Stmt] { &self.block } @@ -445,6 +448,7 @@ impl AsRef<[Stmt]> for StmtBlock { impl AsMut<[Stmt]> for StmtBlock { #[inline(always)] + #[must_use] fn as_mut(&mut self) -> &mut [Stmt] { &mut self.block } diff --git a/src/engine.rs b/src/engine.rs index 3520108f..72f95c6b 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -240,12 +240,12 @@ impl Engine { source.map_or_else( || { if pos.is_none() { - println!("{}", s); + println!("{s}"); } else { - println!("{:?} | {}", pos, s); + println!("{pos:?} | {s}"); } }, - |source| println!("{} @ {:?} | {}", source, pos, s), + |source| println!("{source} @ {pos:?} | {s}"), ) }); } diff --git a/src/eval/target.rs b/src/eval/target.rs index 9c0804ab..3b47533d 100644 --- a/src/eval/target.rs +++ b/src/eval/target.rs @@ -399,6 +399,7 @@ impl Deref for Target<'_> { type Target = Dynamic; #[inline] + #[must_use] fn deref(&self) -> &Dynamic { match self { Self::RefMut(r) => r, @@ -416,6 +417,7 @@ impl Deref for Target<'_> { impl AsRef for Target<'_> { #[inline(always)] + #[must_use] fn as_ref(&self) -> &Dynamic { self } @@ -423,6 +425,7 @@ impl AsRef for Target<'_> { impl DerefMut for Target<'_> { #[inline] + #[must_use] fn deref_mut(&mut self) -> &mut Dynamic { match self { Self::RefMut(r) => r, @@ -440,6 +443,7 @@ impl DerefMut for Target<'_> { impl AsMut for Target<'_> { #[inline(always)] + #[must_use] fn as_mut(&mut self) -> &mut Dynamic { self } diff --git a/src/module/resolvers/file.rs b/src/module/resolvers/file.rs index ee71ce13..729e772f 100644 --- a/src/module/resolvers/file.rs +++ b/src/module/resolvers/file.rs @@ -194,8 +194,8 @@ impl FileModuleResolver { /// Get a reference to the file module resolver's [scope][Scope]. /// /// The [scope][Scope] is used for compiling module scripts. - #[must_use] #[inline(always)] + #[must_use] pub const fn scope(&self) -> &Scope { &self.scope } @@ -211,8 +211,8 @@ impl FileModuleResolver { /// Get a mutable reference to the file module resolver's [scope][Scope]. /// /// The [scope][Scope] is used for compiling module scripts. - #[must_use] #[inline(always)] + #[must_use] pub fn scope_mut(&mut self) -> &mut Scope<'static> { &mut self.scope } diff --git a/src/types/dynamic.rs b/src/types/dynamic.rs index 6823dd74..b310b302 100644 --- a/src/types/dynamic.rs +++ b/src/types/dynamic.rs @@ -252,6 +252,7 @@ impl<'d, T: Any + Clone> Deref for DynamicWriteLock<'d, T> { type Target = T; #[inline] + #[must_use] fn deref(&self) -> &Self::Target { match self.0 { DynamicWriteLockInner::Reference(ref reference) => *reference, @@ -263,6 +264,7 @@ impl<'d, T: Any + Clone> Deref for DynamicWriteLock<'d, T> { impl<'d, T: Any + Clone> DerefMut for DynamicWriteLock<'d, T> { #[inline] + #[must_use] fn deref_mut(&mut self) -> &mut Self::Target { match self.0 { DynamicWriteLockInner::Reference(ref mut reference) => *reference, diff --git a/src/types/error.rs b/src/types/error.rs index d426d5ed..822b1a39 100644 --- a/src/types/error.rs +++ b/src/types/error.rs @@ -23,6 +23,7 @@ use std::prelude::v1::*; /// Turn on the `sync` feature to make it [`Send`] `+` [`Sync`]. #[derive(Debug)] #[non_exhaustive] +#[must_use] pub enum EvalAltResult { /// System error. Wrapped values are the error message and the internal error. #[cfg(not(feature = "sync"))] @@ -494,6 +495,7 @@ impl EvalAltResult { /// The [position][Position] of this error is set to [`NONE`][Position::NONE] afterwards. #[cold] #[inline(never)] + #[must_use] pub fn take_position(&mut self) -> Position { let pos = self.position(); self.set_position(Position::NONE); diff --git a/src/types/immutable_string.rs b/src/types/immutable_string.rs index 6487efeb..507c33d6 100644 --- a/src/types/immutable_string.rs +++ b/src/types/immutable_string.rs @@ -53,6 +53,7 @@ impl Deref for ImmutableString { type Target = SmartString; #[inline(always)] + #[must_use] fn deref(&self) -> &Self::Target { &self.0 } @@ -60,6 +61,7 @@ impl Deref for ImmutableString { impl AsRef for ImmutableString { #[inline(always)] + #[must_use] fn as_ref(&self) -> &SmartString { &self.0 } @@ -67,6 +69,7 @@ impl AsRef for ImmutableString { impl AsRef for ImmutableString { #[inline(always)] + #[must_use] fn as_ref(&self) -> &str { &self.0 } @@ -74,6 +77,7 @@ impl AsRef for ImmutableString { impl Borrow for ImmutableString { #[inline(always)] + #[must_use] fn borrow(&self) -> &SmartString { &self.0 } @@ -81,6 +85,7 @@ impl Borrow for ImmutableString { impl Borrow for ImmutableString { #[inline(always)] + #[must_use] fn borrow(&self) -> &str { self.as_str() } @@ -623,7 +628,7 @@ impl ImmutableString { #[inline] #[must_use] pub fn into_owned(mut self) -> String { - self.make_mut(); // Make sure it is unique reference + let _ = self.make_mut(); // Make sure it is unique reference shared_take(self.0).into() // Should succeed } /// Make sure that the [`ImmutableString`] is unique (i.e. no other outstanding references). @@ -631,6 +636,7 @@ impl ImmutableString { /// /// If there are other references to the same string, a cloned copy is used. #[inline(always)] + #[must_use] pub(crate) fn make_mut(&mut self) -> &mut SmartString { shared_make_mut(&mut self.0) } diff --git a/src/types/parse_error.rs b/src/types/parse_error.rs index 5cbdcb5d..e9b55ce3 100644 --- a/src/types/parse_error.rs +++ b/src/types/parse_error.rs @@ -13,6 +13,7 @@ use std::prelude::v1::*; /// Error encountered when tokenizing the script text. #[derive(Debug, Eq, PartialEq, Clone, Hash)] #[non_exhaustive] +#[must_use] pub enum LexError { /// An unexpected symbol is encountered when tokenizing the script text. UnexpectedInput(String), @@ -58,8 +59,8 @@ impl fmt::Display for LexError { impl LexError { /// Convert a [`LexError`] into a [`ParseError`]. - #[inline(always)] - #[must_use] + #[cold] + #[inline(never)] pub fn into_err(self, pos: Position) -> ParseError { ParseError(Box::new(self.into()), pos) } @@ -72,6 +73,7 @@ impl LexError { /// massive code changes to remove/add back enum variants in match statements. #[derive(Debug, Eq, PartialEq, Clone, Hash)] #[non_exhaustive] +#[must_use] pub enum ParseErrorType { /// The script ends prematurely. UnexpectedEOF, @@ -171,7 +173,8 @@ pub enum ParseErrorType { impl ParseErrorType { /// Make a [`ParseError`] using the current type and position. - #[inline(always)] + #[cold] + #[inline(never)] #[must_use] pub(crate) fn into_err(self, pos: Position) -> ParseError { ParseError(self.into(), pos) @@ -277,6 +280,7 @@ impl From for ParseErrorType { /// Error when parsing a script. #[derive(Debug, Eq, PartialEq, Clone, Hash)] +#[must_use] pub struct ParseError( /// Parse error type. pub Box, diff --git a/tests/operations.rs b/tests/operations.rs index d6b7b69f..c4eb37ab 100644 --- a/tests/operations.rs +++ b/tests/operations.rs @@ -10,7 +10,7 @@ fn test_max_operations() -> Result<(), Box> { engine.on_progress(|count| { if count % 100 == 0 { - println!("{}", count); + println!("{count}"); } None }); @@ -68,7 +68,7 @@ fn test_max_operations_functions() -> Result<(), Box> { engine.on_progress(|count| { if count % 100 == 0 { - println!("{}", count); + println!("{count}"); } None }); @@ -124,7 +124,7 @@ fn test_max_operations_eval() -> Result<(), Box> { engine.on_progress(|count| { if count % 100 == 0 { - println!("{}", count); + println!("{count}"); } None }); diff --git a/tests/plugins.rs b/tests/plugins.rs index d5072eb9..5fe58fdc 100644 --- a/tests/plugins.rs +++ b/tests/plugins.rs @@ -42,7 +42,7 @@ mod test { #[rhai_fn(name = "no_effect", set = "no_effect", pure)] pub fn no_effect(array: &mut Array, value: INT) { // array is not modified - println!("Array = {:?}, Value = {}", array, value); + println!("Array = {array:?}, Value = {value}"); } } } diff --git a/tests/print.rs b/tests/print.rs index 820cce58..982b2ff2 100644 --- a/tests/print.rs +++ b/tests/print.rs @@ -66,7 +66,7 @@ fn test_print_debug() -> Result<(), Box> { ); for entry in logbook.read().unwrap().iter() { - println!("{}", entry); + println!("{entry}"); } Ok(()) diff --git a/tests/serde.rs b/tests/serde.rs index 5df11139..45c16b72 100644 --- a/tests/serde.rs +++ b/tests/serde.rs @@ -820,6 +820,6 @@ fn test_serde_blob() -> Result<(), Box> { #[cfg(not(feature = "no_object"))] fn test_serde_json_borrowed_string() { let value = json!({ "a": "b" }); - println!("value: {:?}", value); + println!("value: {value:?}"); let _: Dynamic = serde_json::from_value(value).unwrap(); } diff --git a/tests/var_scope.rs b/tests/var_scope.rs index 73701746..e249b6de 100644 --- a/tests/var_scope.rs +++ b/tests/var_scope.rs @@ -140,7 +140,7 @@ fn test_scope_eval() -> Result<(), Box> { // Second invocation using the same state let result = engine.eval_with_scope::(&mut scope, "x")?; - println!("result: {}", result); // should print 966 + println!("result: {result}"); // should print 966 // Variable y is changed in the script assert_eq!( From ce56c43bc070a3d6717b2752387d35d4d059fd7d Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 25 Sep 2022 16:20:36 +0800 Subject: [PATCH 04/10] Refactor serde impl. --- src/serde/de.rs | 99 +++++++++++++++------------ src/serde/deserialize.rs | 23 +++++++ src/serde/mod.rs | 5 +- src/serde/ser.rs | 109 +++++++++++++++++++---------- src/serde/serialize.rs | 1 + src/serde/str.rs | 143 --------------------------------------- 6 files changed, 157 insertions(+), 223 deletions(-) delete mode 100644 src/serde/str.rs diff --git a/src/serde/de.rs b/src/serde/de.rs index 76c31c11..dbf43605 100644 --- a/src/serde/de.rs +++ b/src/serde/de.rs @@ -8,12 +8,19 @@ use serde::{Deserialize, Deserializer}; use std::prelude::v1::*; use std::{any::type_name, fmt}; -/// Deserializer for [`Dynamic`][crate::Dynamic] which is kept as a reference. -/// -/// The reference is necessary because the deserialized type may hold references -/// (especially `&str`) to the source [`Dynamic`][crate::Dynamic]. -struct DynamicDeserializer<'a> { - value: &'a Dynamic, +/// Deserializer for [`Dynamic`][crate::Dynamic]. +pub struct DynamicDeserializer<'de> { + value: &'de Dynamic, +} + +impl<'de> IntoDeserializer<'de, RhaiError> for &'de Dynamic { + type Deserializer = DynamicDeserializer<'de>; + + #[inline(always)] + #[must_use] + fn into_deserializer(self) -> Self::Deserializer { + DynamicDeserializer { value: self } + } } impl<'de> DynamicDeserializer<'de> { @@ -21,15 +28,20 @@ impl<'de> DynamicDeserializer<'de> { /// /// The reference is necessary because the deserialized type may hold references /// (especially `&str`) to the source [`Dynamic`][crate::Dynamic]. + #[inline(always)] #[must_use] - pub const fn from_dynamic(value: &'de Dynamic) -> Self { + pub const fn new(value: &'de Dynamic) -> Self { Self { value } } /// Shortcut for a type conversion error. + #[cold] + #[inline(always)] fn type_error(&self) -> RhaiResultOf { self.type_error_str(type_name::()) } /// Shortcut for a type conversion error. + #[cold] + #[inline(never)] fn type_error_str(&self, error: &str) -> RhaiResultOf { Err(ERR::ErrorMismatchOutputType( error.into(), @@ -38,11 +50,8 @@ impl<'de> DynamicDeserializer<'de> { ) .into()) } - fn deserialize_int>( - &mut self, - v: crate::INT, - visitor: V, - ) -> RhaiResultOf { + #[inline(always)] + fn deserialize_int>(self, v: crate::INT, visitor: V) -> RhaiResultOf { #[cfg(not(feature = "only_i32"))] return visitor.visit_i64(v); #[cfg(feature = "only_i32")] @@ -102,10 +111,12 @@ impl<'de> DynamicDeserializer<'de> { /// # } /// ``` pub fn from_dynamic<'de, T: Deserialize<'de>>(value: &'de Dynamic) -> RhaiResultOf { - T::deserialize(&mut DynamicDeserializer::from_dynamic(value)) + T::deserialize(DynamicDeserializer::new(value)) } impl Error for RhaiError { + #[cold] + #[inline(never)] fn custom(err: T) -> Self { LexError::ImproperSymbol(String::new(), err.to_string()) .into_err(Position::NONE) @@ -113,7 +124,7 @@ impl Error for RhaiError { } } -impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { +impl<'de> Deserializer<'de> for DynamicDeserializer<'de> { type Error = RhaiError; fn deserialize_any>(self, visitor: V) -> RhaiResultOf { @@ -458,7 +469,7 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { if let (Some((key, value)), None) = (first, second) { visitor.visit_enum(EnumDeserializer { tag: key, - content: DynamicDeserializer::from_dynamic(value), + content: DynamicDeserializer::new(value), }) } else { self.type_error() @@ -470,10 +481,12 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { } } + #[inline(always)] fn deserialize_identifier>(self, visitor: V) -> RhaiResultOf { self.deserialize_str(visitor) } + #[inline(always)] fn deserialize_ignored_any>(self, visitor: V) -> RhaiResultOf { self.deserialize_any(visitor) } @@ -481,13 +494,14 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { /// `SeqAccess` implementation for arrays. #[cfg(not(feature = "no_index"))] -struct IterateDynamicArray<'a, ITER: Iterator> { +struct IterateDynamicArray<'de, ITER: Iterator> { /// Iterator for a stream of [`Dynamic`][crate::Dynamic] values. iter: ITER, } #[cfg(not(feature = "no_index"))] -impl<'a, ITER: Iterator> IterateDynamicArray<'a, ITER> { +impl<'de, ITER: Iterator> IterateDynamicArray<'de, ITER> { + #[inline(always)] #[must_use] pub const fn new(iter: ITER) -> Self { Self { iter } @@ -495,8 +509,8 @@ impl<'a, ITER: Iterator> IterateDynamicArray<'a, ITER> { } #[cfg(not(feature = "no_index"))] -impl<'a: 'de, 'de, ITER: Iterator> serde::de::SeqAccess<'de> - for IterateDynamicArray<'a, ITER> +impl<'de, ITER: Iterator> serde::de::SeqAccess<'de> + for IterateDynamicArray<'de, ITER> { type Error = RhaiError; @@ -506,17 +520,15 @@ impl<'a: 'de, 'de, ITER: Iterator> serde::de::SeqAccess<'de> ) -> RhaiResultOf> { // Deserialize each item coming out of the iterator. match self.iter.next() { + Some(item) => seed.deserialize(item.into_deserializer()).map(Some), None => Ok(None), - Some(item) => seed - .deserialize(&mut DynamicDeserializer::from_dynamic(item)) - .map(Some), } } } /// `MapAccess` implementation for maps. #[cfg(not(feature = "no_object"))] -struct IterateMap<'a, K: Iterator, V: Iterator> { +struct IterateMap<'de, K: Iterator, V: Iterator> { // Iterator for a stream of [`Dynamic`][crate::Dynamic] keys. keys: K, // Iterator for a stream of [`Dynamic`][crate::Dynamic] values. @@ -524,7 +536,8 @@ struct IterateMap<'a, K: Iterator, V: Iterator, V: Iterator> IterateMap<'a, K, V> { +impl<'de, K: Iterator, V: Iterator> IterateMap<'de, K, V> { + #[inline(always)] #[must_use] pub const fn new(keys: K, values: V) -> Self { Self { keys, values } @@ -532,8 +545,8 @@ impl<'a, K: Iterator, V: Iterator> IterateMa } #[cfg(not(feature = "no_object"))] -impl<'a: 'de, 'de, K: Iterator, V: Iterator> - serde::de::MapAccess<'de> for IterateMap<'a, K, V> +impl<'de, K: Iterator, V: Iterator> serde::de::MapAccess<'de> + for IterateMap<'de, K, V> { type Error = RhaiError; @@ -542,11 +555,9 @@ impl<'a: 'de, 'de, K: Iterator, V: Iterator> seed: S, ) -> RhaiResultOf> { // Deserialize each `Identifier` key coming out of the keys iterator. - match self.keys.next() { + match self.keys.next().map(<_>::into_deserializer) { + Some(d) => seed.deserialize(d).map(Some), None => Ok(None), - Some(item) => seed - .deserialize(&mut super::str::StringSliceDeserializer::from_str(item)) - .map(Some), } } @@ -555,20 +566,18 @@ impl<'a: 'de, 'de, K: Iterator, V: Iterator> seed: S, ) -> RhaiResultOf { // Deserialize each value item coming out of the iterator. - seed.deserialize(&mut DynamicDeserializer::from_dynamic( - self.values.next().unwrap(), - )) + seed.deserialize(self.values.next().unwrap().into_deserializer()) } } #[cfg(not(feature = "no_object"))] -struct EnumDeserializer<'t, 'de: 't> { - tag: &'t str, +struct EnumDeserializer<'de> { + tag: &'de str, content: DynamicDeserializer<'de>, } #[cfg(not(feature = "no_object"))] -impl<'t, 'de> serde::de::EnumAccess<'de> for EnumDeserializer<'t, 'de> { +impl<'de> serde::de::EnumAccess<'de> for EnumDeserializer<'de> { type Error = RhaiError; type Variant = Self; @@ -582,26 +591,30 @@ impl<'t, 'de> serde::de::EnumAccess<'de> for EnumDeserializer<'t, 'de> { } #[cfg(not(feature = "no_object"))] -impl<'t, 'de> serde::de::VariantAccess<'de> for EnumDeserializer<'t, 'de> { +impl<'de> serde::de::VariantAccess<'de> for EnumDeserializer<'de> { type Error = RhaiError; - fn unit_variant(mut self) -> RhaiResultOf<()> { - Deserialize::deserialize(&mut self.content) + #[inline(always)] + fn unit_variant(self) -> RhaiResultOf<()> { + Deserialize::deserialize(self.content) } + #[inline(always)] fn newtype_variant_seed>( - mut self, + self, seed: T, ) -> RhaiResultOf { - seed.deserialize(&mut self.content) + seed.deserialize(self.content) } - fn tuple_variant>(mut self, len: usize, visitor: V) -> RhaiResultOf { + #[inline(always)] + fn tuple_variant>(self, len: usize, visitor: V) -> RhaiResultOf { self.content.deserialize_tuple(len, visitor) } + #[inline(always)] fn struct_variant>( - mut self, + self, fields: &'static [&'static str], visitor: V, ) -> RhaiResultOf { diff --git a/src/serde/deserialize.rs b/src/serde/deserialize.rs index 2f79ef6a..a41f8882 100644 --- a/src/serde/deserialize.rs +++ b/src/serde/deserialize.rs @@ -11,21 +11,28 @@ struct DynamicVisitor; impl<'d> Visitor<'d> for DynamicVisitor { type Value = Dynamic; + #[cold] + #[inline(never)] fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str("any type that can be converted into a Dynamic") } + #[inline(always)] fn visit_bool(self, v: bool) -> Result { Ok(v.into()) } + #[inline(always)] fn visit_i8(self, v: i8) -> Result { Ok(INT::from(v).into()) } + #[inline(always)] fn visit_i16(self, v: i16) -> Result { Ok(INT::from(v).into()) } + #[inline(always)] fn visit_i32(self, v: i32) -> Result { Ok(INT::from(v).into()) } + #[inline] fn visit_i64(self, v: i64) -> Result { #[cfg(not(feature = "only_i32"))] { @@ -38,12 +45,15 @@ impl<'d> Visitor<'d> for DynamicVisitor { self.visit_i32(v as i32) } } + #[inline(always)] fn visit_u8(self, v: u8) -> Result { Ok(INT::from(v).into()) } + #[inline(always)] fn visit_u16(self, v: u16) -> Result { Ok(INT::from(v).into()) } + #[inline] fn visit_u32(self, v: u32) -> Result { #[cfg(not(feature = "only_i32"))] { @@ -56,6 +66,7 @@ impl<'d> Visitor<'d> for DynamicVisitor { self.visit_i32(v as i32) } } + #[inline] fn visit_u64(self, v: u64) -> Result { #[cfg(not(feature = "only_i32"))] if v > i64::MAX as u64 { @@ -72,6 +83,7 @@ impl<'d> Visitor<'d> for DynamicVisitor { } #[cfg(not(feature = "no_float"))] + #[inline(always)] fn visit_f32(self, v: f32) -> Result { #[cfg(not(feature = "f32_float"))] return self.visit_f64(v as f64); @@ -79,6 +91,7 @@ impl<'d> Visitor<'d> for DynamicVisitor { return Ok(v.into()); } #[cfg(not(feature = "no_float"))] + #[inline(always)] fn visit_f64(self, v: f64) -> Result { #[cfg(not(feature = "f32_float"))] return Ok(v.into()); @@ -88,6 +101,7 @@ impl<'d> Visitor<'d> for DynamicVisitor { #[cfg(feature = "no_float")] #[cfg(feature = "decimal")] + #[inline] fn visit_f32(self, v: f32) -> Result { use rust_decimal::Decimal; use std::convert::TryFrom; @@ -98,6 +112,7 @@ impl<'d> Visitor<'d> for DynamicVisitor { } #[cfg(feature = "no_float")] #[cfg(feature = "decimal")] + #[inline] fn visit_f64(self, v: f64) -> Result { use rust_decimal::Decimal; use std::convert::TryFrom; @@ -107,23 +122,29 @@ impl<'d> Visitor<'d> for DynamicVisitor { .map_err(Error::custom) } + #[inline(always)] fn visit_char(self, v: char) -> Result { self.visit_string(v.to_string()) } + #[inline(always)] fn visit_str(self, v: &str) -> Result { Ok(v.into()) } + #[inline(always)] fn visit_borrowed_str(self, v: &str) -> Result { self.visit_str(v) } + #[inline(always)] fn visit_string(self, v: String) -> Result { Ok(v.into()) } + #[inline(always)] fn visit_unit(self) -> Result { Ok(Dynamic::UNIT) } + #[inline(always)] fn visit_newtype_struct>(self, de: D) -> Result { Deserialize::deserialize(de) } @@ -152,12 +173,14 @@ impl<'d> Visitor<'d> for DynamicVisitor { } impl<'d> Deserialize<'d> for Dynamic { + #[inline(always)] fn deserialize>(de: D) -> Result { de.deserialize_any(DynamicVisitor) } } impl<'d> Deserialize<'d> for ImmutableString { + #[inline] fn deserialize>(de: D) -> Result { let s: String = Deserialize::deserialize(de)?; Ok(s.into()) diff --git a/src/serde/mod.rs b/src/serde/mod.rs index 42b57e6e..4c78a939 100644 --- a/src/serde/mod.rs +++ b/src/serde/mod.rs @@ -6,7 +6,6 @@ mod deserialize; mod metadata; mod ser; mod serialize; -mod str; -pub use de::from_dynamic; -pub use ser::to_dynamic; +pub use de::{from_dynamic, DynamicDeserializer}; +pub use ser::{to_dynamic, DynamicSerializer}; diff --git a/src/serde/ser.rs b/src/serde/ser.rs index 1cdc8e28..4104fd19 100644 --- a/src/serde/ser.rs +++ b/src/serde/ser.rs @@ -1,6 +1,6 @@ //! Implement serialization support of [`Dynamic`][crate::Dynamic] for [`serde`]. -use crate::{Dynamic, Position, RhaiError, RhaiResult, RhaiResultOf, ERR}; +use crate::{Dynamic, Identifier, Position, RhaiError, RhaiResult, RhaiResultOf, ERR}; use serde::ser::{ Error, SerializeMap, SerializeSeq, SerializeStruct, SerializeTuple, SerializeTupleStruct, }; @@ -9,10 +9,10 @@ use std::fmt; #[cfg(feature = "no_std")] use std::prelude::v1::*; -/// Serializer for [`Dynamic`][crate::Dynamic] which is kept as a reference. -struct DynamicSerializer { +/// Serializer for [`Dynamic`][crate::Dynamic]. +pub struct DynamicSerializer { /// Buffer to hold a temporary key. - _key: Dynamic, + _key: Identifier, /// Buffer to hold a temporary value. _value: Dynamic, } @@ -20,10 +20,10 @@ struct DynamicSerializer { impl DynamicSerializer { /// Create a [`DynamicSerializer`] from a [`Dynamic`][crate::Dynamic] value. #[must_use] - pub const fn new(_value: Dynamic) -> Self { + pub const fn new(value: Dynamic) -> Self { Self { - _key: Dynamic::UNIT, - _value, + _key: Identifier::new_const(), + _value: value, } } } @@ -105,10 +105,12 @@ impl Serializer for &mut DynamicSerializer { #[cfg(feature = "no_object")] type SerializeStructVariant = serde::ser::Impossible; + #[inline(always)] fn serialize_bool(self, v: bool) -> RhaiResultOf { Ok(v.into()) } + #[inline(always)] fn serialize_i8(self, v: i8) -> RhaiResultOf { #[cfg(not(feature = "only_i32"))] return self.serialize_i64(i64::from(v)); @@ -116,6 +118,7 @@ impl Serializer for &mut DynamicSerializer { return self.serialize_i32(i32::from(v)); } + #[inline(always)] fn serialize_i16(self, v: i16) -> RhaiResultOf { #[cfg(not(feature = "only_i32"))] return self.serialize_i64(i64::from(v)); @@ -123,6 +126,7 @@ impl Serializer for &mut DynamicSerializer { return self.serialize_i32(i32::from(v)); } + #[inline(always)] fn serialize_i32(self, v: i32) -> RhaiResultOf { #[cfg(not(feature = "only_i32"))] return self.serialize_i64(i64::from(v)); @@ -130,6 +134,7 @@ impl Serializer for &mut DynamicSerializer { return Ok(v.into()); } + #[inline] fn serialize_i64(self, v: i64) -> RhaiResultOf { #[cfg(not(feature = "only_i32"))] { @@ -143,6 +148,7 @@ impl Serializer for &mut DynamicSerializer { } } + #[inline] fn serialize_i128(self, v: i128) -> RhaiResultOf { #[cfg(not(feature = "only_i32"))] if v > i64::MAX as i128 { @@ -158,6 +164,7 @@ impl Serializer for &mut DynamicSerializer { } } + #[inline(always)] fn serialize_u8(self, v: u8) -> RhaiResultOf { #[cfg(not(feature = "only_i32"))] return self.serialize_i64(i64::from(v)); @@ -165,6 +172,7 @@ impl Serializer for &mut DynamicSerializer { return self.serialize_i32(i32::from(v)); } + #[inline(always)] fn serialize_u16(self, v: u16) -> RhaiResultOf { #[cfg(not(feature = "only_i32"))] return self.serialize_i64(i64::from(v)); @@ -172,6 +180,7 @@ impl Serializer for &mut DynamicSerializer { return self.serialize_i32(i32::from(v)); } + #[inline] fn serialize_u32(self, v: u32) -> RhaiResultOf { #[cfg(not(feature = "only_i32"))] { @@ -185,6 +194,7 @@ impl Serializer for &mut DynamicSerializer { } } + #[inline] fn serialize_u64(self, v: u64) -> RhaiResultOf { #[cfg(not(feature = "only_i32"))] if v > i64::MAX as u64 { @@ -200,6 +210,7 @@ impl Serializer for &mut DynamicSerializer { } } + #[inline] fn serialize_u128(self, v: u128) -> RhaiResultOf { #[cfg(not(feature = "only_i32"))] if v > i64::MAX as u128 { @@ -215,6 +226,7 @@ impl Serializer for &mut DynamicSerializer { } } + #[inline] fn serialize_f32(self, v: f32) -> RhaiResultOf { #[cfg(any(not(feature = "no_float"), not(feature = "decimal")))] return Ok(Dynamic::from(v)); @@ -231,6 +243,7 @@ impl Serializer for &mut DynamicSerializer { } } + #[inline] fn serialize_f64(self, v: f64) -> RhaiResultOf { #[cfg(any(not(feature = "no_float"), not(feature = "decimal")))] return Ok(Dynamic::from(v)); @@ -247,14 +260,17 @@ impl Serializer for &mut DynamicSerializer { } } + #[inline(always)] fn serialize_char(self, v: char) -> RhaiResultOf { Ok(v.into()) } + #[inline(always)] fn serialize_str(self, v: &str) -> RhaiResultOf { Ok(v.into()) } + #[inline] fn serialize_bytes(self, _v: &[u8]) -> RhaiResultOf { #[cfg(not(feature = "no_index"))] return Ok(Dynamic::from_blob(_v.to_vec())); @@ -262,28 +278,33 @@ impl Serializer for &mut DynamicSerializer { #[cfg(feature = "no_index")] return Err(ERR::ErrorMismatchDataType( "".into(), - "BLOB's are not supported with 'no_index'".into(), + "BLOB's are not supported under 'no_index'".into(), Position::NONE, ) .into()); } + #[inline(always)] fn serialize_none(self) -> RhaiResultOf { Ok(Dynamic::UNIT) } + #[inline(always)] fn serialize_some(self, value: &T) -> RhaiResultOf { value.serialize(&mut *self) } + #[inline(always)] fn serialize_unit(self) -> RhaiResultOf { Ok(Dynamic::UNIT) } + #[inline(always)] fn serialize_unit_struct(self, _name: &'static str) -> RhaiResultOf { self.serialize_unit() } + #[inline(always)] fn serialize_unit_variant( self, _name: &'static str, @@ -293,6 +314,7 @@ impl Serializer for &mut DynamicSerializer { self.serialize_str(variant) } + #[inline(always)] fn serialize_newtype_struct( self, _name: &'static str, @@ -301,6 +323,7 @@ impl Serializer for &mut DynamicSerializer { value.serialize(&mut *self) } + #[inline] fn serialize_newtype_variant( self, _name: &'static str, @@ -316,28 +339,31 @@ impl Serializer for &mut DynamicSerializer { #[cfg(feature = "no_object")] return Err(ERR::ErrorMismatchDataType( "".into(), - "object maps are not supported with 'no_object'".into(), + "object maps are not supported under 'no_object'".into(), Position::NONE, ) .into()); } + #[inline] fn serialize_seq(self, _len: Option) -> RhaiResultOf { #[cfg(not(feature = "no_index"))] return Ok(DynamicSerializer::new(crate::Array::new().into())); #[cfg(feature = "no_index")] return Err(ERR::ErrorMismatchDataType( "".into(), - "arrays are not supported with 'no_index'".into(), + "arrays are not supported under 'no_index'".into(), Position::NONE, ) .into()); } + #[inline(always)] fn serialize_tuple(self, len: usize) -> RhaiResultOf { self.serialize_seq(Some(len)) } + #[inline(always)] fn serialize_tuple_struct( self, _name: &'static str, @@ -346,6 +372,7 @@ impl Serializer for &mut DynamicSerializer { self.serialize_seq(Some(len)) } + #[inline] fn serialize_tuple_variant( self, _name: &'static str, @@ -362,24 +389,26 @@ impl Serializer for &mut DynamicSerializer { #[cfg(any(feature = "no_object", feature = "no_index"))] return Err(ERR::ErrorMismatchDataType( "".into(), - "tuples are not supported with 'no_index' or 'no_object'".into(), + "tuples are not supported under 'no_index' or 'no_object'".into(), Position::NONE, ) .into()); } + #[inline] fn serialize_map(self, _len: Option) -> RhaiResultOf { #[cfg(not(feature = "no_object"))] return Ok(DynamicSerializer::new(crate::Map::new().into())); #[cfg(feature = "no_object")] return Err(ERR::ErrorMismatchDataType( "".into(), - "object maps are not supported with 'no_object'".into(), + "object maps are not supported under 'no_object'".into(), Position::NONE, ) .into()); } + #[inline(always)] fn serialize_struct( self, _name: &'static str, @@ -388,6 +417,7 @@ impl Serializer for &mut DynamicSerializer { self.serialize_map(Some(len)) } + #[inline] fn serialize_struct_variant( self, _name: &'static str, @@ -403,7 +433,7 @@ impl Serializer for &mut DynamicSerializer { #[cfg(feature = "no_object")] return Err(ERR::ErrorMismatchDataType( "".into(), - "object maps are not supported with 'no_object'".into(), + "object maps are not supported under 'no_object'".into(), Position::NONE, ) .into()); @@ -425,20 +455,21 @@ impl SerializeSeq for DynamicSerializer { #[cfg(feature = "no_index")] return Err(ERR::ErrorMismatchDataType( "".into(), - "arrays are not supported with 'no_index'".into(), + "arrays are not supported under 'no_index'".into(), Position::NONE, ) .into()); } // Close the sequence. + #[inline] fn end(self) -> RhaiResultOf { #[cfg(not(feature = "no_index"))] return Ok(self._value); #[cfg(feature = "no_index")] return Err(ERR::ErrorMismatchDataType( "".into(), - "arrays are not supported with 'no_index'".into(), + "arrays are not supported under 'no_index'".into(), Position::NONE, ) .into()); @@ -460,19 +491,20 @@ impl SerializeTuple for DynamicSerializer { #[cfg(feature = "no_index")] return Err(ERR::ErrorMismatchDataType( "".into(), - "tuples are not supported with 'no_index'".into(), + "tuples are not supported under 'no_index'".into(), Position::NONE, ) .into()); } + #[inline] fn end(self) -> RhaiResultOf { #[cfg(not(feature = "no_index"))] return Ok(self._value); #[cfg(feature = "no_index")] return Err(ERR::ErrorMismatchDataType( "".into(), - "tuples are not supported with 'no_index'".into(), + "tuples are not supported under 'no_index'".into(), Position::NONE, ) .into()); @@ -494,19 +526,20 @@ impl SerializeTupleStruct for DynamicSerializer { #[cfg(feature = "no_index")] return Err(ERR::ErrorMismatchDataType( "".into(), - "tuples are not supported with 'no_index'".into(), + "tuples are not supported under 'no_index'".into(), Position::NONE, ) .into()); } + #[inline] fn end(self) -> RhaiResultOf { #[cfg(not(feature = "no_index"))] return Ok(self._value); #[cfg(feature = "no_index")] return Err(ERR::ErrorMismatchDataType( "".into(), - "tuples are not supported with 'no_index'".into(), + "tuples are not supported under 'no_index'".into(), Position::NONE, ) .into()); @@ -520,13 +553,19 @@ impl SerializeMap for DynamicSerializer { fn serialize_key(&mut self, _key: &T) -> RhaiResultOf<()> { #[cfg(not(feature = "no_object"))] { - self._key = _key.serialize(&mut *self)?; + let key = _key.serialize(&mut *self)?; + self._key = key + .into_immutable_string() + .map_err(|typ| { + ERR::ErrorMismatchDataType("string".into(), typ.into(), Position::NONE) + })? + .into(); Ok(()) } #[cfg(feature = "no_object")] return Err(ERR::ErrorMismatchDataType( "".into(), - "object maps are not supported with 'no_object'".into(), + "object maps are not supported under 'no_object'".into(), Position::NONE, ) .into()); @@ -535,20 +574,16 @@ impl SerializeMap for DynamicSerializer { fn serialize_value(&mut self, _value: &T) -> RhaiResultOf<()> { #[cfg(not(feature = "no_object"))] { - let key = std::mem::take(&mut self._key) - .into_immutable_string() - .map_err(|typ| { - ERR::ErrorMismatchDataType("string".into(), typ.into(), Position::NONE) - })?; + let key = std::mem::take(&mut self._key); let value = _value.serialize(&mut *self)?; let map = self._value.downcast_mut::().unwrap(); - map.insert(key.into(), value); + map.insert(key, value); Ok(()) } #[cfg(feature = "no_object")] return Err(ERR::ErrorMismatchDataType( "".into(), - "object maps are not supported with 'no_object'".into(), + "object maps are not supported under 'no_object'".into(), Position::NONE, ) .into()); @@ -573,19 +608,20 @@ impl SerializeMap for DynamicSerializer { #[cfg(feature = "no_object")] return Err(ERR::ErrorMismatchDataType( "".into(), - "object maps are not supported with 'no_object'".into(), + "object maps are not supported under 'no_object'".into(), Position::NONE, ) .into()); } + #[inline] fn end(self) -> RhaiResultOf { #[cfg(not(feature = "no_object"))] return Ok(self._value); #[cfg(feature = "no_object")] return Err(ERR::ErrorMismatchDataType( "".into(), - "object maps are not supported with 'no_object'".into(), + "object maps are not supported under 'no_object'".into(), Position::NONE, ) .into()); @@ -611,19 +647,20 @@ impl SerializeStruct for DynamicSerializer { #[cfg(feature = "no_object")] return Err(ERR::ErrorMismatchDataType( "".into(), - "object maps are not supported with 'no_object'".into(), + "object maps are not supported under 'no_object'".into(), Position::NONE, ) .into()); } + #[inline] fn end(self) -> RhaiResultOf { #[cfg(not(feature = "no_object"))] return Ok(self._value); #[cfg(feature = "no_object")] return Err(ERR::ErrorMismatchDataType( "".into(), - "object maps are not supported with 'no_object'".into(), + "object maps are not supported under 'no_object'".into(), Position::NONE, ) .into()); @@ -632,7 +669,7 @@ impl SerializeStruct for DynamicSerializer { #[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_index"))] -struct TupleVariantSerializer { +pub struct TupleVariantSerializer { variant: &'static str, array: crate::Array, } @@ -649,13 +686,14 @@ impl serde::ser::SerializeTupleVariant for TupleVariantSerializer { Ok(()) } + #[inline] fn end(self) -> RhaiResultOf { make_variant(self.variant, self.array.into()) } } #[cfg(not(feature = "no_object"))] -struct StructVariantSerializer { +pub struct StructVariantSerializer { variant: &'static str, map: crate::Map, } @@ -665,6 +703,7 @@ impl serde::ser::SerializeStructVariant for StructVariantSerializer { type Ok = Dynamic; type Error = RhaiError; + #[inline] fn serialize_field( &mut self, key: &'static str, @@ -675,12 +714,14 @@ impl serde::ser::SerializeStructVariant for StructVariantSerializer { Ok(()) } + #[inline] fn end(self) -> RhaiResultOf { make_variant(self.variant, self.map.into()) } } #[cfg(not(feature = "no_object"))] +#[inline] fn make_variant(variant: &'static str, value: Dynamic) -> RhaiResult { let mut map = crate::Map::new(); map.insert(variant.into(), value); diff --git a/src/serde/serialize.rs b/src/serde/serialize.rs index 579f0d1e..4aaa797e 100644 --- a/src/serde/serialize.rs +++ b/src/serde/serialize.rs @@ -83,6 +83,7 @@ impl Serialize for Dynamic { } impl Serialize for ImmutableString { + #[inline(always)] fn serialize(&self, ser: S) -> Result { ser.serialize_str(self.as_str()) } diff --git a/src/serde/str.rs b/src/serde/str.rs deleted file mode 100644 index df747a42..00000000 --- a/src/serde/str.rs +++ /dev/null @@ -1,143 +0,0 @@ -//! Implement deserialization support of [`ImmutableString`][crate::ImmutableString] for [`serde`]. - -use crate::{Position, RhaiError, RhaiResultOf, ERR}; -use serde::de::{Deserializer, Visitor}; -use std::any::type_name; -#[cfg(feature = "no_std")] -use std::prelude::v1::*; - -/// Deserializer for `ImmutableString`. -pub struct StringSliceDeserializer<'a> { - value: &'a str, -} - -impl<'a> StringSliceDeserializer<'a> { - /// Create an `ImmutableStringDeserializer` from an `&str` reference. - #[must_use] - pub const fn from_str(value: &'a str) -> Self { - Self { value } - } - /// Shortcut for a type conversion error. - fn type_error(&self) -> RhaiResultOf { - Err( - ERR::ErrorMismatchOutputType(type_name::().into(), "string".into(), Position::NONE) - .into(), - ) - } -} - -impl<'de> Deserializer<'de> for &mut StringSliceDeserializer<'de> { - type Error = RhaiError; - - fn deserialize_any>(self, v: V) -> RhaiResultOf { - self.deserialize_str(v) - } - fn deserialize_bool>(self, _: V) -> RhaiResultOf { - self.type_error() - } - fn deserialize_i8>(self, _: V) -> RhaiResultOf { - self.type_error() - } - fn deserialize_i16>(self, _: V) -> RhaiResultOf { - self.type_error() - } - fn deserialize_i32>(self, _: V) -> RhaiResultOf { - self.type_error() - } - fn deserialize_i64>(self, _: V) -> RhaiResultOf { - self.type_error() - } - fn deserialize_u8>(self, _: V) -> RhaiResultOf { - self.type_error() - } - fn deserialize_u16>(self, _: V) -> RhaiResultOf { - self.type_error() - } - fn deserialize_u32>(self, _: V) -> RhaiResultOf { - self.type_error() - } - fn deserialize_u64>(self, _: V) -> RhaiResultOf { - self.type_error() - } - fn deserialize_f32>(self, _: V) -> RhaiResultOf { - self.type_error() - } - fn deserialize_f64>(self, _: V) -> RhaiResultOf { - self.type_error() - } - fn deserialize_char>(self, _: V) -> RhaiResultOf { - self.type_error() - } - fn deserialize_str>(self, v: V) -> RhaiResultOf { - // Only allow deserialization into a string. - v.visit_borrowed_str(self.value) - } - fn deserialize_string>(self, visitor: V) -> RhaiResultOf { - self.deserialize_str(visitor) - } - fn deserialize_bytes>(self, _: V) -> RhaiResultOf { - self.type_error() - } - fn deserialize_byte_buf>(self, _: V) -> RhaiResultOf { - self.type_error() - } - fn deserialize_option>(self, _: V) -> RhaiResultOf { - self.type_error() - } - fn deserialize_unit>(self, _: V) -> RhaiResultOf { - self.type_error() - } - fn deserialize_unit_struct>( - self, - _name: &'static str, - v: V, - ) -> RhaiResultOf { - self.deserialize_unit(v) - } - fn deserialize_newtype_struct>( - self, - _name: &'static str, - v: V, - ) -> RhaiResultOf { - v.visit_newtype_struct(self) - } - fn deserialize_seq>(self, _: V) -> RhaiResultOf { - self.type_error() - } - fn deserialize_tuple>(self, _len: usize, v: V) -> RhaiResultOf { - self.deserialize_seq(v) - } - fn deserialize_tuple_struct>( - self, - _name: &'static str, - _len: usize, - v: V, - ) -> RhaiResultOf { - self.deserialize_seq(v) - } - fn deserialize_map>(self, _: V) -> RhaiResultOf { - self.type_error() - } - fn deserialize_struct>( - self, - _name: &'static str, - _fields: &'static [&'static str], - v: V, - ) -> RhaiResultOf { - self.deserialize_map(v) - } - fn deserialize_enum>( - self, - _name: &'static str, - _variants: &'static [&'static str], - _: V, - ) -> RhaiResultOf { - self.type_error() - } - fn deserialize_identifier>(self, v: V) -> RhaiResultOf { - self.deserialize_str(v) - } - fn deserialize_ignored_any>(self, v: V) -> RhaiResultOf { - self.deserialize_any(v) - } -} From ece522ce2f57514fb78ed580a178c05c6efad727 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 25 Sep 2022 18:22:48 +0800 Subject: [PATCH 05/10] Refactor fast operators. --- src/eval/expr.rs | 96 +++++++---------------------------- src/eval/stmt.rs | 26 +++++++--- src/func/callable_function.rs | 2 +- src/optimizer.rs | 4 +- 4 files changed, 39 insertions(+), 89 deletions(-) diff --git a/src/eval/expr.rs b/src/eval/expr.rs index af762e20..ff3abaff 100644 --- a/src/eval/expr.rs +++ b/src/eval/expr.rs @@ -3,17 +3,9 @@ use super::{Caches, EvalContext, GlobalRuntimeState, Target}; use crate::ast::{Expr, FnCallExpr, OpAssignment}; use crate::engine::{KEYWORD_THIS, OP_CONCAT}; -use crate::eval::FnResolutionCacheEntry; -use crate::func::{ - calc_fn_params_hash, combine_hashes, gen_fn_call_signature, get_builtin_binary_op_fn, - CallableFunction, -}; +use crate::func::get_builtin_binary_op_fn; use crate::types::dynamic::AccessMode; use crate::{Dynamic, Engine, Module, Position, RhaiResult, RhaiResultOf, Scope, ERR}; -#[cfg(feature = "no_std")] -use hashbrown::hash_map::Entry; -#[cfg(not(feature = "no_std"))] -use std::collections::hash_map::Entry; use std::num::NonZeroUsize; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -231,83 +223,31 @@ impl Engine { } = expr; // Short-circuit native binary operator call if under Fast Operators mode - if expr.is_native_operator && self.fast_operators() && (args.len() == 1 || args.len() == 2) - { + if expr.is_native_operator && self.fast_operators() && args.len() == 2 { let mut lhs = self .get_arg_value(scope, global, caches, lib, this_ptr, &args[0], level)? .0 .flatten(); - let mut rhs = if args.len() == 2 { - self.get_arg_value(scope, global, caches, lib, this_ptr, &args[1], level)? - .0 - .flatten() - } else { - Dynamic::UNIT - }; + let mut rhs = self + .get_arg_value(scope, global, caches, lib, this_ptr, &args[1], level)? + .0 + .flatten(); - let mut operands = [&mut lhs, &mut rhs]; - let operands = if args.len() == 2 { - &mut operands[..] - } else { - &mut operands[0..1] - }; + let operands = &mut [&mut lhs, &mut rhs]; - let hash = calc_fn_params_hash(operands.iter().map(|a| a.type_id())); - let hash = combine_hashes(hashes.native, hash); + if let Some(func) = get_builtin_binary_op_fn(name, operands[0], operands[1]) { + // Built-in found + let context = (self, name, None, &*global, lib, pos, level + 1).into(); + let result = func(context, operands); + return self.check_return_value(result, pos); + } - let cache = caches.fn_resolution_cache_mut(); - let local_entry: CallableFunction; - - let func = match cache.map.entry(hash) { - Entry::Vacant(entry) => { - let func = if args.len() == 2 { - get_builtin_binary_op_fn(name, operands[0], operands[1]) - } else { - None - }; - - if let Some(f) = func { - if cache.filter.is_absent_and_set(hash) { - // Do not cache "one-hit wonders" - local_entry = CallableFunction::from_fn_builtin(f); - &local_entry - } else { - // Cache repeated calls - &entry - .insert(Some(FnResolutionCacheEntry { - func: CallableFunction::from_fn_builtin(f), - source: None, - })) - .as_ref() - .unwrap() - .func - } - } else { - let result = self.exec_fn_call( - None, global, caches, lib, name, *hashes, operands, false, false, pos, - level, - ); - return result.map(|(v, ..)| v); - } - } - Entry::Occupied(entry) => { - if let Some(entry) = entry.into_mut() { - &entry.func - } else { - let sig = gen_fn_call_signature(self, name, operands); - return Err(ERR::ErrorFunctionNotFound(sig, pos).into()); - } - } - }; - - let context = (self, name, None, &*global, lib, pos, level).into(); - let result = if func.is_plugin_fn() { - func.get_plugin_fn().unwrap().call(context, operands) - } else { - func.get_native_fn().unwrap()(context, operands) - }; - return self.check_return_value(result, pos); + return self + .exec_fn_call( + None, global, caches, lib, name, *hashes, operands, false, false, pos, level, + ) + .map(|(v, ..)| v); } #[cfg(not(feature = "no_module"))] diff --git a/src/eval/stmt.rs b/src/eval/stmt.rs index 60587b4f..bfb79908 100644 --- a/src/eval/stmt.rs +++ b/src/eval/stmt.rs @@ -5,7 +5,7 @@ use crate::api::events::VarDefInfo; use crate::ast::{ ASTFlags, BinaryExpr, Expr, Ident, OpAssignment, Stmt, SwitchCasesCollection, TryCatchBlock, }; -use crate::func::get_hasher; +use crate::func::{get_builtin_op_assignment_fn, get_hasher}; use crate::types::dynamic::{AccessMode, Union}; use crate::{ Dynamic, Engine, ImmutableString, Module, Position, RhaiResult, RhaiResultOf, Scope, ERR, INT, @@ -145,6 +145,19 @@ impl Engine { let args = &mut [&mut *lock_guard, &mut new_val]; let level = level + 1; + if self.fast_operators() { + if let Some(func) = get_builtin_op_assignment_fn(op_assign, args[0], args[1]) { + // Built-in found + let context = (self, op_assign, None, &*global, lib, op_pos, level).into(); + let result = func(context, args).map(|_| ()); + + #[cfg(not(feature = "unchecked"))] + self.check_data_size(args[0], root.1)?; + + return result; + } + } + match self.call_native_fn( global, caches, lib, op_assign, hash, args, true, true, op_pos, level, ) { @@ -155,16 +168,13 @@ impl Engine { Err(err) if matches!(*err, ERR::ErrorFunctionNotFound(ref f, ..) if f.starts_with(op_assign)) => { // Expand to `var = var op rhs` - let (value, ..) = self + *args[0] = self .call_native_fn( global, caches, lib, op, hash_op, args, true, false, op_pos, level, ) - .map_err(|err| err.fill_position(op_info.pos))?; - - #[cfg(not(feature = "unchecked"))] - self.check_data_size(&value, root.1)?; - - *args[0] = value.flatten(); + .map_err(|err| err.fill_position(op_info.pos))? + .0 + .flatten(); } Err(err) => return Err(err), } diff --git a/src/func/callable_function.rs b/src/func/callable_function.rs index c08eb845..babf1ebf 100644 --- a/src/func/callable_function.rs +++ b/src/func/callable_function.rs @@ -197,7 +197,7 @@ impl CallableFunction { Self::Script(..) => None, } } - /// Create a new [`CallableFunction::Method`] from `FnBuiltin`. + /// Create a new [`CallableFunction::Method`] from a [built-in function][`FnBuiltin`]. #[inline(always)] #[must_use] pub fn from_fn_builtin(func: FnBuiltin) -> Self { diff --git a/src/optimizer.rs b/src/optimizer.rs index b9a4dd97..216e4f3c 100644 --- a/src/optimizer.rs +++ b/src/optimizer.rs @@ -1229,12 +1229,12 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) { => { // First search for script-defined functions (can override built-in) #[cfg(not(feature = "no_function"))] - let has_script_fn = state.lib.iter().copied().any(|m| m.get_script_fn(&x.name, x.args.len()).is_some()); + let has_script_fn = state.lib.iter().find_map(|&m| m.get_script_fn(&x.name, x.args.len())).is_some(); #[cfg(feature = "no_function")] let has_script_fn = false; if !has_script_fn { - let arg_values = &mut x.args.iter().map(|e| e.get_literal_value().unwrap()).collect::>(); + let arg_values = &mut x.args.iter().map(Expr::get_literal_value).collect::>>().unwrap(); let result = match x.name.as_str() { KEYWORD_TYPE_OF if arg_values.len() == 1 => Some(state.engine.map_type_name(arg_values[0].type_name()).into()), From bf02d040e2c6971291498f9fdbf2606d7c8f1ecb Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 25 Sep 2022 23:03:18 +0800 Subject: [PATCH 06/10] Use tokens to speed up function name lookup. --- src/ast/expr.rs | 8 +- src/ast/stmt.rs | 17 +- src/eval/chaining.rs | 4 +- src/eval/expr.rs | 20 +- src/eval/stmt.rs | 18 +- src/func/builtin.rs | 413 +++++++++++++++++------------------- src/func/call.rs | 31 +-- src/optimizer.rs | 4 +- src/packages/blob_basic.rs | 15 ++ src/packages/iter_basic.rs | 11 + src/packages/map_basic.rs | 14 ++ src/packages/string_more.rs | 31 +++ src/parser.rs | 65 +++--- src/tests.rs | 10 +- src/tokenizer.rs | 105 ++++----- 15 files changed, 417 insertions(+), 349 deletions(-) diff --git a/src/ast/expr.rs b/src/ast/expr.rs index 389ac137..e29745a7 100644 --- a/src/ast/expr.rs +++ b/src/ast/expr.rs @@ -193,7 +193,7 @@ pub struct FnCallExpr { /// Does this function call capture the parent scope? pub capture_parent_scope: bool, /// Is this function call a native operator? - pub is_native_operator: bool, + pub operator_token: Option, /// [Position] of the function name. pub pos: Position, } @@ -208,8 +208,8 @@ impl fmt::Debug for FnCallExpr { if self.capture_parent_scope { ff.field("capture_parent_scope", &self.capture_parent_scope); } - if self.is_native_operator { - ff.field("is_native_operator", &self.is_native_operator); + if let Some(ref token) = self.operator_token { + ff.field("operator_token", token); } ff.field("hash", &self.hashes) .field("name", &self.name) @@ -673,7 +673,7 @@ impl Expr { hashes: calc_fn_hash(None, f.fn_name(), 1).into(), args: once(Self::StringConstant(f.fn_name().into(), pos)).collect(), capture_parent_scope: false, - is_native_operator: false, + operator_token: None, pos, } .into(), diff --git a/src/ast/stmt.rs b/src/ast/stmt.rs index 3b830100..3bbb0177 100644 --- a/src/ast/stmt.rs +++ b/src/ast/stmt.rs @@ -19,16 +19,16 @@ use std::{ /// Exported under the `internals` feature only. /// /// This type may hold a straight assignment (i.e. not an op-assignment). -#[derive(Clone, Copy, Eq, PartialEq, Hash)] +#[derive(Clone, PartialEq, Hash)] pub struct OpAssignment { /// Hash of the op-assignment call. pub hash_op_assign: u64, /// Hash of the underlying operator call (for fallback). pub hash_op: u64, /// Op-assignment operator. - pub op_assign: &'static str, + pub op_assign: Token, /// Underlying operator. - pub op: &'static str, + pub op: Token, /// [Position] of the op-assignment operator. pub pos: Position, } @@ -41,8 +41,8 @@ impl OpAssignment { Self { hash_op_assign: 0, hash_op: 0, - op_assign: "=", - op: "=", + op_assign: Token::Equals, + op: Token::Equals, pos, } } @@ -71,12 +71,11 @@ impl OpAssignment { pub fn new_op_assignment_from_token(op: &Token, pos: Position) -> Self { let op_raw = op .get_base_op_from_assignment() - .expect("op-assignment operator") - .literal_syntax(); + .expect("op-assignment operator"); Self { hash_op_assign: calc_fn_hash(None, op.literal_syntax(), 2), - hash_op: calc_fn_hash(None, op_raw, 2), - op_assign: op.literal_syntax(), + hash_op: calc_fn_hash(None, op_raw.literal_syntax(), 2), + op_assign: op.clone(), op: op_raw, pos, } diff --git a/src/eval/chaining.rs b/src/eval/chaining.rs index 0e6869f9..abdd0573 100644 --- a/src/eval/chaining.rs +++ b/src/eval/chaining.rs @@ -50,7 +50,7 @@ impl Engine { idx_values: &mut FnArgsVec, chain_type: ChainType, level: usize, - new_val: Option<(Dynamic, OpAssignment)>, + new_val: Option<(Dynamic, &OpAssignment)>, ) -> RhaiResultOf<(Dynamic, bool)> { let is_ref_mut = target.is_ref(); @@ -558,7 +558,7 @@ impl Engine { this_ptr: &mut Option<&mut Dynamic>, expr: &Expr, level: usize, - new_val: Option<(Dynamic, OpAssignment)>, + new_val: Option<(Dynamic, &OpAssignment)>, ) -> RhaiResult { let chain_type = ChainType::from(expr); let (crate::ast::BinaryExpr { lhs, rhs }, options, op_pos) = match expr { diff --git a/src/eval/expr.rs b/src/eval/expr.rs index ff3abaff..cdbeb95e 100644 --- a/src/eval/expr.rs +++ b/src/eval/expr.rs @@ -219,11 +219,15 @@ impl Engine { level: usize, ) -> RhaiResult { let FnCallExpr { - name, hashes, args, .. + name, + hashes, + args, + operator_token, + .. } = expr; // Short-circuit native binary operator call if under Fast Operators mode - if expr.is_native_operator && self.fast_operators() && args.len() == 2 { + if operator_token.is_some() && self.fast_operators() && args.len() == 2 { let mut lhs = self .get_arg_value(scope, global, caches, lib, this_ptr, &args[0], level)? .0 @@ -236,7 +240,9 @@ impl Engine { let operands = &mut [&mut lhs, &mut rhs]; - if let Some(func) = get_builtin_binary_op_fn(name, operands[0], operands[1]) { + if let Some(func) = + get_builtin_binary_op_fn(operator_token.as_ref().unwrap(), operands[0], operands[1]) + { // Built-in found let context = (self, name, None, &*global, lib, pos, level + 1).into(); let result = func(context, operands); @@ -278,7 +284,7 @@ impl Engine { args, *hashes, expr.capture_parent_scope, - expr.is_native_operator, + expr.operator_token.as_ref(), pos, level, ) @@ -384,9 +390,9 @@ impl Engine { op_info.pos = expr.start_position(); - if let Err(err) = self - .eval_op_assignment(global, caches, lib, op_info, target, root, item, level) - { + if let Err(err) = self.eval_op_assignment( + global, caches, lib, &op_info, target, root, item, level, + ) { result = Err(err); break; } diff --git a/src/eval/stmt.rs b/src/eval/stmt.rs index bfb79908..58f7ce47 100644 --- a/src/eval/stmt.rs +++ b/src/eval/stmt.rs @@ -117,7 +117,7 @@ impl Engine { global: &mut GlobalRuntimeState, caches: &mut Caches, lib: &[&Module], - op_info: OpAssignment, + op_info: &OpAssignment, target: &mut Target, root: (&str, Position), new_val: Dynamic, @@ -141,14 +141,15 @@ impl Engine { let mut lock_guard = target.write_lock::().unwrap(); - let hash = hash_op_assign; + let hash = *hash_op_assign; let args = &mut [&mut *lock_guard, &mut new_val]; let level = level + 1; if self.fast_operators() { if let Some(func) = get_builtin_op_assignment_fn(op_assign, args[0], args[1]) { // Built-in found - let context = (self, op_assign, None, &*global, lib, op_pos, level).into(); + let op = op_assign.literal_syntax(); + let context = (self, op, None, &*global, lib, *op_pos, level).into(); let result = func(context, args).map(|_| ()); #[cfg(not(feature = "unchecked"))] @@ -158,8 +159,11 @@ impl Engine { } } + let op_assign = op_assign.literal_syntax(); + let op = op.literal_syntax(); + match self.call_native_fn( - global, caches, lib, op_assign, hash, args, true, true, op_pos, level, + global, caches, lib, op_assign, hash, args, true, true, *op_pos, level, ) { Ok(_) => { #[cfg(not(feature = "unchecked"))] @@ -170,7 +174,7 @@ impl Engine { // Expand to `var = var op rhs` *args[0] = self .call_native_fn( - global, caches, lib, op, hash_op, args, true, false, op_pos, level, + global, caches, lib, op, *hash_op, args, true, false, *op_pos, level, ) .map_err(|err| err.fill_position(op_info.pos))? .0 @@ -279,7 +283,7 @@ impl Engine { let lhs_ptr = &mut lhs_ptr; self.eval_op_assignment( - global, caches, lib, *op_info, lhs_ptr, root, rhs_val, level, + global, caches, lib, op_info, lhs_ptr, root, rhs_val, level, ) .map(|_| Dynamic::UNIT) } else { @@ -303,7 +307,7 @@ impl Engine { rhs_val }; - let _new_val = Some((rhs_val, *op_info)); + let _new_val = Some((rhs_val, op_info)); // Must be either `var[index] op= val` or `var.prop op= val` match lhs { diff --git a/src/func/builtin.rs b/src/func/builtin.rs index 4ad42130..9052a5fe 100644 --- a/src/func/builtin.rs +++ b/src/func/builtin.rs @@ -2,7 +2,7 @@ use super::call::FnCallArgs; use super::native::FnBuiltin; -use crate::engine::OP_CONTAINS; +use crate::tokenizer::Token; use crate::{Dynamic, ExclusiveRange, ImmutableString, InclusiveRange, INT}; use std::any::TypeId; #[cfg(feature = "no_std")] @@ -67,7 +67,7 @@ fn is_numeric(type_id: TypeId) -> bool { /// /// The return function will be registered as a _method_, so the first parameter cannot be consumed. #[must_use] -pub fn get_builtin_binary_op_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Option { +pub fn get_builtin_binary_op_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Option { let type1 = x.type_id(); let type2 = y.type_id(); @@ -131,46 +131,46 @@ pub fn get_builtin_binary_op_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Option return Some(impl_op!(INT => add(as_int, as_int))), - "-" => return Some(impl_op!(INT => subtract(as_int, as_int))), - "*" => return Some(impl_op!(INT => multiply(as_int, as_int))), - "/" => return Some(impl_op!(INT => divide(as_int, as_int))), - "%" => return Some(impl_op!(INT => modulo(as_int, as_int))), - "**" => return Some(impl_op!(INT => power(as_int, as_int))), - ">>" => return Some(impl_op!(INT => shift_right(as_int, as_int))), - "<<" => return Some(impl_op!(INT => shift_left(as_int, as_int))), + Token::Plus => return Some(impl_op!(INT => add(as_int, as_int))), + Token::Minus => return Some(impl_op!(INT => subtract(as_int, as_int))), + Token::Multiply => return Some(impl_op!(INT => multiply(as_int, as_int))), + Token::Divide => return Some(impl_op!(INT => divide(as_int, as_int))), + Token::Modulo => return Some(impl_op!(INT => modulo(as_int, as_int))), + Token::PowerOf => return Some(impl_op!(INT => power(as_int, as_int))), + Token::RightShift => return Some(impl_op!(INT => shift_right(as_int, as_int))), + Token::LeftShift => return Some(impl_op!(INT => shift_left(as_int, as_int))), _ => (), } #[cfg(feature = "unchecked")] match op { - "+" => return Some(impl_op!(INT => as_int + as_int)), - "-" => return Some(impl_op!(INT => as_int - as_int)), - "*" => return Some(impl_op!(INT => as_int * as_int)), - "/" => return Some(impl_op!(INT => as_int / as_int)), - "%" => return Some(impl_op!(INT => as_int % as_int)), - "**" => return Some(impl_op!(INT => as_int.pow(as_int as u32))), - ">>" => return Some(impl_op!(INT => as_int >> as_int)), - "<<" => return Some(impl_op!(INT => as_int << as_int)), + Token::Plus => return Some(impl_op!(INT => as_int + as_int)), + Token::Minus => return Some(impl_op!(INT => as_int - as_int)), + Token::Multiply => return Some(impl_op!(INT => as_int * as_int)), + Token::Divide => return Some(impl_op!(INT => as_int / as_int)), + Token::Modulo => return Some(impl_op!(INT => as_int % as_int)), + Token::PowerOf => return Some(impl_op!(INT => as_int.pow(as_int as u32))), + Token::RightShift => return Some(impl_op!(INT => as_int >> as_int)), + Token::LeftShift => return Some(impl_op!(INT => as_int << as_int)), _ => (), } return match op { - "==" => Some(impl_op!(INT => as_int == as_int)), - "!=" => Some(impl_op!(INT => as_int != as_int)), - ">" => Some(impl_op!(INT => as_int > as_int)), - ">=" => Some(impl_op!(INT => as_int >= as_int)), - "<" => Some(impl_op!(INT => as_int < as_int)), - "<=" => Some(impl_op!(INT => as_int <= as_int)), - "&" => Some(impl_op!(INT => as_int & as_int)), - "|" => Some(impl_op!(INT => as_int | as_int)), - "^" => Some(impl_op!(INT => as_int ^ as_int)), - ".." => Some(|_, args| { + Token::EqualsTo => Some(impl_op!(INT => as_int == as_int)), + Token::NotEqualsTo => Some(impl_op!(INT => as_int != as_int)), + Token::GreaterThan => Some(impl_op!(INT => as_int > as_int)), + Token::GreaterThanEqualsTo => Some(impl_op!(INT => as_int >= as_int)), + Token::LessThan => Some(impl_op!(INT => as_int < as_int)), + Token::LessThanEqualsTo => Some(impl_op!(INT => as_int <= as_int)), + Token::Ampersand => Some(impl_op!(INT => as_int & as_int)), + Token::Pipe => Some(impl_op!(INT => as_int | as_int)), + Token::XOr => Some(impl_op!(INT => as_int ^ as_int)), + Token::ExclusiveRange => Some(|_, args| { let x = args[0].as_int().expect(BUILTIN); let y = args[1].as_int().expect(BUILTIN); Ok((x..y).into()) }), - "..=" => Some(|_, args| { + Token::InclusiveRange => Some(|_, args| { let x = args[0].as_int().expect(BUILTIN); let y = args[1].as_int().expect(BUILTIN); Ok((x..=y).into()) @@ -181,47 +181,46 @@ pub fn get_builtin_binary_op_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Option() { return match op { - "==" => Some(impl_op!(bool => as_bool == as_bool)), - "!=" => Some(impl_op!(bool => as_bool != as_bool)), - ">" => Some(impl_op!(bool => as_bool > as_bool)), - ">=" => Some(impl_op!(bool => as_bool >= as_bool)), - "<" => Some(impl_op!(bool => as_bool < as_bool)), - "<=" => Some(impl_op!(bool => as_bool <= as_bool)), - "&" => Some(impl_op!(bool => as_bool & as_bool)), - "|" => Some(impl_op!(bool => as_bool | as_bool)), - "^" => Some(impl_op!(bool => as_bool ^ as_bool)), + Token::EqualsTo => Some(impl_op!(bool => as_bool == as_bool)), + Token::NotEqualsTo => Some(impl_op!(bool => as_bool != as_bool)), + Token::GreaterThan => Some(impl_op!(bool => as_bool > as_bool)), + Token::GreaterThanEqualsTo => Some(impl_op!(bool => as_bool >= as_bool)), + Token::LessThan => Some(impl_op!(bool => as_bool < as_bool)), + Token::LessThanEqualsTo => Some(impl_op!(bool => as_bool <= as_bool)), + Token::Ampersand => Some(impl_op!(bool => as_bool & as_bool)), + Token::Pipe => Some(impl_op!(bool => as_bool | as_bool)), + Token::XOr => Some(impl_op!(bool => as_bool ^ as_bool)), _ => None, }; } if type1 == TypeId::of::() { return match op { - "+" => Some(impl_op!(ImmutableString + ImmutableString)), - "-" => Some(impl_op!(ImmutableString - ImmutableString)), - "==" => Some(impl_op!(ImmutableString == ImmutableString)), - "!=" => Some(impl_op!(ImmutableString != ImmutableString)), - ">" => Some(impl_op!(ImmutableString > ImmutableString)), - ">=" => Some(impl_op!(ImmutableString >= ImmutableString)), - "<" => Some(impl_op!(ImmutableString < ImmutableString)), - "<=" => Some(impl_op!(ImmutableString <= ImmutableString)), - OP_CONTAINS => Some(impl_op!(ImmutableString.contains(ImmutableString.as_str()))), + Token::Plus => Some(impl_op!(ImmutableString + ImmutableString)), + Token::Minus => Some(impl_op!(ImmutableString - ImmutableString)), + Token::EqualsTo => Some(impl_op!(ImmutableString == ImmutableString)), + Token::NotEqualsTo => Some(impl_op!(ImmutableString != ImmutableString)), + Token::GreaterThan => Some(impl_op!(ImmutableString > ImmutableString)), + Token::GreaterThanEqualsTo => Some(impl_op!(ImmutableString >= ImmutableString)), + Token::LessThan => Some(impl_op!(ImmutableString < ImmutableString)), + Token::LessThanEqualsTo => Some(impl_op!(ImmutableString <= ImmutableString)), _ => None, }; } if type1 == TypeId::of::() { return match op { - "+" => Some(|_, args| { + Token::Plus => Some(|_, args| { let x = args[0].as_char().expect(BUILTIN); let y = args[1].as_char().expect(BUILTIN); Ok(format!("{x}{y}").into()) }), - "==" => Some(impl_op!(char => as_char == as_char)), - "!=" => Some(impl_op!(char => as_char != as_char)), - ">" => Some(impl_op!(char => as_char > as_char)), - ">=" => Some(impl_op!(char => as_char >= as_char)), - "<" => Some(impl_op!(char => as_char < as_char)), - "<=" => Some(impl_op!(char => as_char <= as_char)), + Token::EqualsTo => Some(impl_op!(char => as_char == as_char)), + Token::NotEqualsTo => Some(impl_op!(char => as_char != as_char)), + Token::GreaterThan => Some(impl_op!(char => as_char > as_char)), + Token::GreaterThanEqualsTo => Some(impl_op!(char => as_char >= as_char)), + Token::LessThan => Some(impl_op!(char => as_char < as_char)), + Token::LessThanEqualsTo => Some(impl_op!(char => as_char <= as_char)), _ => None, }; } @@ -231,7 +230,7 @@ pub fn get_builtin_binary_op_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Option Some(|_, args| { + Token::Plus => Some(|_, args| { let blob1 = &*args[0].read_lock::().expect(BUILTIN); let blob2 = &*args[1].read_lock::().expect(BUILTIN); @@ -245,16 +244,20 @@ pub fn get_builtin_binary_op_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Option Some(impl_op!(Blob == Blob)), - "!=" => Some(impl_op!(Blob != Blob)), + Token::EqualsTo => Some(impl_op!(Blob == Blob)), + Token::NotEqualsTo => Some(impl_op!(Blob != Blob)), _ => None, }; } if type1 == TypeId::of::<()>() { return match op { - "==" => Some(|_, _| Ok(Dynamic::TRUE)), - "!=" | ">" | ">=" | "<" | "<=" => Some(|_, _| Ok(Dynamic::FALSE)), + Token::EqualsTo => Some(|_, _| Ok(Dynamic::TRUE)), + Token::NotEqualsTo + | Token::GreaterThan + | Token::GreaterThanEqualsTo + | Token::LessThan + | Token::LessThanEqualsTo => Some(|_, _| Ok(Dynamic::FALSE)), _ => None, }; } @@ -265,18 +268,18 @@ pub fn get_builtin_binary_op_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Option { if (type1, type2) == (TypeId::of::<$x>(), TypeId::of::<$y>()) { return match op { - "+" => Some(impl_op!(FLOAT => $xx + $yy)), - "-" => Some(impl_op!(FLOAT => $xx - $yy)), - "*" => Some(impl_op!(FLOAT => $xx * $yy)), - "/" => Some(impl_op!(FLOAT => $xx / $yy)), - "%" => Some(impl_op!(FLOAT => $xx % $yy)), - "**" => Some(impl_op!(FLOAT => $xx.powf($yy as FLOAT))), - "==" => Some(impl_op!(FLOAT => $xx == $yy)), - "!=" => Some(impl_op!(FLOAT => $xx != $yy)), - ">" => Some(impl_op!(FLOAT => $xx > $yy)), - ">=" => Some(impl_op!(FLOAT => $xx >= $yy)), - "<" => Some(impl_op!(FLOAT => $xx < $yy)), - "<=" => Some(impl_op!(FLOAT => $xx <= $yy)), + Token::Plus => Some(impl_op!(FLOAT => $xx + $yy)), + Token::Minus => Some(impl_op!(FLOAT => $xx - $yy)), + Token::Multiply => Some(impl_op!(FLOAT => $xx * $yy)), + Token::Divide => Some(impl_op!(FLOAT => $xx / $yy)), + Token::Modulo => Some(impl_op!(FLOAT => $xx % $yy)), + Token::PowerOf => Some(impl_op!(FLOAT => $xx.powf($yy as FLOAT))), + Token::EqualsTo => Some(impl_op!(FLOAT => $xx == $yy)), + Token::NotEqualsTo => Some(impl_op!(FLOAT => $xx != $yy)), + Token::GreaterThan => Some(impl_op!(FLOAT => $xx > $yy)), + Token::GreaterThanEqualsTo => Some(impl_op!(FLOAT => $xx >= $yy)), + Token::LessThan => Some(impl_op!(FLOAT => $xx < $yy)), + Token::LessThanEqualsTo => Some(impl_op!(FLOAT => $xx <= $yy)), _ => None, }; } @@ -299,12 +302,12 @@ pub fn get_builtin_binary_op_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Option return Some(impl_op!(from Decimal => add($xx, $yy))), - "-" => return Some(impl_op!(from Decimal => subtract($xx, $yy))), - "*" => return Some(impl_op!(from Decimal => multiply($xx, $yy))), - "/" => return Some(impl_op!(from Decimal => divide($xx, $yy))), - "%" => return Some(impl_op!(from Decimal => modulo($xx, $yy))), - "**" => return Some(impl_op!(from Decimal => power($xx, $yy))), + Token::Plus => return Some(impl_op!(from Decimal => add($xx, $yy))), + Token::Minus => return Some(impl_op!(from Decimal => subtract($xx, $yy))), + Token::Multiply => return Some(impl_op!(from Decimal => multiply($xx, $yy))), + Token::Divide => return Some(impl_op!(from Decimal => divide($xx, $yy))), + Token::Modulo => return Some(impl_op!(from Decimal => modulo($xx, $yy))), + Token::PowerOf => return Some(impl_op!(from Decimal => power($xx, $yy))), _ => () } @@ -313,22 +316,22 @@ pub fn get_builtin_binary_op_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Option return Some(impl_op!(from Decimal => $xx + $yy)), - "-" => return Some(impl_op!(from Decimal => $xx - $yy)), - "*" => return Some(impl_op!(from Decimal => $xx * $yy)), - "/" => return Some(impl_op!(from Decimal => $xx / $yy)), - "%" => return Some(impl_op!(from Decimal => $xx % $yy)), - "**" => return Some(impl_op!(from Decimal => $xx.powd($yy))), + Token::Plus => return Some(impl_op!(from Decimal => $xx + $yy)), + Token::Minus => return Some(impl_op!(from Decimal => $xx - $yy)), + Token::Multiply => return Some(impl_op!(from Decimal => $xx * $yy)), + Token::Divide => return Some(impl_op!(from Decimal => $xx / $yy)), + Token::Modulo => return Some(impl_op!(from Decimal => $xx % $yy)), + Token::PowerOf => return Some(impl_op!(from Decimal => $xx.powd($yy))), _ => () } return match op { - "==" => Some(impl_op!(from Decimal => $xx == $yy)), - "!=" => Some(impl_op!(from Decimal => $xx != $yy)), - ">" => Some(impl_op!(from Decimal => $xx > $yy)), - ">=" => Some(impl_op!(from Decimal => $xx >= $yy)), - "<" => Some(impl_op!(from Decimal => $xx < $yy)), - "<=" => Some(impl_op!(from Decimal => $xx <= $yy)), + Token::EqualsTo => Some(impl_op!(from Decimal => $xx == $yy)), + Token::NotEqualsTo => Some(impl_op!(from Decimal => $xx != $yy)), + Token::GreaterThan => Some(impl_op!(from Decimal => $xx > $yy)), + Token::GreaterThanEqualsTo => Some(impl_op!(from Decimal => $xx >= $yy)), + Token::LessThan => Some(impl_op!(from Decimal => $xx < $yy)), + Token::LessThanEqualsTo => Some(impl_op!(from Decimal => $xx <= $yy)), _ => None }; } @@ -354,17 +357,17 @@ pub fn get_builtin_binary_op_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Option Some(|_, args| { + Token::Plus => Some(|_, args| { let x = args[0].as_char().expect(BUILTIN); let y = &*args[1].read_lock::().expect(BUILTIN); Ok(format!("{x}{y}").into()) }), - "==" => Some(impl_op!(get_s1s2(==))), - "!=" => Some(impl_op!(get_s1s2(!=))), - ">" => Some(impl_op!(get_s1s2(>))), - ">=" => Some(impl_op!(get_s1s2(>=))), - "<" => Some(impl_op!(get_s1s2(<))), - "<=" => Some(impl_op!(get_s1s2(<=))), + Token::EqualsTo => Some(impl_op!(get_s1s2(==))), + Token::NotEqualsTo => Some(impl_op!(get_s1s2(!=))), + Token::GreaterThan => Some(impl_op!(get_s1s2(>))), + Token::GreaterThanEqualsTo => Some(impl_op!(get_s1s2(>=))), + Token::LessThan => Some(impl_op!(get_s1s2(<))), + Token::LessThanEqualsTo => Some(impl_op!(get_s1s2(<=))), _ => None, }; } @@ -380,45 +383,48 @@ pub fn get_builtin_binary_op_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Option Some(|_, args| { + Token::Plus => Some(|_, args| { let x = &*args[0].read_lock::().expect(BUILTIN); let y = args[1].as_char().expect(BUILTIN); Ok((x + y).into()) }), - "-" => Some(|_, args| { + Token::Minus => Some(|_, args| { let x = &*args[0].read_lock::().expect(BUILTIN); let y = args[1].as_char().expect(BUILTIN); Ok((x - y).into()) }), - "==" => Some(impl_op!(get_s1s2(==))), - "!=" => Some(impl_op!(get_s1s2(!=))), - ">" => Some(impl_op!(get_s1s2(>))), - ">=" => Some(impl_op!(get_s1s2(>=))), - "<" => Some(impl_op!(get_s1s2(<))), - "<=" => Some(impl_op!(get_s1s2(<=))), - OP_CONTAINS => Some(|_, args| { - let s = &*args[0].read_lock::().expect(BUILTIN); - let c = args[1].as_char().expect(BUILTIN); - Ok(s.contains(c).into()) - }), + Token::EqualsTo => Some(impl_op!(get_s1s2(==))), + Token::NotEqualsTo => Some(impl_op!(get_s1s2(!=))), + Token::GreaterThan => Some(impl_op!(get_s1s2(>))), + Token::GreaterThanEqualsTo => Some(impl_op!(get_s1s2(>=))), + Token::LessThan => Some(impl_op!(get_s1s2(<))), + Token::LessThanEqualsTo => Some(impl_op!(get_s1s2(<=))), _ => None, }; } // () op string if (type1, type2) == (TypeId::of::<()>(), TypeId::of::()) { return match op { - "+" => Some(|_, args| Ok(args[1].clone())), - "==" | ">" | ">=" | "<" | "<=" => Some(|_, _| Ok(Dynamic::FALSE)), - "!=" => Some(|_, _| Ok(Dynamic::TRUE)), + Token::Plus => Some(|_, args| Ok(args[1].clone())), + Token::EqualsTo + | Token::GreaterThan + | Token::GreaterThanEqualsTo + | Token::LessThan + | Token::LessThanEqualsTo => Some(|_, _| Ok(Dynamic::FALSE)), + Token::NotEqualsTo => Some(|_, _| Ok(Dynamic::TRUE)), _ => None, }; } // string op () if (type1, type2) == (TypeId::of::(), TypeId::of::<()>()) { return match op { - "+" => Some(|_, args| Ok(args[0].clone())), - "==" | ">" | ">=" | "<" | "<=" => Some(|_, _| Ok(Dynamic::FALSE)), - "!=" => Some(|_, _| Ok(Dynamic::TRUE)), + Token::Plus => Some(|_, args| Ok(args[0].clone())), + Token::EqualsTo + | Token::GreaterThan + | Token::GreaterThanEqualsTo + | Token::LessThan + | Token::LessThanEqualsTo => Some(|_, _| Ok(Dynamic::FALSE)), + Token::NotEqualsTo => Some(|_, _| Ok(Dynamic::TRUE)), _ => None, }; } @@ -428,19 +434,9 @@ pub fn get_builtin_binary_op_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Option() { use crate::Blob; - if type2 == TypeId::of::() { - return match op { - OP_CONTAINS => Some(|_, args| { - let blob = &*args[0].read_lock::().expect(BUILTIN); - let x = (args[1].as_int().expect("`INT`") & 0x0000_00ff) as u8; - Ok((!blob.is_empty() && blob.contains(&x)).into()) - }), - _ => None, - }; - } if type2 == TypeId::of::() { return match op { - "+" => Some(|_, args| { + Token::Plus => Some(|_, args| { let mut buf = [0_u8; 4]; let mut blob = args[0].read_lock::().expect(BUILTIN).clone(); let x = args[1].as_char().expect("`char`").encode_utf8(&mut buf); @@ -452,17 +448,6 @@ pub fn get_builtin_binary_op_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Option(), TypeId::of::()) { - use crate::Map; - - return match op { - OP_CONTAINS => Some(impl_op!(Map.contains_key(ImmutableString.as_str()))), - _ => None, - }; - } - // Non-compatible ranges if (type1, type2) == ( @@ -476,48 +461,28 @@ pub fn get_builtin_binary_op_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Option None, - }; - } if type1 == type2 { return match op { - "==" => Some(impl_op!(ExclusiveRange == ExclusiveRange)), - "!=" => Some(impl_op!(ExclusiveRange != ExclusiveRange)), + Token::EqualsTo => Some(impl_op!(ExclusiveRange == ExclusiveRange)), + Token::NotEqualsTo => Some(impl_op!(ExclusiveRange != ExclusiveRange)), _ => None, }; } } if type1 == TypeId::of::() { - if type2 == TypeId::of::() { - return match op { - OP_CONTAINS => Some(|_, args| { - let range = &*args[0].read_lock::().expect(BUILTIN); - let x = args[1].as_int().expect("`INT`"); - Ok(range.contains(&x).into()) - }), - _ => None, - }; - } if type1 == type2 { return match op { - "==" => Some(impl_op!(InclusiveRange == InclusiveRange)), - "!=" => Some(impl_op!(InclusiveRange != InclusiveRange)), + Token::EqualsTo => Some(impl_op!(InclusiveRange == InclusiveRange)), + Token::NotEqualsTo => Some(impl_op!(InclusiveRange != InclusiveRange)), _ => None, }; } @@ -531,8 +496,12 @@ pub fn get_builtin_binary_op_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Option" | ">=" | "<" | "<=" => Some(|_, _| Ok(Dynamic::FALSE)), + Token::NotEqualsTo => Some(|_, _| Ok(Dynamic::TRUE)), + Token::EqualsTo + | Token::GreaterThan + | Token::GreaterThanEqualsTo + | Token::LessThan + | Token::LessThanEqualsTo => Some(|_, _| Ok(Dynamic::FALSE)), _ => None, } } else { @@ -544,8 +513,12 @@ pub fn get_builtin_binary_op_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Option" | ">=" | "<" | "<=" => Some(|_, _| Ok(Dynamic::FALSE)), + Token::NotEqualsTo => Some(|_, _| Ok(Dynamic::TRUE)), + Token::EqualsTo + | Token::GreaterThan + | Token::GreaterThanEqualsTo + | Token::LessThan + | Token::LessThanEqualsTo => Some(|_, _| Ok(Dynamic::FALSE)), _ => None, }; } @@ -558,7 +531,7 @@ pub fn get_builtin_binary_op_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Option Option { +pub fn get_builtin_op_assignment_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Option { let type1 = x.type_id(); let type2 = y.type_id(); @@ -610,49 +583,51 @@ pub fn get_builtin_op_assignment_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Optio #[cfg(not(feature = "unchecked"))] match op { - "+=" => return Some(impl_op!(INT => add(as_int, as_int))), - "-=" => return Some(impl_op!(INT => subtract(as_int, as_int))), - "*=" => return Some(impl_op!(INT => multiply(as_int, as_int))), - "/=" => return Some(impl_op!(INT => divide(as_int, as_int))), - "%=" => return Some(impl_op!(INT => modulo(as_int, as_int))), - "**=" => return Some(impl_op!(INT => power(as_int, as_int))), - ">>=" => return Some(impl_op!(INT => shift_right(as_int, as_int))), - "<<=" => return Some(impl_op!(INT => shift_left(as_int, as_int))), + Token::PlusAssign => return Some(impl_op!(INT => add(as_int, as_int))), + Token::MinusAssign => return Some(impl_op!(INT => subtract(as_int, as_int))), + Token::MultiplyAssign => return Some(impl_op!(INT => multiply(as_int, as_int))), + Token::DivideAssign => return Some(impl_op!(INT => divide(as_int, as_int))), + Token::ModuloAssign => return Some(impl_op!(INT => modulo(as_int, as_int))), + Token::PowerOfAssign => return Some(impl_op!(INT => power(as_int, as_int))), + Token::RightShiftAssign => { + return Some(impl_op!(INT => shift_right(as_int, as_int))) + } + Token::LeftShiftAssign => return Some(impl_op!(INT => shift_left(as_int, as_int))), _ => (), } #[cfg(feature = "unchecked")] match op { - "+=" => return Some(impl_op!(INT += as_int)), - "-=" => return Some(impl_op!(INT -= as_int)), - "*=" => return Some(impl_op!(INT *= as_int)), - "/=" => return Some(impl_op!(INT /= as_int)), - "%=" => return Some(impl_op!(INT %= as_int)), - "**=" => return Some(impl_op!(INT => as_int.pow(as_int as u32))), - ">>=" => return Some(impl_op!(INT >>= as_int)), - "<<=" => return Some(impl_op!(INT <<= as_int)), + Token::PlusAssign => return Some(impl_op!(INT += as_int)), + Token::MinusAssign => return Some(impl_op!(INT -= as_int)), + Token::MultiplyAssign => return Some(impl_op!(INT *= as_int)), + Token::DivideAssign => return Some(impl_op!(INT /= as_int)), + Token::ModuloAssign => return Some(impl_op!(INT %= as_int)), + Token::PowerOfAssign => return Some(impl_op!(INT => as_int.pow(as_int as u32))), + Token::RightShiftAssign => return Some(impl_op!(INT >>= as_int)), + Token::LeftShiftAssign => return Some(impl_op!(INT <<= as_int)), _ => (), } return match op { - "&=" => Some(impl_op!(INT &= as_int)), - "|=" => Some(impl_op!(INT |= as_int)), - "^=" => Some(impl_op!(INT ^= as_int)), + Token::AndAssign => Some(impl_op!(INT &= as_int)), + Token::OrAssign => Some(impl_op!(INT |= as_int)), + Token::XOrAssign => Some(impl_op!(INT ^= as_int)), _ => None, }; } if type1 == TypeId::of::() { return match op { - "&=" => Some(impl_op!(bool = x && as_bool)), - "|=" => Some(impl_op!(bool = x || as_bool)), + Token::AndAssign => Some(impl_op!(bool = x && as_bool)), + Token::OrAssign => Some(impl_op!(bool = x || as_bool)), _ => None, }; } if type1 == TypeId::of::() { return match op { - "+=" => Some(|_, args| { + Token::PlusAssign => Some(|_, args| { let y = args[1].as_char().expect(BUILTIN); let x = &mut *args[0].write_lock::().expect(BUILTIN); Ok((*x = format!("{x}{y}").into()).into()) @@ -663,13 +638,13 @@ pub fn get_builtin_op_assignment_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Optio if type1 == TypeId::of::() { return match op { - "+=" => Some(|_, args| { + Token::PlusAssign => Some(|_, args| { let (first, second) = args.split_first_mut().expect(BUILTIN); let x = &mut *first.write_lock::().expect(BUILTIN); let y = std::mem::take(second[0]).cast::(); Ok((*x += y).into()) }), - "-=" => Some(|_, args| { + Token::MinusAssign => Some(|_, args| { let (first, second) = args.split_first_mut().expect(BUILTIN); let x = &mut *first.write_lock::().expect(BUILTIN); let y = std::mem::take(second[0]).cast::(); @@ -684,7 +659,7 @@ pub fn get_builtin_op_assignment_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Optio use crate::Blob; return match op { - "+=" => Some(|_, args| { + Token::PlusAssign => Some(|_, args| { let blob2 = std::mem::take(args[1]).cast::(); let blob1 = &mut *args[0].write_lock::().expect(BUILTIN); Ok(crate::packages::blob_basic::blob_functions::append(blob1, blob2).into()) @@ -699,12 +674,12 @@ pub fn get_builtin_op_assignment_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Optio ($x:ident, $xx:ident, $y:ty, $yy:ident) => { if (type1, type2) == (TypeId::of::<$x>(), TypeId::of::<$y>()) { return match op { - "+=" => Some(impl_op!($x += $yy)), - "-=" => Some(impl_op!($x -= $yy)), - "*=" => Some(impl_op!($x *= $yy)), - "/=" => Some(impl_op!($x /= $yy)), - "%=" => Some(impl_op!($x %= $yy)), - "**=" => Some(impl_op!($x => $xx.powf($yy as $x))), + Token::PlusAssign => Some(impl_op!($x += $yy)), + Token::MinusAssign => Some(impl_op!($x -= $yy)), + Token::MultiplyAssign => Some(impl_op!($x *= $yy)), + Token::DivideAssign => Some(impl_op!($x /= $yy)), + Token::ModuloAssign => Some(impl_op!($x %= $yy)), + Token::PowerOfAssign => Some(impl_op!($x => $xx.powf($yy as $x))), _ => None, }; } @@ -726,12 +701,12 @@ pub fn get_builtin_op_assignment_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Optio #[cfg(not(feature = "unchecked"))] return match op { - "+=" => Some(impl_op!(from $x => add($xx, $yy))), - "-=" => Some(impl_op!(from $x => subtract($xx, $yy))), - "*=" => Some(impl_op!(from $x => multiply($xx, $yy))), - "/=" => Some(impl_op!(from $x => divide($xx, $yy))), - "%=" => Some(impl_op!(from $x => modulo($xx, $yy))), - "**=" => Some(impl_op!(from $x => power($xx, $yy))), + Token::PlusAssign => Some(impl_op!(from $x => add($xx, $yy))), + Token::MinusAssign => Some(impl_op!(from $x => subtract($xx, $yy))), + Token::MultiplyAssign => Some(impl_op!(from $x => multiply($xx, $yy))), + Token::DivideAssign => Some(impl_op!(from $x => divide($xx, $yy))), + Token::ModuloAssign => Some(impl_op!(from $x => modulo($xx, $yy))), + Token::PowerOfAssign => Some(impl_op!(from $x => power($xx, $yy))), _ => None, }; @@ -740,12 +715,12 @@ pub fn get_builtin_op_assignment_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Optio #[cfg(feature = "unchecked")] return match op { - "+=" => Some(impl_op!(from $x += $yy)), - "-=" => Some(impl_op!(from $x -= $yy)), - "*=" => Some(impl_op!(from $x *= $yy)), - "/=" => Some(impl_op!(from $x /= $yy)), - "%=" => Some(impl_op!(from $x %= $yy)), - "**=" => Some(impl_op!(from $x => $xx.powd($yy))), + Token::PlusAssign => Some(impl_op!(from $x += $yy)), + Token::MinusAssign => Some(impl_op!(from $x -= $yy)), + Token::MultiplyAssign => Some(impl_op!(from $x *= $yy)), + Token::DivideAssign => Some(impl_op!(from $x /= $yy)), + Token::ModuloAssign => Some(impl_op!(from $x %= $yy)), + Token::PowerOfAssign => Some(impl_op!(from $x => $xx.powd($yy))), _ => None, }; } @@ -761,15 +736,15 @@ pub fn get_builtin_op_assignment_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Optio // string op= char if (type1, type2) == (TypeId::of::(), TypeId::of::()) { return match op { - "+=" => Some(impl_op!(ImmutableString += as_char as char)), - "-=" => Some(impl_op!(ImmutableString -= as_char as char)), + Token::PlusAssign => Some(impl_op!(ImmutableString += as_char as char)), + Token::MinusAssign => Some(impl_op!(ImmutableString -= as_char as char)), _ => None, }; } // char op= string if (type1, type2) == (TypeId::of::(), TypeId::of::()) { return match op { - "+=" => Some(|_, args| { + Token::PlusAssign => Some(|_, args| { let mut ch = args[0].as_char().expect(BUILTIN).to_string(); ch.push_str( args[1] @@ -793,7 +768,7 @@ pub fn get_builtin_op_assignment_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Optio if type2 == TypeId::of::() { return match op { - "+=" => Some(|_, args| { + Token::PlusAssign => Some(|_, args| { let array2 = std::mem::take(args[1]).cast::(); let array1 = &mut *args[0].write_lock::().expect(BUILTIN); Ok(append(array1, array2).into()) @@ -802,7 +777,7 @@ pub fn get_builtin_op_assignment_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Optio }; } return match op { - "+=" => Some(|_, args| { + Token::PlusAssign => Some(|_, args| { let x = std::mem::take(args[1]); let array = &mut *args[0].write_lock::().expect(BUILTIN); Ok(push(array, x).into()) @@ -818,7 +793,7 @@ pub fn get_builtin_op_assignment_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Optio // blob op= int if (type1, type2) == (TypeId::of::(), TypeId::of::()) { return match op { - "+=" => Some(|_, args| { + Token::PlusAssign => Some(|_, args| { let x = args[1].as_int().expect("`INT`"); let blob = &mut *args[0].write_lock::().expect(BUILTIN); Ok(crate::packages::blob_basic::blob_functions::push(blob, x).into()) @@ -830,7 +805,7 @@ pub fn get_builtin_op_assignment_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Optio // blob op= char if (type1, type2) == (TypeId::of::(), TypeId::of::()) { return match op { - "+=" => Some(|_, args| { + Token::PlusAssign => Some(|_, args| { let x = args[1].as_char().expect("`char`"); let blob = &mut *args[0].write_lock::().expect(BUILTIN); Ok(crate::packages::blob_basic::blob_functions::append_char(blob, x).into()) @@ -842,7 +817,7 @@ pub fn get_builtin_op_assignment_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Optio // blob op= string if (type1, type2) == (TypeId::of::(), TypeId::of::()) { return match op { - "+=" => Some(|_, args| { + Token::PlusAssign => Some(|_, args| { let s = std::mem::take(args[1]).cast::(); let blob = &mut *args[0].write_lock::().expect(BUILTIN); Ok(crate::packages::blob_basic::blob_functions::append_str(blob, &s).into()) diff --git a/src/func/call.rs b/src/func/call.rs index e91d63a8..ea6349fe 100644 --- a/src/func/call.rs +++ b/src/func/call.rs @@ -9,6 +9,7 @@ use crate::engine::{ KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_TYPE_OF, }; use crate::eval::{Caches, FnResolutionCacheEntry, GlobalRuntimeState}; +use crate::tokenizer::Token; use crate::{ calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, Engine, FnArgsVec, FnPtr, ImmutableString, Module, OptimizationLevel, Position, RhaiError, RhaiResult, RhaiResultOf, @@ -191,7 +192,7 @@ impl Engine { hash_base: u64, args: Option<&mut FnCallArgs>, allow_dynamic: bool, - is_op_assignment: bool, + op_assignment_token: Option<&Token>, ) -> Option<&'s FnResolutionCacheEntry> { if hash_base == 0 { return None; @@ -279,22 +280,23 @@ impl Engine { // Try to find a built-in version let builtin = args.and_then(|args| { - if is_op_assignment { + if let Some(op_assign) = op_assignment_token { let (first_arg, rest_args) = args.split_first().unwrap(); - get_builtin_op_assignment_fn(fn_name, *first_arg, rest_args[0]).map( - |f| FnResolutionCacheEntry { + get_builtin_op_assignment_fn(op_assign, *first_arg, rest_args[0]) + .map(|f| FnResolutionCacheEntry { func: CallableFunction::from_fn_builtin(f), source: None, - }, - ) - } else { - get_builtin_binary_op_fn(fn_name, args[0], args[1]).map(|f| { + }) + } else if let Some(ref operator) = Token::lookup_from_syntax(fn_name) { + get_builtin_binary_op_fn(operator, args[0], args[1]).map(|f| { FnResolutionCacheEntry { func: CallableFunction::from_fn_builtin(f), source: None, } }) + } else { + None } }); @@ -360,6 +362,11 @@ impl Engine { self.inc_operations(&mut global.num_operations, pos)?; let parent_source = global.source.clone(); + let op_assign = if is_op_assign { + Token::lookup_from_syntax(name) + } else { + None + }; // Check if function access already in the cache let local_entry = &mut None; @@ -373,7 +380,7 @@ impl Engine { hash, Some(args), true, - is_op_assign, + op_assign.as_ref(), ); if func.is_some() { @@ -653,7 +660,7 @@ impl Engine { hashes.script, None, false, - false, + None, ) .cloned() { @@ -993,7 +1000,7 @@ impl Engine { args_expr: &[Expr], hashes: FnCallHashes, capture_scope: bool, - is_operator: bool, + operator_token: Option<&Token>, pos: Position, level: usize, ) -> RhaiResult { @@ -1006,7 +1013,7 @@ impl Engine { let redirected; // Handle call() - Redirect function call match name { - _ if is_operator => (), + _ if operator_token.is_some() => (), // Handle call() KEYWORD_FN_PTR_CALL if total_args >= 1 => { diff --git a/src/optimizer.rs b/src/optimizer.rs index 216e4f3c..6ab19fcb 100644 --- a/src/optimizer.rs +++ b/src/optimizer.rs @@ -1181,8 +1181,8 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) { return; } // Overloaded operators can override built-in. - _ if x.args.len() == 2 && (state.engine.fast_operators() || !has_native_fn_override(state.engine, x.hashes.native, &arg_types)) => { - if let Some(result) = get_builtin_binary_op_fn(&x.name, &arg_values[0], &arg_values[1]) + _ if x.args.len() == 2 && x.operator_token.is_some() && (state.engine.fast_operators() || !has_native_fn_override(state.engine, x.hashes.native, &arg_types)) => { + if let Some(result) = get_builtin_binary_op_fn(x.operator_token.as_ref().unwrap(), &arg_values[0], &arg_values[1]) .and_then(|f| { #[cfg(not(feature = "no_function"))] let lib = state.lib; diff --git a/src/packages/blob_basic.rs b/src/packages/blob_basic.rs index a3bbd532..4b6639c2 100644 --- a/src/packages/blob_basic.rs +++ b/src/packages/blob_basic.rs @@ -146,6 +146,21 @@ pub mod blob_functions { pub fn is_empty(blob: &mut Blob) -> bool { blob.len() == 0 } + /// Return `true` if the BLOB contains a specified byte value. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world!"; + /// + /// print(text.contains('h')); // prints true + /// + /// print(text.contains('x')); // prints false + /// ``` + #[rhai_fn(name = "contains")] + pub fn contains(blob: &mut Blob, value: INT) -> bool { + blob.contains(&((value & 0x0000_00ff) as u8)) + } /// Get the byte value at the `index` position in the BLOB. /// /// * If `index` < 0, position counts from the end of the BLOB (`-1` is the last element). diff --git a/src/packages/iter_basic.rs b/src/packages/iter_basic.rs index 6ec293cc..b9c849fe 100644 --- a/src/packages/iter_basic.rs +++ b/src/packages/iter_basic.rs @@ -668,6 +668,12 @@ mod range_functions { pub fn is_empty_exclusive(range: &mut ExclusiveRange) -> bool { range.is_empty() } + /// Return `true` if the range contains a specified value. + #[rhai_fn(name = "contains")] + pub fn contains_exclusive(range: &mut ExclusiveRange, value: INT) -> bool { + range.contains(&value) + } + /// Return the start of the inclusive range. #[rhai_fn(get = "start", name = "start", pure)] pub fn start_inclusive(range: &mut InclusiveRange) -> INT { @@ -695,4 +701,9 @@ mod range_functions { pub fn is_empty_inclusive(range: &mut InclusiveRange) -> bool { range.is_empty() } + /// Return `true` if the range contains a specified value. + #[rhai_fn(name = "contains")] + pub fn contains_inclusive(range: &mut InclusiveRange, value: INT) -> bool { + range.contains(&value) + } } diff --git a/src/packages/map_basic.rs b/src/packages/map_basic.rs index 09ffdce6..955b8395 100644 --- a/src/packages/map_basic.rs +++ b/src/packages/map_basic.rs @@ -30,6 +30,20 @@ mod map_functions { pub fn is_empty(map: &mut Map) -> bool { map.len() == 0 } + /// Returns `true` if the object map contains a specified property. + /// + /// # Example + /// + /// ```rhai + /// let m = #{a: 1, b: 2, c: 3}; + /// + /// print(m.contains("b")); // prints true + /// + /// print(m.contains("x")); // prints false + /// ``` + pub fn contains(map: &mut Map, property: &str) -> bool { + map.contains_key(property) + } /// Get the value of the `property` in the object map and return a copy. /// /// If `property` does not exist in the object map, `()` is returned. diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index da53a127..93e8edf4 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -506,6 +506,37 @@ mod string_functions { *character = to_lower_char(*character); } + /// Return `true` if the string contains a specified string. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world!"; + /// + /// print(text.contains("hello")); // prints true + /// + /// print(text.contains("hey")); // prints false + /// ``` + pub fn contains(string: &str, match_string: &str) -> bool { + string.contains(match_string) + } + + /// Return `true` if the string contains a specified character. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world!"; + /// + /// print(text.contains('h')); // prints true + /// + /// print(text.contains('x')); // prints false + /// ``` + #[rhai_fn(name = "contains")] + pub fn contains_char(string: &str, character: char) -> bool { + string.contains(character).into() + } + /// Return `true` if the string starts with a specified string. /// /// # Example diff --git a/src/parser.rs b/src/parser.rs index 621a1b47..9941fc53 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -472,7 +472,7 @@ fn match_token(input: &mut TokenStream, token: Token) -> (bool, Position) { fn parse_var_name(input: &mut TokenStream) -> ParseResult<(SmartString, Position)> { match input.next().expect(NEVER_ENDS) { // Variable name - (Token::Identifier(s), pos) => Ok((s, pos)), + (Token::Identifier(s), pos) => Ok((*s, pos)), // Reserved keyword (Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => { Err(PERR::Reserved(s.to_string()).into_err(pos)) @@ -492,7 +492,7 @@ fn parse_symbol(input: &mut TokenStream) -> ParseResult<(SmartString, Position)> // Symbol (token, pos) if token.is_standard_symbol() => Ok((token.literal_syntax().into(), pos)), // Reserved symbol - (Token::Reserved(s), pos) if !is_valid_identifier(s.chars()) => Ok((s, pos)), + (Token::Reserved(s), pos) if !is_valid_identifier(s.chars()) => Ok((*s, pos)), // Bad symbol (Token::LexError(err), pos) => Err(err.into_err(pos)), // Not a symbol @@ -616,7 +616,7 @@ impl Engine { return Ok(FnCallExpr { name: state.get_interned_string(id), capture_parent_scope, - is_native_operator: false, + operator_token: None, #[cfg(not(feature = "no_module"))] namespace, hashes, @@ -684,7 +684,7 @@ impl Engine { return Ok(FnCallExpr { name: state.get_interned_string(id), capture_parent_scope, - is_native_operator: false, + operator_token: None, #[cfg(not(feature = "no_module"))] namespace, hashes, @@ -1000,10 +1000,10 @@ impl Engine { let (name, pos) = match input.next().expect(NEVER_ENDS) { (Token::Identifier(s) | Token::StringConstant(s), pos) => { - if map.iter().any(|(p, ..)| **p == s) { + if map.iter().any(|(p, ..)| **p == *s) { return Err(PERR::DuplicatedProperty(s.to_string()).into_err(pos)); } - (s, pos) + (*s, pos) } (Token::InterpolatedString(..), pos) => { return Err(PERR::PropertyExpected.into_err(pos)) @@ -1342,7 +1342,7 @@ impl Engine { Token::IntegerConstant(x) => Expr::IntegerConstant(x, settings.pos), Token::CharConstant(c) => Expr::CharConstant(c, settings.pos), Token::StringConstant(s) => { - Expr::StringConstant(state.get_interned_string(s), settings.pos) + Expr::StringConstant(state.get_interned_string(*s), settings.pos) } Token::True => Expr::BoolConstant(true, settings.pos), Token::False => Expr::BoolConstant(false, settings.pos), @@ -1356,7 +1356,7 @@ impl Engine { } #[cfg(feature = "decimal")] Token::DecimalConstant(x) => { - let x = (*x).into(); + let x = (**x).into(); input.next(); Expr::DynamicConstant(Box::new(x), settings.pos) } @@ -1479,7 +1479,7 @@ impl Engine { match input.next().expect(NEVER_ENDS) { (Token::InterpolatedString(s), ..) if s.is_empty() => (), (Token::InterpolatedString(s), pos) => { - segments.push(Expr::StringConstant(s.into(), pos)) + segments.push(Expr::StringConstant(state.get_interned_string(*s), pos)) } token => { unreachable!("Token::InterpolatedString expected but gets {:?}", token) @@ -1502,14 +1502,16 @@ impl Engine { match input.next().expect(NEVER_ENDS) { (Token::StringConstant(s), pos) => { if !s.is_empty() { - segments.push(Expr::StringConstant(s.into(), pos)); + segments + .push(Expr::StringConstant(state.get_interned_string(*s), pos)); } // End the interpolated string if it is terminated by a back-tick. break; } (Token::InterpolatedString(s), pos) => { if !s.is_empty() { - segments.push(Expr::StringConstant(s.into(), pos)); + segments + .push(Expr::StringConstant(state.get_interned_string(*s), pos)); } } (Token::LexError(err), pos) @@ -1574,7 +1576,7 @@ impl Engine { state.allow_capture = true; } Expr::Variable( - (None, ns, 0, state.get_interned_string(s)).into(), + (None, ns, 0, state.get_interned_string(*s)).into(), None, settings.pos, ) @@ -1587,7 +1589,7 @@ impl Engine { // Once the identifier consumed we must enable next variables capturing state.allow_capture = true; } - let name = state.get_interned_string(s); + let name = state.get_interned_string(*s); Expr::Variable((None, ns, 0, name).into(), None, settings.pos) } // Normal variable access @@ -1612,7 +1614,7 @@ impl Engine { None } }); - let name = state.get_interned_string(s); + let name = state.get_interned_string(*s); Expr::Variable((index, ns, 0, name).into(), short_index, settings.pos) } } @@ -1634,7 +1636,7 @@ impl Engine { // Function call is allowed to have reserved keyword Token::LeftParen | Token::Bang | Token::Unit if is_keyword_function(&s) => { Expr::Variable( - (None, ns, 0, state.get_interned_string(s)).into(), + (None, ns, 0, state.get_interned_string(*s)).into(), None, settings.pos, ) @@ -1642,7 +1644,7 @@ impl Engine { // Access to `this` as a variable is OK within a function scope #[cfg(not(feature = "no_function"))] _ if &*s == KEYWORD_THIS && settings.in_fn_scope => Expr::Variable( - (None, ns, 0, state.get_interned_string(s)).into(), + (None, ns, 0, state.get_interned_string(*s)).into(), None, settings.pos, ), @@ -1888,7 +1890,7 @@ impl Engine { // -expr Token::Minus | Token::UnaryMinus => { let token = token.clone(); - let pos = eat_token(input, token); + let pos = eat_token(input, token.clone()); match self.parse_unary(input, state, lib, settings.level_up())? { // Negative integer @@ -1918,7 +1920,7 @@ impl Engine { hashes: FnCallHashes::from_native(calc_fn_hash(None, "-", 1)), args, pos, - is_native_operator: true, + operator_token: Some(token), ..Default::default() } .into_fn_call_expr(pos)) @@ -1928,7 +1930,7 @@ impl Engine { // +expr Token::Plus | Token::UnaryPlus => { let token = token.clone(); - let pos = eat_token(input, token); + let pos = eat_token(input, token.clone()); match self.parse_unary(input, state, lib, settings.level_up())? { expr @ Expr::IntegerConstant(..) => Ok(expr), @@ -1946,7 +1948,7 @@ impl Engine { hashes: FnCallHashes::from_native(calc_fn_hash(None, "+", 1)), args, pos, - is_native_operator: true, + operator_token: Some(token), ..Default::default() } .into_fn_call_expr(pos)) @@ -1955,7 +1957,9 @@ impl Engine { } // !expr Token::Bang => { + let token = token.clone(); let pos = eat_token(input, Token::Bang); + let mut args = StaticVec::new_const(); args.push(self.parse_unary(input, state, lib, settings.level_up())?); args.shrink_to_fit(); @@ -1965,7 +1969,7 @@ impl Engine { hashes: FnCallHashes::from_native(calc_fn_hash(None, "!", 1)), args, pos, - is_native_operator: true, + operator_token: Some(token), ..Default::default() } .into_fn_call_expr(pos)) @@ -2283,7 +2287,7 @@ impl Engine { #[cfg(not(feature = "no_custom_syntax"))] Token::Custom(c) => self .custom_keywords - .get(c) + .get(&**c) .copied() .ok_or_else(|| PERR::Reserved(c.to_string()).into_err(*current_pos))?, Token::Reserved(c) if !is_valid_identifier(c.chars()) => { @@ -2308,7 +2312,7 @@ impl Engine { #[cfg(not(feature = "no_custom_syntax"))] Token::Custom(c) => self .custom_keywords - .get(c) + .get(&**c) .copied() .ok_or_else(|| PERR::Reserved(c.to_string()).into_err(*next_pos))?, Token::Reserved(c) if !is_valid_identifier(c.chars()) => { @@ -2335,12 +2339,17 @@ impl Engine { let op = op_token.syntax(); let hash = calc_fn_hash(None, &op, 2); + let operator_token = if is_valid_function_name(&op) { + None + } else { + Some(op_token.clone()) + }; let op_base = FnCallExpr { name: state.get_interned_string(op.as_ref()), hashes: FnCallHashes::from_native(hash), pos, - is_native_operator: !is_valid_function_name(&op), + operator_token, ..Default::default() }; @@ -2578,7 +2587,7 @@ impl Engine { }, CUSTOM_SYNTAX_MARKER_STRING => match input.next().expect(NEVER_ENDS) { (Token::StringConstant(s), pos) => { - let s = state.get_interned_string(s); + let s = state.get_interned_string(*s); inputs.push(Expr::StringConstant(s.clone(), pos)); segments.push(s); tokens.push(state.get_interned_string(CUSTOM_SYNTAX_MARKER_STRING)); @@ -3228,7 +3237,7 @@ impl Engine { match input.next().expect(NEVER_ENDS).0 { Token::Comment(comment) => { - comments.push(comment); + comments.push(*comment); match input.peek().expect(NEVER_ENDS) { (Token::Fn | Token::Private, ..) => break, @@ -3562,7 +3571,7 @@ impl Engine { return Err(PERR::FnDuplicatedParam(name.to_string(), s.to_string()) .into_err(pos)); } - let s = state.get_interned_string(s); + let s = state.get_interned_string(*s); state.stack.push(s.clone(), ()); params.push((s, pos)); } @@ -3700,7 +3709,7 @@ impl Engine { PERR::FnDuplicatedParam(String::new(), s.to_string()).into_err(pos) ); } - let s = state.get_interned_string(s); + let s = state.get_interned_string(*s); state.stack.push(s.clone(), ()); params_list.push(s); } diff --git a/src/tests.rs b/src/tests.rs index 1d897871..c91a1e1e 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -22,15 +22,7 @@ fn check_struct_sizes() { ); assert_eq!( size_of::(), - if IS_32_BIT { - if cfg!(feature = "decimal") { - 24 - } else { - 16 - } - } else { - 32 - } + if IS_32_BIT { 8 } else { 16 } ); assert_eq!(size_of::(), if PACKED { 12 } else { 16 }); assert_eq!(size_of::>(), if PACKED { 12 } else { 16 }); diff --git a/src/tokenizer.rs b/src/tokenizer.rs index 6cc7eb57..e3d615e8 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -381,15 +381,15 @@ pub enum Token { /// /// Requires the `decimal` feature. #[cfg(feature = "decimal")] - DecimalConstant(rust_decimal::Decimal), + DecimalConstant(Box), /// An identifier. - Identifier(Identifier), + Identifier(Box), /// A character constant. CharConstant(char), /// A string constant. - StringConstant(SmartString), + StringConstant(Box), /// An interpolated string. - InterpolatedString(SmartString), + InterpolatedString(Box), /// `{` LeftBrace, /// `}` @@ -570,14 +570,14 @@ pub enum Token { /// A lexer error. LexError(Box), /// A comment block. - Comment(SmartString), + Comment(Box), /// A reserved symbol. - Reserved(SmartString), + Reserved(Box), /// A custom keyword. /// /// Not available under `no_custom_syntax`. #[cfg(not(feature = "no_custom_syntax"))] - Custom(SmartString), + Custom(Box), /// End of the input stream. EOF, } @@ -898,21 +898,23 @@ impl Token { // List of reserved operators "===" | "!==" | "->" | "<-" | "?" | ":=" | ":;" | "~" | "!." | "::<" | "(*" | "*)" - | "#" | "#!" | "@" | "$" | "++" | "--" | "..." | "<|" | "|>" => Reserved(syntax.into()), + | "#" | "#!" | "@" | "$" | "++" | "--" | "..." | "<|" | "|>" => { + Reserved(Box::new(syntax.into())) + } // List of reserved keywords "public" | "protected" | "super" | "new" | "use" | "module" | "package" | "var" | "static" | "shared" | "with" | "is" | "goto" | "exit" | "match" | "case" | "default" | "void" | "null" | "nil" | "spawn" | "thread" | "go" | "sync" - | "async" | "await" | "yield" => Reserved(syntax.into()), + | "async" | "await" | "yield" => Reserved(Box::new(syntax.into())), KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR | KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_THIS | KEYWORD_IS_DEF_VAR => { - Reserved(syntax.into()) + Reserved(Box::new(syntax.into())) } #[cfg(not(feature = "no_function"))] - crate::engine::KEYWORD_IS_DEF_FN => Reserved(syntax.into()), + crate::engine::KEYWORD_IS_DEF_FN => Reserved(Box::new(syntax.into())), _ => return None, }) @@ -1097,8 +1099,8 @@ impl Token { pub(crate) fn into_function_name_for_override(self) -> Result { match self { #[cfg(not(feature = "no_custom_syntax"))] - Self::Custom(s) if is_valid_function_name(&s) => Ok(s), - Self::Identifier(s) if is_valid_function_name(&s) => Ok(s), + Self::Custom(s) if is_valid_function_name(&s) => Ok(*s), + Self::Identifier(s) if is_valid_function_name(&s) => Ok(*s), _ => Err(self), } } @@ -1510,7 +1512,7 @@ fn get_next_token_inner( let return_comment = return_comment || is_doc_comment(comment.as_ref().expect("`Some`")); if return_comment { - return Some((Token::Comment(comment.expect("`Some`")), start_pos)); + return Some((Token::Comment(comment.expect("`Some`").into()), start_pos)); } if state.comment_level > 0 { // Reached EOF without ending comment block @@ -1524,9 +1526,9 @@ fn get_next_token_inner( |(err, err_pos)| Some((Token::LexError(err.into()), err_pos)), |(result, interpolated, start_pos)| { if interpolated { - Some((Token::InterpolatedString(result), start_pos)) + Some((Token::InterpolatedString(result.into()), start_pos)) } else { - Some((Token::StringConstant(result), start_pos)) + Some((Token::StringConstant(result.into()), start_pos)) } }, ); @@ -1676,13 +1678,16 @@ fn get_next_token_inner( // Then try decimal #[cfg(feature = "decimal")] let num = num.or_else(|_| { - rust_decimal::Decimal::from_str(&result).map(Token::DecimalConstant) + rust_decimal::Decimal::from_str(&result) + .map(Box::new) + .map(Token::DecimalConstant) }); // Then try decimal in scientific notation #[cfg(feature = "decimal")] let num = num.or_else(|_| { rust_decimal::Decimal::from_scientific(&result) + .map(Box::new) .map(Token::DecimalConstant) }); @@ -1709,7 +1714,7 @@ fn get_next_token_inner( return parse_string_literal(stream, state, pos, c, false, true, false) .map_or_else( |(err, err_pos)| Some((Token::LexError(err.into()), err_pos)), - |(result, ..)| Some((Token::StringConstant(result), start_pos)), + |(result, ..)| Some((Token::StringConstant(result.into()), start_pos)), ); } // ` - string literal @@ -1737,9 +1742,9 @@ fn get_next_token_inner( |(err, err_pos)| Some((Token::LexError(err.into()), err_pos)), |(result, interpolated, ..)| { if interpolated { - Some((Token::InterpolatedString(result), start_pos)) + Some((Token::InterpolatedString(result.into()), start_pos)) } else { - Some((Token::StringConstant(result), start_pos)) + Some((Token::StringConstant(result.into()), start_pos)) } }, ); @@ -1786,7 +1791,7 @@ fn get_next_token_inner( // Parentheses ('(', '*') => { eat_next(stream, pos); - return Some((Token::Reserved("(*".into()), start_pos)); + return Some((Token::Reserved(Box::new("(*".into())), start_pos)); } ('(', ..) => return Some((Token::LeftParen, start_pos)), (')', ..) => return Some((Token::RightParen, start_pos)), @@ -1802,7 +1807,7 @@ fn get_next_token_inner( return Some((Token::MapStart, start_pos)); } // Shebang - ('#', '!') => return Some((Token::Reserved("#!".into()), start_pos)), + ('#', '!') => return Some((Token::Reserved(Box::new("#!".into())), start_pos)), ('#', ' ') => { eat_next(stream, pos); @@ -1812,10 +1817,10 @@ fn get_next_token_inner( } else { "#" }; - return Some((Token::Reserved(token.into()), start_pos)); + return Some((Token::Reserved(Box::new(token.into())), start_pos)); } - ('#', ..) => return Some((Token::Reserved("#".into()), start_pos)), + ('#', ..) => return Some((Token::Reserved(Box::new("#".into())), start_pos)), // Operators ('+', '=') => { @@ -1824,7 +1829,7 @@ fn get_next_token_inner( } ('+', '+') => { eat_next(stream, pos); - return Some((Token::Reserved("++".into()), start_pos)); + return Some((Token::Reserved(Box::new("++".into())), start_pos)); } ('+', ..) if !state.next_token_cannot_be_unary => { return Some((Token::UnaryPlus, start_pos)) @@ -1839,11 +1844,11 @@ fn get_next_token_inner( } ('-', '>') => { eat_next(stream, pos); - return Some((Token::Reserved("->".into()), start_pos)); + return Some((Token::Reserved(Box::new("->".into())), start_pos)); } ('-', '-') => { eat_next(stream, pos); - return Some((Token::Reserved("--".into()), start_pos)); + return Some((Token::Reserved(Box::new("--".into())), start_pos)); } ('-', ..) if !state.next_token_cannot_be_unary => { return Some((Token::UnaryMinus, start_pos)) @@ -1852,7 +1857,7 @@ fn get_next_token_inner( ('*', ')') => { eat_next(stream, pos); - return Some((Token::Reserved("*)".into()), start_pos)); + return Some((Token::Reserved(Box::new("*)".into())), start_pos)); } ('*', '=') => { eat_next(stream, pos); @@ -1925,7 +1930,7 @@ fn get_next_token_inner( .borrow_mut() .global_comments .push(comment), - _ => return Some((Token::Comment(comment), start_pos)), + _ => return Some((Token::Comment(comment.into()), start_pos)), } } } @@ -1953,7 +1958,7 @@ fn get_next_token_inner( scan_block_comment(stream, state.comment_level, pos, comment.as_mut()); if let Some(comment) = comment { - return Some((Token::Comment(comment), start_pos)); + return Some((Token::Comment(comment.into()), start_pos)); } } @@ -1972,7 +1977,7 @@ fn get_next_token_inner( match stream.peek_next() { Some('.') => { eat_next(stream, pos); - Token::Reserved("...".into()) + Token::Reserved(Box::new("...".into())) } Some('=') => { eat_next(stream, pos); @@ -1990,7 +1995,7 @@ fn get_next_token_inner( if stream.peek_next() == Some('=') { eat_next(stream, pos); - return Some((Token::Reserved("===".into()), start_pos)); + return Some((Token::Reserved(Box::new("===".into())), start_pos)); } return Some((Token::EqualsTo, start_pos)); @@ -2007,18 +2012,18 @@ fn get_next_token_inner( if stream.peek_next() == Some('<') { eat_next(stream, pos); - return Some((Token::Reserved("::<".into()), start_pos)); + return Some((Token::Reserved(Box::new("::<".into())), start_pos)); } return Some((Token::DoubleColon, start_pos)); } (':', '=') => { eat_next(stream, pos); - return Some((Token::Reserved(":=".into()), start_pos)); + return Some((Token::Reserved(Box::new(":=".into())), start_pos)); } (':', ';') => { eat_next(stream, pos); - return Some((Token::Reserved(":;".into()), start_pos)); + return Some((Token::Reserved(Box::new(":;".into())), start_pos)); } (':', ..) => return Some((Token::Colon, start_pos)), @@ -2028,7 +2033,7 @@ fn get_next_token_inner( } ('<', '-') => { eat_next(stream, pos); - return Some((Token::Reserved("<-".into()), start_pos)); + return Some((Token::Reserved(Box::new("<-".into())), start_pos)); } ('<', '<') => { eat_next(stream, pos); @@ -2045,7 +2050,7 @@ fn get_next_token_inner( } ('<', '|') => { eat_next(stream, pos); - return Some((Token::Reserved("<|".into()), start_pos)); + return Some((Token::Reserved(Box::new("<|".into())), start_pos)); } ('<', ..) => return Some((Token::LessThan, start_pos)), @@ -2073,14 +2078,14 @@ fn get_next_token_inner( if stream.peek_next() == Some('=') { eat_next(stream, pos); - return Some((Token::Reserved("!==".into()), start_pos)); + return Some((Token::Reserved(Box::new("!==".into())), start_pos)); } return Some((Token::NotEqualsTo, start_pos)); } ('!', '.') => { eat_next(stream, pos); - return Some((Token::Reserved("!.".into()), start_pos)); + return Some((Token::Reserved(Box::new("!.".into())), start_pos)); } ('!', ..) => return Some((Token::Bang, start_pos)), @@ -2094,7 +2099,7 @@ fn get_next_token_inner( } ('|', '>') => { eat_next(stream, pos); - return Some((Token::Reserved("|>".into()), start_pos)); + return Some((Token::Reserved(Box::new("|>".into())), start_pos)); } ('|', ..) => return Some((Token::Pipe, start_pos)), @@ -2114,7 +2119,7 @@ fn get_next_token_inner( } ('^', ..) => return Some((Token::XOr, start_pos)), - ('~', ..) => return Some((Token::Reserved("~".into()), start_pos)), + ('~', ..) => return Some((Token::Reserved(Box::new("~".into())), start_pos)), ('%', '=') => { eat_next(stream, pos); @@ -2122,9 +2127,9 @@ fn get_next_token_inner( } ('%', ..) => return Some((Token::Modulo, start_pos)), - ('@', ..) => return Some((Token::Reserved("@".into()), start_pos)), + ('@', ..) => return Some((Token::Reserved(Box::new("@".into())), start_pos)), - ('$', ..) => return Some((Token::Reserved("$".into()), start_pos)), + ('$', ..) => return Some((Token::Reserved(Box::new("$".into())), start_pos)), ('?', '.') => { eat_next(stream, pos); @@ -2132,7 +2137,7 @@ fn get_next_token_inner( #[cfg(not(feature = "no_object"))] Token::Elvis, #[cfg(feature = "no_object")] - Token::Reserved("?.".into()), + Token::Reserved(Box::new("?.".into())), start_pos, )); } @@ -2146,11 +2151,11 @@ fn get_next_token_inner( #[cfg(not(feature = "no_index"))] Token::QuestionBracket, #[cfg(feature = "no_index")] - Token::Reserved("?[".into()), + Token::Reserved(Box::new("?[".into())), start_pos, )); } - ('?', ..) => return Some((Token::Reserved("?".into()), start_pos)), + ('?', ..) => return Some((Token::Reserved(Box::new("?".into())), start_pos)), (ch, ..) if ch.is_whitespace() => (), @@ -2201,7 +2206,7 @@ fn get_identifier( ); } - (Token::Identifier(identifier), start_pos) + (Token::Identifier(identifier.into()), start_pos) } /// Is a keyword allowed as a function? @@ -2382,7 +2387,7 @@ impl<'a> Iterator for TokenIterator<'a> { } // Reserved keyword/symbol Some((Token::Reserved(s), pos)) => (match - (&*s, + (s.as_str(), #[cfg(not(feature = "no_custom_syntax"))] (!self.engine.custom_keywords.is_empty() && self.engine.custom_keywords.contains_key(&*s)), #[cfg(feature = "no_custom_syntax")] @@ -2438,7 +2443,7 @@ impl<'a> Iterator for TokenIterator<'a> { Some((token, pos)) if !self.engine.custom_keywords.is_empty() && self.engine.custom_keywords.contains_key(token.literal_syntax()) => { if !self.engine.disabled_symbols.is_empty() && self.engine.disabled_symbols.contains(token.literal_syntax()) { // Disabled standard keyword/symbol - (Token::Custom(token.literal_syntax().into()), pos) + (Token::Custom(Box::new(token.literal_syntax().into())), pos) } else { // Active standard keyword - should never be a custom keyword! unreachable!("{:?} is an active keyword", token) @@ -2446,7 +2451,7 @@ impl<'a> Iterator for TokenIterator<'a> { } // Disabled symbol Some((token, pos)) if !self.engine.disabled_symbols.is_empty() && self.engine.disabled_symbols.contains(token.literal_syntax()) => { - (Token::Reserved(token.literal_syntax().into()), pos) + (Token::Reserved(Box::new(token.literal_syntax().into())), pos) } // Normal symbol Some(r) => r, From e9a270f6baace6cf54087114671dcee97d4de829 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 25 Sep 2022 23:07:47 +0800 Subject: [PATCH 07/10] Fix feature builds. --- src/tokenizer.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/tokenizer.rs b/src/tokenizer.rs index e3d615e8..7cea143e 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -874,9 +874,9 @@ impl Token { "**=" => PowerOfAssign, #[cfg(feature = "no_object")] - "?." => Reserved(syntax.into()), + "?." => Reserved(Box::new(syntax.into())), #[cfg(feature = "no_index")] - "?[" => Reserved(syntax.into()), + "?[" => Reserved(Box::new(syntax.into())), #[cfg(not(feature = "no_function"))] "fn" => Fn, @@ -884,7 +884,7 @@ impl Token { "private" => Private, #[cfg(feature = "no_function")] - "fn" | "private" => Reserved(syntax.into()), + "fn" | "private" => Reserved(Box::new(syntax.into())), #[cfg(not(feature = "no_module"))] "import" => Import, @@ -894,7 +894,7 @@ impl Token { "as" => As, #[cfg(feature = "no_module")] - "import" | "export" | "as" => Reserved(syntax.into()), + "import" | "export" | "as" => Reserved(Box::new(syntax.into())), // List of reserved operators "===" | "!==" | "->" | "<-" | "?" | ":=" | ":;" | "~" | "!." | "::<" | "(*" | "*)" From 7d87a59315bb433bc6eeaf774180a29fdaacdf16 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 25 Sep 2022 23:45:51 +0800 Subject: [PATCH 08/10] Fix doc test. --- src/api/events.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/events.rs b/src/api/events.rs index 01463d2b..42511be3 100644 --- a/src/api/events.rs +++ b/src/api/events.rs @@ -172,7 +172,7 @@ impl Engine { /// engine.on_parse_token(|token, _, _| { /// match token { /// // Convert all integer literals to strings - /// Token::IntegerConstant(n) => Token::StringConstant(n.to_string().into()), + /// Token::IntegerConstant(n) => Token::StringConstant(Box::new(n.to_string().into())), /// // Convert 'begin' .. 'end' to '{' .. '}' /// Token::Identifier(s) if &*s == "begin" => Token::LeftBrace, /// Token::Identifier(s) if &*s == "end" => Token::RightBrace, From 335d12e182ed5d604ee1f12b0176727591542b8b Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 26 Sep 2022 12:19:45 +0800 Subject: [PATCH 09/10] Move Variant into separate file. --- src/api/events.rs | 1 + src/types/dynamic.rs | 105 ++----------------------------------------- src/types/mod.rs | 2 + src/types/variant.rs | 105 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 111 insertions(+), 102 deletions(-) create mode 100644 src/types/variant.rs diff --git a/src/api/events.rs b/src/api/events.rs index 42511be3..a44ee2a4 100644 --- a/src/api/events.rs +++ b/src/api/events.rs @@ -169,6 +169,7 @@ impl Engine { /// let mut engine = Engine::new(); /// /// // Register a token mapper. + /// # #[allow(deprecated)] /// engine.on_parse_token(|token, _, _| { /// match token { /// // Convert all integer literals to strings diff --git a/src/types/dynamic.rs b/src/types/dynamic.rs index b310b302..c1fe1dcf 100644 --- a/src/types/dynamic.rs +++ b/src/types/dynamic.rs @@ -1,7 +1,5 @@ -//! Helper module which defines the [`Dynamic`] data type and the -//! [`Any`] trait to to allow custom type handling. +//! Helper module which defines the [`Dynamic`] data type. -use crate::func::SendSync; use crate::{reify, ExclusiveRange, FnPtr, ImmutableString, InclusiveRange, INT}; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -14,6 +12,8 @@ use std::{ str::FromStr, }; +pub use super::Variant; + #[cfg(not(feature = "no_std"))] #[cfg(not(target_family = "wasm"))] pub use std::time::Instant; @@ -26,105 +26,6 @@ pub use instant::Instant; #[allow(dead_code)] const CHECKED: &str = "data type was checked"; -mod private { - use crate::func::SendSync; - use std::any::Any; - - /// A sealed trait that prevents other crates from implementing [`Variant`][super::Variant]. - pub trait Sealed {} - - impl Sealed for T {} -} - -/// _(internals)_ Trait to represent any type. -/// Exported under the `internals` feature only. -/// -/// This trait is sealed and cannot be implemented. -/// -/// Currently, [`Variant`] is not [`Send`] nor [`Sync`], so it can practically be any type. -/// Turn on the `sync` feature to restrict it to only types that implement [`Send`] `+` [`Sync`]. -#[cfg(not(feature = "sync"))] -pub trait Variant: Any + private::Sealed { - /// Convert this [`Variant`] trait object to [`&dyn Any`][Any]. - #[must_use] - fn as_any(&self) -> &dyn Any; - - /// Convert this [`Variant`] trait object to [`&mut dyn Any`][Any]. - #[must_use] - fn as_any_mut(&mut self) -> &mut dyn Any; - - /// Convert this [`Variant`] trait object to [`Box`][Any]. - #[must_use] - fn as_boxed_any(self: Box) -> Box; - - /// Get the name of this type. - #[must_use] - fn type_name(&self) -> &'static str; - - /// Clone this [`Variant`] trait object. - #[must_use] - fn clone_object(&self) -> Box; -} - -/// _(internals)_ Trait to represent any type. -/// Exported under the `internals` feature only. -/// -/// This trait is sealed and cannot be implemented. -#[cfg(feature = "sync")] -pub trait Variant: Any + Send + Sync + private::Sealed { - /// Convert this [`Variant`] trait object to [`&dyn Any`][Any]. - #[must_use] - fn as_any(&self) -> &dyn Any; - - /// Convert this [`Variant`] trait object to [`&mut dyn Any`][Any]. - #[must_use] - fn as_any_mut(&mut self) -> &mut dyn Any; - - /// Convert this [`Variant`] trait object to [`Box`][Any]. - #[must_use] - fn as_boxed_any(self: Box) -> Box; - - /// Get the name of this type. - #[must_use] - fn type_name(&self) -> &'static str; - - /// Clone this [`Variant`] trait object. - #[must_use] - fn clone_object(&self) -> Box; -} - -impl Variant for T { - #[inline(always)] - fn as_any(&self) -> &dyn Any { - self - } - #[inline(always)] - fn as_any_mut(&mut self) -> &mut dyn Any { - self - } - #[inline(always)] - fn as_boxed_any(self: Box) -> Box { - self - } - #[inline(always)] - fn type_name(&self) -> &'static str { - type_name::() - } - #[inline(always)] - fn clone_object(&self) -> Box { - Box::new(self.clone()) as Box - } -} - -impl dyn Variant { - /// Is this [`Variant`] a specific type? - #[inline(always)] - #[must_use] - pub fn is(&self) -> bool { - TypeId::of::() == self.type_id() - } -} - /// _(internals)_ Modes of access. /// Exported under the `internals` feature only. #[derive(Debug, Eq, PartialEq, Hash, Copy, Clone)] diff --git a/src/types/mod.rs b/src/types/mod.rs index 347bc85c..475ca2c9 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -9,6 +9,7 @@ pub mod immutable_string; pub mod interner; pub mod parse_error; pub mod scope; +pub mod variant; pub use bloom_filter::BloomFilterU64; pub use custom_types::{CustomTypeInfo, CustomTypesCollection}; @@ -21,3 +22,4 @@ pub use immutable_string::ImmutableString; pub use interner::StringsInterner; pub use parse_error::{LexError, ParseError, ParseErrorType}; pub use scope::Scope; +pub use variant::Variant; diff --git a/src/types/variant.rs b/src/types/variant.rs new file mode 100644 index 00000000..0e1d4e6c --- /dev/null +++ b/src/types/variant.rs @@ -0,0 +1,105 @@ +//! [`Variant`] trait to to allow custom type handling. + +use crate::func::SendSync; +use std::any::{type_name, Any, TypeId}; +#[cfg(feature = "no_std")] +use std::prelude::v1::*; + +mod private { + use crate::func::SendSync; + use std::any::Any; + + /// A sealed trait that prevents other crates from implementing [`Variant`][super::Variant]. + pub trait Sealed {} + + impl Sealed for T {} +} + +/// _(internals)_ Trait to represent any type. +/// Exported under the `internals` feature only. +/// +/// This trait is sealed and cannot be implemented. +/// +/// Currently, [`Variant`] is not [`Send`] nor [`Sync`], so it can practically be any type. +/// Turn on the `sync` feature to restrict it to only types that implement [`Send`] `+` [`Sync`]. +#[cfg(not(feature = "sync"))] +pub trait Variant: Any + private::Sealed { + /// Convert this [`Variant`] trait object to [`&dyn Any`][Any]. + #[must_use] + fn as_any(&self) -> &dyn Any; + + /// Convert this [`Variant`] trait object to [`&mut dyn Any`][Any]. + #[must_use] + fn as_any_mut(&mut self) -> &mut dyn Any; + + /// Convert this [`Variant`] trait object to [`Box`][Any]. + #[must_use] + fn as_boxed_any(self: Box) -> Box; + + /// Get the name of this type. + #[must_use] + fn type_name(&self) -> &'static str; + + /// Clone this [`Variant`] trait object. + #[must_use] + fn clone_object(&self) -> Box; +} + +/// _(internals)_ Trait to represent any type. +/// Exported under the `internals` feature only. +/// +/// This trait is sealed and cannot be implemented. +#[cfg(feature = "sync")] +pub trait Variant: Any + Send + Sync + private::Sealed { + /// Convert this [`Variant`] trait object to [`&dyn Any`][Any]. + #[must_use] + fn as_any(&self) -> &dyn Any; + + /// Convert this [`Variant`] trait object to [`&mut dyn Any`][Any]. + #[must_use] + fn as_any_mut(&mut self) -> &mut dyn Any; + + /// Convert this [`Variant`] trait object to [`Box`][Any]. + #[must_use] + fn as_boxed_any(self: Box) -> Box; + + /// Get the name of this type. + #[must_use] + fn type_name(&self) -> &'static str; + + /// Clone this [`Variant`] trait object. + #[must_use] + fn clone_object(&self) -> Box; +} + +impl Variant for T { + #[inline(always)] + fn as_any(&self) -> &dyn Any { + self + } + #[inline(always)] + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } + #[inline(always)] + fn as_boxed_any(self: Box) -> Box { + self + } + #[inline(always)] + fn type_name(&self) -> &'static str { + type_name::() + } + #[inline(always)] + fn clone_object(&self) -> Box { + Box::new(self.clone()) as Box + } +} + +impl dyn Variant { + /// Is this [`Variant`] a specific type? + #[inline(always)] + #[must_use] + pub fn is(&self) -> bool { + TypeId::of::() == self.type_id() + } +} From 8d1310c0f30141246ecd21acaaa375d994801da9 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 26 Sep 2022 18:14:45 +0800 Subject: [PATCH 10/10] Make Scope serializable. --- CHANGELOG.md | 4 ++ src/serde/de.rs | 86 +++++++++++++++++++--------------------- src/serde/deserialize.rs | 81 +++++++++++++++++++++++++++++++------ src/serde/serialize.rs | 34 +++++++++++++++- src/types/scope.rs | 32 ++++++++++++++- tests/serde.rs | 33 ++++++++++++++- 6 files changed, 208 insertions(+), 62 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a057eebe..d95bc3d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,12 @@ Version 1.11.0 ============== Enhancements +------------ * The look-ahead symbol for custom syntax now renders a string literal in quotes (instead of the generic term `string`). This facilitates more accurate parsing by separating strings and identifiers. +* Due to a code refactor, built-in operators for standard types now run even faster, in certain cases by 20-30%. +* `Scope` is now serializable and deserializable via `serde`. +* `Scope` now contains a const generic parameter that allows specifying how many entries to be kept inline. Version 1.10.1 diff --git a/src/serde/de.rs b/src/serde/de.rs index dbf43605..8f4cd237 100644 --- a/src/serde/de.rs +++ b/src/serde/de.rs @@ -9,9 +9,7 @@ use std::prelude::v1::*; use std::{any::type_name, fmt}; /// Deserializer for [`Dynamic`][crate::Dynamic]. -pub struct DynamicDeserializer<'de> { - value: &'de Dynamic, -} +pub struct DynamicDeserializer<'de>(&'de Dynamic); impl<'de> IntoDeserializer<'de, RhaiError> for &'de Dynamic { type Deserializer = DynamicDeserializer<'de>; @@ -19,7 +17,7 @@ impl<'de> IntoDeserializer<'de, RhaiError> for &'de Dynamic { #[inline(always)] #[must_use] fn into_deserializer(self) -> Self::Deserializer { - DynamicDeserializer { value: self } + DynamicDeserializer(self) } } @@ -31,7 +29,7 @@ impl<'de> DynamicDeserializer<'de> { #[inline(always)] #[must_use] pub const fn new(value: &'de Dynamic) -> Self { - Self { value } + Self(value) } /// Shortcut for a type conversion error. #[cold] @@ -43,12 +41,10 @@ impl<'de> DynamicDeserializer<'de> { #[cold] #[inline(never)] fn type_error_str(&self, error: &str) -> RhaiResultOf { - Err(ERR::ErrorMismatchOutputType( - error.into(), - self.value.type_name().into(), - Position::NONE, + Err( + ERR::ErrorMismatchOutputType(error.into(), self.0.type_name().into(), Position::NONE) + .into(), ) - .into()) } #[inline(always)] fn deserialize_int>(self, v: crate::INT, visitor: V) -> RhaiResultOf { @@ -128,7 +124,7 @@ impl<'de> Deserializer<'de> for DynamicDeserializer<'de> { type Error = RhaiError; fn deserialize_any>(self, visitor: V) -> RhaiResultOf { - match self.value.0 { + match self.0 .0 { Union::Unit(..) => self.deserialize_unit(visitor), Union::Bool(..) => self.deserialize_bool(visitor), Union::Str(..) => self.deserialize_str(visitor), @@ -182,110 +178,110 @@ impl<'de> Deserializer<'de> for DynamicDeserializer<'de> { } fn deserialize_bool>(self, visitor: V) -> RhaiResultOf { - visitor.visit_bool(self.value.as_bool().or_else(|_| self.type_error())?) + visitor.visit_bool(self.0.as_bool().or_else(|_| self.type_error())?) } fn deserialize_i8>(self, visitor: V) -> RhaiResultOf { - if let Ok(v) = self.value.as_int() { + if let Ok(v) = self.0.as_int() { self.deserialize_int(v, visitor) } else { - self.value + self.0 .downcast_ref::() .map_or_else(|| self.type_error(), |&x| visitor.visit_i8(x)) } } fn deserialize_i16>(self, visitor: V) -> RhaiResultOf { - if let Ok(v) = self.value.as_int() { + if let Ok(v) = self.0.as_int() { self.deserialize_int(v, visitor) } else { - self.value + self.0 .downcast_ref::() .map_or_else(|| self.type_error(), |&x| visitor.visit_i16(x)) } } fn deserialize_i32>(self, visitor: V) -> RhaiResultOf { - if let Ok(v) = self.value.as_int() { + if let Ok(v) = self.0.as_int() { self.deserialize_int(v, visitor) } else if cfg!(feature = "only_i32") { self.type_error() } else { - self.value + self.0 .downcast_ref::() .map_or_else(|| self.type_error(), |&x| visitor.visit_i32(x)) } } fn deserialize_i64>(self, visitor: V) -> RhaiResultOf { - if let Ok(v) = self.value.as_int() { + if let Ok(v) = self.0.as_int() { self.deserialize_int(v, visitor) } else if cfg!(not(feature = "only_i32")) { self.type_error() } else { - self.value + self.0 .downcast_ref::() .map_or_else(|| self.type_error(), |&x| visitor.visit_i64(x)) } } fn deserialize_i128>(self, visitor: V) -> RhaiResultOf { - if let Ok(v) = self.value.as_int() { + if let Ok(v) = self.0.as_int() { self.deserialize_int(v, visitor) } else if cfg!(not(feature = "only_i32")) { self.type_error() } else { - self.value + self.0 .downcast_ref::() .map_or_else(|| self.type_error(), |&x| visitor.visit_i128(x)) } } fn deserialize_u8>(self, visitor: V) -> RhaiResultOf { - if let Ok(v) = self.value.as_int() { + if let Ok(v) = self.0.as_int() { self.deserialize_int(v, visitor) } else { - self.value + self.0 .downcast_ref::() .map_or_else(|| self.type_error(), |&x| visitor.visit_u8(x)) } } fn deserialize_u16>(self, visitor: V) -> RhaiResultOf { - if let Ok(v) = self.value.as_int() { + if let Ok(v) = self.0.as_int() { self.deserialize_int(v, visitor) } else { - self.value + self.0 .downcast_ref::() .map_or_else(|| self.type_error(), |&x| visitor.visit_u16(x)) } } fn deserialize_u32>(self, visitor: V) -> RhaiResultOf { - if let Ok(v) = self.value.as_int() { + if let Ok(v) = self.0.as_int() { self.deserialize_int(v, visitor) } else { - self.value + self.0 .downcast_ref::() .map_or_else(|| self.type_error(), |&x| visitor.visit_u32(x)) } } fn deserialize_u64>(self, visitor: V) -> RhaiResultOf { - if let Ok(v) = self.value.as_int() { + if let Ok(v) = self.0.as_int() { self.deserialize_int(v, visitor) } else { - self.value + self.0 .downcast_ref::() .map_or_else(|| self.type_error(), |&x| visitor.visit_u64(x)) } } fn deserialize_u128>(self, visitor: V) -> RhaiResultOf { - if let Ok(v) = self.value.as_int() { + if let Ok(v) = self.0.as_int() { self.deserialize_int(v, visitor) } else { - self.value + self.0 .downcast_ref::() .map_or_else(|| self.type_error(), |&x| visitor.visit_u128(x)) } @@ -294,7 +290,7 @@ impl<'de> Deserializer<'de> for DynamicDeserializer<'de> { fn deserialize_f32>(self, _visitor: V) -> RhaiResultOf { #[cfg(not(feature = "no_float"))] return self - .value + .0 .downcast_ref::() .map_or_else(|| self.type_error(), |&x| _visitor.visit_f32(x)); @@ -304,7 +300,7 @@ impl<'de> Deserializer<'de> for DynamicDeserializer<'de> { use rust_decimal::prelude::ToPrimitive; return self - .value + .0 .downcast_ref::() .and_then(|&x| x.to_f32()) .map_or_else(|| self.type_error(), |v| _visitor.visit_f32(v)); @@ -318,7 +314,7 @@ impl<'de> Deserializer<'de> for DynamicDeserializer<'de> { fn deserialize_f64>(self, _visitor: V) -> RhaiResultOf { #[cfg(not(feature = "no_float"))] return self - .value + .0 .downcast_ref::() .map_or_else(|| self.type_error(), |&x| _visitor.visit_f64(x)); @@ -328,7 +324,7 @@ impl<'de> Deserializer<'de> for DynamicDeserializer<'de> { use rust_decimal::prelude::ToPrimitive; return self - .value + .0 .downcast_ref::() .and_then(|&x| x.to_f64()) .map_or_else(|| self.type_error(), |v| _visitor.visit_f64(v)); @@ -340,13 +336,13 @@ impl<'de> Deserializer<'de> for DynamicDeserializer<'de> { } fn deserialize_char>(self, visitor: V) -> RhaiResultOf { - self.value + self.0 .downcast_ref::() .map_or_else(|| self.type_error(), |&x| visitor.visit_char(x)) } fn deserialize_str>(self, visitor: V) -> RhaiResultOf { - self.value.downcast_ref::().map_or_else( + self.0.downcast_ref::().map_or_else( || self.type_error(), |x| visitor.visit_borrowed_str(x.as_str()), ) @@ -359,7 +355,7 @@ impl<'de> Deserializer<'de> for DynamicDeserializer<'de> { fn deserialize_bytes>(self, _visitor: V) -> RhaiResultOf { #[cfg(not(feature = "no_index"))] return self - .value + .0 .downcast_ref::() .map_or_else(|| self.type_error(), |x| _visitor.visit_bytes(x)); @@ -372,7 +368,7 @@ impl<'de> Deserializer<'de> for DynamicDeserializer<'de> { } fn deserialize_option>(self, visitor: V) -> RhaiResultOf { - if self.value.is::<()>() { + if self.0.is::<()>() { visitor.visit_none() } else { visitor.visit_some(self) @@ -380,7 +376,7 @@ impl<'de> Deserializer<'de> for DynamicDeserializer<'de> { } fn deserialize_unit>(self, visitor: V) -> RhaiResultOf { - self.value + self.0 .downcast_ref::<()>() .map_or_else(|| self.type_error(), |_| visitor.visit_unit()) } @@ -403,7 +399,7 @@ impl<'de> Deserializer<'de> for DynamicDeserializer<'de> { fn deserialize_seq>(self, _visitor: V) -> RhaiResultOf { #[cfg(not(feature = "no_index"))] - return self.value.downcast_ref::().map_or_else( + return self.0.downcast_ref::().map_or_else( || self.type_error(), |arr| _visitor.visit_seq(IterateDynamicArray::new(arr.iter())), ); @@ -427,7 +423,7 @@ impl<'de> Deserializer<'de> for DynamicDeserializer<'de> { fn deserialize_map>(self, _visitor: V) -> RhaiResultOf { #[cfg(not(feature = "no_object"))] - return self.value.downcast_ref::().map_or_else( + return self.0.downcast_ref::().map_or_else( || self.type_error(), |map| { _visitor.visit_map(IterateMap::new( @@ -456,11 +452,11 @@ impl<'de> Deserializer<'de> for DynamicDeserializer<'de> { _variants: &'static [&'static str], visitor: V, ) -> RhaiResultOf { - if let Some(s) = self.value.read_lock::() { + if let Some(s) = self.0.read_lock::() { visitor.visit_enum(s.as_str().into_deserializer()) } else { #[cfg(not(feature = "no_object"))] - return self.value.downcast_ref::().map_or_else( + return self.0.downcast_ref::().map_or_else( || self.type_error(), |map| { let mut iter = map.iter(); diff --git a/src/serde/deserialize.rs b/src/serde/deserialize.rs index a41f8882..e6c5ae3a 100644 --- a/src/serde/deserialize.rs +++ b/src/serde/deserialize.rs @@ -1,14 +1,17 @@ //! Implementations of [`serde::Deserialize`]. -use crate::{Dynamic, ImmutableString, INT}; -use serde::de::{Deserialize, Deserializer, Error, Visitor}; +use crate::{Dynamic, Identifier, ImmutableString, Scope, INT}; +use serde::{ + de::{Error, SeqAccess, Visitor}, + Deserialize, Deserializer, +}; use std::fmt; #[cfg(feature = "no_std")] use std::prelude::v1::*; struct DynamicVisitor; -impl<'d> Visitor<'d> for DynamicVisitor { +impl<'de> Visitor<'de> for DynamicVisitor { type Value = Dynamic; #[cold] @@ -145,12 +148,12 @@ impl<'d> Visitor<'d> for DynamicVisitor { } #[inline(always)] - fn visit_newtype_struct>(self, de: D) -> Result { + fn visit_newtype_struct>(self, de: D) -> Result { Deserialize::deserialize(de) } #[cfg(not(feature = "no_index"))] - fn visit_seq>(self, mut seq: A) -> Result { + fn visit_seq>(self, mut seq: A) -> Result { let mut arr = crate::Array::new(); while let Some(v) = seq.next_element()? { @@ -161,7 +164,7 @@ impl<'d> Visitor<'d> for DynamicVisitor { } #[cfg(not(feature = "no_object"))] - fn visit_map>(self, mut map: M) -> Result { + fn visit_map>(self, mut map: M) -> Result { let mut m = crate::Map::new(); while let Some((k, v)) = map.next_entry()? { @@ -172,17 +175,69 @@ impl<'d> Visitor<'d> for DynamicVisitor { } } -impl<'d> Deserialize<'d> for Dynamic { +impl<'de> Deserialize<'de> for Dynamic { #[inline(always)] - fn deserialize>(de: D) -> Result { - de.deserialize_any(DynamicVisitor) + fn deserialize>(deserializer: D) -> Result { + deserializer.deserialize_any(DynamicVisitor) } } -impl<'d> Deserialize<'d> for ImmutableString { - #[inline] - fn deserialize>(de: D) -> Result { - let s: String = Deserialize::deserialize(de)?; +impl<'de> Deserialize<'de> for ImmutableString { + #[inline(always)] + fn deserialize>(deserializer: D) -> Result { + let s: String = Deserialize::deserialize(deserializer)?; Ok(s.into()) } } + +impl<'de> Deserialize<'de> for Scope<'de> { + #[inline(always)] + fn deserialize>(deserializer: D) -> Result { + #[derive(Debug, Clone, Hash, Deserialize)] + struct ScopeEntry { + pub name: Identifier, + pub value: Dynamic, + #[serde(default)] + pub is_constant: bool, + } + + struct VecVisitor; + + impl<'de> Visitor<'de> for VecVisitor { + type Value = Scope<'static>; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a sequence") + } + + #[inline] + fn visit_seq(self, mut access: A) -> Result + where + A: SeqAccess<'de>, + { + let mut scope = if let Some(size) = access.size_hint() { + Scope::with_capacity(size) + } else { + Scope::new() + }; + + while let Some(ScopeEntry { + name, + value, + is_constant, + }) = access.next_element()? + { + if is_constant { + scope.push_constant_dynamic(name, value); + } else { + scope.push_dynamic(name, value); + } + } + + Ok(scope) + } + } + + deserializer.deserialize_seq(VecVisitor) + } +} diff --git a/src/serde/serialize.rs b/src/serde/serialize.rs index 4aaa797e..818b0e9e 100644 --- a/src/serde/serialize.rs +++ b/src/serde/serialize.rs @@ -1,8 +1,8 @@ //! Implementations of [`serde::Serialize`]. use crate::types::dynamic::Union; -use crate::{Dynamic, ImmutableString}; -use serde::ser::{Serialize, Serializer}; +use crate::{Dynamic, ImmutableString, Scope}; +use serde::{ser::SerializeSeq, Serialize, Serializer}; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -88,3 +88,33 @@ impl Serialize for ImmutableString { ser.serialize_str(self.as_str()) } } + +impl Serialize for Scope<'_> { + #[inline(always)] + fn serialize(&self, ser: S) -> Result { + #[derive(Debug, Clone, Hash, Serialize)] + struct ScopeEntry<'a> { + pub name: &'a str, + pub value: &'a Dynamic, + #[serde(default, skip_serializing_if = "is_false")] + pub is_constant: bool, + } + + fn is_false(value: &bool) -> bool { + !value + } + + let mut ser = ser.serialize_seq(Some(self.len()))?; + + for (name, is_constant, value) in self.iter_raw() { + let entry = ScopeEntry { + name, + value, + is_constant, + }; + ser.serialize_element(&entry)?; + } + + ser.end() + } +} diff --git a/src/types/scope.rs b/src/types/scope.rs index 46dd1d1f..4cc5a91c 100644 --- a/src/types/scope.rs +++ b/src/types/scope.rs @@ -22,6 +22,14 @@ const SCOPE_ENTRIES_INLINED: usize = 8; /// Currently the lifetime parameter is not used, but it is not guaranteed to remain unused for /// future versions. Until then, `'static` can be used. /// +/// # Constant Generic Parameter +/// +/// There is a constant generic parameter that indicates how many entries to keep inline. +/// As long as the number of entries does not exceed this limit, no allocations occur. +/// The default is 8. +/// +/// A larger value makes [`Scope`] larger, but reduces the chance of allocations. +/// /// # Thread Safety /// /// Currently, [`Scope`] is neither [`Send`] nor [`Sync`]. Turn on the `sync` feature to make it @@ -61,7 +69,7 @@ const SCOPE_ENTRIES_INLINED: usize = 8; // // [`Dynamic`] is reasonably small so packing it tightly improves cache performance. #[derive(Debug, Hash, Default)] -pub struct Scope<'a> { +pub struct Scope<'a, const N: usize = SCOPE_ENTRIES_INLINED> { /// Current value of the entry. values: SmallVec<[Dynamic; SCOPE_ENTRIES_INLINED]>, /// Name of the entry. @@ -170,6 +178,28 @@ impl Scope<'_> { dummy: PhantomData, } } + /// Create a new [`Scope`] with a particular capacity. + /// + /// # Example + /// + /// ``` + /// use rhai::Scope; + /// + /// let mut my_scope = Scope::with_capacity(10); + /// + /// my_scope.push("x", 42_i64); + /// assert_eq!(my_scope.get_value::("x").expect("x should exist"), 42); + /// ``` + #[inline(always)] + #[must_use] + pub fn with_capacity(capacity: usize) -> Self { + Self { + values: SmallVec::with_capacity(capacity), + names: SmallVec::with_capacity(capacity), + aliases: SmallVec::with_capacity(capacity), + dummy: PhantomData, + } + } /// Empty the [`Scope`]. /// /// # Example diff --git a/tests/serde.rs b/tests/serde.rs index 45c16b72..4a08bb81 100644 --- a/tests/serde.rs +++ b/tests/serde.rs @@ -2,7 +2,7 @@ use rhai::{ serde::{from_dynamic, to_dynamic}, - Dynamic, Engine, EvalAltResult, ImmutableString, INT, + Dynamic, Engine, EvalAltResult, ImmutableString, Scope, INT, }; use serde::{Deserialize, Serialize}; use serde_json::json; @@ -823,3 +823,34 @@ fn test_serde_json_borrowed_string() { println!("value: {value:?}"); let _: Dynamic = serde_json::from_value(value).unwrap(); } + +#[test] +#[cfg(not(feature = "no_object"))] +fn test_serde_scope() { + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] + struct TestStruct { + foo: Option, + } + + let mut scope = Scope::new(); + scope.push("x", 42 as INT); + scope.push_constant("y", true); + scope.push("z", TestStruct { foo: None }); + + let json = serde_json::to_string(&scope).unwrap(); + + assert_eq!( + json, + r#"[{"name":"x","value":42},{"name":"y","value":true,"is_constant":true},{"name":"z","value":"serde::test_serde_scope::TestStruct"}]"# + ); + + scope = serde_json::from_str(&json).unwrap(); + + assert_eq!(scope.len(), 3); + assert_eq!(scope.get_value::("x").unwrap(), 42); + assert_eq!(scope.get_value::("y").unwrap(), true); + assert_eq!( + scope.get_value::("z").unwrap(), + "serde::test_serde_scope::TestStruct" + ); +}