use crate::{alloc::ToRefOwned, strings::ZStr, sys::*, values::ZVal};
use derive_more::From;
use std::{
borrow::Borrow,
convert::TryInto,
marker::PhantomData,
mem::ManuallyDrop,
ops::{Deref, DerefMut},
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<'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 {
(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> {
(ptr as *const Self).as_ref()
}
#[inline]
pub unsafe fn from_mut_ptr<'a>(ptr: *mut zend_array) -> &'a mut Self {
(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> {
(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 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(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(val.as_mut_z_arr().unwrap().as_mut_ptr())
}
}
}
#[repr(transparent)]
pub struct ZArray {
inner: *mut 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(ptr)
}
}
#[inline]
pub unsafe fn from_raw(ptr: *mut zend_array) -> Self {
Self {
inner: ZArr::from_mut_ptr(ptr),
}
}
#[inline]
pub fn into_raw(self) -> *mut zend_array {
ManuallyDrop::new(self).as_mut_ptr()
}
}
impl Default for ZArray {
fn default() -> Self {
Self::new()
}
}
impl Deref for ZArray {
type Target = ZArr;
fn deref(&self) -> &Self::Target {
unsafe { self.inner.as_ref().unwrap() }
}
}
impl DerefMut for ZArray {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { self.inner.as_mut().unwrap() }
}
}
impl Borrow<ZArr> for ZArray {
fn borrow(&self) -> &ZArr {
self.deref()
}
}
impl Clone for ZArray {
fn clone(&self) -> Self {
self.deref().to_owned()
}
}
impl Drop for ZArray {
fn drop(&mut self) {
unsafe {
zend_array_destroy(self.as_mut_ptr());
}
}
}
#[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<'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> {
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()
}
}
}
}