diff --git a/.gitignore b/.gitignore index 140811c2..e884ce0f 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,6 @@ target/ Cargo.lock .vscode/ .cargo/ -doc/book/ \ No newline at end of file +doc/book/ +before +after diff --git a/doc/src/language/loop.md b/doc/src/language/loop.md index b92c3f7e..e625082a 100644 --- a/doc/src/language/loop.md +++ b/doc/src/language/loop.md @@ -12,7 +12,7 @@ Like C, `continue` can be used to skip to the next iteration, by-passing all fol let x = 10; loop { - x = x - 1; + x -= 1; if x > 5 { continue; } // skip to the next iteration diff --git a/doc/src/language/while.md b/doc/src/language/while.md index e912175e..5b7a5ac8 100644 --- a/doc/src/language/while.md +++ b/doc/src/language/while.md @@ -12,7 +12,7 @@ Like C, `continue` can be used to skip to the next iteration, by-passing all fol let x = 10; while x > 0 { - x = x - 1; + x -= 1; if x < 6 { continue; } // skip to the next iteration print(x); if x == 5 { break; } // break out of while loop diff --git a/src/api.rs b/src/api.rs index 42805fa3..5df12aba 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1046,8 +1046,8 @@ impl Engine { /// let mut scope = Scope::new(); /// scope.push("x", 40_i64); /// - /// assert_eq!(engine.eval_with_scope::(&mut scope, "x = x + 2; x")?, 42); - /// assert_eq!(engine.eval_with_scope::(&mut scope, "x = x + 2; x")?, 44); + /// assert_eq!(engine.eval_with_scope::(&mut scope, "x += 2; x")?, 42); + /// assert_eq!(engine.eval_with_scope::(&mut scope, "x += 2; x")?, 44); /// /// // The variable in the scope is modified /// assert_eq!(scope.get_value::("x").expect("variable x should exist"), 44); @@ -1160,7 +1160,7 @@ impl Engine { /// scope.push("x", 40_i64); /// /// // Compile a script to an AST and store it for later evaluation - /// let ast = engine.compile("x = x + 2; x")?; + /// let ast = engine.compile("x += 2; x")?; /// /// // Evaluate it /// assert_eq!(engine.eval_ast_with_scope::(&mut scope, &ast)?, 42); diff --git a/src/engine.rs b/src/engine.rs index 50e30837..937c01e8 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -11,7 +11,7 @@ use crate::parser::{Expr, FnAccess, ImmutableString, ReturnType, ScriptFnDef, St use crate::r#unsafe::unsafe_cast_var_name_to_lifetime; use crate::result::EvalAltResult; use crate::scope::{EntryType as ScopeEntryType, Scope}; -use crate::token::{is_valid_identifier, Position}; +use crate::token::Position; use crate::utils::StaticVec; #[cfg(not(feature = "no_float"))] @@ -22,6 +22,7 @@ use crate::stdlib::{ borrow::Cow, boxed::Box, collections::{HashMap, HashSet}, + convert::TryFrom, format, iter::{empty, once}, mem, @@ -1735,6 +1736,7 @@ impl Engine { let expr = args_expr.get(0); let arg_value = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; + return arg_value .take_immutable_string() .map_err(|typ| { @@ -1744,17 +1746,9 @@ impl Engine { expr.position(), )) }) - .and_then(|s| { - if is_valid_identifier(s.chars()) { - Ok(s) - } else { - Err(Box::new(EvalAltResult::ErrorFunctionNotFound( - s.to_string(), - expr.position(), - ))) - } - }) - .map(|s| FnPtr::from(s).into()); + .and_then(|s| FnPtr::try_from(s)) + .map(Into::::into) + .map_err(|err| err.new_position(*pos)); } } diff --git a/src/fn_native.rs b/src/fn_native.rs index b4c57324..8bfc241d 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -4,9 +4,10 @@ use crate::module::Module; use crate::parser::ScriptFnDef; use crate::plugin::PluginFunction; use crate::result::EvalAltResult; +use crate::token::{is_valid_identifier, Position}; use crate::utils::ImmutableString; -use crate::stdlib::{boxed::Box, fmt, rc::Rc, sync::Arc}; +use crate::stdlib::{boxed::Box, convert::TryFrom, fmt, rc::Rc, sync::Arc}; /// Trait that maps to `Send + Sync` only under the `sync` feature. #[cfg(feature = "sync")] @@ -53,6 +54,10 @@ pub type FnCallArgs<'a> = [&'a mut Dynamic]; pub struct FnPtr(ImmutableString); impl FnPtr { + /// Create a new function pointer. + pub(crate) fn new>(name: S) -> Self { + Self(name.into()) + } /// Get the name of the function. pub fn fn_name(&self) -> &str { self.get_fn_name().as_ref() @@ -73,9 +78,36 @@ impl fmt::Display for FnPtr { } } -impl> From for FnPtr { - fn from(value: S) -> Self { - Self(value.into()) +impl TryFrom for FnPtr { + type Error = Box; + + fn try_from(value: ImmutableString) -> Result { + if is_valid_identifier(value.chars()) { + Ok(Self(value)) + } else { + Err(Box::new(EvalAltResult::ErrorFunctionNotFound( + value.to_string(), + Position::none(), + ))) + } + } +} + +impl TryFrom for FnPtr { + type Error = Box; + + fn try_from(value: String) -> Result { + let s: ImmutableString = value.into(); + Self::try_from(s) + } +} + +impl TryFrom<&str> for FnPtr { + type Error = Box; + + fn try_from(value: &str) -> Result { + let s: ImmutableString = value.into(); + Self::try_from(s) } } diff --git a/src/lib.rs b/src/lib.rs index 391a71f7..064e0c89 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -102,7 +102,7 @@ mod utils; pub use any::Dynamic; pub use engine::Engine; pub use error::{ParseError, ParseErrorType}; -pub use fn_native::IteratorFn; +pub use fn_native::{FnPtr, IteratorFn}; pub use fn_register::{RegisterFn, RegisterPlugin, RegisterResultFn}; pub use module::Module; pub use parser::{ImmutableString, AST, INT}; diff --git a/src/serde/de.rs b/src/serde/de.rs index 82910073..88533718 100644 --- a/src/serde/de.rs +++ b/src/serde/de.rs @@ -7,7 +7,10 @@ use crate::result::EvalAltResult; use crate::token::Position; use crate::utils::ImmutableString; -use serde::de::{DeserializeSeed, Deserializer, Error, MapAccess, SeqAccess, Visitor}; +use serde::de::{ + DeserializeSeed, Deserializer, EnumAccess, Error, IntoDeserializer, MapAccess, SeqAccess, + VariantAccess, Visitor, +}; use serde::Deserialize; #[cfg(not(feature = "no_index"))] @@ -49,6 +52,20 @@ impl<'de> DynamicDeserializer<'de> { Position::none(), ))) } + fn deserialize_int>( + &mut self, + v: crate::INT, + visitor: V, + ) -> Result> { + #[cfg(not(feature = "only_i32"))] + { + visitor.visit_i64(v) + } + #[cfg(feature = "only_i32")] + { + visitor.visit_i32(v) + } + } } /// Deserialize a `Dynamic` value into a Rust type that implements `serde::Deserialize`. @@ -159,51 +176,87 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { } fn deserialize_i8>(self, visitor: V) -> Result> { - self.value - .downcast_ref::() - .map_or_else(|| self.type_error(), |&x| visitor.visit_i8(x)) + if let Ok(v) = self.value.as_int() { + self.deserialize_int(v, visitor) + } else { + self.value + .downcast_ref::() + .map_or_else(|| self.type_error(), |&x| visitor.visit_i8(x)) + } } fn deserialize_i16>(self, visitor: V) -> Result> { - self.value - .downcast_ref::() - .map_or_else(|| self.type_error(), |&x| visitor.visit_i16(x)) + if let Ok(v) = self.value.as_int() { + self.deserialize_int(v, visitor) + } else { + self.value + .downcast_ref::() + .map_or_else(|| self.type_error(), |&x| visitor.visit_i16(x)) + } } fn deserialize_i32>(self, visitor: V) -> Result> { - self.value - .downcast_ref::() - .map_or_else(|| self.type_error(), |&x| visitor.visit_i32(x)) + if let Ok(v) = self.value.as_int() { + self.deserialize_int(v, visitor) + } else if cfg!(feature = "only_i32") { + self.type_error() + } else { + self.value + .downcast_ref::() + .map_or_else(|| self.type_error(), |&x| visitor.visit_i32(x)) + } } fn deserialize_i64>(self, visitor: V) -> Result> { - self.value - .downcast_ref::() - .map_or_else(|| self.type_error(), |&x| visitor.visit_i64(x)) + if let Ok(v) = self.value.as_int() { + self.deserialize_int(v, visitor) + } else if cfg!(not(feature = "only_i32")) { + self.type_error() + } else { + self.value + .downcast_ref::() + .map_or_else(|| self.type_error(), |&x| visitor.visit_i64(x)) + } } fn deserialize_u8>(self, visitor: V) -> Result> { - self.value - .downcast_ref::() - .map_or_else(|| self.type_error(), |&x| visitor.visit_u8(x)) + if let Ok(v) = self.value.as_int() { + self.deserialize_int(v, visitor) + } else { + self.value + .downcast_ref::() + .map_or_else(|| self.type_error(), |&x| visitor.visit_u8(x)) + } } fn deserialize_u16>(self, visitor: V) -> Result> { - self.value - .downcast_ref::() - .map_or_else(|| self.type_error(), |&x| visitor.visit_u16(x)) + if let Ok(v) = self.value.as_int() { + self.deserialize_int(v, visitor) + } else { + self.value + .downcast_ref::() + .map_or_else(|| self.type_error(), |&x| visitor.visit_u16(x)) + } } fn deserialize_u32>(self, visitor: V) -> Result> { - self.value - .downcast_ref::() - .map_or_else(|| self.type_error(), |&x| visitor.visit_u32(x)) + if let Ok(v) = self.value.as_int() { + self.deserialize_int(v, visitor) + } else { + self.value + .downcast_ref::() + .map_or_else(|| self.type_error(), |&x| visitor.visit_u32(x)) + } } fn deserialize_u64>(self, visitor: V) -> Result> { - self.value - .downcast_ref::() - .map_or_else(|| self.type_error(), |&x| visitor.visit_u64(x)) + if let Ok(v) = self.value.as_int() { + self.deserialize_int(v, visitor) + } else { + self.value + .downcast_ref::() + .map_or_else(|| self.type_error(), |&x| visitor.visit_u64(x)) + } } fn deserialize_f32>(self, visitor: V) -> Result> { @@ -334,9 +387,30 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { self, _name: &'static str, _variants: &'static [&'static str], - _: V, + visitor: V, ) -> Result> { - self.type_error() + if let Ok(s) = self.value.as_str() { + visitor.visit_enum(s.into_deserializer()) + } else { + #[cfg(not(feature = "no_object"))] + if let Some(map) = self.value.downcast_ref::() { + let mut iter = map.iter(); + let first = iter.next(); + let second = iter.next(); + if let (Some((key, value)), None) = (first, second) { + visitor.visit_enum(EnumDeserializer { + tag: &key, + content: DynamicDeserializer::from_dynamic(value), + }) + } else { + self.type_error() + } + } else { + self.type_error() + } + #[cfg(feature = "no_object")] + return self.type_error(); + } } fn deserialize_identifier>( @@ -444,3 +518,57 @@ where )) } } + +#[cfg(not(feature = "no_object"))] +struct EnumDeserializer<'t, 'de: 't> { + tag: &'t str, + content: DynamicDeserializer<'de>, +} + +#[cfg(not(feature = "no_object"))] +impl<'t, 'de> EnumAccess<'de> for EnumDeserializer<'t, 'de> { + type Error = Box; + type Variant = Self; + + fn variant_seed(self, seed: V) -> Result<(V::Value, Self::Variant), Self::Error> + where + V: DeserializeSeed<'de>, + { + seed.deserialize(self.tag.into_deserializer()) + .map(|v| (v, self)) + } +} + +#[cfg(not(feature = "no_object"))] +impl<'t, 'de> VariantAccess<'de> for EnumDeserializer<'t, 'de> { + type Error = Box; + + fn unit_variant(mut self) -> Result<(), Self::Error> { + Deserialize::deserialize(&mut self.content) + } + + fn newtype_variant_seed(mut self, seed: T) -> Result + where + T: DeserializeSeed<'de>, + { + seed.deserialize(&mut self.content) + } + + fn tuple_variant(mut self, len: usize, visitor: V) -> Result + where + V: Visitor<'de>, + { + self.content.deserialize_tuple(len, visitor) + } + + fn struct_variant( + mut self, + fields: &'static [&'static str], + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + self.content.deserialize_struct("", fields, visitor) + } +} diff --git a/src/serde/ser.rs b/src/serde/ser.rs index 1477da2d..181573b0 100644 --- a/src/serde/ser.rs +++ b/src/serde/ser.rs @@ -103,10 +103,16 @@ impl Serializer for &mut DynamicSerializer { type SerializeSeq = DynamicSerializer; type SerializeTuple = DynamicSerializer; type SerializeTupleStruct = DynamicSerializer; - type SerializeTupleVariant = DynamicSerializer; + #[cfg(not(any(feature = "no_object", feature = "no_index")))] + type SerializeTupleVariant = TupleVariantSerializer; + #[cfg(any(feature = "no_object", feature = "no_index"))] + type SerializeTupleVariant = serde::ser::Impossible>; type SerializeMap = DynamicSerializer; type SerializeStruct = DynamicSerializer; - type SerializeStructVariant = DynamicSerializer; + #[cfg(not(feature = "no_object"))] + type SerializeStructVariant = StructVariantSerializer; + #[cfg(feature = "no_object")] + type SerializeStructVariant = serde::ser::Impossible>; fn serialize_bool(self, v: bool) -> Result> { Ok(v.into()) @@ -162,7 +168,7 @@ impl Serializer for &mut DynamicSerializer { #[cfg(not(feature = "only_i32"))] return self.serialize_i64(i64::from(v)); #[cfg(feature = "only_i32")] - if v > i32::MAX as u64 { + if v > i32::MAX as u32 { return Ok(Dynamic::from(v)); } else { return self.serialize_i32(v as i32); @@ -244,10 +250,20 @@ impl Serializer for &mut DynamicSerializer { self, _name: &'static str, _variant_index: u32, - _variant: &'static str, + variant: &'static str, value: &T, ) -> Result> { - value.serialize(&mut *self) + #[cfg(not(feature = "no_object"))] + { + let content = to_dynamic(value)?; + make_variant(variant, content) + } + #[cfg(feature = "no_object")] + return Err(Box::new(EvalAltResult::ErrorMismatchOutputType( + "Dynamic".into(), + "map".into(), + Position::none(), + ))); } fn serialize_seq(self, _len: Option) -> Result> { @@ -277,10 +293,26 @@ impl Serializer for &mut DynamicSerializer { self, _name: &'static str, _variant_index: u32, - _variant: &'static str, + variant: &'static str, len: usize, ) -> Result> { - self.serialize_seq(Some(len)) + #[cfg(not(any(feature = "no_object", feature = "no_index")))] + return Ok(TupleVariantSerializer { + variant, + array: Array::with_capacity(len), + }); + #[cfg(any(feature = "no_object", feature = "no_index"))] + { + #[cfg(feature = "no_object")] + let err_type = "map"; + #[cfg(not(feature = "no_object"))] + let err_type = "array"; + Err(Box::new(EvalAltResult::ErrorMismatchOutputType( + "Dynamic".into(), + err_type.into(), + Position::none(), + ))) + } } fn serialize_map(self, _len: Option) -> Result> { @@ -306,10 +338,20 @@ impl Serializer for &mut DynamicSerializer { self, _name: &'static str, _variant_index: u32, - _variant: &'static str, + variant: &'static str, len: usize, ) -> Result> { - self.serialize_map(Some(len)) + #[cfg(not(feature = "no_object"))] + return Ok(StructVariantSerializer { + variant, + map: Map::with_capacity(len), + }); + #[cfg(feature = "no_object")] + return Err(Box::new(EvalAltResult::ErrorMismatchOutputType( + "Dynamic".into(), + "map".into(), + Position::none(), + ))); } } @@ -395,33 +437,6 @@ impl SerializeTupleStruct for DynamicSerializer { } } -impl SerializeTupleVariant for DynamicSerializer { - type Ok = Dynamic; - type Error = Box; - - fn serialize_field( - &mut self, - value: &T, - ) -> Result<(), Box> { - #[cfg(not(feature = "no_index"))] - { - let value = value.serialize(&mut *self)?; - let arr = self.value.downcast_mut::().unwrap(); - arr.push(value); - Ok(()) - } - #[cfg(feature = "no_index")] - unreachable!() - } - - fn end(self) -> Result> { - #[cfg(not(feature = "no_index"))] - return Ok(self.value); - #[cfg(feature = "no_index")] - unreachable!() - } -} - impl SerializeMap for DynamicSerializer { type Ok = Dynamic; type Error = Box; @@ -520,7 +535,39 @@ impl SerializeStruct for DynamicSerializer { } } -impl SerializeStructVariant for DynamicSerializer { +#[cfg(not(any(feature = "no_object", feature = "no_index")))] +pub struct TupleVariantSerializer { + variant: &'static str, + array: Array, +} + +#[cfg(not(any(feature = "no_object", feature = "no_index")))] +impl SerializeTupleVariant for TupleVariantSerializer { + type Ok = Dynamic; + type Error = Box; + + fn serialize_field( + &mut self, + value: &T, + ) -> Result<(), Box> { + let value = to_dynamic(value)?; + self.array.push(value); + Ok(()) + } + + fn end(self) -> Result> { + make_variant(self.variant, self.array.into()) + } +} + +#[cfg(not(feature = "no_object"))] +pub struct StructVariantSerializer { + variant: &'static str, + map: Map, +} + +#[cfg(not(feature = "no_object"))] +impl SerializeStructVariant for StructVariantSerializer { type Ok = Dynamic; type Error = Box; @@ -529,21 +576,19 @@ impl SerializeStructVariant for DynamicSerializer { key: &'static str, value: &T, ) -> Result<(), Box> { - #[cfg(not(feature = "no_object"))] - { - let value = value.serialize(&mut *self)?; - let map = self.value.downcast_mut::().unwrap(); - map.insert(key.into(), value); - Ok(()) - } - #[cfg(feature = "no_object")] - unreachable!() + let value = to_dynamic(value)?; + self.map.insert(key.into(), value); + Ok(()) } fn end(self) -> Result> { - #[cfg(not(feature = "no_object"))] - return Ok(self.value); - #[cfg(feature = "no_object")] - unreachable!() + make_variant(self.variant, self.map.into()) } } + +#[cfg(not(feature = "no_object"))] +fn make_variant(variant: &'static str, value: Dynamic) -> Result> { + let mut map = Map::with_capacity(1); + map.insert(variant.into(), value); + Ok(map.into()) +} diff --git a/tests/call_fn.rs b/tests/call_fn.rs index 690577ce..715fbc87 100644 --- a/tests/call_fn.rs +++ b/tests/call_fn.rs @@ -32,7 +32,7 @@ fn test_call_fn() -> Result<(), Box> { x + y } fn hello(x) { - x = x * foo; + x *= foo; foo = 1; x } diff --git a/tests/looping.rs b/tests/looping.rs index 3a4804ce..951f8cd7 100644 --- a/tests/looping.rs +++ b/tests/looping.rs @@ -14,7 +14,7 @@ fn test_loop() -> Result<(), Box> { if i < 10 { i += 1; if x > 20 { continue; } - x = x + i; + x += i; } else { break; } diff --git a/tests/serde.rs b/tests/serde.rs index 82ec330b..005e4e8c 100644 --- a/tests/serde.rs +++ b/tests/serde.rs @@ -10,25 +10,33 @@ use rhai::Map; #[test] fn test_serde_ser_primary_types() -> Result<(), Box> { - assert_eq!( - to_dynamic(42_u64)?.type_name(), - std::any::type_name::() - ); - assert_eq!(to_dynamic(u64::MAX)?.type_name(), "u64"); - assert_eq!( - to_dynamic(42 as INT)?.type_name(), - std::any::type_name::() - ); - assert_eq!(to_dynamic(true)?.type_name(), "bool"); - assert_eq!(to_dynamic(())?.type_name(), "()"); + assert!(to_dynamic(42_u64)?.is::()); + assert!(to_dynamic(u64::MAX)?.is::()); + assert!(to_dynamic(42 as INT)?.is::()); + assert!(to_dynamic(true)?.is::()); + assert!(to_dynamic(())?.is::<()>()); #[cfg(not(feature = "no_float"))] { - assert_eq!(to_dynamic(123.456_f64)?.type_name(), "f64"); - assert_eq!(to_dynamic(123.456_f32)?.type_name(), "f32"); + assert!(to_dynamic(123.456_f64)?.is::()); + assert!(to_dynamic(123.456_f32)?.is::()); } - assert_eq!(to_dynamic("hello".to_string())?.type_name(), "string"); + assert!(to_dynamic("hello".to_string())?.is::()); + + Ok(()) +} + +#[test] +fn test_serde_ser_integer_types() -> Result<(), Box> { + assert!(to_dynamic(42_i8)?.is::()); + assert!(to_dynamic(42_i16)?.is::()); + assert!(to_dynamic(42_i32)?.is::()); + assert!(to_dynamic(42_i64)?.is::()); + assert!(to_dynamic(42_u8)?.is::()); + assert!(to_dynamic(42_u16)?.is::()); + assert!(to_dynamic(42_u32)?.is::()); + assert!(to_dynamic(42_u64)?.is::()); Ok(()) } @@ -40,7 +48,7 @@ fn test_serde_ser_array() -> Result<(), Box> { let d = to_dynamic(arr)?; assert!(d.is::()); - assert_eq!(d.cast::().len(), 4); + assert_eq!(4, d.cast::().len()); Ok(()) } @@ -73,14 +81,226 @@ fn test_serde_ser_struct() -> Result<(), Box> { assert!(d.is::()); let mut map = d.cast::(); - let mut obj = map.remove("obj").unwrap().cast::(); - let mut seq = map.remove("seq").unwrap().cast::(); + let obj = map.remove("obj").unwrap().cast::(); + let seq = map.remove("seq").unwrap().cast::(); - assert_eq!(obj.remove("a").unwrap().cast::(), 123); - assert!(obj.remove("b").unwrap().cast::()); - assert_eq!(map.remove("int").unwrap().cast::(), 42); + assert_eq!(Ok(123), obj["a"].as_int()); + assert!(obj["b"].as_bool().unwrap()); + assert_eq!(Ok(42), map["int"].as_int()); assert_eq!(seq.len(), 3); - assert_eq!(seq.remove(1).cast::(), "kitty"); + assert_eq!(Ok("kitty"), seq[1].as_str()); + + Ok(()) +} + +#[test] +fn test_serde_ser_unit_enum() -> Result<(), Box> { + #[derive(Serialize)] + enum MyEnum { + VariantFoo, + VariantBar, + } + + let d = to_dynamic(MyEnum::VariantFoo)?; + assert_eq!(Ok("VariantFoo"), d.as_str()); + + let d = to_dynamic(MyEnum::VariantBar)?; + assert_eq!(Ok("VariantBar"), d.as_str()); + + Ok(()) +} + +#[test] +#[cfg(not(feature = "no_object"))] +fn test_serde_ser_externally_tagged_enum() -> Result<(), Box> { + #[derive(Serialize)] + enum MyEnum { + VariantUnit, + #[cfg(not(feature = "no_index"))] + VariantUnitTuple(), + VariantNewtype(i32), + #[cfg(not(feature = "no_index"))] + VariantTuple(i32, i32), + VariantEmptyStruct {}, + VariantStruct { + a: i32, + }, + } + + { + assert_eq!(Ok("VariantUnit"), to_dynamic(MyEnum::VariantUnit)?.as_str()); + } + + #[cfg(not(feature = "no_index"))] + { + let mut map = to_dynamic(MyEnum::VariantUnitTuple())?.cast::(); + let content = map.remove("VariantUnitTuple").unwrap().cast::(); + assert!(map.is_empty()); + assert!(content.is_empty()); + } + + { + let mut map = to_dynamic(MyEnum::VariantNewtype(123))?.cast::(); + let content = map.remove("VariantNewtype").unwrap(); + assert!(map.is_empty()); + assert_eq!(Ok(123), content.as_int()); + } + + #[cfg(not(feature = "no_index"))] + { + let mut map = to_dynamic(MyEnum::VariantTuple(123, 456))?.cast::(); + let content = map.remove("VariantTuple").unwrap().cast::(); + assert!(map.is_empty()); + assert_eq!(2, content.len()); + assert_eq!(Ok(123), content[0].as_int()); + assert_eq!(Ok(456), content[1].as_int()); + } + + { + let mut map = to_dynamic(MyEnum::VariantEmptyStruct {})?.cast::(); + let map_inner = map.remove("VariantEmptyStruct").unwrap().cast::(); + assert!(map.is_empty()); + assert!(map_inner.is_empty()); + } + + { + let mut map = to_dynamic(MyEnum::VariantStruct { a: 123 })?.cast::(); + let mut map_inner = map.remove("VariantStruct").unwrap().cast::(); + assert!(map.is_empty()); + assert_eq!(Ok(123), map_inner.remove("a").unwrap().as_int()); + assert!(map_inner.is_empty()); + } + + Ok(()) +} + +#[test] +#[cfg(not(feature = "no_object"))] +fn test_serde_ser_internally_tagged_enum() -> Result<(), Box> { + #[derive(Serialize)] + #[serde(tag = "tag")] + enum MyEnum { + VariantEmptyStruct {}, + VariantStruct { a: i32 }, + } + + let mut map = to_dynamic(MyEnum::VariantEmptyStruct {})?.cast::(); + assert_eq!( + Ok("VariantEmptyStruct"), + map.remove("tag").unwrap().as_str() + ); + assert!(map.is_empty()); + + let mut map = to_dynamic(MyEnum::VariantStruct { a: 123 })?.cast::(); + assert_eq!(Ok("VariantStruct"), map.remove("tag").unwrap().as_str()); + assert_eq!(Ok(123), map.remove("a").unwrap().as_int()); + assert!(map.is_empty()); + + Ok(()) +} + +#[test] +#[cfg(not(feature = "no_object"))] +fn test_serde_ser_adjacently_tagged_enum() -> Result<(), Box> { + #[derive(Serialize)] + #[serde(tag = "tag", content = "content")] + enum MyEnum { + VariantUnit, + #[cfg(not(feature = "no_index"))] + VariantUnitTuple(), + VariantNewtype(i32), + #[cfg(not(feature = "no_index"))] + VariantTuple(i32, i32), + VariantEmptyStruct {}, + VariantStruct { + a: i32, + }, + } + + { + let mut map = to_dynamic(MyEnum::VariantUnit)?.cast::(); + assert_eq!(Ok("VariantUnit"), map.remove("tag").unwrap().as_str()); + assert!(map.is_empty()); + } + + #[cfg(not(feature = "no_index"))] + { + let mut map = to_dynamic(MyEnum::VariantUnitTuple())?.cast::(); + assert_eq!(Ok("VariantUnitTuple"), map.remove("tag").unwrap().as_str()); + let content = map.remove("content").unwrap().cast::(); + assert!(map.is_empty()); + assert!(content.is_empty()); + } + + { + let mut map = to_dynamic(MyEnum::VariantNewtype(123))?.cast::(); + assert_eq!(Ok("VariantNewtype"), map.remove("tag").unwrap().as_str()); + let content = map.remove("content").unwrap(); + assert!(map.is_empty()); + assert_eq!(Ok(123), content.as_int()); + } + + #[cfg(not(feature = "no_index"))] + { + let mut map = to_dynamic(MyEnum::VariantTuple(123, 456))?.cast::(); + assert_eq!(Ok("VariantTuple"), map.remove("tag").unwrap().as_str()); + let content = map.remove("content").unwrap().cast::(); + assert!(map.is_empty()); + assert_eq!(2, content.len()); + assert_eq!(Ok(123), content[0].as_int()); + assert_eq!(Ok(456), content[1].as_int()); + } + + { + let mut map = to_dynamic(MyEnum::VariantEmptyStruct {})?.cast::(); + assert_eq!( + Ok("VariantEmptyStruct"), + map.remove("tag").unwrap().as_str() + ); + let map_inner = map.remove("content").unwrap().cast::(); + assert!(map.is_empty()); + assert!(map_inner.is_empty()); + } + + { + let mut map = to_dynamic(MyEnum::VariantStruct { a: 123 })?.cast::(); + assert_eq!(Ok("VariantStruct"), map.remove("tag").unwrap().as_str()); + let mut map_inner = map.remove("content").unwrap().cast::(); + assert!(map.is_empty()); + assert_eq!(Ok(123), map_inner.remove("a").unwrap().as_int()); + assert!(map_inner.is_empty()); + } + + Ok(()) +} + +#[test] +#[cfg(not(feature = "no_object"))] +fn test_serde_ser_untagged_enum() -> Result<(), Box> { + #[derive(Serialize)] + #[serde(untagged)] + enum MyEnum { + VariantEmptyStruct {}, + VariantStruct1 { a: i32 }, + VariantStruct2 { b: i32 }, + } + + { + let map = to_dynamic(MyEnum::VariantEmptyStruct {})?.cast::(); + assert!(map.is_empty()); + } + + { + let mut map = to_dynamic(MyEnum::VariantStruct1 { a: 123 })?.cast::(); + assert_eq!(Ok(123), map.remove("a").unwrap().as_int()); + assert!(map.is_empty()); + } + + { + let mut map = to_dynamic(MyEnum::VariantStruct2 { b: 123 })?.cast::(); + assert_eq!(Ok(123), map.remove("b").unwrap().as_int()); + assert!(map.is_empty()); + } Ok(()) } @@ -106,6 +326,20 @@ fn test_serde_de_primary_types() -> Result<(), Box> { Ok(()) } +#[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))?); + + Ok(()) +} + #[test] #[cfg(not(feature = "no_index"))] fn test_serde_de_array() -> Result<(), Box> { @@ -190,3 +424,261 @@ fn test_serde_de_script() -> Result<(), Box> { Ok(()) } + +#[test] +fn test_serde_de_unit_enum() -> Result<(), Box> { + #[derive(Debug, PartialEq, Deserialize)] + enum MyEnum { + VariantFoo, + VariantBar, + } + + { + let d = Dynamic::from("VariantFoo".to_string()); + assert_eq!(MyEnum::VariantFoo, from_dynamic(&d)?); + } + + { + let d = Dynamic::from("VariantBar".to_string()); + assert_eq!(MyEnum::VariantBar, from_dynamic(&d)?); + } + + Ok(()) +} + +#[test] +#[cfg(not(feature = "no_object"))] +fn test_serde_de_externally_tagged_enum() -> Result<(), Box> { + #[derive(Debug, PartialEq, Deserialize)] + #[serde(deny_unknown_fields)] + enum MyEnum { + VariantUnit, + #[cfg(not(feature = "no_index"))] + VariantUnitTuple(), + VariantNewtype(i32), + #[cfg(not(feature = "no_index"))] + VariantTuple(i32, i32), + VariantEmptyStruct {}, + VariantStruct { + a: i32, + }, + } + + { + let d = Dynamic::from("VariantUnit".to_string()); + assert_eq!(MyEnum::VariantUnit, from_dynamic(&d).unwrap()); + } + + #[cfg(not(feature = "no_index"))] + { + let array: Array = vec![]; + let mut map_outer = Map::new(); + map_outer.insert("VariantUnitTuple".into(), array.into()); + assert_eq!( + MyEnum::VariantUnitTuple(), + from_dynamic(&map_outer.into()).unwrap() + ); + } + + { + let mut map_outer = Map::new(); + map_outer.insert("VariantNewtype".into(), (123 as INT).into()); + assert_eq!( + MyEnum::VariantNewtype(123), + from_dynamic(&map_outer.into()).unwrap() + ); + } + + #[cfg(not(feature = "no_index"))] + { + let array: Array = vec![(123 as INT).into(), (456 as INT).into()]; + let mut map_outer = Map::new(); + map_outer.insert("VariantTuple".into(), array.into()); + assert_eq!( + MyEnum::VariantTuple(123, 456), + from_dynamic(&map_outer.into()).unwrap() + ); + } + + { + let map_inner = Map::new(); + let mut map_outer = Map::new(); + map_outer.insert("VariantEmptyStruct".into(), map_inner.into()); + assert_eq!( + MyEnum::VariantEmptyStruct {}, + from_dynamic(&map_outer.into()).unwrap() + ); + } + + { + let mut map_inner = Map::new(); + map_inner.insert("a".into(), (123 as INT).into()); + let mut map_outer = Map::new(); + map_outer.insert("VariantStruct".into(), map_inner.into()); + assert_eq!( + MyEnum::VariantStruct { a: 123 }, + from_dynamic(&map_outer.into()).unwrap() + ); + } + + Ok(()) +} + +#[test] +#[cfg(not(feature = "no_object"))] +fn test_serde_de_internally_tagged_enum() -> Result<(), Box> { + #[derive(Debug, PartialEq, Deserialize)] + #[serde(tag = "tag", deny_unknown_fields)] + enum MyEnum { + VariantEmptyStruct {}, + VariantStruct { a: i32 }, + } + + { + let mut map = Map::new(); + map.insert("tag".into(), "VariantStruct".into()); + map.insert("a".into(), (123 as INT).into()); + assert_eq!( + MyEnum::VariantStruct { a: 123 }, + from_dynamic(&map.into()).unwrap() + ); + } + + { + let mut map = Map::new(); + map.insert("tag".into(), "VariantEmptyStruct".into()); + assert_eq!( + MyEnum::VariantEmptyStruct {}, + from_dynamic(&map.into()).unwrap() + ); + } + + Ok(()) +} + +#[test] +#[cfg(not(feature = "no_object"))] +fn test_serde_de_adjacently_tagged_enum() -> Result<(), Box> { + #[derive(Debug, PartialEq, Deserialize)] + #[serde(tag = "tag", content = "content", deny_unknown_fields)] + enum MyEnum { + VariantUnit, + #[cfg(not(feature = "no_index"))] + VariantUnitTuple(), + VariantNewtype(i32), + #[cfg(not(feature = "no_index"))] + VariantTuple(i32, i32), + VariantEmptyStruct {}, + VariantStruct { + a: i32, + }, + } + + { + let mut map_outer = Map::new(); + map_outer.insert("tag".into(), "VariantUnit".into()); + assert_eq!( + MyEnum::VariantUnit, + from_dynamic(&map_outer.into()).unwrap() + ); + } + + #[cfg(not(feature = "no_index"))] + { + let array: Array = vec![]; + let mut map_outer = Map::new(); + map_outer.insert("tag".into(), "VariantUnitTuple".into()); + map_outer.insert("content".into(), array.into()); + assert_eq!( + MyEnum::VariantUnitTuple(), + from_dynamic(&map_outer.into()).unwrap() + ); + } + + { + let mut map_outer = Map::new(); + map_outer.insert("tag".into(), "VariantNewtype".into()); + map_outer.insert("content".into(), (123 as INT).into()); + assert_eq!( + MyEnum::VariantNewtype(123), + from_dynamic(&map_outer.into()).unwrap() + ); + } + + #[cfg(not(feature = "no_index"))] + { + let array: Array = vec![(123 as INT).into(), (456 as INT).into()]; + let mut map_outer = Map::new(); + map_outer.insert("tag".into(), "VariantTuple".into()); + map_outer.insert("content".into(), array.into()); + assert_eq!( + MyEnum::VariantTuple(123, 456), + from_dynamic(&map_outer.into()).unwrap() + ); + } + + { + let map_inner = Map::new(); + let mut map_outer = Map::new(); + map_outer.insert("tag".into(), "VariantEmptyStruct".into()); + map_outer.insert("content".into(), map_inner.into()); + assert_eq!( + MyEnum::VariantEmptyStruct {}, + from_dynamic(&map_outer.into()).unwrap() + ); + } + + { + let mut map_inner = Map::new(); + map_inner.insert("a".into(), (123 as INT).into()); + let mut map_outer = Map::new(); + map_outer.insert("tag".into(), "VariantStruct".into()); + map_outer.insert("content".into(), map_inner.into()); + assert_eq!( + MyEnum::VariantStruct { a: 123 }, + from_dynamic(&map_outer.into()).unwrap() + ); + } + + Ok(()) +} + +#[test] +#[cfg(not(feature = "no_object"))] +fn test_serde_de_untagged_enum() -> Result<(), Box> { + #[derive(Debug, PartialEq, Deserialize)] + #[serde(untagged, deny_unknown_fields)] + enum MyEnum { + VariantEmptyStruct {}, + VariantStruct1 { a: i32 }, + VariantStruct2 { b: i32 }, + } + + { + let map = Map::new(); + assert_eq!( + MyEnum::VariantEmptyStruct {}, + from_dynamic(&map.into()).unwrap() + ); + } + + { + let mut map = Map::new(); + map.insert("a".into(), (123 as INT).into()); + assert_eq!( + MyEnum::VariantStruct1 { a: 123 }, + from_dynamic(&map.into()).unwrap() + ); + } + + { + let mut map = Map::new(); + map.insert("b".into(), (123 as INT).into()); + assert_eq!( + MyEnum::VariantStruct2 { b: 123 }, + from_dynamic(&map.into()).unwrap() + ); + } + + Ok(()) +} diff --git a/tests/var_scope.rs b/tests/var_scope.rs index fde83a05..0b3cc1b9 100644 --- a/tests/var_scope.rs +++ b/tests/var_scope.rs @@ -7,7 +7,7 @@ fn test_var_scope() -> Result<(), Box> { engine.eval_with_scope::<()>(&mut scope, "let x = 4 + 5")?; assert_eq!(engine.eval_with_scope::(&mut scope, "x")?, 9); - engine.eval_with_scope::<()>(&mut scope, "x = x + 1; x = x + 2;")?; + engine.eval_with_scope::<()>(&mut scope, "x += 1; x += 2;")?; assert_eq!(engine.eval_with_scope::(&mut scope, "x")?, 12); scope.set_value("x", 42 as INT); diff --git a/tests/while_loop.rs b/tests/while_loop.rs index 8916cd7c..bbcd091b 100644 --- a/tests/while_loop.rs +++ b/tests/while_loop.rs @@ -10,10 +10,10 @@ fn test_while() -> Result<(), Box> { let x = 0; while x < 10 { - x = x + 1; + x += 1; if x > 5 { break; } if x > 3 { continue; } - x = x + 3; + x += 3; } x