use std::{
convert::{TryFrom, TryInto},
ffi::c_void,
fmt::Debug,
ptr,
};
use crate::{
bindings::{
_zval_struct__bindgen_ty_1, _zval_struct__bindgen_ty_2, zend_is_callable, zend_resource,
zend_value, zval, zval_ptr_dtor,
},
errors::{Error, Result},
php::pack::Pack,
};
use crate::php::{
enums::DataType,
flags::ZvalTypeFlags,
types::{long::ZendLong, string::ZendString},
};
use super::{
array::{HashTable, OwnedHashTable},
callable::Callable,
object::ZendObject,
rc::PhpRc,
string::ZendStr,
};
pub type Zval = zval;
unsafe impl Send for Zval {}
unsafe impl Sync for 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<&HashTable> {
if self.is_array() {
unsafe { self.value.arr.as_ref() }
} else {
None
}
}
pub fn array_mut(&mut self) -> Option<&mut HashTable> {
if self.is_array() {
unsafe { self.value.arr.as_mut() }
} else {
None
}
}
pub fn object(&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<Callable> {
Callable::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(ZendString::new(val, persistent)?);
Ok(())
}
pub fn set_zend_string(&mut self, val: ZendString) {
self.change_type(ZvalTypeFlags::StringEx);
self.value.str_ = val.into_inner();
}
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(ZendString::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<OwnedHashTable, Error = Error>>(&mut self, val: T) -> Result<()> {
self.set_hashtable(val.try_into()?);
Ok(())
}
pub fn set_hashtable(&mut self, val: OwnedHashTable) {
self.change_type(ZvalTypeFlags::ArrayEx);
self.value.arr = val.into_inner();
}
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();
}
}
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()
}
}
pub trait IntoZval: Sized {
const TYPE: DataType;
fn into_zval(self, persistent: bool) -> Result<Zval> {
let mut zval = Zval::new();
self.set_zval(&mut zval, persistent)?;
Ok(zval)
}
fn set_zval(self, zv: &mut Zval, persistent: bool) -> Result<()>;
}
impl IntoZval for Zval {
const TYPE: DataType = DataType::Mixed;
fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> {
*zv = self;
Ok(())
}
}
pub trait IntoZvalDyn {
fn as_zval(&self, persistent: bool) -> Result<Zval>;
fn get_type(&self) -> DataType;
}
impl<T: IntoZval + Clone> IntoZvalDyn for T {
fn as_zval(&self, persistent: bool) -> Result<Zval> {
self.clone().into_zval(persistent)
}
fn get_type(&self) -> DataType {
Self::TYPE
}
}
macro_rules! into_zval {
($type: ty, $fn: ident, $dt: ident) => {
impl From<$type> for Zval {
fn from(val: $type) -> Self {
let mut zv = Self::new();
zv.$fn(val);
zv
}
}
impl IntoZval for $type {
const TYPE: DataType = DataType::$dt;
fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> {
zv.$fn(self);
Ok(())
}
}
};
}
into_zval!(i8, set_long, Long);
into_zval!(i16, set_long, Long);
into_zval!(i32, set_long, Long);
into_zval!(u8, set_long, Long);
into_zval!(u16, set_long, Long);
into_zval!(f32, set_double, Double);
into_zval!(f64, set_double, Double);
into_zval!(bool, set_bool, Bool);
macro_rules! try_into_zval_int {
($type: ty) => {
impl TryFrom<$type> for Zval {
type Error = Error;
fn try_from(val: $type) -> Result<Self> {
let mut zv = Self::new();
let val: ZendLong = val.try_into().map_err(|_| Error::IntegerOverflow)?;
zv.set_long(val);
Ok(zv)
}
}
impl IntoZval for $type {
const TYPE: DataType = DataType::Long;
fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> {
let val: ZendLong = self.try_into().map_err(|_| Error::IntegerOverflow)?;
zv.set_long(val);
Ok(())
}
}
};
}
try_into_zval_int!(i64);
try_into_zval_int!(u32);
try_into_zval_int!(u64);
try_into_zval_int!(isize);
try_into_zval_int!(usize);
macro_rules! try_into_zval_str {
($type: ty) => {
impl TryFrom<$type> for Zval {
type Error = Error;
fn try_from(value: $type) -> Result<Self> {
let mut zv = Self::new();
zv.set_string(&value, false)?;
Ok(zv)
}
}
impl IntoZval for $type {
const TYPE: DataType = DataType::String;
fn set_zval(self, zv: &mut Zval, persistent: bool) -> Result<()> {
zv.set_string(&self, persistent)
}
}
};
}
try_into_zval_str!(String);
try_into_zval_str!(&str);
impl IntoZval for () {
const TYPE: DataType = DataType::Void;
fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> {
zv.set_null();
Ok(())
}
}
impl<T> IntoZval for Option<T>
where
T: IntoZval,
{
const TYPE: DataType = T::TYPE;
fn set_zval(self, zv: &mut Zval, persistent: bool) -> Result<()> {
match self {
Some(val) => val.set_zval(zv, persistent),
None => {
zv.set_null();
Ok(())
}
}
}
}
pub trait FromZval<'a>: Sized {
const TYPE: DataType;
fn from_zval(zval: &'a Zval) -> Option<Self>;
}
impl<'a, T> FromZval<'a> for Option<T>
where
T: FromZval<'a>,
{
const TYPE: DataType = T::TYPE;
fn from_zval(zval: &'a Zval) -> Option<Self> {
Some(T::from_zval(zval))
}
}
macro_rules! try_from_zval {
($type: ty, $fn: ident, $dt: ident) => {
impl FromZval<'_> for $type {
const TYPE: DataType = DataType::$dt;
fn from_zval(zval: &Zval) -> Option<Self> {
zval.$fn().and_then(|val| val.try_into().ok())
}
}
impl TryFrom<Zval> for $type {
type Error = Error;
fn try_from(value: Zval) -> Result<Self> {
Self::from_zval(&value).ok_or(Error::ZvalConversion(value.get_type()))
}
}
};
}
try_from_zval!(i8, long, Long);
try_from_zval!(i16, long, Long);
try_from_zval!(i32, long, Long);
try_from_zval!(i64, long, Long);
try_from_zval!(u8, long, Long);
try_from_zval!(u16, long, Long);
try_from_zval!(u32, long, Long);
try_from_zval!(u64, long, Long);
try_from_zval!(usize, long, Long);
try_from_zval!(isize, long, Long);
try_from_zval!(f64, double, Double);
try_from_zval!(bool, bool, Bool);
try_from_zval!(String, string, String);
impl FromZval<'_> for f32 {
const TYPE: DataType = DataType::Double;
fn from_zval(zval: &Zval) -> Option<Self> {
zval.double().map(|v| v as f32)
}
}
impl<'a> FromZval<'a> for &'a str {
const TYPE: DataType = DataType::String;
fn from_zval(zval: &'a Zval) -> Option<Self> {
zval.str()
}
}
impl<'a> FromZval<'a> for Callable<'a> {
const TYPE: DataType = DataType::Callable;
fn from_zval(zval: &'a Zval) -> Option<Self> {
Callable::new(zval).ok()
}
}
impl<'a> TryFrom<Zval> for Callable<'a> {
type Error = Error;
fn try_from(value: Zval) -> Result<Self> {
Callable::new_owned(value)
}
}