feat(iterator): add iterable, add method to get traversable or iterable

This commit is contained in:
Joel Wurtz 2023-10-17 20:30:24 +02:00 committed by Joel Wurtz
parent 8f425fe5f1
commit e5b1ebd99e
No known key found for this signature in database
GPG Key ID: ED264D1967A51B0D
10 changed files with 162 additions and 37 deletions

View File

@ -160,6 +160,7 @@ bind! {
IS_UNDEF,
IS_VOID,
IS_PTR,
IS_ITERABLE,
MAY_BE_ANY,
MAY_BE_BOOL,
PHP_INI_USER,

View File

@ -172,6 +172,7 @@ impl ToStub for DataType {
DataType::Reference => "reference",
DataType::Callable => "callable",
DataType::Bool => "bool",
DataType::Iterable => "iterable",
_ => "mixed",
}
)

View File

@ -8,14 +8,14 @@ use crate::ffi::{
CONST_CS, CONST_DEPRECATED, CONST_NO_FILE_CACHE, CONST_PERSISTENT, E_COMPILE_ERROR,
E_COMPILE_WARNING, E_CORE_ERROR, E_CORE_WARNING, E_DEPRECATED, E_ERROR, E_NOTICE, E_PARSE,
E_RECOVERABLE_ERROR, E_STRICT, E_USER_DEPRECATED, E_USER_ERROR, E_USER_NOTICE, E_USER_WARNING,
E_WARNING, IS_ARRAY, IS_CALLABLE, IS_CONSTANT_AST, IS_DOUBLE, IS_FALSE, IS_LONG, IS_MIXED,
IS_NULL, IS_OBJECT, IS_PTR, IS_REFERENCE, IS_RESOURCE, IS_STRING, IS_TRUE, IS_TYPE_COLLECTABLE,
IS_TYPE_REFCOUNTED, IS_UNDEF, IS_VOID, PHP_INI_ALL, PHP_INI_PERDIR, PHP_INI_SYSTEM,
PHP_INI_USER, ZEND_ACC_ABSTRACT, ZEND_ACC_ANON_CLASS, ZEND_ACC_CALL_VIA_TRAMPOLINE,
ZEND_ACC_CHANGED, ZEND_ACC_CLOSURE, ZEND_ACC_CONSTANTS_UPDATED, ZEND_ACC_CTOR,
ZEND_ACC_DEPRECATED, ZEND_ACC_DONE_PASS_TWO, ZEND_ACC_EARLY_BINDING, ZEND_ACC_FAKE_CLOSURE,
ZEND_ACC_FINAL, ZEND_ACC_GENERATOR, ZEND_ACC_HAS_FINALLY_BLOCK, ZEND_ACC_HAS_RETURN_TYPE,
ZEND_ACC_HAS_TYPE_HINTS, ZEND_ACC_HEAP_RT_CACHE, ZEND_ACC_IMMUTABLE,
E_WARNING, IS_ARRAY, IS_CALLABLE, IS_CONSTANT_AST, IS_DOUBLE, IS_FALSE, IS_ITERABLE, IS_LONG,
IS_MIXED, IS_NULL, IS_OBJECT, IS_PTR, IS_REFERENCE, IS_RESOURCE, IS_STRING, IS_TRUE,
IS_TYPE_COLLECTABLE, IS_TYPE_REFCOUNTED, IS_UNDEF, IS_VOID, PHP_INI_ALL, PHP_INI_PERDIR,
PHP_INI_SYSTEM, PHP_INI_USER, ZEND_ACC_ABSTRACT, ZEND_ACC_ANON_CLASS,
ZEND_ACC_CALL_VIA_TRAMPOLINE, ZEND_ACC_CHANGED, ZEND_ACC_CLOSURE, ZEND_ACC_CONSTANTS_UPDATED,
ZEND_ACC_CTOR, ZEND_ACC_DEPRECATED, ZEND_ACC_DONE_PASS_TWO, ZEND_ACC_EARLY_BINDING,
ZEND_ACC_FAKE_CLOSURE, ZEND_ACC_FINAL, ZEND_ACC_GENERATOR, ZEND_ACC_HAS_FINALLY_BLOCK,
ZEND_ACC_HAS_RETURN_TYPE, ZEND_ACC_HAS_TYPE_HINTS, ZEND_ACC_HEAP_RT_CACHE, ZEND_ACC_IMMUTABLE,
ZEND_ACC_IMPLICIT_ABSTRACT_CLASS, ZEND_ACC_INTERFACE, ZEND_ACC_LINKED, ZEND_ACC_NEARLY_LINKED,
ZEND_ACC_NEVER_CACHE, ZEND_ACC_NO_DYNAMIC_PROPERTIES, ZEND_ACC_PRELOADED, ZEND_ACC_PRIVATE,
ZEND_ACC_PROMOTED, ZEND_ACC_PROTECTED, ZEND_ACC_PUBLIC, ZEND_ACC_RESOLVED_INTERFACES,
@ -49,6 +49,7 @@ bitflags! {
const ConstantExpression = IS_CONSTANT_AST;
const Void = IS_VOID;
const Ptr = IS_PTR;
const Iterable = IS_ITERABLE;
const InternedStringEx = Self::String.bits();
const StringEx = Self::String.bits() | Self::RefCounted.bits();
@ -237,6 +238,7 @@ pub enum DataType {
Double,
String,
Array,
Iterable,
Object(Option<&'static str>),
Resource,
Reference,
@ -275,6 +277,7 @@ impl DataType {
DataType::Mixed => IS_MIXED,
DataType::Bool => _IS_BOOL,
DataType::Ptr => IS_PTR,
DataType::Iterable => IS_ITERABLE,
}
}
}
@ -379,6 +382,7 @@ impl Display for DataType {
DataType::Bool => write!(f, "Bool"),
DataType::Mixed => write!(f, "Mixed"),
DataType::Ptr => write!(f, "Pointer"),
DataType::Iterable => write!(f, "Iterable"),
}
}
}

