// Copyright (c) 2022 PHPER Framework Team
// PHPER is licensed under Mulan PSL v2.
// You can use this software according to the terms and conditions of the Mulan
// PSL v2. You may obtain a copy of Mulan PSL v2 at:
// http://license.coscl.org.cn/MulanPSL2
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
// See the Mulan PSL v2 for more details.
//! Apis relate to [crate::sys::zend_array].
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,
};
/// Key for [ZArr].
#[derive(Debug, Clone, PartialEq, From)]
pub enum Key<'a> {
/// Index type key.
Index(u64),
/// String type key.
Str(&'a str),
/// String type key.
Bytes(&'a [u8]),
/// String type key.
ZStr(&'a ZStr),
}
/// Insert key for [ZArr].
#[derive(Debug, Clone, PartialEq, From)]
pub enum InsertKey<'a> {
/// Insert with next index type key, like `$arr[] = "foo"` in PHP.
NextIndex,
/// Insert with index type key.
Index(u64),
/// Insert with string type key.
Str(&'a str),
/// Insert with string type key.
Bytes(&'a [u8]),
/// Insert with string type key.
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),
}
}
}
/// Wrapper of [crate::sys::zend_array].
#[repr(transparent)]
pub struct ZArr {
inner: zend_array,
_p: PhantomData<*mut ()>,
}
impl ZArr {
/// Wraps a raw pointer.
///
/// # Safety
///
/// Create from raw pointer.
///
/// # Panics
///
/// Panics if pointer is null.
#[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")
}
/// Wraps a raw pointer, return None if pointer is null.
///
/// # Safety
///
/// Create from raw pointer.
#[inline]
pub unsafe fn try_from_ptr<'a>(ptr: *const zend_array) -> Option<&'a Self> {
(ptr as *const Self).as_ref()
}
/// Wraps a raw pointer.
///
/// # Safety
///
/// Create from raw pointer.
///
/// # Panics
///
/// Panics if pointer is null.
#[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")
}
/// Wraps a raw pointer, return None if pointer is null.
///
/// # Safety
///
/// Create from raw pointer.
#[inline]
pub unsafe fn try_from_mut_ptr<'a>(ptr: *mut zend_array) -> Option<&'a mut Self> {
(ptr as *mut Self).as_mut()
}
/// Returns a raw pointer wrapped.
pub const fn as_ptr(&self) -> *const zend_array {
&self.inner
}
/// Returns a raw pointer wrapped.
#[inline]
pub fn as_mut_ptr(&mut self) -> *mut zend_array {
&mut self.inner
}
/// Returns true if the array has a length of 0.
#[inline]
pub fn is_empty(&mut self) -> bool {
self.len() == 0
}
/// Get array items length.
#[inline]
pub fn len(&mut self) -> usize {
unsafe { zend_array_count(self.as_mut_ptr()).try_into().unwrap() }
}
/// Add or update item by key.
///
/// Notice that phper prefer to use [`Symtables`](https://www.phpinternalsbook.com/php5/hashtables/array_api.html#symtables) api `zend_symtable_*`,
/// so `insert(42)` and `insert("42")` should be considered the same.
#[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,
);
}
}
}
}
/// Get item by key.
///
/// Notice that phper prefer to use [`Symtables`](https://www.phpinternalsbook.com/php5/hashtables/array_api.html#symtables) api `zend_symtable_*`,
/// so `get(42)` and `get("42")` should be considered the same.
pub fn get<'a>(&self, key: impl Into<Key<'a>>) -> Option<&'a ZVal> {
self.inner_get(key).map(|v| &*v)
}
/// Get item by key.
///
/// Notice that phper prefer to use [`Symtables`](https://www.phpinternalsbook.com/php5/hashtables/array_api.html#symtables) api `zend_symtable_*`,
/// so `get_mut(42)` and `get_mut("42")` should be considered the same.
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))
}
}
}
/// Check if the key exists.
///
/// Notice that phper prefer to use [`Symtables`](https://www.phpinternalsbook.com/php5/hashtables/array_api.html#symtables) api `zend_symtable_*`,
/// so `exists(42)` and `exists("42")` should be considered the same.
#[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(),
),
}
}
}
/// Remove the item under the key
///
/// Notice that phper prefer to use [`Symtables`](https://www.phpinternalsbook.com/php5/hashtables/array_api.html#symtables) api `zend_symtable_*`,
/// so `remove(42)` and `remove("42")` should be considered the same.
#[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(),
),
}
}
}
/// Gets the given key’s corresponding entry in the array for in-place
/// manipulation.
///
/// # Examples
///
/// ```no_run
/// use phper::arrays::ZArray;
///
/// let mut arr = ZArray::new();
///
/// // count the number of occurrences of letters in the vec
/// for x in ["a", "b", "a", "c", "a", "b"] {
/// arr.entry(x)
/// .and_modify(|cur| *cur.as_mut_long().unwrap() += 1)
/// .or_insert(1);
/// }
/// ```
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 }),
}
}
/// Provides a forward iterator.
#[inline]
pub fn iter(&self) -> Iter<'_> {
Iter::new(self)
}
/// Provides a forward iterator with mutable references.
#[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 {
// TODO The source really immutable?
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())
}
}
}
/// Wrapper of [crate::sys::zend_array].
#[repr(transparent)]
pub struct ZArray {
inner: *mut ZArr,
}
impl ZArray {
/// Creates an empty `ZArray`.
#[inline]
pub fn new() -> Self {
Self::with_capacity(0)
}
/// Creates an empty `ZArray` with at least the specified capacity.
///
/// Note that the actual capacity is always a power of two, so if you have
/// 12 elements in a hashtable the actual table capacity will be 16.
pub fn with_capacity(n: usize) -> Self {
unsafe {
let ptr = phper_zend_new_array(n.try_into().unwrap());
Self::from_raw(ptr)
}
}
/// Create owned object From raw pointer, usually used in pairs with
/// `into_raw`.
///
/// # Safety
///
/// This function is unsafe because improper use may lead to memory
/// problems. For example, a double-free may occur if the function is called
/// twice on the same raw pointer.
#[inline]
pub unsafe fn from_raw(ptr: *mut zend_array) -> Self {
Self {
inner: ZArr::from_mut_ptr(ptr),
}
}
/// Consumes the `ZArray` and transfers ownership to a raw pointer.
///
/// Failure to call [`ZArray::from_raw`] will lead to a memory leak.
#[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());
}
}
}
/// Iterator key for [`ZArr::iter`] and [`ZArr::iter_mut`].
#[derive(Debug, Clone, PartialEq, From)]
pub enum IterKey<'a> {
/// Index type iterator key.
Index(u64),
/// String type iterator key.
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))
}
}
}
/// An iterator over the elements of a `ZArr`.
///
/// This is created by [`iter`].
///
/// [`iter`]: ZArr::iter
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) }))
}
}
/// An mutable iterator over the elements of a `ZArr`.
///
/// This is created by [`iter_mut`].
///
/// [`iter_mut`]: ZArr::iter_mut
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) }))
}
}
/// A view into a single entry in an array, which may either be vacant or
/// occupied.
///
/// This `enum` is constructed from the [`entry`] method on [`ZArr`].
///
/// [`entry`]: ZArr::entry
pub enum Entry<'a> {
/// An occupied entry.
Occupied(OccupiedEntry<'a>),
/// A vacant entry.
Vacant(VacantEntry<'a>),
}
/// A view into an occupied entry in a `ZArr`.
/// It is part of the [`Entry`] enum.
pub struct OccupiedEntry<'a>(&'a mut ZVal);
/// A view into a vacant entry in a `ZArr`.
/// It is part of the [`Entry`] enum.
pub struct VacantEntry<'a> {
arr: &'a mut ZArr,
key: Key<'a>,
}
impl<'a> Entry<'a> {
/// Provides in-place mutable access to an occupied entry before any
/// potential inserts into the array.
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,
}
}
/// Ensures a value is in the entry by inserting the default if empty, and
/// returns a mutable reference to the value in the 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()
}
}
}
}