use core::slice;
use std::{
collections::HashMap,
convert::{TryFrom, TryInto},
fmt::Debug,
ptr,
};
use crate::{
bindings::{
_zval_struct__bindgen_ty_1, _zval_struct__bindgen_ty_2, ext_php_rs_zend_string_release,
zend_is_callable, zend_resource, zend_value, zval,
},
errors::{Error, Result},
php::pack::Pack,
};
use crate::php::{
enums::DataType,
flags::ZvalTypeFlags,
types::{long::ZendLong, string::ZendString},
};
use super::{array::ZendHashTable, callable::Callable, object::ZendObject};
pub type Zval = zval;
unsafe impl Send for Zval {}
unsafe impl Sync for Zval {}
impl<'a> Zval {
pub(crate) 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 string(&self) -> Option<String> {
self.str()
.map(|s| s.to_string())
.or_else(|| self.double().map(|x| x.to_string()))
}
pub fn str(&'a self) -> Option<&'a str> {
if self.is_string() {
unsafe {
std::str::from_utf8(slice::from_raw_parts(
(*self.value.str_).val.as_ptr() as *const u8,
(*self.value.str_).len as usize,
))
.ok()
}
} else {
None
}
}
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<'a>> {
if self.is_array() {
unsafe { ZendHashTable::from_ptr(self.value.arr, false) }.ok()
} 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<&mut Zval> {
if self.is_reference() {
Some(&mut unsafe { self.value.ref_.as_mut() }?.val)
} else {
None
}
}
pub fn callable(&'a self) -> Option<Callable<'a>> {
Callable::new(self).ok()
}
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) -> Result<DataType> {
DataType::try_from(unsafe { self.u1.v.type_ } as u32)
}
pub fn is_long(&self) -> bool {
unsafe { self.u1.v.type_ == DataType::Long as u8 }
}
pub fn is_null(&self) -> bool {
unsafe { self.u1.v.type_ == DataType::Null as u8 }
}
pub fn is_true(&self) -> bool {
unsafe { self.u1.v.type_ == DataType::True as u8 }
}
pub fn is_false(&self) -> bool {
unsafe { self.u1.v.type_ == DataType::False as u8 }
}
pub fn is_bool(&self) -> bool {
self.is_true() || self.is_false()
}
pub fn is_double(&self) -> bool {
unsafe { self.u1.v.type_ == DataType::Double as u8 }
}
pub fn is_string(&self) -> bool {
unsafe { self.u1.v.type_ == DataType::String as u8 }
}
pub fn is_resource(&self) -> bool {
unsafe { self.u1.v.type_ == DataType::Resource as u8 }
}
pub fn is_array(&self) -> bool {
unsafe { self.u1.v.type_ == DataType::Array as u8 }
}
pub fn is_object(&self) -> bool {
unsafe { self.u1.v.type_ == DataType::Object as u8 }
}
pub fn is_reference(&self) -> bool {
unsafe { self.u1.v.type_ == DataType::Reference as u8 }
}
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 set_string(&mut self, val: &str, persistent: bool) -> Result<()> {
let zend_str = ZendString::new(val, persistent)?;
self.value.str_ = zend_str.release();
self.u1.type_info = ZvalTypeFlags::StringEx.bits();
Ok(())
}
pub fn set_binary<T: Pack>(&mut self, val: Vec<T>) {
let ptr = T::pack_into(val);
self.value.str_ = ptr;
self.u1.type_info = ZvalTypeFlags::StringEx.bits();
}
pub fn set_interned_string(&mut self, val: &str) -> Result<()> {
let zend_str = ZendString::new_interned(val)?;
self.value.str_ = zend_str.release();
self.u1.type_info = ZvalTypeFlags::InternedStringEx.bits();
Ok(())
}
pub fn set_long<T: Into<ZendLong>>(&mut self, val: T) {
self.value.lval = val.into();
self.u1.type_info = ZvalTypeFlags::Long.bits();
}
pub fn set_double<T: Into<f64>>(&mut self, val: T) {
self.value.dval = val.into();
self.u1.type_info = ZvalTypeFlags::Double.bits();
}
pub fn set_bool<T: Into<bool>>(&mut self, val: T) {
self.u1.type_info = if val.into() {
DataType::True as u32
} else {
DataType::False as u32
};
}
pub fn set_null(&mut self) {
self.u1.type_info = ZvalTypeFlags::Null.bits();
}
pub fn set_resource(&mut self, val: *mut zend_resource) {
self.u1.type_info = ZvalTypeFlags::ResourceEx.bits();
self.value.res = val;
}
pub fn set_object(&mut self, val: &mut ZendObject) {
val.refcount_inc();
self.u1.type_info = ZvalTypeFlags::ObjectEx.bits();
self.value.obj = (val as *const ZendObject) as *mut ZendObject;
}
pub fn set_array(&mut self, val: ZendHashTable) {
self.u1.type_info = ZvalTypeFlags::ArrayEx.bits();
self.value.arr = val.into_ptr();
}
pub(crate) fn release(mut self) {
self.u1.type_info = ZvalTypeFlags::Null.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);
if let Ok(ty) = 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()),
};
}
dbg.finish()
}
}
impl Drop for Zval {
fn drop(&mut self) {
if self.is_string() {
unsafe { ext_php_rs_zend_string_release(self.value.str_) };
}
}
}
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<()>;
}
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::Null;
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(())
}
}
}
}
impl<'a> IntoZval for ZendHashTable<'a> {
const TYPE: DataType = DataType::Array;
fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> {
zv.set_array(self);
Ok(())
}
}
impl<T> IntoZval for Vec<T>
where
T: IntoZval,
{
const TYPE: DataType = DataType::Array;
fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> {
let hm = self
.try_into()
.map_err(|_| Error::ZvalConversion(DataType::Array))?;
zv.set_array(hm);
Ok(())
}
}
impl<K, V> IntoZval for HashMap<K, V>
where
K: AsRef<str>,
V: IntoZval,
{
const TYPE: DataType = DataType::Array;
fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> {
let hm = self.try_into()?;
zv.set_array(hm);
Ok(())
}
}
pub trait FromZval<'a>: TryFrom<&'a Zval> {
const TYPE: DataType;
}
impl<'a, T> FromZval<'a> for Option<T>
where
T: FromZval<'a>,
{
const TYPE: DataType = T::TYPE;
}
impl<'a, T> From<&'a Zval> for Option<T>
where
T: FromZval<'a>,
{
fn from(val: &'a Zval) -> Self {
val.try_into().ok()
}
}
macro_rules! try_from_zval {
($type: ty, $fn: ident, $dt: ident) => {
impl<'a> FromZval<'a> for $type {
const TYPE: DataType = DataType::$dt;
}
impl TryFrom<&Zval> for $type {
type Error = Error;
fn try_from(value: &Zval) -> Result<Self> {
value
.$fn()
.and_then(|val| val.try_into().ok())
.ok_or(Error::ZvalConversion(value.get_type()?))
}
}
impl TryFrom<Zval> for $type {
type Error = Error;
fn try_from(value: Zval) -> Result<Self> {
(&value).try_into()
}
}
};
}
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<'a> FromZval<'a> for f32 {
const TYPE: DataType = DataType::Double;
}
impl<'a> TryFrom<&'a Zval> for f32 {
type Error = Error;
fn try_from(value: &'a Zval) -> Result<Self> {
value
.double()
.map(|v| v as f32)
.ok_or(Error::ZvalConversion(value.get_type()?))
}
}
impl<'a> FromZval<'a> for &'a str {
const TYPE: DataType = DataType::String;
}
impl<'a> TryFrom<&'a Zval> for &'a str {
type Error = Error;
fn try_from(value: &'a Zval) -> Result<Self> {
value.str().ok_or(Error::ZvalConversion(value.get_type()?))
}
}
impl<'a> FromZval<'a> for ZendHashTable<'a> {
const TYPE: DataType = DataType::Array;
}
impl<'a> TryFrom<&'a Zval> for ZendHashTable<'a> {
type Error = Error;
fn try_from(value: &'a Zval) -> Result<Self> {
value
.array()
.ok_or(Error::ZvalConversion(value.get_type()?))
}
}
impl<'a, T> FromZval<'a> for Vec<T>
where
T: FromZval<'a>,
{
const TYPE: DataType = DataType::Array;
}
impl<'a, T> TryFrom<&'a Zval> for Vec<T>
where
T: FromZval<'a>,
{
type Error = Error;
fn try_from(value: &'a Zval) -> Result<Self> {
value
.array()
.ok_or(Error::ZvalConversion(value.get_type()?))?
.try_into()
}
}
impl<'a, T> TryFrom<Zval> for Vec<T>
where
T: FromZval<'a>,
{
type Error = Error;
fn try_from(value: Zval) -> Result<Self> {
value
.array()
.ok_or(Error::ZvalConversion(value.get_type()?))?
.try_into()
}
}
impl<'a, T> FromZval<'a> for HashMap<String, T>
where
T: FromZval<'a>,
{
const TYPE: DataType = DataType::Array;
}
impl<'a, T> TryFrom<&'a Zval> for HashMap<String, T>
where
T: FromZval<'a>,
{
type Error = Error;
fn try_from(value: &'a Zval) -> Result<Self> {
value
.array()
.ok_or(Error::ZvalConversion(value.get_type()?))?
.try_into()
}
}
impl<'a, T> TryFrom<Zval> for HashMap<String, T>
where
T: FromZval<'a>,
{
type Error = Error;
fn try_from(value: Zval) -> Result<Self> {
value
.array()
.ok_or(Error::ZvalConversion(value.get_type()?))?
.try_into()
}
}
impl<'a> FromZval<'a> for Callable<'a> {
const TYPE: DataType = DataType::Callable;
}
impl<'a> TryFrom<&'a Zval> for Callable<'a> {
type Error = Error;
fn try_from(value: &'a Zval) -> Result<Self> {
Callable::new(value)
}
}
impl<'a> TryFrom<Zval> for Callable<'a> {
type Error = Error;
fn try_from(value: Zval) -> Result<Self> {
Callable::new_owned(value)
}
}