diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 493911b5..df6f3b20 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,7 +18,7 @@ jobs: os: [ubuntu-latest] flags: - "" - - "--features serde,internals" + - "--features metadata,internals" - "--features unchecked" - "--features sync" - "--features no_optimize" @@ -32,7 +32,7 @@ jobs: - "--features no_module" - "--features no_closure" - "--features unicode-xid-ident" - - "--features sync,no_function,no_float,no_optimize,no_module,no_closure,serde,unchecked" + - "--features sync,no_function,no_float,no_optimize,no_module,no_closure,metadata,unchecked" toolchain: [stable] experimental: [false] include: diff --git a/Cargo.toml b/Cargo.toml index cb68a5e0..e433e996 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ members = [ [package] name = "rhai" -version = "0.19.10" +version = "0.19.11" edition = "2018" authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung", "jhwgh1968"] description = "Embedded scripting for Rust" @@ -23,7 +23,8 @@ keywords = [ "scripting" ] categories = [ "no-std", "embedded", "wasm", "parser-implementations" ] [dependencies] -smallvec = { version = "1.4.2", default-features = false } +smallvec = { version = "1.6", default-features = false, features = ["union"] } +ahash = { version = "0.5", default-features = false } rhai_codegen = { version = "0.3", path = "codegen" } [features] @@ -45,7 +46,11 @@ unicode-xid-ident = ["unicode-xid"] # allow Unicode Standard Annex #31 for ident metadata = [ "serde", "serde_json"] # enables exporting functions metadata to JSON # compiling for no-std -no_std = [ "smallvec/union", "num-traits/libm", "hashbrown", "core-error", "libm", "ahash" ] +no_std = [ "smallvec/union", "num-traits/libm", "hashbrown", "core-error", "libm", "ahash/compile-time-rng" ] + +# compiling for WASM +wasm-bindgen = [ "instant/wasm-bindgen" ] +stdweb = [ "instant/stdweb" ] [profile.release] lto = "fat" @@ -54,52 +59,46 @@ codegen-units = 1 #panic = 'abort' # remove stack backtrace for no-std [dependencies.libm] -version = "0.2.1" +version = "0.2" default_features = false optional = true [dependencies.num-traits] -version = "0.2.11" +version = "0.2" default-features = false optional = true [dependencies.core-error] -version = "0.0.0" +version = "0.0" default_features = false features = ["alloc"] optional = true [dependencies.hashbrown] -version = "0.7.1" +version = "0.7" default-features = false features = ["ahash", "nightly", "inline-more"] optional = true -[dependencies.ahash] -version = "0.3.2" -default-features = false -features = ["compile-time-rng"] -optional = true - [dependencies.serde] -version = "1.0.116" +version = "1.0" default_features = false features = ["derive", "alloc"] optional = true [dependencies.serde_json] -version = "1.0.60" +version = "1.0" default_features = false features = ["alloc"] optional = true [dependencies.unicode-xid] -version = "0.2.1" +version = "0.2" default_features = false optional = true [target.'cfg(target_arch = "wasm32")'.dependencies] -instant= { version = "0.1.7", features = ["wasm-bindgen"] } # WASM implementation of std::time::Instant +instant= { version = "0.1" } # WASM implementation of std::time::Instant [package.metadata.docs.rs] -features = [ "serde", "internals" ] +features = [ "metadata", "internals" ] diff --git a/README.md b/README.md index 82aa7dec..83428cf0 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Supported targets and builds * All common CPU targets for Windows, Linux and MacOS. * WebAssembly (WASM) * `no-std` -* Minimum Rust version 1.45 +* Minimum Rust version 1.49 Standard features @@ -35,7 +35,7 @@ Standard features * Freely pass Rust variables/constants into a script via an external [`Scope`](https://rhaiscript.github.io/book/rust/scope.html) - all clonable Rust types are supported; no need to implement any special trait. * Easily [call a script-defined function](https://rhaiscript.github.io/book/engine/call-fn.html) from Rust. * Relatively little `unsafe` code (yes there are some for performance reasons). -* Few dependencies (currently only [`smallvec`](https://crates.io/crates/smallvec)). +* Few dependencies (currently only [`smallvec`](https://crates.io/crates/smallvec) and [`ahash`](https://crates.io/crates/ahash)). * Re-entrant scripting engine can be made `Send + Sync` (via the `sync` feature). * Scripts are [optimized](https://rhaiscript.github.io/book/engine/optimize.html) (useful for template-based machine-generated scripts) for repeated evaluations. * Easy custom API development via [plugins](https://rhaiscript.github.io/book/plugins/index.html) system powered by procedural macros. diff --git a/RELEASES.md b/RELEASES.md index 1240a3cf..4fcc5001 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,6 +1,25 @@ Rhai Release Notes ================== +Version 0.19.11 +=============== + +Breaking changes +---------------- + +Rust compiler requirement raised to 1.49. + +Bug fixes +--------- + +* Fixes compilation errors in `metadata` feature build. + +Enhancements +------------ + +* `ahash` is used to hash function call parameters. This should yield speed improvements. + + Version 0.19.10 =============== diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml index 073843b1..3088cdab 100644 --- a/codegen/Cargo.toml +++ b/codegen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rhai_codegen" -version = "0.3.1" +version = "0.3.2" edition = "2018" authors = ["jhwgh1968"] description = "Procedural macro support package for Rhai, a scripting language for Rust" diff --git a/src/ast.rs b/src/ast.rs index 12bf72ec..910352fe 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -7,9 +7,9 @@ use crate::stdlib::{ borrow::Cow, boxed::Box, fmt, - hash::{Hash, Hasher}, + hash::Hash, num::{NonZeroU64, NonZeroUsize}, - ops::{Add, AddAssign, Deref, DerefMut}, + ops::{Add, AddAssign}, string::String, vec, vec::Vec, @@ -60,7 +60,7 @@ impl FnAccess { /// _(INTERNALS)_ A type containing information on a scripted function. /// Exported under the `internals` feature only. /// -/// # WARNING +/// # Volatile Data Structure /// /// This type is volatile and may change. #[derive(Debug, Clone)] @@ -266,12 +266,23 @@ impl AST { &mut self.statements } /// Get the internal shared [`Module`] containing all script-defined functions. + #[cfg(not(feature = "internals"))] #[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_function"))] #[inline(always)] pub(crate) fn shared_lib(&self) -> Shared { self.functions.clone() } + /// _(INTERNALS)_ Get the internal shared [`Module`] containing all script-defined functions. + /// Exported under the `internals` feature only. + #[cfg(feature = "internals")] + #[deprecated = "this method is volatile and may change"] + #[cfg(not(feature = "no_module"))] + #[cfg(not(feature = "no_function"))] + #[inline(always)] + pub fn shared_lib(&self) -> Shared { + self.functions.clone() + } /// Get the internal [`Module`] containing all script-defined functions. #[cfg(not(feature = "internals"))] #[inline(always)] @@ -755,7 +766,7 @@ impl AsRef for AST { /// _(INTERNALS)_ An identifier containing an [immutable string][ImmutableString] name and a [position][Position]. /// Exported under the `internals` feature only. /// -/// # WARNING +/// # Volatile Data Structure /// /// This type is volatile and may change. #[derive(Clone, Eq, PartialEq, Hash)] @@ -776,7 +787,7 @@ impl fmt::Debug for Ident { /// _(INTERNALS)_ A type encapsulating the mode of a `return`/`throw` statement. /// Exported under the `internals` feature only. /// -/// # WARNING +/// # Volatile Data Structure /// /// This type is volatile and may change. #[derive(Debug, Eq, PartialEq, Clone, Copy, Hash)] @@ -790,7 +801,7 @@ pub enum ReturnType { /// _(INTERNALS)_ An [`AST`] node, consisting of either an [`Expr`] or a [`Stmt`]. /// Exported under the `internals` feature only. /// -/// # WARNING +/// # Volatile Data Structure /// /// This type is volatile and may change. #[derive(Debug, Clone, Hash)] @@ -814,7 +825,7 @@ impl<'a> From<&'a Expr> for ASTNode<'a> { /// _(INTERNALS)_ A statement. /// Exported under the `internals` feature only. /// -/// # WARNING +/// # Volatile Data Structure /// /// This type is volatile and may change. #[derive(Debug, Clone, Hash)] @@ -1061,7 +1072,7 @@ impl Stmt { /// _(INTERNALS)_ A custom syntax expression. /// Exported under the `internals` feature only. /// -/// # WARNING +/// # Volatile Data Structure /// /// This type is volatile and may change. #[derive(Debug, Clone, Hash)] @@ -1077,7 +1088,7 @@ pub struct CustomExpr { /// _(INTERNALS)_ A binary expression. /// Exported under the `internals` feature only. /// -/// # WARNING +/// # Volatile Data Structure /// /// This type is volatile and may change. #[derive(Debug, Clone, Hash)] @@ -1091,7 +1102,7 @@ pub struct BinaryExpr { /// _(INTERNALS)_ A function call. /// Exported under the `internals` feature only. /// -/// # WARNING +/// # Volatile Data Structure /// /// This type is volatile and may change. #[derive(Debug, Clone, Default, Hash)] @@ -1120,7 +1131,7 @@ pub struct FloatWrapper(FLOAT); #[cfg(not(feature = "no_float"))] impl Hash for FloatWrapper { - fn hash(&self, state: &mut H) { + fn hash(&self, state: &mut H) { self.0.to_le_bytes().hash(state); } } @@ -1140,7 +1151,7 @@ impl AsMut for FloatWrapper { } #[cfg(not(feature = "no_float"))] -impl Deref for FloatWrapper { +impl crate::stdlib::ops::Deref for FloatWrapper { type Target = FLOAT; fn deref(&self) -> &Self::Target { @@ -1149,7 +1160,7 @@ impl Deref for FloatWrapper { } #[cfg(not(feature = "no_float"))] -impl DerefMut for FloatWrapper { +impl crate::stdlib::ops::DerefMut for FloatWrapper { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } @@ -1186,7 +1197,7 @@ impl FloatWrapper { /// _(INTERNALS)_ An expression sub-tree. /// Exported under the `internals` feature only. /// -/// # WARNING +/// # Volatile Data Structure /// /// This type is volatile and may change. #[derive(Debug, Clone, Hash)] diff --git a/src/engine.rs b/src/engine.rs index 77f0b8aa..c6dce576 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -45,7 +45,7 @@ pub const TYPICAL_MAP_SIZE: usize = 8; // Small maps are typical /// _(INTERNALS)_ A stack of imported [modules][Module]. /// Exported under the `internals` feature only. /// -/// # WARNING +/// # Volatile Data Structure /// /// This type is volatile and may change. // @@ -498,7 +498,7 @@ impl> From for Target<'_> { /// _(INTERNALS)_ A type that holds all the current states of the [`Engine`]. /// Exported under the `internals` feature only. /// -/// # WARNING +/// # Volatile Data Structure /// /// This type is volatile and may change. #[derive(Debug, Clone, Default)] @@ -538,7 +538,7 @@ impl State { /// _(INTERNALS)_ A type containing all the limits imposed by the [`Engine`]. /// Exported under the `internals` feature only. /// -/// # WARNING +/// # Volatile Data Structure /// /// This type is volatile and may change. #[cfg(not(feature = "unchecked"))] diff --git a/src/fn_call.rs b/src/fn_call.rs index 21bc570c..5ee9387f 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -269,7 +269,7 @@ impl Engine { if let Some(prop) = extract_prop_from_getter(fn_name) { return EvalAltResult::ErrorDotExpr( format!( - "Failed to get property '{}' of '{}' - the property may not exist, or it may be write-only", + "Unknown property '{}' - a getter is not registered for type '{}'", prop, self.map_type_name(args[0].type_name()) ), @@ -283,7 +283,7 @@ impl Engine { if let Some(prop) = extract_prop_from_setter(fn_name) { return EvalAltResult::ErrorDotExpr( format!( - "Failed to set property '{}' of '{}' - the property may not exist, may be read-only, or '{}' is the wrong type", + "No writable property '{}' - a setter is not registered for type '{}' to handle '{}'", prop, self.map_type_name(args[0].type_name()), self.map_type_name(args[1].type_name()), diff --git a/src/module/mod.rs b/src/module/mod.rs index 0d9046fe..64788b74 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -1676,9 +1676,8 @@ impl Module { } /// Get an iterator to the functions in the [`Module`]. - #[cfg(not(feature = "no_optimize"))] - #[cfg(not(feature = "no_function"))] #[inline(always)] + #[allow(dead_code)] pub(crate) fn iter_fn(&self) -> impl Iterator { self.functions.values() } @@ -2004,7 +2003,7 @@ impl Module { /// A [`StaticVec`] is used because most namespace-qualified access contains only one level, /// and it is wasteful to always allocate a [`Vec`] with one element. /// -/// # WARNING +/// # Volatile Data Structure /// /// This type is volatile and may change. #[derive(Clone, Eq, PartialEq, Default, Hash)] diff --git a/src/parse_error.rs b/src/parse_error.rs index e42c851a..a965af0b 100644 --- a/src/parse_error.rs +++ b/src/parse_error.rs @@ -11,7 +11,7 @@ use crate::{EvalAltResult, Position}; /// _(INTERNALS)_ Error encountered when tokenizing the script text. /// Exported under the `internals` feature only. /// -/// # WARNING +/// # Volatile Data Structure /// /// This type is volatile and may change. #[derive(Debug, Eq, PartialEq, Clone, Hash)] diff --git a/src/result.rs b/src/result.rs index d0ca7e52..39a0b315 100644 --- a/src/result.rs +++ b/src/result.rs @@ -178,8 +178,9 @@ impl fmt::Display for EvalAltResult { Self::ErrorDotExpr(s, _) if !s.is_empty() => write!(f, "{}", s)?, - Self::ErrorIndexingType(_, _) - | Self::ErrorUnboundThis(_) + Self::ErrorIndexingType(s, _) => write!(f, "Indexer not registered for type '{}'", s)?, + + Self::ErrorUnboundThis(_) | Self::ErrorFor(_) | Self::ErrorInExpr(_) | Self::ErrorDotExpr(_, _) diff --git a/src/serde_impl/de.rs b/src/serde_impl/de.rs index e17d177c..eb35ae63 100644 --- a/src/serde_impl/de.rs +++ b/src/serde_impl/de.rs @@ -4,10 +4,8 @@ use super::str::ImmutableStringDeserializer; use crate::dynamic::Union; use crate::stdlib::{any::type_name, boxed::Box, fmt, string::ToString}; use crate::{Dynamic, EvalAltResult, ImmutableString, LexError, Position}; -use serde::de::{ - DeserializeSeed, Deserializer, Error, IntoDeserializer, MapAccess, SeqAccess, Visitor, -}; -use serde::Deserialize; +use serde::de::{DeserializeSeed, Error, IntoDeserializer, MapAccess, SeqAccess, Visitor}; +use serde::{Deserialize, Deserializer}; #[cfg(not(feature = "no_index"))] use crate::Array; @@ -50,13 +48,9 @@ impl<'de> DynamicDeserializer<'de> { visitor: V, ) -> Result> { #[cfg(not(feature = "only_i32"))] - { - visitor.visit_i64(v) - } + return visitor.visit_i64(v); #[cfg(feature = "only_i32")] - { - visitor.visit_i32(v) - } + return visitor.visit_i32(v); } } diff --git a/src/serde_impl/deserialize.rs b/src/serde_impl/deserialize.rs new file mode 100644 index 00000000..8d998ad8 --- /dev/null +++ b/src/serde_impl/deserialize.rs @@ -0,0 +1,144 @@ +//! Implementations of [`serde::Deserialize`]. + +use crate::stdlib::{fmt, string::ToString}; +use crate::{Dynamic, ImmutableString, INT}; +use serde::de::{Deserialize, Deserializer, Error, MapAccess, SeqAccess, Visitor}; + +#[cfg(not(feature = "no_index"))] +use crate::Array; + +#[cfg(not(feature = "no_object"))] +use crate::Map; + +struct DynamicVisitor; + +impl<'d> Visitor<'d> for DynamicVisitor { + type Value = Dynamic; + + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("any type that can be converted into a Dynamic") + } + fn visit_bool(self, v: bool) -> Result { + Ok(v.into()) + } + fn visit_i8(self, v: i8) -> Result { + Ok(INT::from(v).into()) + } + fn visit_i16(self, v: i16) -> Result { + Ok(INT::from(v).into()) + } + fn visit_i32(self, v: i32) -> Result { + Ok(INT::from(v).into()) + } + fn visit_i64(self, v: i64) -> Result { + #[cfg(not(feature = "only_i32"))] + return Ok(v.into()); + #[cfg(feature = "only_i32")] + if v > i32::MAX as i64 { + return Ok(Dynamic::from(v)); + } else { + return self.visit_i32(v as i32); + } + } + fn visit_u8(self, v: u8) -> Result { + Ok(INT::from(v).into()) + } + fn visit_u16(self, v: u16) -> Result { + Ok(INT::from(v).into()) + } + fn visit_u32(self, v: u32) -> Result { + #[cfg(not(feature = "only_i32"))] + return Ok(INT::from(v).into()); + #[cfg(feature = "only_i32")] + if v > i32::MAX as u32 { + return Ok(Dynamic::from(v)); + } else { + return self.visit_i32(v as i32); + } + } + fn visit_u64(self, v: u64) -> Result { + #[cfg(not(feature = "only_i32"))] + if v > i64::MAX as u64 { + return Ok(Dynamic::from(v)); + } else { + return self.visit_i64(v as i64); + } + #[cfg(feature = "only_i32")] + if v > i32::MAX as u64 { + return Ok(Dynamic::from(v)); + } else { + return self.visit_i32(v as i32); + } + } + + #[cfg(not(feature = "no_float"))] + fn visit_f32(self, v: f32) -> Result { + #[cfg(not(feature = "f32_float"))] + return self.visit_f64(v as f64); + #[cfg(feature = "f32_float")] + return Ok(v.into()); + } + #[cfg(not(feature = "no_float"))] + fn visit_f64(self, v: f64) -> Result { + #[cfg(not(feature = "f32_float"))] + return Ok(v.into()); + #[cfg(feature = "f32_float")] + return self.visit_f32(v as f32); + } + + fn visit_char(self, v: char) -> Result { + self.visit_string(v.to_string()) + } + fn visit_str(self, v: &str) -> Result { + Ok(v.into()) + } + fn visit_borrowed_str(self, v: &str) -> Result { + self.visit_str(v) + } + fn visit_string(self, v: String) -> Result { + Ok(v.into()) + } + + fn visit_unit(self) -> Result { + Ok(Dynamic::UNIT) + } + + fn visit_newtype_struct>(self, de: D) -> Result { + Deserialize::deserialize(de) + } + + #[cfg(not(feature = "no_index"))] + fn visit_seq>(self, mut seq: A) -> Result { + let mut arr: Array = Default::default(); + + while let Some(v) = seq.next_element()? { + arr.push(v); + } + + Ok(arr.into()) + } + + #[cfg(not(feature = "no_object"))] + fn visit_map>(self, mut map: M) -> Result { + let mut m: Map = Default::default(); + + while let Some((k, v)) = map.next_entry()? { + m.insert(k, v); + } + + Ok(m.into()) + } +} + +impl<'d> Deserialize<'d> for Dynamic { + fn deserialize>(de: D) -> Result { + de.deserialize_any(DynamicVisitor) + } +} + +impl<'d> Deserialize<'d> for ImmutableString { + fn deserialize>(de: D) -> Result { + let s: String = Deserialize::deserialize(de)?; + Ok(s.into()) + } +} diff --git a/src/serde_impl/metadata.rs b/src/serde_impl/metadata.rs index 80a6cd01..94fc32c6 100644 --- a/src/serde_impl/metadata.rs +++ b/src/serde_impl/metadata.rs @@ -2,7 +2,6 @@ use crate::stdlib::{ cmp::Ordering, collections::BTreeMap, string::{String, ToString}, - vec, vec::Vec, }; use crate::{Engine, AST}; @@ -89,7 +88,7 @@ struct FnMetadata { #[serde(default, skip_serializing_if = "Option::is_none")] pub return_type: Option, pub signature: String, - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub doc_comments: Vec, } @@ -125,34 +124,35 @@ impl From<&crate::module::FuncInfo> for FnMetadata { FnType::Native }, num_params: info.params, - params: if let Some(ref names) = info.param_names { - names - .iter() - .take(info.params) - .map(|s| { - let mut seg = s.splitn(2, ':'); - let name = seg - .next() - .map(|s| s.trim().to_string()) - .unwrap_or("_".to_string()); - let typ = seg.next().map(|s| s.trim().to_string()); - FnParam { name, typ } - }) - .collect() - } else { - vec![] - }, - return_type: if let Some(ref names) = info.param_names { - names - .last() - .map(|s| s.to_string()) - .or_else(|| Some("()".to_string())) - } else { - None - }, + params: info + .param_names + .iter() + .take(info.params) + .map(|s| { + let mut seg = s.splitn(2, ':'); + let name = seg + .next() + .map(|s| s.trim().to_string()) + .unwrap_or("_".to_string()); + let typ = seg.next().map(|s| s.trim().to_string()); + FnParam { name, typ } + }) + .collect(), + return_type: info + .param_names + .last() + .map(|s| s.to_string()) + .or_else(|| Some("()".to_string())), signature: info.gen_signature(), doc_comments: if info.func.is_script() { - info.func.get_fn_def().comments.clone() + #[cfg(feature = "no_function")] + { + unreachable!() + } + #[cfg(not(feature = "no_function"))] + { + info.func.get_fn_def().comments.clone() + } } else { Default::default() }, @@ -178,11 +178,7 @@ impl From> for FnMetadata { .collect(), return_type: Some("Dynamic".to_string()), signature: info.to_string(), - doc_comments: if info.comments.is_empty() { - None - } else { - Some(info.comments.iter().map(|s| s.to_string()).collect()) - }, + doc_comments: info.comments.iter().map(|s| s.to_string()).collect(), } } } @@ -211,10 +207,10 @@ impl From<&crate::Module> for ModuleMetadata { } } -#[cfg(feature = "serde")] +#[cfg(feature = "metadata")] impl Engine { - /// Generate a list of all functions (including those defined in an [`AST`][crate::AST]) - /// in JSON format. Available only under the `metadata` feature. + /// _(METADATA)_ Generate a list of all functions (including those defined in an + /// [`AST`][crate::AST]) in JSON format. Available only under the `metadata` feature. /// /// Functions from the following sources are included: /// 1) Functions defined in an [`AST`][crate::AST] @@ -223,7 +219,7 @@ impl Engine { /// 4) Functions in global modules (optional) pub fn gen_fn_metadata_with_ast_to_json( &self, - ast: &AST, + _ast: &AST, include_global: bool, ) -> serde_json::Result { let mut global: ModuleMetadata = Default::default(); @@ -244,11 +240,10 @@ impl Engine { .map(|f| f.into()) .for_each(|info| global.functions.push(info)); - if let Some(ast) = ast { - ast.iter_functions() - .map(|f| f.into()) - .for_each(|info| global.functions.push(info)); - } + #[cfg(not(feature = "no_function"))] + _ast.iter_functions() + .map(|f| f.into()) + .for_each(|info| global.functions.push(info)); global.functions.sort(); diff --git a/src/serde_impl/mod.rs b/src/serde_impl/mod.rs index d38105a1..1c90abbf 100644 --- a/src/serde_impl/mod.rs +++ b/src/serde_impl/mod.rs @@ -1,7 +1,9 @@ //! Helper module defining serialization/deserialization support for [`serde`]. pub mod de; +mod deserialize; pub mod ser; +mod serialize; mod str; #[cfg(feature = "metadata")] diff --git a/src/serde_impl/ser.rs b/src/serde_impl/ser.rs index dc62d3c7..edc272cb 100644 --- a/src/serde_impl/ser.rs +++ b/src/serde_impl/ser.rs @@ -4,9 +4,8 @@ use crate::stdlib::{boxed::Box, fmt, string::ToString}; use crate::{Dynamic, EvalAltResult, Position}; use serde::ser::{ Error, SerializeMap, SerializeSeq, SerializeStruct, SerializeTuple, SerializeTupleStruct, - Serializer, }; -use serde::Serialize; +use serde::{Serialize, Serializer}; #[cfg(not(feature = "no_index"))] use crate::Array; diff --git a/src/serde_impl/serialize.rs b/src/serde_impl/serialize.rs new file mode 100644 index 00000000..bdab788f --- /dev/null +++ b/src/serde_impl/serialize.rs @@ -0,0 +1,55 @@ +//! Implementations of [`serde::Serialize`]. + +use crate::dynamic::Union; +use crate::stdlib::string::ToString; +use crate::{Dynamic, ImmutableString}; +use serde::ser::{Serialize, SerializeMap, Serializer}; + +impl Serialize for Dynamic { + fn serialize(&self, ser: S) -> Result { + match &self.0 { + Union::Unit(_, _) => ser.serialize_unit(), + Union::Bool(x, _) => ser.serialize_bool(*x), + Union::Str(s, _) => ser.serialize_str(s.as_str()), + Union::Char(c, _) => ser.serialize_str(&c.to_string()), + #[cfg(not(feature = "only_i32"))] + Union::Int(x, _) => ser.serialize_i64(*x), + #[cfg(feature = "only_i32")] + Union::Int(x, _) => ser.serialize_i32(*x), + #[cfg(not(feature = "no_float"))] + #[cfg(not(feature = "f32_float"))] + Union::Float(x, _) => ser.serialize_f64(**x), + #[cfg(not(feature = "no_float"))] + #[cfg(feature = "f32_float")] + Union::Float(x, _) => ser.serialize_f32(*x), + #[cfg(not(feature = "no_index"))] + Union::Array(a, _) => (**a).serialize(ser), + #[cfg(not(feature = "no_object"))] + Union::Map(m, _) => { + let mut map = ser.serialize_map(Some(m.len()))?; + for (k, v) in m.iter() { + map.serialize_entry(k, v)?; + } + map.end() + } + Union::FnPtr(f, _) => ser.serialize_str(f.fn_name()), + #[cfg(not(feature = "no_std"))] + Union::TimeStamp(_, _) => unimplemented!("serialization of timestamp is not supported"), + + Union::Variant(v, _) => ser.serialize_str((***v).type_name()), + + #[cfg(not(feature = "no_closure"))] + #[cfg(not(feature = "sync"))] + Union::Shared(cell, _) => cell.borrow().serialize(ser), + #[cfg(not(feature = "no_closure"))] + #[cfg(feature = "sync")] + Union::Shared(cell, _) => cell.read().unwrap().serialize(ser), + } + } +} + +impl Serialize for ImmutableString { + fn serialize(&self, ser: S) -> Result { + ser.serialize_str(self.as_str()) + } +} diff --git a/src/token.rs b/src/token.rs index de291fbe..89da190c 100644 --- a/src/token.rs +++ b/src/token.rs @@ -150,7 +150,7 @@ impl fmt::Debug for Position { /// _(INTERNALS)_ A Rhai language token. /// Exported under the `internals` feature only. /// -/// # WARNING +/// # Volatile Data Structure /// /// This type is volatile and may change. #[derive(Debug, PartialEq, Clone)] @@ -742,7 +742,7 @@ impl From for String { /// _(INTERNALS)_ State of the tokenizer. /// Exported under the `internals` feature only. /// -/// # WARNING +/// # Volatile Data Structure /// /// This type is volatile and may change. #[derive(Debug, Clone, Eq, PartialEq, Default)] @@ -764,23 +764,26 @@ pub struct TokenizeState { /// _(INTERNALS)_ Trait that encapsulates a peekable character input stream. /// Exported under the `internals` feature only. /// -/// # WARNING +/// # Volatile Data Structure /// /// This trait is volatile and may change. pub trait InputStream { - fn unread(&mut self, ch: char); - /// Get the next character + /// Un-get a character back into the `InputStream`. + /// The next [`get_next`][InputStream::get_next] or [`peek_next`][InputStream::peek_next] + /// will return this character instead. + fn unget(&mut self, ch: char); + /// Get the next character from the `InputStream`. fn get_next(&mut self) -> Option; - /// Peek the next character + /// Peek the next character in the `InputStream`. fn peek_next(&mut self) -> Option; } /// _(INTERNALS)_ Parse a string literal wrapped by `enclosing_char`. /// Exported under the `internals` feature only. /// -/// # WARNING +/// # Volatile API /// -/// This type is volatile and may change. +/// This function is volatile and may change. pub fn parse_string_literal( stream: &mut impl InputStream, state: &mut TokenizeState, @@ -973,9 +976,9 @@ fn scan_block_comment( /// _(INTERNALS)_ Get the next token from the `stream`. /// Exported under the `internals` feature only. /// -/// # WARNING +/// # Volatile API /// -/// This type is volatile and may change. +/// This function is volatile and may change. #[inline] pub fn get_next_token( stream: &mut impl InputStream, @@ -1088,12 +1091,12 @@ fn get_next_token_inner( } // _ - cannot follow a decimal point '_' => { - stream.unread(next_char); + stream.unget(next_char); break; } // .. - reserved symbol, not a floating-point number '.' => { - stream.unread(next_char); + stream.unget(next_char); break; } // symbol after period - probably a float @@ -1104,7 +1107,7 @@ fn get_next_token_inner( } // Not a floating-point number _ => { - stream.unread(next_char); + stream.unget(next_char); break; } } @@ -1634,12 +1637,10 @@ pub struct MultiInputsStream<'a> { } impl InputStream for MultiInputsStream<'_> { - /// Buffer a character. #[inline(always)] - fn unread(&mut self, ch: char) { + fn unget(&mut self, ch: char) { self.buf = Some(ch); } - /// Get the next character fn get_next(&mut self) -> Option { if let Some(ch) = self.buf.take() { return Some(ch); @@ -1658,7 +1659,6 @@ impl InputStream for MultiInputsStream<'_> { } } } - /// Peek the next character fn peek_next(&mut self) -> Option { if let Some(ch) = self.buf { return Some(ch); diff --git a/src/utils.rs b/src/utils.rs index 6931476a..7cbed35c 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -59,11 +59,7 @@ impl BuildHasher for StraightHasherBuilder { /// Create an instance of the default hasher. #[inline(always)] pub fn get_hasher() -> impl Hasher { - #[cfg(feature = "no_std")] let s: ahash::AHasher = Default::default(); - #[cfg(not(feature = "no_std"))] - let s = crate::stdlib::collections::hash_map::DefaultHasher::new(); - s } diff --git a/tests/serde.rs b/tests/serde.rs index 75291413..1e1a0ad6 100644 --- a/tests/serde.rs +++ b/tests/serde.rs @@ -2,7 +2,7 @@ use rhai::{ serde::{from_dynamic, to_dynamic}, - Dynamic, Engine, EvalAltResult, INT, + Dynamic, Engine, EvalAltResult, ImmutableString, INT, }; use serde::{Deserialize, Serialize}; @@ -290,15 +290,15 @@ fn test_serde_ser_untagged_enum() -> Result<(), Box> { #[test] fn test_serde_de_primary_types() -> Result<(), Box> { - assert_eq!(42_u16, from_dynamic(&Dynamic::from(42_u16))?); - assert_eq!(42 as INT, from_dynamic(&(42 as INT).into())?); - assert_eq!(true, from_dynamic(&true.into())?); - assert_eq!((), from_dynamic(&().into())?); + assert_eq!(42, from_dynamic::(&Dynamic::from(42_u16))?); + assert_eq!(42, from_dynamic::(&(42 as INT).into())?); + assert_eq!(true, from_dynamic::(&true.into())?); + assert_eq!((), from_dynamic::<()>(&().into())?); #[cfg(not(feature = "no_float"))] { - assert_eq!(123.456_f64, from_dynamic(&123.456_f64.into())?); - assert_eq!(123.456_f32, from_dynamic(&Dynamic::from(123.456_f32))?); + assert_eq!(123.456, from_dynamic::(&123.456_f64.into())?); + assert_eq!(123.456, from_dynamic::(&Dynamic::from(123.456_f32))?); } assert_eq!( @@ -311,14 +311,14 @@ fn test_serde_de_primary_types() -> Result<(), Box> { #[test] fn test_serde_de_integer_types() -> Result<(), Box> { - assert_eq!(42_i8, from_dynamic(&Dynamic::from(42 as INT))?); - assert_eq!(42_i16, from_dynamic(&Dynamic::from(42 as INT))?); - assert_eq!(42_i32, from_dynamic(&Dynamic::from(42 as INT))?); - assert_eq!(42_i64, from_dynamic(&Dynamic::from(42 as INT))?); - assert_eq!(42_u8, from_dynamic(&Dynamic::from(42 as INT))?); - assert_eq!(42_u16, from_dynamic(&Dynamic::from(42 as INT))?); - assert_eq!(42_u32, from_dynamic(&Dynamic::from(42 as INT))?); - assert_eq!(42_u64, from_dynamic(&Dynamic::from(42 as INT))?); + assert_eq!(42, from_dynamic::(&Dynamic::from(42 as INT))?); + assert_eq!(42, from_dynamic::(&Dynamic::from(42 as INT))?); + assert_eq!(42, from_dynamic::(&Dynamic::from(42 as INT))?); + assert_eq!(42, from_dynamic::(&Dynamic::from(42 as INT))?); + assert_eq!(42, from_dynamic::(&Dynamic::from(42 as INT))?); + assert_eq!(42, from_dynamic::(&Dynamic::from(42 as INT))?); + assert_eq!(42, from_dynamic::(&Dynamic::from(42 as INT))?); + assert_eq!(42, from_dynamic::(&Dynamic::from(42 as INT))?); Ok(()) } @@ -635,3 +635,42 @@ fn test_serde_de_untagged_enum() -> Result<(), Box> { Ok(()) } + +#[test] +#[cfg(feature = "metadata")] +#[cfg(not(feature = "no_object"))] +#[cfg(not(feature = "no_index"))] +fn test_serde_json() -> serde_json::Result<()> { + let s: ImmutableString = "hello".into(); + assert_eq!(serde_json::to_string(&s)?, r#""hello""#); + + let mut map = Map::new(); + map.insert("a".into(), (123 as INT).into()); + + let arr: Array = vec![(1 as INT).into(), (2 as INT).into(), (3 as INT).into()]; + map.insert("b".into(), arr.into()); + map.insert("c".into(), true.into()); + let d: Dynamic = map.into(); + + let json = serde_json::to_string(&d)?; + + assert!(json.contains("\"a\":123")); + assert!(json.contains("\"b\":[1,2,3]")); + assert!(json.contains("\"c\":true")); + + let d2: Dynamic = serde_json::from_str(&json)?; + + assert!(d2.is::()); + + let mut m = d2.cast::(); + + assert_eq!(m["a"].as_int().unwrap(), 123); + assert!(m["c"].as_bool().unwrap()); + + let a = m.remove("b").unwrap().cast::(); + + assert_eq!(a.len(), 3); + assert_eq!(format!("{:?}", a), "[1, 2, 3]"); + + Ok(()) +}