use std::{convert::TryInto, ffi::c_void, fmt::Debug, ptr};
use crate::{
binary::Pack,
boxed::ZBox,
convert::{FromZval, IntoZval, IntoZvalDyn},
error::{Error, Result},
ffi::{
_zval_struct__bindgen_ty_1, _zval_struct__bindgen_ty_2, zend_is_callable, zend_resource,
zend_value, zval, zval_ptr_dtor,
},
flags::DataType,
flags::ZvalTypeFlags,
rc::PhpRc,
types::{ZendCallable, ZendHashTable, ZendLong, ZendObject, ZendStr},
};
pub type Zval = zval;
impl Zval {
pub const fn new() -> Self {
Self {
value: zend_value {
ptr: ptr::null_mut(),
},
u1: _zval_struct__bindgen_ty_1 {
type_info: DataType::Null.as_u32(),
},
u2: _zval_struct__bindgen_ty_2 { next: 0 },
}
}
pub fn long(&self) -> Option<ZendLong> {
if self.is_long() {
Some(unsafe { self.value.lval })
} else {
None
}
}
pub fn bool(&self) -> Option<bool> {
if self.is_true() {
Some(true)
} else if self.is_false() {
Some(false)
} else {
None
}
}
pub fn double(&self) -> Option<f64> {
if self.is_double() {
Some(unsafe { self.value.dval })
} else {
self.long().map(|x| x as f64)
}
}
pub fn zend_str(&self) -> Option<&ZendStr> {
if self.is_string() {
unsafe { self.value.str_.as_ref() }
} else {
None
}
}
pub fn string(&self) -> Option<String> {
self.str()
.map(|s| s.to_string())
.or_else(|| self.double().map(|x| x.to_string()))
}
pub fn str(&self) -> Option<&str> {
self.zend_str().and_then(|zs| zs.as_str())
}
pub fn binary<T: Pack>(&self) -> Option<Vec<T>> {
if self.is_string() {
Some(T::unpack_into(unsafe { self.value.str_.as_ref() }?))
} else {
None
}
}
pub fn resource(&self) -> Option<*mut zend_resource> {
if self.is_resource() {
Some(unsafe { self.value.res })
} else {
None
}
}
pub fn array(&self) -> Option<&ZendHashTable> {
if self.is_array() {
unsafe { self.value.arr.as_ref() }
} else {
None
}
}
pub fn array_mut(&mut self) -> Option<&mut ZendHashTable> {
if self.is_array() {
unsafe { self.value.arr.as_mut() }
} else {
None
}
}
pub fn object(&self) -> Option<&ZendObject> {
if self.is_object() {
unsafe { self.value.obj.as_ref() }
} else {
None
}
}
pub fn object_mut(&mut self) -> Option<&mut ZendObject> {
if self.is_object() {
unsafe { self.value.obj.as_mut() }
} else {
None
}
}
pub fn reference(&self) -> Option<&Zval> {
if self.is_reference() {
Some(&unsafe { self.value.ref_.as_ref() }?.val)
} else {
None
}
}
pub fn reference_mut(&mut self) -> Option<&mut Zval> {
if self.is_reference() {
Some(&mut unsafe { self.value.ref_.as_mut() }?.val)
} else {
None
}
}
pub fn callable(&self) -> Option<ZendCallable> {
ZendCallable::new(self).ok()
}
pub unsafe fn ptr<T>(&self) -> Option<*mut T> {
if self.is_ptr() {
Some(self.value.ptr as *mut T)
} else {
None
}
}
pub fn try_call(&self, params: Vec<&dyn IntoZvalDyn>) -> Result<Zval> {
self.callable().ok_or(Error::Callable)?.try_call(params)
}
pub fn get_type(&self) -> DataType {
DataType::from(unsafe { self.u1.v.type_ } as u32)
}
pub fn is_long(&self) -> bool {
self.get_type() == DataType::Long
}
pub fn is_null(&self) -> bool {
self.get_type() == DataType::Null
}
pub fn is_true(&self) -> bool {
self.get_type() == DataType::False
}
pub fn is_false(&self) -> bool {
self.get_type() == DataType::False
}
pub fn is_bool(&self) -> bool {
self.is_true() || self.is_false()
}
pub fn is_double(&self) -> bool {
self.get_type() == DataType::Double
}
pub fn is_string(&self) -> bool {
self.get_type() == DataType::String
}
pub fn is_resource(&self) -> bool {
self.get_type() == DataType::Resource
}
pub fn is_array(&self) -> bool {
self.get_type() == DataType::Array
}
pub fn is_object(&self) -> bool {
matches!(self.get_type(), DataType::Object(_))
}
pub fn is_reference(&self) -> bool {
self.get_type() == DataType::Reference
}
pub fn is_callable(&self) -> bool {
let ptr: *const Self = self;
unsafe { zend_is_callable(ptr as *mut Self, 0, std::ptr::null_mut()) }
}
pub fn is_ptr(&self) -> bool {
self.get_type() == DataType::Ptr
}
pub fn set_string(&mut self, val: &str, persistent: bool) -> Result<()> {
self.set_zend_string(ZendStr::new(val, persistent)?);
Ok(())
}
pub fn set_zend_string(&mut self, val: ZBox<ZendStr>) {
self.change_type(ZvalTypeFlags::StringEx);
self.value.str_ = val.into_raw();
}
pub fn set_binary<T: Pack>(&mut self, val: Vec<T>) {
self.change_type(ZvalTypeFlags::StringEx);
let ptr = T::pack_into(val);
self.value.str_ = ptr;
}
pub fn set_interned_string(&mut self, val: &str, persistent: bool) -> Result<()> {
self.set_zend_string(ZendStr::new_interned(val, persistent)?);
Ok(())
}
pub fn set_long<T: Into<ZendLong>>(&mut self, val: T) {
self._set_long(val.into())
}
fn _set_long(&mut self, val: ZendLong) {
self.change_type(ZvalTypeFlags::Long);
self.value.lval = val;
}
pub fn set_double<T: Into<f64>>(&mut self, val: T) {
self._set_double(val.into())
}
fn _set_double(&mut self, val: f64) {
self.change_type(ZvalTypeFlags::Double);
self.value.dval = val;
}
pub fn set_bool<T: Into<bool>>(&mut self, val: T) {
self._set_bool(val.into())
}
fn _set_bool(&mut self, val: bool) {
self.change_type(if val {
ZvalTypeFlags::True
} else {
ZvalTypeFlags::False
});
}
pub fn set_null(&mut self) {
self.change_type(ZvalTypeFlags::Null);
}
pub fn set_resource(&mut self, val: *mut zend_resource) {
self.change_type(ZvalTypeFlags::ResourceEx);
self.value.res = val;
}
pub fn set_object(&mut self, val: &mut ZendObject) {
self.change_type(ZvalTypeFlags::ObjectEx);
val.inc_count(); self.value.obj = (val as *const ZendObject) as *mut ZendObject;
}
pub fn set_array<T: TryInto<ZBox<ZendHashTable>, Error = Error>>(
&mut self,
val: T,
) -> Result<()> {
self.set_hashtable(val.try_into()?);
Ok(())
}
pub fn set_hashtable(&mut self, val: ZBox<ZendHashTable>) {
self.change_type(ZvalTypeFlags::ArrayEx);
self.value.arr = val.into_raw();
}
pub fn set_ptr<T>(&mut self, ptr: *mut T) {
self.u1.type_info = ZvalTypeFlags::Ptr.bits();
self.value.ptr = ptr as *mut c_void;
}
pub(crate) fn release(mut self) {
self.u1.type_info = ZvalTypeFlags::Null.bits();
}
fn change_type(&mut self, ty: ZvalTypeFlags) {
unsafe { zval_ptr_dtor(self) };
self.u1.type_info = ty.bits();
}
pub fn extract<'a, T>(&'a self) -> Option<T>
where
T: FromZval<'a>,
{
FromZval::from_zval(self)
}
}
impl Debug for Zval {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut dbg = f.debug_struct("Zval");
let ty = self.get_type();
dbg.field("type", &ty);
macro_rules! field {
($value: expr) => {
dbg.field("val", &$value)
};
}
match ty {
DataType::Undef => field!(Option::<()>::None),
DataType::Null => field!(Option::<()>::None),
DataType::False => field!(false),
DataType::True => field!(true),
DataType::Long => field!(self.long()),
DataType::Double => field!(self.double()),
DataType::String | DataType::Mixed => field!(self.string()),
DataType::Array => field!(self.array()),
DataType::Object(_) => field!(self.object()),
DataType::Resource => field!(self.resource()),
DataType::Reference => field!(self.reference()),
DataType::Callable => field!(self.string()),
DataType::ConstantExpression => field!(Option::<()>::None),
DataType::Void => field!(Option::<()>::None),
DataType::Bool => field!(self.bool()),
DataType::Ptr => field!(unsafe { self.ptr::<c_void>() }),
};
dbg.finish()
}
}
impl Drop for Zval {
fn drop(&mut self) {
self.change_type(ZvalTypeFlags::Null);
}
}
impl Default for Zval {
fn default() -> Self {
Self::new()
}
}
impl IntoZval for Zval {
const TYPE: DataType = DataType::Mixed;
fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> {
*zv = self;
Ok(())
}
}