View File

@ -10,6 +10,8 @@ use std::{
u64,
};
use crate::types::iterator::IterKey;
use crate::types::ZendLong;
use crate::{
boxed::{ZBox, ZBoxable},
convert::{FromZval, IntoZval},
@ -25,8 +27,6 @@ use crate::{
flags::DataType,
types::Zval,
};
use crate::types::iterator::IterKey;
use crate::types::ZendLong;
/// A PHP hashtable.
///
@ -512,7 +512,7 @@ impl ZendHashTable {
/// // ^ Optional string key, if inserted like a hashtable.
/// // ^ Inserted value.
///
/// dbg!(idx, key, val);
/// dbg!(key, val);
/// }
#[inline]
pub fn iter(&self) -> Iter {
@ -548,10 +548,7 @@ unsafe impl ZBoxable for ZendHashTable {
impl Debug for ZendHashTable {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_map()
.entries(
self.iter()
.map(|(k, v)| (k.to_string(), v)),
)
.entries(self.iter().map(|(k, v)| (k.to_string(), v)))
.finish()
}
}
@ -626,7 +623,10 @@ impl<'a> Iterator for Iter<'a> {
};
let r = match key.is_long() {
true => (IterKey::Long(key.long().unwrap_or(self.current_num as ZendLong) as u64), value),
true => (
IterKey::Long(key.long().unwrap_or(self.current_num as ZendLong) as u64),
value,
),
false => match key.try_into() {
Ok(key) => (IterKey::String(key), value),
Err(_) => (IterKey::Long(self.current_num), value),
@ -687,7 +687,10 @@ impl<'a> DoubleEndedIterator for Iter<'a> {
};
let r = match key.is_long() {
true => (IterKey::Long(key.long().unwrap_or(self.current_num as ZendLong) as u64), value),
true => (
IterKey::Long(key.long().unwrap_or(self.current_num as ZendLong) as u64),
value,
),
false => match key.try_into() {
Ok(key) => (IterKey::String(key), value),
Err(_) => (IterKey::Long(self.current_num), value),

53
src/types/iterable.rs Normal file
View File

@ -0,0 +1,53 @@
use super::array::Iter as ZendHashTableIter;
use super::iterator::Iter as ZendIteratorIter;
use crate::convert::FromZval;
use crate::flags::DataType;
use crate::types::iterator::IterKey;
use crate::types::{ZendHashTable, ZendIterator, Zval};
#[derive(Debug)]
pub enum Iterable<'a> {
Array(&'a ZendHashTable),
Traversable(&'a mut ZendIterator),
}
impl<'a> Iterable<'a> {
pub fn iter(&mut self) -> Iter {
match self {
Iterable::Array(array) => Iter::Array(array.iter()),
Iterable::Traversable(traversable) => Iter::Traversable(traversable.iter()),
}
}
}
impl<'a> FromZval<'a> for Iterable<'a> {
const TYPE: DataType = DataType::Iterable;
fn from_zval(zval: &'a Zval) -> Option<Self> {
if let Some(array) = zval.array() {
return Some(Iterable::Array(array));
}
if let Some(traversable) = zval.traversable() {
return Some(Iterable::Traversable(traversable));
}
None
}
}
pub enum Iter<'a> {
Array(ZendHashTableIter<'a>),
Traversable(ZendIteratorIter<'a>),
}
impl<'a> Iterator for Iter<'a> {
type Item = (IterKey, &'a Zval);
fn next(&mut self) -> Option<Self::Item> {
match self {
Iter::Array(array) => array.next(),
Iter::Traversable(traversable) => traversable.next(),
}
}
}

View File

@ -3,8 +3,14 @@ use crate::ffi::zend_object_iterator;
use crate::flags::DataType;
use crate::types::{ZendLong, Zval};
use std::convert::TryInto;
use std::fmt::Display;
use std::fmt::{Debug, Display, Formatter};
/// A PHP Iterator.
///
/// In PHP, iterators are represented as zend_object_iterator. This allow user to iterate
/// over object implementing Traversable interface using foreach.
///
/// ```
pub type ZendIterator = zend_object_iterator;
impl ZendIterator {
@ -57,7 +63,13 @@ impl ZendIterator {
}
}
#[derive(PartialEq)]
impl Debug for ZendIterator {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ZendIterator").finish()
}
}
#[derive(Debug, PartialEq)]
pub enum IterKey {
Long(u64),
String(String),
@ -110,7 +122,10 @@ impl<'a> Iterator for Iter<'a> {
Ok(key) => (IterKey::String(key), value),
Err(_) => (IterKey::Long(real_index), value),
},
true => (IterKey::Long(key.long().unwrap_or(real_index as ZendLong) as u64), value),
true => (
IterKey::Long(key.long().unwrap_or(real_index as ZendLong) as u64),
value,
),
},
None => (IterKey::Long(real_index), value),
})
@ -121,10 +136,6 @@ impl<'a> FromZvalMut<'a> for &'a mut ZendIterator {
const TYPE: DataType = DataType::Object(Some("Traversable"));
fn from_zval_mut(zval: &'a mut Zval) -> Option<Self> {
let zend_object = zval.object()?;
let ce = zend_object.get_class_entry_mut();
let iterator = unsafe { ce.get_iterator?(&mut *ce, &mut *zval, 0) };
unsafe { iterator.as_mut() }
zval.object()?.get_class_entry().get_iterator(zval, false)
}
}

View File

@ -6,6 +6,7 @@
mod array;
mod callable;
mod class_object;
mod iterable;
mod iterator;
mod long;
mod object;
@ -15,6 +16,7 @@ mod zval;
pub use array::ZendHashTable;
pub use callable::ZendCallable;
pub use class_object::ZendClassObject;
pub use iterator::ZendIterator;
pub use long::ZendLong;
pub use object::{PropertyQuery, ZendObject};
pub use string::ZendStr;

View File

@ -103,17 +103,6 @@ impl ZendObject {
unsafe { self.ce.as_ref() }.expect("Could not retrieve class entry.")
}
/// Returns the [`ClassEntry`] associated with this object.
///
/// # Panics
///
/// Panics if the class entry is invalid.
pub fn get_class_entry_mut(&self) -> &'static mut ClassEntry {
// SAFETY: it is OK to panic here since PHP would segfault anyway
// when encountering an object with no class entry.
unsafe { self.ce.as_mut() }.expect("Could not retrieve class entry.")
}
/// Attempts to retrieve the class name of the object.
pub fn get_class_name(&self) -> Result<String> {
unsafe {
@ -144,6 +133,15 @@ impl ZendObject {
(self.ce as *const ClassEntry).eq(&(T::get_metadata().ce() as *const _))
}
/// Returns whether this object is an instance of \Traversable
///
/// # Panics
///
/// Panics if the class entry is invalid.
pub fn is_traversable(&self) -> bool {
self.instance_of(ce::traversable())
}
#[inline(always)]
pub fn try_call_method(&self, name: &str, params: Vec<&dyn IntoZvalDyn>) -> Result<Zval> {
let mut retval = Zval::new();

View File

@ -4,6 +4,8 @@
use std::{convert::TryInto, ffi::c_void, fmt::Debug, ptr};
use crate::types::iterable::Iterable;
use crate::types::ZendIterator;
use crate::{
binary::Pack,
binary_slice::PackSlice,
@ -12,7 +14,7 @@ use crate::{
error::{Error, Result},
ffi::{
_zval_struct__bindgen_ty_1, _zval_struct__bindgen_ty_2, zend_is_callable,
zend_is_identical, zend_resource, zend_value, zval, zval_ptr_dtor,
zend_is_identical, zend_is_iterable, zend_resource, zend_value, zval, zval_ptr_dtor,
},
flags::DataType,
flags::ZvalTypeFlags,
@ -237,6 +239,22 @@ impl Zval {
ZendCallable::new(self).ok()
}
pub fn traversable(&self) -> Option<&mut ZendIterator> {
if self.is_traversable() {
self.object()?.get_class_entry().get_iterator(self, false)
} else {
None
}
}
pub fn iterable(&self) -> Option<Iterable> {
if self.is_iterable() {
Iterable::from_zval(self)
} else {
None
}
}
/// Returns the value of the zval if it is a pointer.
///
/// # Safety
@ -347,6 +365,20 @@ impl Zval {
unsafe { zend_is_identical(self_p as *mut Self, other_p as *mut Self) }
}
/// Returns true if the zval is traversable, false otherwise.
pub fn is_traversable(&self) -> bool {
match self.object() {
None => false,
Some(obj) => obj.is_traversable(),
}
}
/// Returns true if the zval is iterable (array or traversable), false otherwise.
pub fn is_iterable(&self) -> bool {
let ptr: *const Self = self;
unsafe { zend_is_iterable(ptr as *mut Self) }
}
/// Returns true if the zval contains a pointer, false otherwise.
pub fn is_ptr(&self) -> bool {
self.get_type() == DataType::Ptr
@ -601,6 +633,7 @@ impl Debug for Zval {
DataType::ConstantExpression => field!(Option::<()>::None),
DataType::Void => field!(Option::<()>::None),
DataType::Bool => field!(self.bool()),
DataType::Iterable => field!(self.iterable()),
// SAFETY: We are not accessing the pointer.
DataType::Ptr => field!(unsafe { self.ptr::<c_void>() }),
};

View File

@ -1,5 +1,6 @@
//! Builder and objects for creating classes in the PHP world.
use crate::types::{ZendIterator, Zval};
use crate::{
boxed::ZBox,
ffi::zend_class_entry,
@ -97,6 +98,24 @@ impl ClassEntry {
}
}
/// Returns the iterator for the class for a specific instance
///
/// Returns [`None`] if there is no associated iterator for the class.
pub fn get_iterator<'a>(&self, zval: &'a Zval, by_ref: bool) -> Option<&'a mut ZendIterator> {
let ptr: *const Self = self;
let zval_ptr: *const Zval = zval;
let iterator = unsafe {
(*ptr).get_iterator?(
ptr as *mut ClassEntry,
zval_ptr as *mut Zval,
if by_ref { 1 } else { 0 },
)
};
unsafe { iterator.as_mut() }
}
pub fn name(&self) -> Option<&str> {
unsafe { self.name.as_ref().and_then(|s| s.as_str().ok()) }
}