Skip to content

Commit

Permalink
fix(array): fix double ended iterator implementation
Browse files Browse the repository at this point in the history
Refs: #316
  • Loading branch information
Xenira committed Jan 9, 2025
1 parent a493b8b commit 9a5810d
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 6 deletions.
29 changes: 24 additions & 5 deletions src/types/array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -573,7 +573,9 @@ impl ToOwned for ZendHashTable {
pub struct Iter<'a> {
ht: &'a ZendHashTable,
current_num: i64,
end_num: i64,
pos: HashPosition,
end_pos: HashPosition,
}

#[derive(Debug, PartialEq)]
Expand Down Expand Up @@ -627,10 +629,19 @@ impl<'a> Iter<'a> {
///
/// * `ht` - The hashtable to iterate.
pub fn new(ht: &'a ZendHashTable) -> Self {
let end_num: i64 = ht.len().try_into().unwrap();
let end_pos = if ht.nNumOfElements > 0 {
ht.nNumOfElements - 1
} else {
0
};

Self {
ht,
current_num: 0,
end_num,
pos: 0,
end_pos,
}
}
}
Expand Down Expand Up @@ -686,6 +697,10 @@ impl ExactSizeIterator for Iter<'_> {

impl DoubleEndedIterator for Iter<'_> {
fn next_back(&mut self) -> Option<Self::Item> {
if self.end_num <= self.current_num {
return None;
}

let key_type = unsafe {
zend_hash_get_current_key_type_ex(
self.ht as *const ZendHashTable as *mut ZendHashTable,
Expand All @@ -703,35 +718,39 @@ impl DoubleEndedIterator for Iter<'_> {
zend_hash_get_current_key_zval_ex(
self.ht as *const ZendHashTable as *mut ZendHashTable,
&key as *const Zval as *mut Zval,
&mut self.pos as *mut HashPosition,
&mut self.end_pos as *mut HashPosition,
);
}
let value = unsafe {
&*zend_hash_get_current_data_ex(
self.ht as *const ZendHashTable as *mut ZendHashTable,
&mut self.pos as *mut HashPosition,
&mut self.end_pos as *mut HashPosition,
)
};

let key = match ArrayKey::from_zval(&key) {
Some(key) => key,
None => ArrayKey::Long(self.current_num),
None => ArrayKey::Long(self.end_num),
};

unsafe {
zend_hash_move_backwards_ex(
self.ht as *const ZendHashTable as *mut ZendHashTable,
&mut self.pos as *mut HashPosition,
&mut self.end_pos as *mut HashPosition,
)
};
self.current_num -= 1;
self.end_num -= 1;

Some((key, value))
}
}

impl<'a> Iter<'a> {
pub fn next_zval(&mut self) -> Option<(Zval, &'a Zval)> {
if self.current_num >= self.end_num {
return None;
}

let key_type = unsafe {
zend_hash_get_current_key_type_ex(
self.ht as *const ZendHashTable as *mut ZendHashTable,
Expand Down
11 changes: 11 additions & 0 deletions tests/src/integration/iterator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

assert(iter_next([]) === []);
assert(iter_next([1, 2, 3]) === [0, 1, 1, 2, 2, 3]);
assert(iter_back([]) === []);
assert(iter_back([1, 2, 3]) === [2, 3, 1, 2, 0, 1]);

assert(iter_next_back([], 2) === [null, null]);
assert(iter_next_back([1, 2 ,3], 2) === [2, 3, 0, 1, 1, 2, null, null]);
var_dump(iter_next_back([1, 2, 3, 4, 5], 3));
assert(iter_next_back([1, 2, 3, 4, 5], 3) === [4, 5, 0, 1, 1, 2, 3, 4, 2, 3, null, null, null]);
4 changes: 4 additions & 0 deletions tests/src/integration/iterator.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#[test]
fn iterator_works() {
assert!(crate::integration::run_php("iterator.php"));
}
60 changes: 59 additions & 1 deletion tests/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
#![cfg_attr(windows, feature(abi_vectorcall))]
use ext_php_rs::{binary::Binary, prelude::*, types::ZendObject, types::Zval};
use ext_php_rs::{
binary::Binary,
prelude::*,
types::{ArrayKey, ZendHashTable, ZendObject, Zval},
};
use std::collections::HashMap;

#[php_function]
Expand Down Expand Up @@ -72,6 +76,59 @@ pub fn test_callable(call: ZendCallable, a: String) -> Zval {
call.try_call(vec![&a]).expect("Failed to call function")
}

#[php_function]
pub fn iter_next(ht: &ZendHashTable) -> Vec<Zval> {
ht.iter()
.flat_map(|(k, v)| [key_to_zval(k), v.shallow_clone()])
.collect()
}

#[php_function]
pub fn iter_back(ht: &ZendHashTable) -> Vec<Zval> {
ht.iter()
.rev()
.flat_map(|(k, v)| [key_to_zval(k), v.shallow_clone()])
.collect()
}

#[php_function]
pub fn iter_next_back(ht: &ZendHashTable, modulus: usize) -> Vec<Option<Zval>> {
let mut result = Vec::with_capacity(ht.len());
let mut iter = ht.iter();

for i in 0..ht.len() + modulus {
let entry = if i % modulus == 0 {
iter.next_back()
} else {
iter.next()
};

if let Some((k, v)) = entry {
result.push(Some(key_to_zval(k)));
result.push(Some(v.shallow_clone()));
} else {
result.push(None);
}
}

result
}

fn key_to_zval(key: ArrayKey) -> Zval {
match key {
ArrayKey::String(s) => {
let mut zval = Zval::new();
let _ = zval.set_string(s.as_str(), false);
zval
}
ArrayKey::Long(l) => {
let mut zval = Zval::new();
zval.set_long(l);
zval
}
}
}

#[php_class]
pub struct TestClass {
string: String,
Expand Down Expand Up @@ -179,6 +236,7 @@ mod integration {
mod callable;
mod class;
mod closure;
mod iterator;
mod nullable;
mod number;
mod object;
Expand Down

0 comments on commit 9a5810d

Please sign in to comment.