use crate::{alloc::EBox, strings::ZStr, sys::*, values::ZVal};
use cfg_if::cfg_if;
use derive_more::From;
use phper_alloc::ToRefOwned;
use std::{
fmt::{self, Debug},
marker::PhantomData,
mem::{ManuallyDrop, MaybeUninit},
ops::Deref,
ptr::null_mut,
};
#[derive(Debug, Clone, PartialEq, From)]
pub enum Key<'a> {
Index(u64),
Str(&'a str),
Bytes(&'a [u8]),
ZStr(&'a ZStr),
}
#[derive(Debug, Clone, PartialEq, From)]
pub enum InsertKey<'a> {
NextIndex,
Index(u64),
Str(&'a str),
Bytes(&'a [u8]),
ZStr(&'a ZStr),
}
impl From<()> for InsertKey<'_> {
fn from(_: ()) -> Self {
Self::NextIndex
}
}
impl<'a> From<Key<'a>> for InsertKey<'a> {
fn from(k: Key<'a>) -> Self {
match k {
Key::Index(i) => InsertKey::Index(i),
Key::Str(s) => InsertKey::Str(s),
Key::Bytes(b) => InsertKey::Bytes(b),
Key::ZStr(s) => InsertKey::ZStr(s),
}
}
}
#[repr(transparent)]
pub struct ZArr {
inner: zend_array,
_p: PhantomData<*mut ()>,
}
impl ZArr {
#[inline]
pub unsafe fn from_ptr<'a>(ptr: *const zend_array) -> &'a Self {
unsafe { (ptr as *const Self).as_ref().expect("ptr should't be null") }
}
#[inline]
pub unsafe fn try_from_ptr<'a>(ptr: *const zend_array) -> Option<&'a Self> {
unsafe { (ptr as *const Self).as_ref() }
}
#[inline]
pub unsafe fn from_mut_ptr<'a>(ptr: *mut zend_array) -> &'a mut Self {
unsafe { (ptr as *mut Self).as_mut().expect("ptr should't be null") }
}
#[inline]
pub unsafe fn try_from_mut_ptr<'a>(ptr: *mut zend_array) -> Option<&'a mut Self> {
unsafe { (ptr as *mut Self).as_mut() }
}
pub const fn as_ptr(&self) -> *const zend_array {
&self.inner
}
#[inline]
pub fn as_mut_ptr(&mut self) -> *mut zend_array {
&mut self.inner
}
#[inline]
pub fn is_empty(&mut self) -> bool {
self.len() == 0
}
#[inline]
pub fn len(&mut self) -> usize {
unsafe { zend_array_count(self.as_mut_ptr()).try_into().unwrap() }
}
#[allow(clippy::useless_conversion)]
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 {
match key {
InsertKey::NextIndex => {
phper_zend_hash_next_index_insert(self.as_mut_ptr(), val);
}
InsertKey::Index(i) => {
phper_zend_hash_index_update(self.as_mut_ptr(), i, val);
}
InsertKey::Str(s) => {
phper_zend_symtable_str_update(
self.as_mut_ptr(),
s.as_ptr().cast(),
s.len().try_into().unwrap(),
val,
);
}
InsertKey::Bytes(b) => {
phper_zend_symtable_str_update(
self.as_mut_ptr(),
b.as_ptr().cast(),
b.len().try_into().unwrap(),
val,
);
}
InsertKey::ZStr(s) => {
phper_zend_symtable_str_update(
self.as_mut_ptr(),
s.as_c_str_ptr().cast(),
s.len().try_into().unwrap(),
val,
);
}
}
}
}
pub fn get<'a>(&self, key: impl Into<Key<'a>>) -> Option<&'a ZVal> {
self.inner_get(key).map(|v| &*v)
}
pub fn get_mut<'a>(&mut self, key: impl Into<Key<'a>>) -> Option<&'a mut ZVal> {
self.inner_get(key)
}
#[allow(clippy::useless_conversion)]
fn inner_get<'a>(&self, key: impl Into<Key<'a>>) -> Option<&'a mut ZVal> {
let key = key.into();
let ptr = self.as_ptr() as *mut _;
unsafe {
let value = match key {
Key::Index(i) => phper_zend_hash_index_find(ptr, i),
Key::Str(s) => phper_zend_symtable_str_find(
ptr,
s.as_ptr().cast(),
s.len().try_into().unwrap(),
),
Key::Bytes(b) => phper_zend_symtable_str_find(
ptr,
b.as_ptr().cast(),
b.len().try_into().unwrap(),
),
Key::ZStr(s) => {
phper_zend_symtable_str_find(ptr, s.as_c_str_ptr(), s.len().try_into().unwrap())
}
};
if value.is_null() {
None
} else {
Some(ZVal::from_mut_ptr(value))
}
}
}
#[allow(clippy::useless_conversion)]
pub fn exists<'a>(&self, key: impl Into<Key<'a>>) -> bool {
let key = key.into();
let ptr = self.as_ptr() as *mut _;
unsafe {
match key {
Key::Index(i) => phper_zend_hash_index_exists(ptr, i),
Key::Str(s) => phper_zend_symtable_str_exists(
ptr,
s.as_ptr().cast(),
s.len().try_into().unwrap(),
),
Key::Bytes(b) => phper_zend_symtable_str_exists(
ptr,
b.as_ptr().cast(),
b.len().try_into().unwrap(),
),
Key::ZStr(s) => phper_zend_symtable_str_exists(
ptr,
s.to_bytes().as_ptr().cast(),
s.len().try_into().unwrap(),
),
}
}
}
#[allow(clippy::useless_conversion)]
pub fn remove<'a>(&mut self, key: impl Into<Key<'a>>) -> bool {
let key = key.into();
unsafe {
match key {
Key::Index(i) => phper_zend_hash_index_del(&mut self.inner, i),
Key::Str(s) => phper_zend_symtable_str_del(
&mut self.inner,
s.as_ptr().cast(),
s.len().try_into().unwrap(),
),
Key::Bytes(b) => phper_zend_symtable_str_del(
&mut self.inner,
b.as_ptr().cast(),
b.len().try_into().unwrap(),
),
Key::ZStr(s) => phper_zend_symtable_str_del(
&mut self.inner,
s.as_c_str_ptr().cast(),
s.len().try_into().unwrap(),
),
}
}
}
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(OccupiedEntry(val)),
None => Entry::Vacant(VacantEntry { arr: self, key }),
}
}
#[inline]
pub fn iter(&self) -> Iter<'_> {
Iter::new(self)
}
#[inline]
pub fn iter_mut(&mut self) -> IterMut<'_> {
IterMut::new(self)
}
}
impl Drop for ZArr {
fn drop(&mut self) {
let mut val = MaybeUninit::<zval>::uninit();
unsafe {
phper_zval_arr(val.as_mut_ptr().cast(), self.as_mut_ptr());
phper_zval_ptr_dtor(val.as_mut_ptr());
}
}
}
impl Debug for ZArr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
common_fmt(self, f, "ZArr")
}
}
impl ToOwned for ZArr {
type Owned = ZArray;
fn to_owned(&self) -> Self::Owned {
unsafe {
let dest = phper_zend_array_dup(self.as_ptr() as *mut _);
ZArray::from_raw_cast(dest)
}
}
}
impl ToRefOwned for ZArr {
type Owned = ZArray;
fn to_ref_owned(&mut self) -> Self::Owned {
let mut val = ManuallyDrop::new(ZVal::default());
unsafe {
phper_zval_arr(val.as_mut_ptr(), self.as_mut_ptr());
phper_z_addref_p(val.as_mut_ptr());
ZArray::from_raw_cast(val.as_mut_z_arr().unwrap().as_mut_ptr())
}
}
}
pub type ZArray = EBox<ZArr>;
impl ZArray {
#[inline]
pub fn new() -> Self {
Self::with_capacity(0)
}
pub fn with_capacity(n: usize) -> Self {
unsafe {
let ptr = phper_zend_new_array(n.try_into().unwrap());
Self::from_raw_cast(ptr)
}
}
}
impl Default for ZArray {
fn default() -> Self {
Self::new()
}
}
impl Clone for ZArray {
fn clone(&self) -> Self {
self.deref().to_owned()
}
}
#[derive(Debug, Clone, PartialEq, From)]
pub enum IterKey<'a> {
Index(u64),
ZStr(&'a ZStr),
}
struct RawIter<'a> {
arr: *mut zend_array,
pos: HashPosition,
finished: bool,
_p: PhantomData<&'a ()>,
}
impl RawIter<'_> {
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;
const IS_STRING: u32 = {
cfg_if! {
if #[cfg(all(phper_major_version = "8", phper_minor_version = "5"))] {
zend_hash_key_type_HASH_KEY_IS_STRING
} else {
HASH_KEY_IS_STRING
}
}
};
const IS_LONG: u32 = {
cfg_if! {
if #[cfg(all(phper_major_version = "8", phper_minor_version = "5"))] {
zend_hash_key_type_HASH_KEY_IS_LONG
} else {
HASH_KEY_IS_LONG
}
}
};
let iter_key = if result == IS_STRING {
IterKey::ZStr(ZStr::from_mut_ptr(str_index))
} else if result == IS_LONG {
#[allow(clippy::unnecessary_cast)]
IterKey::Index(num_index as u64)
} else {
self.finished = true;
return None;
};
#[allow(clippy::unnecessary_mut_passed)]
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> {
Occupied(OccupiedEntry<'a>),
Vacant(VacantEntry<'a>),
}
pub struct OccupiedEntry<'a>(&'a mut ZVal);
pub struct VacantEntry<'a> {
arr: &'a mut ZArr,
key: Key<'a>,
}
impl<'a> Entry<'a> {
pub fn and_modify<F>(self, f: F) -> Self
where
F: FnOnce(&mut ZVal),
{
match self {
Entry::Occupied(entry) => {
f(entry.0);
Entry::Occupied(entry)
}
entry => entry,
}
}
pub fn or_insert(self, val: impl Into<ZVal>) -> &'a mut ZVal {
match self {
Entry::Occupied(entry) => entry.0,
Entry::Vacant(entry) => {
let insert_key: InsertKey<'_> = entry.key.clone().into();
entry.arr.insert(insert_key, val);
entry.arr.get_mut(entry.key).unwrap()
}
}
}
}
fn common_fmt(this: &ZArr, f: &mut fmt::Formatter<'_>, name: &str) -> fmt::Result {
struct Debugger<'a>(&'a ZArr);
impl Debug for Debugger<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_map().entries(self.0.iter()).finish()
}
}
let zd = Debugger(this);
f.debug_tuple(name).field(&zd).finish()
}