phper/
arrays.rs

1// Copyright (c) 2022 PHPER Framework Team
2// PHPER is licensed under Mulan PSL v2.
3// You can use this software according to the terms and conditions of the Mulan
4// PSL v2. You may obtain a copy of Mulan PSL v2 at:
5//          http://license.coscl.org.cn/MulanPSL2
6// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY
7// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
8// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
9// See the Mulan PSL v2 for more details.
10
11//! Apis relate to [zend_array].
12
13use crate::{alloc::EBox, strings::ZStr, sys::*, values::ZVal};
14use cfg_if::cfg_if;
15use derive_more::From;
16use phper_alloc::ToRefOwned;
17use std::{
18    fmt::{self, Debug},
19    marker::PhantomData,
20    mem::{ManuallyDrop, MaybeUninit},
21    ops::Deref,
22    ptr::null_mut,
23};
24
25/// Key for [ZArr].
26#[derive(Debug, Clone, PartialEq, From)]
27pub enum Key<'a> {
28    /// Index type key.
29    Index(u64),
30    /// String type key.
31    Str(&'a str),
32    /// String type key.
33    Bytes(&'a [u8]),
34    /// String type key.
35    ZStr(&'a ZStr),
36}
37
38/// Insert key for [ZArr].
39#[derive(Debug, Clone, PartialEq, From)]
40pub enum InsertKey<'a> {
41    /// Insert with next index type key, like `$arr[] = "foo"` in PHP.
42    NextIndex,
43    /// Insert with index type key.
44    Index(u64),
45    /// Insert with string type key.
46    Str(&'a str),
47    /// Insert with string type key.
48    Bytes(&'a [u8]),
49    /// Insert with string type key.
50    ZStr(&'a ZStr),
51}
52
53impl From<()> for InsertKey<'_> {
54    fn from(_: ()) -> Self {
55        Self::NextIndex
56    }
57}
58
59impl<'a> From<Key<'a>> for InsertKey<'a> {
60    fn from(k: Key<'a>) -> Self {
61        match k {
62            Key::Index(i) => InsertKey::Index(i),
63            Key::Str(s) => InsertKey::Str(s),
64            Key::Bytes(b) => InsertKey::Bytes(b),
65            Key::ZStr(s) => InsertKey::ZStr(s),
66        }
67    }
68}
69
70/// Wrapper of [zend_array].
71#[repr(transparent)]
72pub struct ZArr {
73    inner: zend_array,
74    _p: PhantomData<*mut ()>,
75}
76
77impl ZArr {
78    /// Wraps a raw pointer.
79    ///
80    /// # Safety
81    ///
82    /// Create from raw pointer.
83    ///
84    /// # Panics
85    ///
86    /// Panics if pointer is null.
87    #[inline]
88    pub unsafe fn from_ptr<'a>(ptr: *const zend_array) -> &'a Self {
89        unsafe { (ptr as *const Self).as_ref().expect("ptr should't be null") }
90    }
91
92    /// Wraps a raw pointer, return None if pointer is null.
93    ///
94    /// # Safety
95    ///
96    /// Create from raw pointer.
97    #[inline]
98    pub unsafe fn try_from_ptr<'a>(ptr: *const zend_array) -> Option<&'a Self> {
99        unsafe { (ptr as *const Self).as_ref() }
100    }
101
102    /// Wraps a raw pointer.
103    ///
104    /// # Safety
105    ///
106    /// Create from raw pointer.
107    ///
108    /// # Panics
109    ///
110    /// Panics if pointer is null.
111    #[inline]
112    pub unsafe fn from_mut_ptr<'a>(ptr: *mut zend_array) -> &'a mut Self {
113        unsafe { (ptr as *mut Self).as_mut().expect("ptr should't be null") }
114    }
115
116    /// Wraps a raw pointer, return None if pointer is null.
117    ///
118    /// # Safety
119    ///
120    /// Create from raw pointer.
121    #[inline]
122    pub unsafe fn try_from_mut_ptr<'a>(ptr: *mut zend_array) -> Option<&'a mut Self> {
123        unsafe { (ptr as *mut Self).as_mut() }
124    }
125
126    /// Returns a raw pointer wrapped.
127    pub const fn as_ptr(&self) -> *const zend_array {
128        &self.inner
129    }
130
131    /// Returns a raw pointer wrapped.
132    #[inline]
133    pub fn as_mut_ptr(&mut self) -> *mut zend_array {
134        &mut self.inner
135    }
136
137    /// Returns true if the array has a length of 0.
138    #[inline]
139    pub fn is_empty(&mut self) -> bool {
140        self.len() == 0
141    }
142
143    /// Get array items length.
144    #[inline]
145    pub fn len(&mut self) -> usize {
146        unsafe { zend_array_count(self.as_mut_ptr()).try_into().unwrap() }
147    }
148
149    /// Add or update item by key.
150    ///
151    /// Notice that phper prefer to use [`Symtables`](https://www.phpinternalsbook.com/php5/hashtables/array_api.html#symtables) api `zend_symtable_*`,
152    /// so `insert(42)` and `insert("42")` should be considered the same.
153    #[allow(clippy::useless_conversion)]
154    pub fn insert<'a>(&mut self, key: impl Into<InsertKey<'a>>, value: impl Into<ZVal>) {
155        let key = key.into();
156        let mut value = ManuallyDrop::new(value.into());
157        let val = value.as_mut_ptr();
158
159        unsafe {
160            match key {
161                InsertKey::NextIndex => {
162                    phper_zend_hash_next_index_insert(self.as_mut_ptr(), val);
163                }
164                InsertKey::Index(i) => {
165                    phper_zend_hash_index_update(self.as_mut_ptr(), i, val);
166                }
167                InsertKey::Str(s) => {
168                    phper_zend_symtable_str_update(
169                        self.as_mut_ptr(),
170                        s.as_ptr().cast(),
171                        s.len().try_into().unwrap(),
172                        val,
173                    );
174                }
175                InsertKey::Bytes(b) => {
176                    phper_zend_symtable_str_update(
177                        self.as_mut_ptr(),
178                        b.as_ptr().cast(),
179                        b.len().try_into().unwrap(),
180                        val,
181                    );
182                }
183                InsertKey::ZStr(s) => {
184                    phper_zend_symtable_str_update(
185                        self.as_mut_ptr(),
186                        s.as_c_str_ptr().cast(),
187                        s.len().try_into().unwrap(),
188                        val,
189                    );
190                }
191            }
192        }
193    }
194
195    /// Get item by key.
196    ///
197    /// Notice that phper prefer to use [`Symtables`](https://www.phpinternalsbook.com/php5/hashtables/array_api.html#symtables) api `zend_symtable_*`,
198    /// so `get(42)` and `get("42")` should be considered the same.
199    pub fn get<'a>(&self, key: impl Into<Key<'a>>) -> Option<&'a ZVal> {
200        self.inner_get(key).map(|v| &*v)
201    }
202
203    /// Get item by key.
204    ///
205    /// Notice that phper prefer to use [`Symtables`](https://www.phpinternalsbook.com/php5/hashtables/array_api.html#symtables) api `zend_symtable_*`,
206    /// so `get_mut(42)` and `get_mut("42")` should be considered the same.
207    pub fn get_mut<'a>(&mut self, key: impl Into<Key<'a>>) -> Option<&'a mut ZVal> {
208        self.inner_get(key)
209    }
210
211    #[allow(clippy::useless_conversion)]
212    fn inner_get<'a>(&self, key: impl Into<Key<'a>>) -> Option<&'a mut ZVal> {
213        let key = key.into();
214        let ptr = self.as_ptr() as *mut _;
215        unsafe {
216            let value = match key {
217                Key::Index(i) => phper_zend_hash_index_find(ptr, i),
218                Key::Str(s) => phper_zend_symtable_str_find(
219                    ptr,
220                    s.as_ptr().cast(),
221                    s.len().try_into().unwrap(),
222                ),
223                Key::Bytes(b) => phper_zend_symtable_str_find(
224                    ptr,
225                    b.as_ptr().cast(),
226                    b.len().try_into().unwrap(),
227                ),
228                Key::ZStr(s) => {
229                    phper_zend_symtable_str_find(ptr, s.as_c_str_ptr(), s.len().try_into().unwrap())
230                }
231            };
232            if value.is_null() {
233                None
234            } else {
235                Some(ZVal::from_mut_ptr(value))
236            }
237        }
238    }
239
240    /// Check if the key exists.
241    ///
242    /// Notice that phper prefer to use [`Symtables`](https://www.phpinternalsbook.com/php5/hashtables/array_api.html#symtables) api `zend_symtable_*`,
243    /// so `exists(42)` and `exists("42")` should be considered the same.
244    #[allow(clippy::useless_conversion)]
245    pub fn exists<'a>(&self, key: impl Into<Key<'a>>) -> bool {
246        let key = key.into();
247        let ptr = self.as_ptr() as *mut _;
248        unsafe {
249            match key {
250                Key::Index(i) => phper_zend_hash_index_exists(ptr, i),
251                Key::Str(s) => phper_zend_symtable_str_exists(
252                    ptr,
253                    s.as_ptr().cast(),
254                    s.len().try_into().unwrap(),
255                ),
256                Key::Bytes(b) => phper_zend_symtable_str_exists(
257                    ptr,
258                    b.as_ptr().cast(),
259                    b.len().try_into().unwrap(),
260                ),
261                Key::ZStr(s) => phper_zend_symtable_str_exists(
262                    ptr,
263                    s.to_bytes().as_ptr().cast(),
264                    s.len().try_into().unwrap(),
265                ),
266            }
267        }
268    }
269
270    /// Remove the item under the key
271    ///
272    /// Notice that phper prefer to use [`Symtables`](https://www.phpinternalsbook.com/php5/hashtables/array_api.html#symtables) api `zend_symtable_*`,
273    /// so `remove(42)` and `remove("42")` should be considered the same.
274    #[allow(clippy::useless_conversion)]
275    pub fn remove<'a>(&mut self, key: impl Into<Key<'a>>) -> bool {
276        let key = key.into();
277        unsafe {
278            match key {
279                Key::Index(i) => phper_zend_hash_index_del(&mut self.inner, i),
280                Key::Str(s) => phper_zend_symtable_str_del(
281                    &mut self.inner,
282                    s.as_ptr().cast(),
283                    s.len().try_into().unwrap(),
284                ),
285                Key::Bytes(b) => phper_zend_symtable_str_del(
286                    &mut self.inner,
287                    b.as_ptr().cast(),
288                    b.len().try_into().unwrap(),
289                ),
290                Key::ZStr(s) => phper_zend_symtable_str_del(
291                    &mut self.inner,
292                    s.as_c_str_ptr().cast(),
293                    s.len().try_into().unwrap(),
294                ),
295            }
296        }
297    }
298
299    /// Gets the given key’s corresponding entry in the array for in-place
300    /// manipulation.
301    ///
302    /// # Examples
303    ///
304    /// ```no_run
305    /// use phper::arrays::ZArray;
306    ///
307    /// let mut arr = ZArray::new();
308    ///
309    /// // count the number of occurrences of letters in the vec
310    /// for x in ["a", "b", "a", "c", "a", "b"] {
311    ///     arr.entry(x)
312    ///         .and_modify(|cur| *cur.as_mut_long().unwrap() += 1)
313    ///         .or_insert(1);
314    /// }
315    /// ```
316    pub fn entry<'a>(&'a mut self, key: impl Into<Key<'a>>) -> Entry<'a> {
317        let key = key.into();
318        match self.get_mut(key.clone()) {
319            Some(val) => Entry::Occupied(OccupiedEntry(val)),
320            None => Entry::Vacant(VacantEntry { arr: self, key }),
321        }
322    }
323
324    /// Provides a forward iterator.
325    #[inline]
326    pub fn iter(&self) -> Iter<'_> {
327        Iter::new(self)
328    }
329
330    /// Provides a forward iterator with mutable references.
331    #[inline]
332    pub fn iter_mut(&mut self) -> IterMut<'_> {
333        IterMut::new(self)
334    }
335}
336
337impl Drop for ZArr {
338    fn drop(&mut self) {
339        let mut val = MaybeUninit::<zval>::uninit();
340        unsafe {
341            phper_zval_arr(val.as_mut_ptr().cast(), self.as_mut_ptr());
342            phper_zval_ptr_dtor(val.as_mut_ptr());
343        }
344    }
345}
346
347impl Debug for ZArr {
348    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
349        common_fmt(self, f, "ZArr")
350    }
351}
352
353impl ToOwned for ZArr {
354    type Owned = ZArray;
355
356    fn to_owned(&self) -> Self::Owned {
357        unsafe {
358            // TODO The source really immutable?
359            let dest = phper_zend_array_dup(self.as_ptr() as *mut _);
360            ZArray::from_raw_cast(dest)
361        }
362    }
363}
364
365impl ToRefOwned for ZArr {
366    type Owned = ZArray;
367
368    fn to_ref_owned(&mut self) -> Self::Owned {
369        let mut val = ManuallyDrop::new(ZVal::default());
370        unsafe {
371            phper_zval_arr(val.as_mut_ptr(), self.as_mut_ptr());
372            phper_z_addref_p(val.as_mut_ptr());
373            ZArray::from_raw_cast(val.as_mut_z_arr().unwrap().as_mut_ptr())
374        }
375    }
376}
377
378/// An owned PHP array value.
379///
380/// `ZArray` represents an owned PHP array (hashtable) allocated in the Zend
381/// Engine memory. It provides safe access to PHP array operations and
382/// automatically manages memory cleanup.
383pub type ZArray = EBox<ZArr>;
384
385impl ZArray {
386    /// Creates an empty `ZArray`.
387    #[inline]
388    pub fn new() -> Self {
389        Self::with_capacity(0)
390    }
391
392    /// Creates an empty `ZArray` with at least the specified capacity.
393    ///
394    /// Note that the actual capacity is always a power of two, so if you have
395    /// 12 elements in a hashtable the actual table capacity will be 16.
396    pub fn with_capacity(n: usize) -> Self {
397        unsafe {
398            let ptr = phper_zend_new_array(n.try_into().unwrap());
399            Self::from_raw_cast(ptr)
400        }
401    }
402}
403
404impl Default for ZArray {
405    fn default() -> Self {
406        Self::new()
407    }
408}
409
410impl Clone for ZArray {
411    fn clone(&self) -> Self {
412        self.deref().to_owned()
413    }
414}
415
416/// Iterator key for [`ZArr::iter`] and [`ZArr::iter_mut`].
417#[derive(Debug, Clone, PartialEq, From)]
418pub enum IterKey<'a> {
419    /// Index type iterator key.
420    Index(u64),
421    /// String type iterator key.
422    ZStr(&'a ZStr),
423}
424
425struct RawIter<'a> {
426    arr: *mut zend_array,
427    pos: HashPosition,
428    finished: bool,
429    _p: PhantomData<&'a ()>,
430}
431
432impl RawIter<'_> {
433    fn new(arr: *mut zend_array) -> Self {
434        let mut pos: HashPosition = 0;
435        unsafe {
436            zend_hash_internal_pointer_reset_ex(arr, &mut pos);
437        }
438        Self {
439            arr,
440            pos,
441            finished: false,
442            _p: PhantomData,
443        }
444    }
445}
446
447impl<'a> Iterator for RawIter<'a> {
448    type Item = (IterKey<'a>, *mut zval);
449
450    fn next(&mut self) -> Option<Self::Item> {
451        unsafe {
452            if self.finished {
453                return None;
454            }
455
456            let mut str_index: *mut zend_string = null_mut();
457            let mut num_index: zend_ulong = 0;
458
459            #[allow(clippy::unnecessary_mut_passed)]
460            let result = zend_hash_get_current_key_ex(
461                self.arr,
462                &mut str_index,
463                &mut num_index,
464                &mut self.pos,
465            ) as u32;
466
467            const IS_STRING: u32 = {
468                cfg_if! {
469                    if #[cfg(all(phper_major_version = "8", phper_minor_version = "5"))] {
470                        zend_hash_key_type_HASH_KEY_IS_STRING
471                    } else {
472                        HASH_KEY_IS_STRING
473                    }
474                }
475            };
476
477            const IS_LONG: u32 = {
478                cfg_if! {
479                    if #[cfg(all(phper_major_version = "8", phper_minor_version = "5"))] {
480                        zend_hash_key_type_HASH_KEY_IS_LONG
481                    } else {
482                        HASH_KEY_IS_LONG
483                    }
484                }
485            };
486
487            let iter_key = if result == IS_STRING {
488                IterKey::ZStr(ZStr::from_mut_ptr(str_index))
489            } else if result == IS_LONG {
490                #[allow(clippy::unnecessary_cast)]
491                IterKey::Index(num_index as u64)
492            } else {
493                self.finished = true;
494                return None;
495            };
496
497            #[allow(clippy::unnecessary_mut_passed)]
498            let val = zend_hash_get_current_data_ex(self.arr, &mut self.pos);
499            if val.is_null() {
500                self.finished = true;
501                return None;
502            }
503
504            if zend_hash_move_forward_ex(self.arr, &mut self.pos) == ZEND_RESULT_CODE_FAILURE {
505                self.finished = true;
506            }
507
508            Some((iter_key, val))
509        }
510    }
511}
512
513/// An iterator over the elements of a `ZArr`.
514///
515/// This is created by [`iter`].
516///
517/// [`iter`]: ZArr::iter
518pub struct Iter<'a>(RawIter<'a>);
519
520impl<'a> Iter<'a> {
521    fn new(arr: &'a ZArr) -> Self {
522        Self(RawIter::new(arr.as_ptr() as *mut _))
523    }
524}
525
526impl<'a> Iterator for Iter<'a> {
527    type Item = (IterKey<'a>, &'a ZVal);
528
529    fn next(&mut self) -> Option<Self::Item> {
530        self.0
531            .next()
532            .map(|(key, val)| (key, unsafe { ZVal::from_ptr(val) }))
533    }
534}
535
536/// An mutable iterator over the elements of a `ZArr`.
537///
538/// This is created by [`iter_mut`].
539///
540/// [`iter_mut`]: ZArr::iter_mut
541pub struct IterMut<'a>(RawIter<'a>);
542
543impl<'a> IterMut<'a> {
544    fn new(arr: &'a mut ZArr) -> Self {
545        Self(RawIter::new(arr.as_mut_ptr()))
546    }
547}
548
549impl<'a> Iterator for IterMut<'a> {
550    type Item = (IterKey<'a>, &'a mut ZVal);
551
552    fn next(&mut self) -> Option<Self::Item> {
553        self.0
554            .next()
555            .map(|(key, val)| (key, unsafe { ZVal::from_mut_ptr(val) }))
556    }
557}
558
559/// A view into a single entry in an array, which may either be vacant or
560/// occupied.
561///
562/// This `enum` is constructed from the [`entry`] method on [`ZArr`].
563///
564/// [`entry`]: ZArr::entry
565pub enum Entry<'a> {
566    /// An occupied entry.
567    Occupied(OccupiedEntry<'a>),
568    /// A vacant entry.
569    Vacant(VacantEntry<'a>),
570}
571
572/// A view into an occupied entry in a `ZArr`.
573/// It is part of the [`Entry`] enum.
574pub struct OccupiedEntry<'a>(&'a mut ZVal);
575
576/// A view into a vacant entry in a `ZArr`.
577/// It is part of the [`Entry`] enum.
578pub struct VacantEntry<'a> {
579    arr: &'a mut ZArr,
580    key: Key<'a>,
581}
582
583impl<'a> Entry<'a> {
584    /// Provides in-place mutable access to an occupied entry before any
585    /// potential inserts into the array.
586    pub fn and_modify<F>(self, f: F) -> Self
587    where
588        F: FnOnce(&mut ZVal),
589    {
590        match self {
591            Entry::Occupied(entry) => {
592                f(entry.0);
593                Entry::Occupied(entry)
594            }
595            entry => entry,
596        }
597    }
598
599    /// Ensures a value is in the entry by inserting the default if empty, and
600    /// returns a mutable reference to the value in the entry.
601    pub fn or_insert(self, val: impl Into<ZVal>) -> &'a mut ZVal {
602        match self {
603            Entry::Occupied(entry) => entry.0,
604            Entry::Vacant(entry) => {
605                let insert_key: InsertKey<'_> = entry.key.clone().into();
606                entry.arr.insert(insert_key, val);
607                entry.arr.get_mut(entry.key).unwrap()
608            }
609        }
610    }
611}
612
613fn common_fmt(this: &ZArr, f: &mut fmt::Formatter<'_>, name: &str) -> fmt::Result {
614    struct Debugger<'a>(&'a ZArr);
615
616    impl Debug for Debugger<'_> {
617        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
618            f.debug_map().entries(self.0.iter()).finish()
619        }
620    }
621
622    let zd = Debugger(this);
623
624    f.debug_tuple(name).field(&zd).finish()
625}