Skip to content

Commit

Permalink
Re add ZArr::iter(). (#94)
Browse files Browse the repository at this point in the history
  • Loading branch information
jmjoy authored Dec 18, 2022
1 parent d4c7814 commit 57cf7b9
Show file tree
Hide file tree
Showing 3 changed files with 169 additions and 59 deletions.
12 changes: 3 additions & 9 deletions phper-doc/doc/_05_internal_types/_02_z_arr/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,24 +41,18 @@ 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;
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`.

Expand Down
143 changes: 116 additions & 27 deletions phper/src/arrays.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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].
Expand Down Expand Up @@ -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<InsertKey<'a>>, mut value: ZVal) {
pub fn insert<'a>(&mut self, key: impl Into<InsertKey<'a>>, value: impl Into<ZVal>) {
let key = key.into();
let mut value = ManuallyDrop::new(value.into());
let val = value.as_mut_ptr();

unsafe {
Expand Down Expand Up @@ -150,8 +151,6 @@ impl ZArr {
}
}
}

forget(value);
}

// Get item by key.
Expand Down Expand Up @@ -244,21 +243,23 @@ impl ZArr {
}
}

pub fn for_each<'a>(&self, f: impl FnMut(IterKey<'a>, &'a ZVal)) {
let mut f: Box<dyn FnMut(IterKey<'a>, &'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<Key<'a>>) -> Entry<'a> {
let key = key.into();
match self.get_mut(key.clone()) {
Some(val) => Entry::Occupied(val),
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 {
Expand Down Expand Up @@ -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<dyn FnMut(IterKey<'_>, &'_ 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<Self::Item> {
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::Item> {
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::Item> {
self.0
.next()
.map(|(key, val)| (key, unsafe { ZVal::from_mut_ptr(val) }))
}
}

pub enum Entry<'a> {
Expand Down
73 changes: 50 additions & 23 deletions tests/integration/src/arrays.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
},
Expand Down

0 comments on commit 57cf7b9

Please sign in to comment.