From a757dfe89d93c8be1e82520066b456a7bbd3e3d0 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 23 Nov 2021 14:58:54 +0800 Subject: [PATCH] Add blobs. --- src/engine.rs | 99 ++++++-- src/lib.rs | 5 + src/packages/blob_basic.rs | 507 +++++++++++++++++++++++++++++++++++++ src/packages/mod.rs | 7 + src/packages/pkg_std.rs | 7 +- src/serde/de.rs | 2 + src/serde/serialize.rs | 2 + src/types/dynamic.rs | 84 +++++- tests/blobs.rs | 68 +++++ 9 files changed, 760 insertions(+), 21 deletions(-) create mode 100644 src/packages/blob_basic.rs create mode 100644 tests/blobs.rs diff --git a/src/engine.rs b/src/engine.rs index 64ce6087..dd1337e0 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -30,7 +30,7 @@ use std::{ }; #[cfg(not(feature = "no_index"))] -use crate::Array; +use crate::{Array, Blob}; #[cfg(not(feature = "no_object"))] use crate::Map; @@ -482,6 +482,10 @@ pub enum Target<'a> { /// This is necessary because directly pointing to a bit inside an [`INT`][crate::INT] is impossible. #[cfg(not(feature = "no_index"))] BitField(&'a mut Dynamic, usize, Dynamic), + /// The target is a byte inside a Blob. + /// This is necessary because directly pointing to a byte (in [`Dynamic`] form) inside a blob is impossible. + #[cfg(not(feature = "no_index"))] + BlobByte(&'a mut Dynamic, usize, Dynamic), /// The target is a character inside a String. /// This is necessary because directly pointing to a char inside a String is impossible. #[cfg(not(feature = "no_index"))] @@ -500,9 +504,7 @@ impl<'a> Target<'a> { Self::LockGuard(_) => true, Self::TempValue(_) => false, #[cfg(not(feature = "no_index"))] - Self::BitField(_, _, _) => false, - #[cfg(not(feature = "no_index"))] - Self::StringChar(_, _, _) => false, + Self::BitField(_, _, _) | Self::BlobByte(_, _, _) | Self::StringChar(_, _, _) => false, } } /// Is the `Target` a temp value? @@ -515,9 +517,7 @@ impl<'a> Target<'a> { Self::LockGuard(_) => false, Self::TempValue(_) => true, #[cfg(not(feature = "no_index"))] - Self::BitField(_, _, _) => false, - #[cfg(not(feature = "no_index"))] - Self::StringChar(_, _, _) => false, + Self::BitField(_, _, _) | Self::BlobByte(_, _, _) | Self::StringChar(_, _, _) => false, } } /// Is the `Target` a shared value? @@ -531,9 +531,7 @@ impl<'a> Target<'a> { Self::LockGuard(_) => true, Self::TempValue(r) => r.is_shared(), #[cfg(not(feature = "no_index"))] - Self::BitField(_, _, _) => false, - #[cfg(not(feature = "no_index"))] - Self::StringChar(_, _, _) => false, + Self::BitField(_, _, _) | Self::BlobByte(_, _, _) | Self::StringChar(_, _, _) => false, } } /// Is the `Target` a specific type? @@ -549,6 +547,8 @@ impl<'a> Target<'a> { #[cfg(not(feature = "no_index"))] Self::BitField(_, _, _) => TypeId::of::() == TypeId::of::(), #[cfg(not(feature = "no_index"))] + Self::BlobByte(_, _, _) => TypeId::of::() == TypeId::of::(), + #[cfg(not(feature = "no_index"))] Self::StringChar(_, _, _) => TypeId::of::() == TypeId::of::(), } } @@ -564,6 +564,8 @@ impl<'a> Target<'a> { #[cfg(not(feature = "no_index"))] Self::BitField(_, _, value) => value, // Boolean is taken #[cfg(not(feature = "no_index"))] + Self::BlobByte(_, _, value) => value, // Byte is taken + #[cfg(not(feature = "no_index"))] Self::StringChar(_, _, ch) => ch, // Character is taken } } @@ -617,6 +619,27 @@ impl<'a> Target<'a> { } } #[cfg(not(feature = "no_index"))] + Self::BlobByte(value, index, new_val) => { + // Replace the byte at the specified index position + let new_byte = new_val.as_int().map_err(|err| { + Box::new(EvalAltResult::ErrorMismatchDataType( + "INT".to_string(), + err.to_string(), + Position::NONE, + )) + })?; + + let value = &mut *value.write_lock::().expect("`Blob`"); + + let index = *index; + + if index < value.len() { + value[index] = (new_byte & 0x000f) as u8; + } else { + unreachable!("blob index out of bounds: {}", index); + } + } + #[cfg(not(feature = "no_index"))] Self::StringChar(s, index, new_val) => { // Replace the character at the specified index position let new_ch = new_val.as_char().map_err(|err| { @@ -670,9 +693,9 @@ impl Deref for Target<'_> { Self::LockGuard((r, _)) => &**r, Self::TempValue(ref r) => r, #[cfg(not(feature = "no_index"))] - Self::BitField(_, _, ref r) => r, - #[cfg(not(feature = "no_index"))] - Self::StringChar(_, _, ref r) => r, + Self::BitField(_, _, ref r) + | Self::BlobByte(_, _, ref r) + | Self::StringChar(_, _, ref r) => r, } } } @@ -693,9 +716,9 @@ impl DerefMut for Target<'_> { Self::LockGuard((r, _)) => r.deref_mut(), Self::TempValue(ref mut r) => r, #[cfg(not(feature = "no_index"))] - Self::BitField(_, _, ref mut r) => r, - #[cfg(not(feature = "no_index"))] - Self::StringChar(_, _, ref mut r) => r, + Self::BitField(_, _, ref mut r) + | Self::BlobByte(_, _, ref mut r) + | Self::StringChar(_, _, ref mut r) => r, } } } @@ -2059,6 +2082,45 @@ impl Engine { .ok_or_else(|| EvalAltResult::ErrorArrayBounds(arr_len, index, idx_pos).into()) } + #[cfg(not(feature = "no_index"))] + Dynamic(Union::Blob(arr, _, _)) => { + // val_blob[idx] + let index = idx + .as_int() + .map_err(|typ| self.make_type_mismatch_err::(typ, idx_pos))?; + + let arr_len = arr.len(); + + #[cfg(not(feature = "unchecked"))] + let arr_idx = if index < 0 { + // Count from end if negative + arr_len + - index + .checked_abs() + .ok_or_else(|| EvalAltResult::ErrorArrayBounds(arr_len, index, idx_pos)) + .and_then(|n| { + if n as usize > arr_len { + Err(EvalAltResult::ErrorArrayBounds(arr_len, index, idx_pos) + .into()) + } else { + Ok(n as usize) + } + })? + } else { + index as usize + }; + #[cfg(feature = "unchecked")] + let arr_idx = if index < 0 { + // Count from end if negative + arr_len - index.abs() as usize + } else { + index as usize + }; + + let value = (arr[arr_idx] as INT).into(); + Ok(Target::BlobByte(target, arr_idx, value)) + } + #[cfg(not(feature = "no_object"))] Dynamic(Union::Map(map, _, _)) => { // val_map[idx] @@ -3203,6 +3265,7 @@ impl Engine { let (a, m, s) = calc_size(value); (arrays + a + 1, maps + m, strings + s) } + Union::Blob(ref a, _, _) => (arrays + 1 + a.len(), maps, strings), #[cfg(not(feature = "no_object"))] Union::Map(_, _, _) => { let (a, m, s) = calc_size(value); @@ -3212,6 +3275,8 @@ impl Engine { _ => (arrays + 1, maps, strings), }) } + #[cfg(not(feature = "no_index"))] + Union::Blob(ref arr, _, _) => (arr.len(), 0, 0), #[cfg(not(feature = "no_object"))] Union::Map(ref map, _, _) => { map.values() @@ -3221,6 +3286,8 @@ impl Engine { let (a, m, s) = calc_size(value); (arrays + a, maps + m + 1, strings + s) } + #[cfg(not(feature = "no_index"))] + Union::Blob(ref a, _, _) => (arrays + a.len(), maps, strings), Union::Map(_, _, _) => { let (a, m, s) = calc_size(value); (arrays + a, maps + m + 1, strings + s) diff --git a/src/lib.rs b/src/lib.rs index 5d7a7776..3f12bc68 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -176,6 +176,11 @@ pub use ast::ScriptFnMetadata; #[cfg(not(feature = "no_index"))] pub type Array = Vec; +/// Variable-sized array of [`u8`] values (byte array). +/// Not available under `no_index`. +#[cfg(not(feature = "no_index"))] +pub type Blob = Vec; + /// Hash map of [`Dynamic`] values with [`SmartString`](https://crates.io/crates/smartstring) keys. /// Not available under `no_object`. #[cfg(not(feature = "no_object"))] diff --git a/src/packages/blob_basic.rs b/src/packages/blob_basic.rs new file mode 100644 index 00000000..22a229fa --- /dev/null +++ b/src/packages/blob_basic.rs @@ -0,0 +1,507 @@ +#![cfg(not(feature = "no_index"))] +#![allow(non_snake_case)] + +use crate::plugin::*; +use crate::{def_package, Blob, Dynamic, EvalAltResult, FnPtr, NativeCallContext, Position, INT}; +#[cfg(feature = "no_std")] +use std::prelude::v1::*; +use std::{any::TypeId, mem}; + +def_package!(crate:BasicBlobPackage:"Basic BLOB utilities.", lib, { + lib.standard = true; + + combine_with_exported_module!(lib, "blob", blob_functions); + + // Register blob iterator + lib.set_iterable::(); +}); + +#[export_module] +mod blob_functions { + pub fn blob() -> Blob { + Blob::new() + } + #[rhai_fn(name = "blob", return_raw)] + pub fn blob_with_capacity( + ctx: NativeCallContext, + len: INT, + ) -> Result> { + blob_with_capacity_and_value(ctx, len, 0) + } + #[rhai_fn(name = "blob", return_raw)] + pub fn blob_with_capacity_and_value( + _ctx: NativeCallContext, + len: INT, + value: INT, + ) -> Result> { + let len = if len < 0 { 0 } else { len as usize }; + + // Check if blob will be over max size limit + #[cfg(not(feature = "unchecked"))] + if _ctx.engine().max_array_size() > 0 && len > _ctx.engine().max_array_size() { + return Err(EvalAltResult::ErrorDataTooLarge( + "Size of BLOB".to_string(), + Position::NONE, + ) + .into()); + } + + let value = (value & 0x000f) as u8; + let mut blob = Blob::with_capacity(len); + for _ in 0..len { + blob.push(value); + } + Ok(blob) + } + #[rhai_fn(name = "len", get = "len", pure)] + pub fn len(blob: &mut Blob) -> INT { + blob.len() as INT + } + #[rhai_fn(name = "push", name = "+=")] + pub fn push(blob: &mut Blob, item: INT) { + let item = (item & 0x000f) as u8; + blob.push(item); + } + #[rhai_fn(name = "append", name = "+=")] + pub fn append(blob: &mut Blob, y: Blob) { + if !y.is_empty() { + if blob.is_empty() { + *blob = y; + } else { + blob.extend(y); + } + } + } + #[rhai_fn(name = "+")] + pub fn concat(mut blob: Blob, y: Blob) -> Blob { + if !y.is_empty() { + if blob.is_empty() { + blob = y; + } else { + blob.extend(y); + } + } + blob + } + pub fn insert(blob: &mut Blob, position: INT, item: INT) { + let item = (item & 0x000f) as u8; + + if blob.is_empty() { + blob.push(item); + } else if position < 0 { + if let Some(n) = position.checked_abs() { + if n as usize > blob.len() { + blob.insert(0, item); + } else { + blob.insert(blob.len() - n as usize, item); + } + } else { + blob.insert(0, item); + } + } else if (position as usize) >= blob.len() { + blob.push(item); + } else { + blob.insert(position as usize, item); + } + } + #[rhai_fn(return_raw)] + pub fn pad( + _ctx: NativeCallContext, + blob: &mut Blob, + len: INT, + item: INT, + ) -> Result<(), Box> { + if len <= 0 { + return Ok(()); + } + + let item = (item & 0x000f) as u8; + + // Check if blob will be over max size limit + #[cfg(not(feature = "unchecked"))] + if _ctx.engine().max_array_size() > 0 && (len as usize) > _ctx.engine().max_array_size() { + return Err(EvalAltResult::ErrorDataTooLarge( + "Size of BLOB".to_string(), + Position::NONE, + ) + .into()); + } + + if len as usize > blob.len() { + blob.resize(len as usize, item); + } + + Ok(()) + } + pub fn pop(blob: &mut Blob) -> INT { + if blob.is_empty() { + 0 + } else { + blob.pop().map_or_else(|| 0, |v| v as INT) + } + } + pub fn shift(blob: &mut Blob) -> INT { + if blob.is_empty() { + 0 + } else { + blob.remove(0) as INT + } + } + pub fn remove(blob: &mut Blob, len: INT) -> INT { + if len < 0 || (len as usize) >= blob.len() { + 0 + } else { + blob.remove(len as usize) as INT + } + } + pub fn clear(blob: &mut Blob) { + if !blob.is_empty() { + blob.clear(); + } + } + pub fn truncate(blob: &mut Blob, len: INT) { + if !blob.is_empty() { + if len >= 0 { + blob.truncate(len as usize); + } else { + blob.clear(); + } + } + } + pub fn chop(blob: &mut Blob, len: INT) { + if !blob.is_empty() && len as usize >= blob.len() { + if len >= 0 { + blob.drain(0..blob.len() - len as usize); + } else { + blob.clear(); + } + } + } + pub fn reverse(blob: &mut Blob) { + if !blob.is_empty() { + blob.reverse(); + } + } + pub fn splice(blob: &mut Blob, start: INT, len: INT, replace: Blob) { + if blob.is_empty() { + *blob = replace; + return; + } + + let start = if start < 0 { + let arr_len = blob.len(); + start + .checked_abs() + .map_or(0, |n| arr_len - (n as usize).min(arr_len)) + } else if start as usize >= blob.len() { + blob.extend(replace.into_iter()); + return; + } else { + start as usize + }; + + let len = if len < 0 { + 0 + } else if len as usize > blob.len() - start { + blob.len() - start + } else { + len as usize + }; + + blob.splice(start..start + len, replace.into_iter()); + } + pub fn extract(blob: &mut Blob, start: INT, len: INT) -> Blob { + if blob.is_empty() || len <= 0 { + return Blob::new(); + } + + let start = if start < 0 { + let arr_len = blob.len(); + start + .checked_abs() + .map_or(0, |n| arr_len - (n as usize).min(arr_len)) + } else if start as usize >= blob.len() { + return Blob::new(); + } else { + start as usize + }; + + let len = if len <= 0 { + 0 + } else if len as usize > blob.len() - start { + blob.len() - start + } else { + len as usize + }; + + if len == 0 { + Blob::new() + } else { + blob[start..start + len].to_vec() + } + } + #[rhai_fn(name = "extract")] + pub fn extract_tail(blob: &mut Blob, start: INT) -> Blob { + if blob.is_empty() { + return Blob::new(); + } + + let start = if start < 0 { + let arr_len = blob.len(); + start + .checked_abs() + .map_or(0, |n| arr_len - (n as usize).min(arr_len)) + } else if start as usize >= blob.len() { + return Blob::new(); + } else { + start as usize + }; + + blob[start..].to_vec() + } + #[rhai_fn(name = "split")] + pub fn split_at(blob: &mut Blob, start: INT) -> Blob { + if blob.is_empty() { + Blob::new() + } else if start < 0 { + if let Some(n) = start.checked_abs() { + if n as usize > blob.len() { + mem::take(blob) + } else { + let mut result = Blob::new(); + result.extend(blob.drain(blob.len() - n as usize..)); + result + } + } else { + mem::take(blob) + } + } else if start as usize >= blob.len() { + Blob::new() + } else { + let mut result = Blob::new(); + result.extend(blob.drain(start as usize..)); + result + } + } + #[rhai_fn(return_raw, pure)] + pub fn drain( + ctx: NativeCallContext, + blob: &mut Blob, + filter: FnPtr, + ) -> Result> { + drain_with_fn_name(ctx, blob, filter.fn_name()) + } + #[rhai_fn(name = "drain", return_raw)] + pub fn drain_with_fn_name( + ctx: NativeCallContext, + blob: &mut Blob, + filter: &str, + ) -> Result> { + if blob.is_empty() { + return Ok(Blob::new()); + } + + let mut index_val = Dynamic::ZERO; + let mut removed = Vec::with_capacity(blob.len()); + let mut count = 0; + + for (i, &item) in blob.iter().enumerate() { + let mut item = (item as INT).into(); + let mut args = [&mut item, &mut index_val]; + + let remove = match ctx.call_fn_raw(filter, true, false, &mut args[..1]) { + Ok(r) => r, + Err(err) => match *err { + EvalAltResult::ErrorFunctionNotFound(fn_sig, _) + if fn_sig.starts_with(filter) => + { + *args[1] = Dynamic::from(i as INT); + ctx.call_fn_raw(filter, true, false, &mut args)? + } + _ => { + return Err(EvalAltResult::ErrorInFunctionCall( + "drain".to_string(), + ctx.source().unwrap_or("").to_string(), + err, + Position::NONE, + ) + .into()) + } + }, + } + .as_bool() + .unwrap_or(false); + + removed.push(remove); + + if remove { + count += 1; + } + } + + if count == 0 { + return Ok(Blob::new()); + } + + let mut result = Vec::with_capacity(count); + let mut x = 0; + let mut i = 0; + + while i < blob.len() { + if removed[x] { + result.push(blob.remove(i)); + } else { + i += 1; + } + x += 1; + } + + Ok(result.into()) + } + #[rhai_fn(name = "drain")] + pub fn drain_range(blob: &mut Blob, start: INT, len: INT) -> Blob { + if blob.is_empty() || len <= 0 { + return Blob::new(); + } + + let start = if start < 0 { + let arr_len = blob.len(); + start + .checked_abs() + .map_or(0, |n| arr_len - (n as usize).min(arr_len)) + } else if start as usize >= blob.len() { + return Blob::new(); + } else { + start as usize + }; + + let len = if len <= 0 { + 0 + } else if len as usize > blob.len() - start { + blob.len() - start + } else { + len as usize + }; + + blob.drain(start..start + len).collect() + } + #[rhai_fn(return_raw, pure)] + pub fn retain( + ctx: NativeCallContext, + blob: &mut Blob, + filter: FnPtr, + ) -> Result> { + retain_with_fn_name(ctx, blob, filter.fn_name()) + } + #[rhai_fn(name = "retain", return_raw)] + pub fn retain_with_fn_name( + ctx: NativeCallContext, + blob: &mut Blob, + filter: &str, + ) -> Result> { + if blob.is_empty() { + return Ok(Blob::new()); + } + + let mut index_val = Dynamic::ZERO; + let mut removed = Vec::with_capacity(blob.len()); + let mut count = 0; + + for (i, &item) in blob.iter().enumerate() { + let mut item = (item as INT).into(); + let mut args = [&mut item, &mut index_val]; + + let keep = match ctx.call_fn_raw(filter, true, false, &mut args[..1]) { + Ok(r) => r, + Err(err) => match *err { + EvalAltResult::ErrorFunctionNotFound(fn_sig, _) + if fn_sig.starts_with(filter) => + { + *args[1] = Dynamic::from(i as INT); + ctx.call_fn_raw(filter, true, false, &mut args)? + } + _ => { + return Err(EvalAltResult::ErrorInFunctionCall( + "retain".to_string(), + ctx.source().unwrap_or("").to_string(), + err, + Position::NONE, + ) + .into()) + } + }, + } + .as_bool() + .unwrap_or(false); + + removed.push(!keep); + + if !keep { + count += 1; + } + } + + if count == 0 { + return Ok(Blob::new()); + } + + let mut result = Vec::with_capacity(count); + let mut x = 0; + let mut i = 0; + + while i < blob.len() { + if removed[x] { + result.push(blob.remove(i)); + } else { + i += 1; + } + x += 1; + } + + Ok(result.into()) + } + #[rhai_fn(name = "retain")] + pub fn retain_range(blob: &mut Blob, start: INT, len: INT) -> Blob { + if blob.is_empty() || len <= 0 { + return Blob::new(); + } + + let start = if start < 0 { + let arr_len = blob.len(); + start + .checked_abs() + .map_or(0, |n| arr_len - (n as usize).min(arr_len)) + } else if start as usize >= blob.len() { + return mem::take(blob); + } else { + start as usize + }; + + let len = if len < 0 { + 0 + } else if len as usize > blob.len() - start { + blob.len() - start + } else { + len as usize + }; + + let mut drained: Blob = blob.drain(..start).collect(); + drained.extend(blob.drain(len..)); + + drained + } + #[rhai_fn(name = "==", pure)] + pub fn equals(blob1: &mut Blob, blob2: Blob) -> bool { + if blob1.len() != blob2.len() { + false + } else if blob1.is_empty() { + true + } else { + blob1.iter().zip(blob2.iter()).all(|(&v1, &v2)| v1 == v2) + } + } + #[rhai_fn(name = "!=", pure)] + pub fn not_equals(blob1: &mut Blob, blob2: Blob) -> bool { + !equals(blob1, blob2) + } +} diff --git a/src/packages/mod.rs b/src/packages/mod.rs index 7b89ccdf..36344cb3 100644 --- a/src/packages/mod.rs +++ b/src/packages/mod.rs @@ -3,22 +3,29 @@ use crate::{Module, Shared}; pub(crate) mod arithmetic; +#[cfg(not(feature = "no_index"))] mod array_basic; +#[cfg(not(feature = "no_index"))] +mod blob_basic; mod fn_basic; mod iter_basic; mod lang_core; mod logic; +#[cfg(not(feature = "no_object"))] mod map_basic; mod math_basic; mod pkg_core; mod pkg_std; mod string_basic; mod string_more; +#[cfg(not(feature = "no_std"))] mod time_basic; pub use arithmetic::ArithmeticPackage; #[cfg(not(feature = "no_index"))] pub use array_basic::BasicArrayPackage; +#[cfg(not(feature = "no_index"))] +pub use blob_basic::BasicBlobPackage; pub use fn_basic::BasicFnPackage; pub use iter_basic::BasicIteratorPackage; pub use logic::LogicPackage; diff --git a/src/packages/pkg_std.rs b/src/packages/pkg_std.rs index afdece41..3c881232 100644 --- a/src/packages/pkg_std.rs +++ b/src/packages/pkg_std.rs @@ -1,5 +1,7 @@ #[cfg(not(feature = "no_index"))] use super::array_basic::BasicArrayPackage; +#[cfg(not(feature = "no_index"))] +use super::blob_basic::BasicBlobPackage; #[cfg(not(feature = "no_object"))] use super::map_basic::BasicMapPackage; use super::math_basic::BasicMathPackage; @@ -18,7 +20,10 @@ def_package!(crate:StandardPackage:"_Standard_ package containing all built-in f CorePackage::init(lib); BasicMathPackage::init(lib); #[cfg(not(feature = "no_index"))] - BasicArrayPackage::init(lib); + { + BasicArrayPackage::init(lib); + BasicBlobPackage::init(lib); + } #[cfg(not(feature = "no_object"))] BasicMapPackage::init(lib); #[cfg(not(feature = "no_std"))] diff --git a/src/serde/de.rs b/src/serde/de.rs index 8c7420e2..a93cdc8c 100644 --- a/src/serde/de.rs +++ b/src/serde/de.rs @@ -153,6 +153,8 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { #[cfg(not(feature = "no_index"))] Union::Array(_, _, _) => self.deserialize_seq(visitor), + #[cfg(not(feature = "no_index"))] + Union::Blob(_, _, _) => self.deserialize_seq(visitor), #[cfg(not(feature = "no_object"))] Union::Map(_, _, _) => self.deserialize_map(visitor), Union::FnPtr(_, _, _) => self.type_error(), diff --git a/src/serde/serialize.rs b/src/serde/serialize.rs index f8eeffd5..daf0fd32 100644 --- a/src/serde/serialize.rs +++ b/src/serde/serialize.rs @@ -57,6 +57,8 @@ impl Serialize for Dynamic { #[cfg(not(feature = "no_index"))] Union::Array(ref a, _, _) => (**a).serialize(ser), + #[cfg(not(feature = "no_index"))] + Union::Blob(ref a, _, _) => (**a).serialize(ser), #[cfg(not(feature = "no_object"))] Union::Map(ref m, _, _) => { let mut map = ser.serialize_map(Some(m.len()))?; diff --git a/src/types/dynamic.rs b/src/types/dynamic.rs index 7fcd54f9..a2b5b970 100644 --- a/src/types/dynamic.rs +++ b/src/types/dynamic.rs @@ -20,7 +20,7 @@ use crate::{ast::FloatWrapper, FLOAT}; use rust_decimal::Decimal; #[cfg(not(feature = "no_index"))] -use crate::Array; +use crate::{Array, Blob}; #[cfg(not(feature = "no_object"))] use crate::Map; @@ -200,6 +200,11 @@ pub enum Union { /// Not available under `no_index`. #[cfg(not(feature = "no_index"))] Array(Box, Tag, AccessMode), + /// An blob (byte array). + /// + /// Not available under `no_index`. + #[cfg(not(feature = "no_index"))] + Blob(Box, Tag, AccessMode), /// An object map value. /// /// Not available under `no_object`. @@ -332,7 +337,7 @@ impl Dynamic { #[cfg(feature = "decimal")] Union::Decimal(_, tag, _) => tag, #[cfg(not(feature = "no_index"))] - Union::Array(_, tag, _) => tag, + Union::Array(_, tag, _) | Union::Blob(_, tag, _) => tag, #[cfg(not(feature = "no_object"))] Union::Map(_, tag, _) => tag, #[cfg(not(feature = "no_std"))] @@ -357,7 +362,7 @@ impl Dynamic { #[cfg(feature = "decimal")] Union::Decimal(_, ref mut tag, _) => *tag = value, #[cfg(not(feature = "no_index"))] - Union::Array(_, ref mut tag, _) => *tag = value, + Union::Array(_, ref mut tag, _) | Union::Blob(_, ref mut tag, _) => *tag = value, #[cfg(not(feature = "no_object"))] Union::Map(_, ref mut tag, _) => *tag = value, #[cfg(not(feature = "no_std"))] @@ -419,6 +424,8 @@ impl Dynamic { Union::Decimal(_, _, _) => TypeId::of::(), #[cfg(not(feature = "no_index"))] Union::Array(_, _, _) => TypeId::of::(), + #[cfg(not(feature = "no_index"))] + Union::Blob(_, _, _) => TypeId::of::(), #[cfg(not(feature = "no_object"))] Union::Map(_, _, _) => TypeId::of::(), Union::FnPtr(_, _, _) => TypeId::of::(), @@ -456,6 +463,8 @@ impl Dynamic { Union::Decimal(_, _, _) => "decimal", #[cfg(not(feature = "no_index"))] Union::Array(_, _, _) => "array", + #[cfg(not(feature = "no_index"))] + Union::Blob(_, _, _) => "blob", #[cfg(not(feature = "no_object"))] Union::Map(_, _, _) => "map", Union::FnPtr(_, _, _) => "Fn", @@ -498,6 +507,8 @@ impl Hash for Dynamic { Union::Decimal(ref d, _, _) => d.hash(state), #[cfg(not(feature = "no_index"))] Union::Array(ref a, _, _) => a.as_ref().hash(state), + #[cfg(not(feature = "no_index"))] + Union::Blob(ref a, _, _) => a.as_ref().hash(state), #[cfg(not(feature = "no_object"))] Union::Map(ref m, _, _) => m.as_ref().hash(state), Union::FnPtr(ref f, _, _) => f.hash(state), @@ -586,6 +597,10 @@ pub(crate) fn map_std_type_name(name: &str) -> &str { if name == type_name::() { return "array"; } + #[cfg(not(feature = "no_index"))] + if name == type_name::() { + return "blob"; + } #[cfg(not(feature = "no_object"))] if name == type_name::() { return "map"; @@ -612,6 +627,8 @@ impl fmt::Display for Dynamic { Union::Decimal(ref value, _, _) => fmt::Display::fmt(value, f), #[cfg(not(feature = "no_index"))] Union::Array(ref value, _, _) => fmt::Debug::fmt(value, f), + #[cfg(not(feature = "no_index"))] + Union::Blob(ref value, _, _) => fmt::Debug::fmt(value, f), #[cfg(not(feature = "no_object"))] Union::Map(ref value, _, _) => { f.write_str("#")?; @@ -692,6 +709,8 @@ impl fmt::Debug for Dynamic { Union::Decimal(ref value, _, _) => fmt::Debug::fmt(value, f), #[cfg(not(feature = "no_index"))] Union::Array(ref value, _, _) => fmt::Debug::fmt(value, f), + #[cfg(not(feature = "no_index"))] + Union::Blob(ref value, _, _) => fmt::Debug::fmt(value, f), #[cfg(not(feature = "no_object"))] Union::Map(ref value, _, _) => { f.write_str("#")?; @@ -781,6 +800,8 @@ impl Clone for Dynamic { } #[cfg(not(feature = "no_index"))] Union::Array(ref value, tag, _) => Self(Union::Array(value.clone(), tag, ReadWrite)), + #[cfg(not(feature = "no_index"))] + Union::Blob(ref value, tag, _) => Self(Union::Blob(value.clone(), tag, ReadWrite)), #[cfg(not(feature = "no_object"))] Union::Map(ref value, tag, _) => Self(Union::Map(value.clone(), tag, ReadWrite)), Union::FnPtr(ref value, tag, _) => Self(Union::FnPtr(value.clone(), tag, ReadWrite)), @@ -972,7 +993,7 @@ impl Dynamic { #[cfg(feature = "decimal")] Union::Decimal(_, _, access) => access, #[cfg(not(feature = "no_index"))] - Union::Array(_, _, access) => access, + Union::Array(_, _, access) | Union::Blob(_, _, access) => access, #[cfg(not(feature = "no_object"))] Union::Map(_, _, access) => access, #[cfg(not(feature = "no_std"))] @@ -1003,6 +1024,8 @@ impl Dynamic { v.set_access_mode(typ); }); } + #[cfg(not(feature = "no_index"))] + Union::Blob(_, _, ref mut access) => *access = typ, #[cfg(not(feature = "no_object"))] Union::Map(ref mut m, _, ref mut access) => { *access = typ; @@ -1166,6 +1189,13 @@ impl Dynamic { Err(value) => value, }; } + #[cfg(not(feature = "no_index"))] + { + value = match unsafe_try_cast::<_, Blob>(value) { + Ok(blob) => return Self(Union::Blob(Box::new(blob), DEFAULT_TAG_VALUE, ReadWrite)), + Err(value) => value, + }; + } #[cfg(not(feature = "no_object"))] { @@ -1327,6 +1357,14 @@ impl Dynamic { }; } + #[cfg(not(feature = "no_index"))] + if TypeId::of::() == TypeId::of::() { + return match self.0 { + Union::Blob(value, _, _) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v), + _ => None, + }; + } + #[cfg(not(feature = "no_object"))] if TypeId::of::() == TypeId::of::() { return match self.0 { @@ -1660,6 +1698,13 @@ impl Dynamic { _ => None, }; } + #[cfg(not(feature = "no_index"))] + if TypeId::of::() == TypeId::of::() { + return match self.0 { + Union::Blob(ref value, _, _) => value.as_ref().as_any().downcast_ref::(), + _ => None, + }; + } #[cfg(not(feature = "no_object"))] if TypeId::of::() == TypeId::of::() { return match self.0 { @@ -1757,6 +1802,13 @@ impl Dynamic { _ => None, }; } + #[cfg(not(feature = "no_index"))] + if TypeId::of::() == TypeId::of::() { + return match self.0 { + Union::Blob(ref mut value, _, _) => value.as_mut().as_mut_any().downcast_mut::(), + _ => None, + }; + } #[cfg(not(feature = "no_object"))] if TypeId::of::() == TypeId::of::() { return match self.0 { @@ -2097,6 +2149,30 @@ impl Dynamic { } } } +#[cfg(not(feature = "no_index"))] +impl Dynamic { + /// Convert the [`Dynamic`] into a [`Vec`]. + /// Returns the name of the actual type if the cast fails. + #[inline(always)] + pub fn into_blob(self) -> Result { + match self.0 { + Union::Blob(a, _, _) => Ok(*a), + #[cfg(not(feature = "no_closure"))] + Union::Shared(cell, _, _) => { + #[cfg(not(feature = "sync"))] + let value = cell.borrow(); + #[cfg(feature = "sync")] + let value = cell.read().unwrap(); + + match value.0 { + Union::Blob(ref a, _, _) => Ok(a.as_ref().clone()), + _ => Err((*value).type_name()), + } + } + _ => Err(self.type_name()), + } + } +} #[cfg(not(feature = "no_object"))] impl Dynamic { /// Create a [`Dynamic`] from a [`Map`]. diff --git a/tests/blobs.rs b/tests/blobs.rs new file mode 100644 index 00000000..8f42eb56 --- /dev/null +++ b/tests/blobs.rs @@ -0,0 +1,68 @@ +#![cfg(not(feature = "no_index"))] +use rhai::{Blob, Engine, EvalAltResult, Scope, INT}; + +#[test] +fn test_blobs() -> Result<(), Box> { + let mut a = Blob::new(); + a.push(1); + a.push(2); + a.push(3); + + let engine = Engine::new(); + let mut orig_scope = Scope::new(); + orig_scope.push("x", a); + + let mut scope = orig_scope.clone(); + + assert_eq!(engine.eval_with_scope::(&mut scope, "x[1]")?, 2); + assert_eq!(engine.eval_with_scope::(&mut scope, "x[0]")?, 1); + assert_eq!(engine.eval_with_scope::(&mut scope, "x[-1]")?, 3); + assert_eq!(engine.eval_with_scope::(&mut scope, "x[-3]")?, 1); + assert_eq!( + engine.eval_with_scope::(&mut scope, "x += 4; x[3]")?, + 4 + ); + + #[cfg(not(feature = "no_object"))] + { + assert_eq!( + engine.eval_with_scope::(&mut orig_scope.clone(), "x.push(4); x")?, + [1, 2, 3, 4] + ); + assert_eq!( + engine.eval_with_scope::(&mut orig_scope.clone(), "x.insert(0, 4); x")?, + [4, 1, 2, 3] + ); + assert_eq!( + engine.eval_with_scope::(&mut orig_scope.clone(), "x.insert(999, 4); x")?, + [1, 2, 3, 4] + ); + assert_eq!( + engine.eval_with_scope::(&mut orig_scope.clone(), "x.insert(-2, 4); x")?, + [1, 4, 2, 3] + ); + assert_eq!( + engine.eval_with_scope::(&mut orig_scope.clone(), "x.insert(-999, 4); x")?, + [4, 1, 2, 3] + ); + assert_eq!( + engine.eval_with_scope::(&mut orig_scope.clone(), "let z = [42]; x[z.len]")?, + 2 + ); + assert_eq!( + engine.eval_with_scope::(&mut orig_scope.clone(), "let z = [2]; x[z[0]]")?, + 3 + ); + } + + assert_eq!( + engine.eval_with_scope::(&mut orig_scope.clone(), "x += x; x")?, + [1, 2, 3, 1, 2, 3] + ); + assert_eq!( + engine.eval_with_scope::(&mut orig_scope.clone(), "x + x")?, + [1, 2, 3, 1, 2, 3] + ); + + Ok(()) +}