diff --git a/phper-doc/doc/_05_internal_types/_02_z_arr/index.md b/phper-doc/doc/_05_internal_types/_02_z_arr/index.md index 0b9163b8..069e275c 100644 --- a/phper-doc/doc/_05_internal_types/_02_z_arr/index.md +++ b/phper-doc/doc/_05_internal_types/_02_z_arr/index.md @@ -41,7 +41,7 @@ let _i = arr.get("10"); arr.remove("foo"); ``` -`ZArr` can be iterated by `for_each()`. +`ZArr` can be iterated by `iter()`. ```rust,no_run use phper::arrays::ZArray; @@ -49,16 +49,10 @@ use phper::values::ZVal; let arr = ZArray::new(); - -arr.for_each(|k, v| { - dbg!(k, v); -}); +for (k, v) in arr.iter() { +} ``` -*I used to provide the `iter()` method for `ZArr`, and let `Iter` implement -`Iterator`, but if using the PHP stable macro `ZEND_HASH_FOREACH_KEY_VAL`, it is a -bit difficult to provide `iter`, so it is deleted.*; - `ZArr` implements `ToOwned`, can upgrade to `ZArray` by value copy via `zend_array_dup`. diff --git a/phper/src/arrays.rs b/phper/src/arrays.rs index 183da418..78fdcda7 100644 --- a/phper/src/arrays.rs +++ b/phper/src/arrays.rs @@ -15,10 +15,10 @@ use derive_more::From; use std::{ borrow::Borrow, convert::TryInto, - ffi::c_void, marker::PhantomData, - mem::{forget, ManuallyDrop}, + mem::ManuallyDrop, ops::{Deref, DerefMut}, + ptr::null_mut, }; /// Key for [ZArr]. @@ -112,8 +112,9 @@ impl ZArr { /// Add or update item by key. #[allow(clippy::useless_conversion)] - pub fn insert<'a>(&mut self, key: impl Into>, mut value: ZVal) { + pub fn insert<'a>(&mut self, key: impl Into>, value: impl Into) { let key = key.into(); + let mut value = ManuallyDrop::new(value.into()); let val = value.as_mut_ptr(); unsafe { @@ -150,8 +151,6 @@ impl ZArr { } } } - - forget(value); } // Get item by key. @@ -244,14 +243,6 @@ impl ZArr { } } - pub fn for_each<'a>(&self, f: impl FnMut(IterKey<'a>, &'a ZVal)) { - let mut f: Box, &'a ZVal)> = Box::new(f); - let f = &mut f as *mut Box<_> as *mut c_void; - unsafe { - phper_zend_hash_foreach_key_val(self.as_ptr() as *mut _, Some(for_each_callback), f); - } - } - pub fn entry<'a>(&'a mut self, key: impl Into>) -> Entry<'a> { let key = key.into(); match self.get_mut(key.clone()) { @@ -259,6 +250,16 @@ impl ZArr { None => Entry::Vacant { arr: self, key }, } } + + #[inline] + pub fn iter(&self) -> Iter<'_> { + Iter::new(self) + } + + #[inline] + pub fn iter_mut(&mut self) -> IterMut<'_> { + IterMut::new(self) + } } impl ToOwned for ZArr { @@ -368,26 +369,114 @@ impl Drop for ZArray { } } -/// Iterator key for [`ZArr::for_each`]. +/// Iterator key for [`ZArr::iter`] and [`ZArr::iter_mut`]. #[derive(Debug, Clone, PartialEq, From)] pub enum IterKey<'a> { Index(u64), ZStr(&'a ZStr), } -#[allow(clippy::unnecessary_cast)] -unsafe extern "C" fn for_each_callback( - idx: zend_ulong, key: *mut zend_string, val: *mut zval, argument: *mut c_void, -) { - let f = (argument as *mut Box, &'_ ZVal)>) - .as_mut() - .unwrap(); - let iter_key = if key.is_null() { - IterKey::Index(idx as u64) - } else { - IterKey::ZStr(ZStr::from_ptr(key)) - }; - f(iter_key, ZVal::from_ptr(val)); +struct RawIter<'a> { + arr: *mut zend_array, + pos: HashPosition, + finished: bool, + _p: PhantomData<&'a ()>, +} + +impl<'a> RawIter<'a> { + fn new(arr: *mut zend_array) -> Self { + let mut pos: HashPosition = 0; + unsafe { + zend_hash_internal_pointer_reset_ex(arr, &mut pos); + } + Self { + arr, + pos, + finished: false, + _p: PhantomData, + } + } +} + +impl<'a> Iterator for RawIter<'a> { + type Item = (IterKey<'a>, *mut zval); + + fn next(&mut self) -> Option { + unsafe { + if self.finished { + return None; + } + + let mut str_index: *mut zend_string = null_mut(); + let mut num_index: zend_ulong = 0; + + #[allow(clippy::unnecessary_mut_passed)] + let result = zend_hash_get_current_key_ex( + self.arr, + &mut str_index, + &mut num_index, + &mut self.pos, + ) as u32; + + let iter_key = if result == HASH_KEY_IS_STRING { + IterKey::ZStr(ZStr::from_mut_ptr(str_index)) + } else if result == HASH_KEY_IS_LONG { + #[allow(clippy::unnecessary_cast)] + IterKey::Index(num_index as u64) + } else { + self.finished = true; + return None; + }; + + let val = zend_hash_get_current_data_ex(self.arr, &mut self.pos); + if val.is_null() { + self.finished = true; + return None; + } + + if zend_hash_move_forward_ex(self.arr, &mut self.pos) == ZEND_RESULT_CODE_FAILURE { + self.finished = true; + } + + Some((iter_key, val)) + } + } +} + +pub struct Iter<'a>(RawIter<'a>); + +impl<'a> Iter<'a> { + fn new(arr: &'a ZArr) -> Self { + Self(RawIter::new(arr.as_ptr() as *mut _)) + } +} + +impl<'a> Iterator for Iter<'a> { + type Item = (IterKey<'a>, &'a ZVal); + + fn next(&mut self) -> Option { + self.0 + .next() + .map(|(key, val)| (key, unsafe { ZVal::from_ptr(val) })) + } +} + +pub struct IterMut<'a>(RawIter<'a>); + +impl<'a> IterMut<'a> { + fn new(arr: &'a mut ZArr) -> Self { + Self(RawIter::new(arr.as_mut_ptr())) + } +} + +impl<'a> Iterator for IterMut<'a> { + type Item = (IterKey<'a>, &'a mut ZVal); + + fn next(&mut self) -> Option { + self.0 + .next() + .map(|(key, val)| (key, unsafe { ZVal::from_mut_ptr(val) })) + } } pub enum Entry<'a> { diff --git a/tests/integration/src/arrays.rs b/tests/integration/src/arrays.rs index d7862874..582aff34 100644 --- a/tests/integration/src/arrays.rs +++ b/tests/integration/src/arrays.rs @@ -195,29 +195,56 @@ pub fn integrate(module: &mut Module) { a.insert((), ZVal::from(1)); a.insert("foo", ZVal::from("bar")); - let mut i = 0; - - a.for_each(|k, v| { - match i { - 0 => { - assert_eq!(k, 0.into()); - assert_eq!(v.as_long(), Some(0)); - } - 1 => { - assert_eq!(k, 1.into()); - assert_eq!(v.as_long(), Some(1)); - } - 2 => { - assert_eq!(k, IterKey::ZStr(&ZString::new("foo"))); - assert_eq!(v.as_z_str().unwrap().to_str(), Ok("bar")); - } - _ => unreachable!(), - } - - i += 1; - }); - - assert_eq!(i, 3); + let mut it = a.iter(); + { + let (k, v) = it.next().unwrap(); + assert_eq!(k, 0.into()); + assert_eq!(v.as_long(), Some(0)); + } + { + let (k, v) = it.next().unwrap(); + assert_eq!(k, 1.into()); + assert_eq!(v.as_long(), Some(1)); + } + { + let (k, v) = it.next().unwrap(); + assert_eq!(k, IterKey::ZStr(&ZString::new("foo"))); + assert_eq!(v.as_z_str().unwrap().to_str(), Ok("bar")); + } + { + assert!(it.next().is_none()); + } + { + assert!(it.next().is_none()); + } + + let mut it = a.iter_mut(); + { + let (k, v) = it.next().unwrap(); + assert_eq!(k, 0.into()); + assert_eq!(v.as_long(), Some(0)); + *v.as_mut_long().unwrap() += 100; + } + { + let (k, v) = it.next().unwrap(); + assert_eq!(k, 1.into()); + assert_eq!(v.as_long(), Some(1)); + *v.as_mut_long().unwrap() += 100; + } + { + let (k, v) = it.next().unwrap(); + assert_eq!(k, IterKey::ZStr(&ZString::new("foo"))); + assert_eq!(v.as_z_str().unwrap().to_str(), Ok("bar")); + } + { + assert!(it.next().is_none()); + } + { + assert!(it.next().is_none()); + } + + assert_eq!(a.get(0).unwrap().as_long(), Some(100)); + assert_eq!(a.get(1).unwrap().as_long(), Some(101)); Ok(()) },