use std::{convert::TryInto, ffi::c_void, fmt::Debug, ptr};
use cfg_if::cfg_if;
use crate::types::ZendIterator;
use crate::types::iterable::Iterable;
use crate::{
binary::Pack,
binary_slice::PackSlice,
boxed::ZBox,
convert::{FromZval, FromZvalMut, IntoZval, IntoZvalDyn},
error::{Error, Result},
ffi::{
_zval_struct__bindgen_ty_1, _zval_struct__bindgen_ty_2, GC_IMMUTABLE,
ext_php_rs_zend_string_release, zend_array_dup, zend_is_callable, zend_is_identical,
zend_is_iterable, zend_is_true, 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 {
#[must_use]
pub const fn new() -> Self {
Self {
value: zend_value {
ptr: ptr::null_mut(),
},
#[allow(clippy::used_underscore_items)]
u1: _zval_struct__bindgen_ty_1 {
type_info: DataType::Null.as_u32(),
},
#[allow(clippy::used_underscore_items)]
u2: _zval_struct__bindgen_ty_2 { next: 0 },
}
}
#[must_use]
pub fn null() -> Zval {
let mut zval = Zval::new();
zval.set_null();
zval
}
#[must_use]
pub fn new_array() -> Zval {
let mut zval = Zval::new();
zval.set_hashtable(ZendHashTable::new());
zval
}
#[must_use]
#[inline]
pub fn dereference(&self) -> &Self {
if self.is_reference() {
unsafe { &(*self.value.ref_).val }
} else if self.is_indirect() {
unsafe { &*(self.value.zv.cast::<Zval>()) }
} else {
self
}
}
#[must_use]
#[inline]
pub fn dereference_mut(&mut self) -> &mut Self {
if self.is_reference() {
let reference = unsafe { self.value.ref_.as_mut().unwrap_unchecked() };
return &mut reference.val;
}
if self.is_indirect() {
return unsafe { &mut *self.value.zv.cast::<Zval>() };
}
self
}
#[must_use]
pub fn long(&self) -> Option<ZendLong> {
if self.get_type() == DataType::Long {
return Some(unsafe { self.value.lval });
}
let zval = self.dereference();
if zval.get_type() == DataType::Long {
Some(unsafe { zval.value.lval })
} else {
None
}
}
#[must_use]
pub fn bool(&self) -> Option<bool> {
match self.get_type() {
DataType::True => return Some(true),
DataType::False => return Some(false),
_ => {}
}
let zval = self.dereference();
match zval.get_type() {
DataType::True => Some(true),
DataType::False => Some(false),
_ => None,
}
}
#[must_use]
pub fn double(&self) -> Option<f64> {
if self.get_type() == DataType::Double {
return Some(unsafe { self.value.dval });
}
let zval = self.dereference();
if zval.get_type() == DataType::Double {
Some(unsafe { zval.value.dval })
} else {
None
}
}
#[must_use]
pub fn zend_str(&self) -> Option<&ZendStr> {
if self.get_type() == DataType::String {
return unsafe { self.value.str_.as_ref() };
}
let zval = self.dereference();
if zval.get_type() == DataType::String {
unsafe { zval.value.str_.as_ref() }
} else {
None
}
}
pub fn string(&self) -> Option<String> {
self.str().map(ToString::to_string)
}
#[inline]
#[must_use]
pub fn str(&self) -> Option<&str> {
self.zend_str().and_then(|zs| zs.as_str().ok())
}
pub fn binary<T: Pack>(&self) -> Option<Vec<T>> {
self.zend_str().map(T::unpack_into)
}
pub fn binary_slice<T: PackSlice>(&self) -> Option<&[T]> {
self.zend_str().map(T::unpack_into)
}
#[must_use]
pub fn resource(&self) -> Option<*mut zend_resource> {
if self.get_type() == DataType::Resource {
return Some(unsafe { self.value.res });
}
let zval = self.dereference();
if zval.get_type() == DataType::Resource {
Some(unsafe { zval.value.res })
} else {
None
}
}
#[must_use]
pub fn array(&self) -> Option<&ZendHashTable> {
if self.get_type() == DataType::Array {
return unsafe { self.value.arr.as_ref() };
}
let zval = self.dereference();
if zval.get_type() == DataType::Array {
unsafe { zval.value.arr.as_ref() }
} else {
None
}
}
pub fn array_mut(&mut self) -> Option<&mut ZendHashTable> {
let zval = if self.get_type() == DataType::Array {
self
} else {
self.dereference_mut()
};
if zval.get_type() == DataType::Array {
unsafe {
let arr = zval.value.arr;
let ht = &*arr;
if ht.is_immutable() {
zval.value.arr = zend_array_dup(arr);
} else if (*arr).gc.refcount > 1 {
(*arr).gc.refcount -= 1;
zval.value.arr = zend_array_dup(arr);
}
zval.value.arr.as_mut()
}
} else {
None
}
}
#[must_use]
pub fn object(&self) -> Option<&ZendObject> {
if matches!(self.get_type(), DataType::Object(_)) {
return unsafe { self.value.obj.as_ref() };
}
let zval = self.dereference();
if matches!(zval.get_type(), DataType::Object(_)) {
unsafe { zval.value.obj.as_ref() }
} else {
None
}
}
pub fn object_mut(&mut self) -> Option<&mut ZendObject> {
if matches!(self.get_type(), DataType::Object(_)) {
return unsafe { self.value.obj.as_mut() };
}
let zval = self.dereference_mut();
if matches!(zval.get_type(), DataType::Object(_)) {
unsafe { zval.value.obj.as_mut() }
} else {
None
}
}
#[allow(clippy::inline_always)]
#[inline(always)]
pub fn try_call_method(&self, name: &str, params: Vec<&dyn IntoZvalDyn>) -> Result<Zval> {
self.object()
.ok_or(Error::Object)?
.try_call_method(name, params)
}
#[must_use]
pub fn indirect(&self) -> Option<&Zval> {
if self.is_indirect() {
Some(unsafe { &*(self.value.zv.cast::<Zval>()) })
} else {
None
}
}
#[allow(clippy::mut_from_ref)]
#[must_use]
pub fn indirect_mut(&self) -> Option<&mut Zval> {
if self.is_indirect() {
Some(unsafe { &mut *(self.value.zv.cast::<Zval>()) })
} else {
None
}
}
#[inline]
#[must_use]
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
}
}
#[must_use]
pub fn callable(&self) -> Option<ZendCallable<'_>> {
ZendCallable::new(self).ok()
}
#[must_use]
pub fn traversable(&self) -> Option<&mut ZendIterator> {
if self.is_traversable() {
self.object()?.get_class_entry().get_iterator(self, false)
} else {
None
}
}
#[must_use]
pub fn iterable(&self) -> Option<Iterable<'_>> {
if self.is_iterable() {
Iterable::from_zval(self)
} else {
None
}
}
#[must_use]
pub unsafe fn ptr<T>(&self) -> Option<*mut T> {
if self.is_ptr() {
Some(unsafe { self.value.ptr.cast::<T>() })
} else {
None
}
}
#[allow(clippy::inline_always)]
#[inline(always)]
pub fn try_call(&self, params: Vec<&dyn IntoZvalDyn>) -> Result<Zval> {
self.callable().ok_or(Error::Callable)?.try_call(params)
}
#[must_use]
pub fn get_type(&self) -> DataType {
DataType::from(u32::from(unsafe { self.u1.v.type_ }))
}
#[must_use]
pub fn is_long(&self) -> bool {
self.get_type() == DataType::Long
}
#[must_use]
pub fn is_null(&self) -> bool {
self.get_type() == DataType::Null
}
#[must_use]
pub fn is_true(&self) -> bool {
self.get_type() == DataType::True
}
#[must_use]
pub fn is_false(&self) -> bool {
self.get_type() == DataType::False
}
#[must_use]
pub fn is_bool(&self) -> bool {
self.is_true() || self.is_false()
}
#[must_use]
pub fn is_double(&self) -> bool {
self.get_type() == DataType::Double
}
#[must_use]
pub fn is_string(&self) -> bool {
self.get_type() == DataType::String
}
#[must_use]
pub fn is_resource(&self) -> bool {
self.get_type() == DataType::Resource
}
#[must_use]
pub fn is_array(&self) -> bool {
self.get_type() == DataType::Array
}
#[must_use]
pub fn is_object(&self) -> bool {
matches!(self.get_type(), DataType::Object(_))
}
#[inline]
#[must_use]
pub fn is_reference(&self) -> bool {
self.get_type() == DataType::Reference
}
#[must_use]
pub fn is_indirect(&self) -> bool {
self.get_type() == DataType::Indirect
}
#[must_use]
pub fn is_callable(&self) -> bool {
let ptr: *const Self = self;
unsafe { zend_is_callable(ptr.cast_mut(), 0, std::ptr::null_mut()) }
}
#[must_use]
pub fn is_identical(&self, other: &Self) -> bool {
let self_p: *const Self = self;
let other_p: *const Self = other;
unsafe { zend_is_identical(self_p.cast_mut(), other_p.cast_mut()) }
}
#[must_use]
pub fn is_traversable(&self) -> bool {
match self.object() {
None => false,
Some(obj) => obj.is_traversable(),
}
}
#[must_use]
pub fn is_iterable(&self) -> bool {
let ptr: *const Self = self;
unsafe { zend_is_iterable(ptr.cast_mut()) }
}
#[must_use]
pub fn is_ptr(&self) -> bool {
self.get_type() == DataType::Ptr
}
#[must_use]
pub fn is_scalar(&self) -> bool {
matches!(
self.get_type(),
DataType::Long | DataType::Double | DataType::String | DataType::True | DataType::False
)
}
#[must_use]
pub fn coerce_to_bool(&self) -> bool {
cfg_if! {
if #[cfg(php84)] {
let ptr: *const Self = self;
unsafe { zend_is_true(ptr) }
} else {
let ptr = self as *const Self as *mut Self;
unsafe { zend_is_true(ptr) != 0 }
}
}
}
#[must_use]
pub fn coerce_to_string(&self) -> Option<String> {
if let Some(s) = self.str() {
return Some(s.to_string());
}
if let Some(b) = self.bool() {
return Some(if b { "1".to_string() } else { String::new() });
}
if self.is_null() {
return Some(String::new());
}
if let Some(l) = self.long() {
return Some(l.to_string());
}
if let Some(d) = self.double() {
if d.is_nan() {
return Some("NAN".to_string());
}
if d.is_infinite() {
return Some(if d.is_sign_positive() {
"INF".to_string()
} else {
"-INF".to_string()
});
}
return Some(php_float_to_string(d));
}
if let Some(obj) = self.object()
&& let Ok(result) = obj.try_call_method("__toString", vec![])
{
return result.str().map(ToString::to_string);
}
None
}
#[must_use]
pub fn coerce_to_long(&self) -> Option<ZendLong> {
if let Some(l) = self.long() {
return Some(l);
}
if let Some(b) = self.bool() {
return Some(ZendLong::from(b));
}
if self.is_null() {
return Some(0);
}
if let Some(d) = self.double() {
#[allow(clippy::cast_possible_truncation)]
return Some(d as ZendLong);
}
if let Some(s) = self.str() {
return Some(parse_long_from_str(s));
}
None
}
#[must_use]
pub fn coerce_to_double(&self) -> Option<f64> {
if let Some(d) = self.double() {
return Some(d);
}
if let Some(l) = self.long() {
#[allow(clippy::cast_precision_loss)]
return Some(l as f64);
}
if let Some(b) = self.bool() {
return Some(if b { 1.0 } else { 0.0 });
}
if self.is_null() {
return Some(0.0);
}
if let Some(s) = self.str() {
return Some(parse_double_from_str(s));
}
None
}
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>) {
let is_interned = unsafe { val.gc.u.type_info } & GC_IMMUTABLE != 0;
let flags = if is_interned {
ZvalTypeFlags::InternedStringEx
} else {
ZvalTypeFlags::StringEx
};
self.change_type(flags);
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.change_type(ZvalTypeFlags::InternedStringEx);
self.value.str_ = ZendStr::new_interned(val, persistent).into_raw();
Ok(())
}
pub fn set_long<T: Into<ZendLong>>(&mut self, val: T) {
self.internal_set_long(val.into());
}
fn internal_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.internal_set_double(val.into());
}
fn internal_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.internal_set_bool(val.into());
}
fn internal_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 = ptr::from_ref(val).cast_mut();
}
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>) {
let type_info = if val.is_immutable() {
ZvalTypeFlags::Array
} else {
ZvalTypeFlags::ArrayEx
};
self.change_type(type_info);
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.cast::<c_void>();
}
pub(crate) fn release(mut self) {
self.u1.type_info = ZvalTypeFlags::Null.bits();
}
fn change_type(&mut self, ty: ZvalTypeFlags) {
if self.is_string() {
unsafe {
if let Some(str_ptr) = self.value.str_.as_mut() {
ext_php_rs_zend_string_release(str_ptr);
}
}
} else if self.is_array()
|| self.is_object()
|| self.is_resource()
|| self.is_reference()
|| self.get_type() == DataType::ConstantExpression
{
unsafe { zval_ptr_dtor(self) };
}
self.u1.type_info = ty.bits();
}
#[must_use]
pub fn extract<'a, T>(&'a self) -> Option<T>
where
T: FromZval<'a>,
{
FromZval::from_zval(self)
}
#[must_use]
pub fn shallow_clone(&self) -> Zval {
let mut new = Zval::new();
new.u1 = self.u1;
new.value = self.value;
unsafe {
let flags = ZvalTypeFlags::from_bits_retain(self.u1.type_info);
if flags.contains(ZvalTypeFlags::RefCounted) {
(*self.value.counted).gc.refcount += 1;
}
}
new
}
}
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 | DataType::Null | DataType::ConstantExpression | DataType::Void => {
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 | DataType::Callable => field!(self.string()),
DataType::Array => field!(self.array()),
DataType::Object(_) => field!(self.object()),
DataType::Resource => field!(self.resource()),
DataType::Reference => field!(self.reference()),
DataType::Bool => field!(self.bool()),
DataType::Indirect => field!(self.indirect()),
DataType::Iterable => field!(self.iterable()),
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;
const NULLABLE: bool = true;
fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> {
*zv = self;
Ok(())
}
}
impl<'a> FromZval<'a> for &'a Zval {
const TYPE: DataType = DataType::Mixed;
fn from_zval(zval: &'a Zval) -> Option<Self> {
Some(zval)
}
}
impl<'a> FromZvalMut<'a> for &'a mut Zval {
const TYPE: DataType = DataType::Mixed;
fn from_zval_mut(zval: &'a mut Zval) -> Option<Self> {
Some(zval)
}
}
fn php_float_to_string(d: f64) -> String {
let formatted = format!("{d:.14E}");
if let Some(e_pos) = formatted.find('E') {
let mantissa_part = &formatted[..e_pos];
let exp_part = &formatted[e_pos..];
let exp: i32 = exp_part[1..].parse().unwrap_or(0);
if exp >= 14 || exp <= -5 {
let mantissa = mantissa_part.trim_end_matches('0').trim_end_matches('.');
let mantissa = if mantissa.contains('.') && mantissa.ends_with('.') {
format!("{mantissa}0")
} else if !mantissa.contains('.') {
format!("{mantissa}.0")
} else {
mantissa.to_string()
};
if exp >= 0 {
format!("{mantissa}E+{exp}")
} else {
format!("{mantissa}E{exp}")
}
} else {
let s = d.to_string();
if s.contains('.') {
s.trim_end_matches('0').trim_end_matches('.').to_string()
} else {
s
}
}
} else {
formatted
}
}
fn parse_long_from_str(s: &str) -> ZendLong {
let s = s.trim_start();
let bytes = s.as_bytes();
if bytes.is_empty() {
return 0;
}
let mut i = 0;
let mut is_negative = false;
if bytes[i] == b'-' || bytes[i] == b'+' {
is_negative = bytes[i] == b'-';
i += 1;
}
let digits_start = i;
while i < bytes.len() && bytes[i].is_ascii_digit() {
i += 1;
}
if i == digits_start {
return 0;
}
match s[..i].parse::<ZendLong>() {
Ok(n) => n,
Err(_) => {
if is_negative {
ZendLong::MIN
} else {
ZendLong::MAX
}
}
}
}
fn parse_double_from_str(s: &str) -> f64 {
let s = s.trim_start();
let bytes = s.as_bytes();
if bytes.is_empty() {
return 0.0;
}
let mut i = 0;
let mut has_decimal = false;
let mut has_exponent = false;
let mut has_digits = false;
if bytes[i] == b'-' || bytes[i] == b'+' {
i += 1;
}
while i < bytes.len() {
let c = bytes[i];
if c.is_ascii_digit() {
has_digits = true;
i += 1;
} else if c == b'.' && !has_decimal && !has_exponent {
has_decimal = true;
i += 1;
} else if (c == b'e' || c == b'E') && !has_exponent && has_digits {
has_exponent = true;
i += 1;
if i < bytes.len() && (bytes[i] == b'-' || bytes[i] == b'+') {
i += 1;
}
} else {
break;
}
}
s[..i].parse().unwrap_or(0.0)
}
#[cfg(test)]
#[cfg(feature = "embed")]
#[allow(clippy::unwrap_used, clippy::approx_constant)]
mod tests {
use super::*;
use crate::embed::Embed;
#[test]
fn test_zval_null() {
Embed::run(|| {
let zval = Zval::null();
assert!(zval.is_null());
});
}
#[test]
fn test_is_scalar() {
Embed::run(|| {
let mut zval_long = Zval::new();
zval_long.set_long(42);
assert!(zval_long.is_scalar());
let mut zval_double = Zval::new();
zval_double.set_double(1.5);
assert!(zval_double.is_scalar());
let mut zval_true = Zval::new();
zval_true.set_bool(true);
assert!(zval_true.is_scalar());
let mut zval_false = Zval::new();
zval_false.set_bool(false);
assert!(zval_false.is_scalar());
let mut zval_string = Zval::new();
zval_string
.set_string("hello", false)
.expect("set_string should succeed");
assert!(zval_string.is_scalar());
let zval_null = Zval::null();
assert!(!zval_null.is_scalar());
let zval_array = Zval::new_array();
assert!(!zval_array.is_scalar());
});
}
#[test]
fn test_coerce_to_bool() {
Embed::run(|| {
let mut zv = Zval::new();
zv.set_long(42);
assert!(zv.coerce_to_bool(), "(bool)42 should be true");
zv.set_long(1);
assert!(zv.coerce_to_bool(), "(bool)1 should be true");
zv.set_long(-1);
assert!(zv.coerce_to_bool(), "(bool)-1 should be true");
zv.set_double(0.1);
assert!(zv.coerce_to_bool(), "(bool)0.1 should be true");
zv.set_double(-0.1);
assert!(zv.coerce_to_bool(), "(bool)-0.1 should be true");
zv.set_string("hello", false).unwrap();
assert!(zv.coerce_to_bool(), "(bool)'hello' should be true");
zv.set_string("1", false).unwrap();
assert!(zv.coerce_to_bool(), "(bool)'1' should be true");
zv.set_string("false", false).unwrap();
assert!(zv.coerce_to_bool(), "(bool)'false' should be true");
zv.set_string("true", false).unwrap();
assert!(zv.coerce_to_bool(), "(bool)'true' should be true");
zv.set_string(" ", false).unwrap();
assert!(zv.coerce_to_bool(), "(bool)' ' should be true");
zv.set_string("00", false).unwrap();
assert!(zv.coerce_to_bool(), "(bool)'00' should be true");
zv.set_bool(true);
assert!(zv.coerce_to_bool(), "(bool)true should be true");
zv.set_long(0);
assert!(!zv.coerce_to_bool(), "(bool)0 should be false");
zv.set_double(0.0);
assert!(!zv.coerce_to_bool(), "(bool)0.0 should be false");
zv.set_double(-0.0);
assert!(!zv.coerce_to_bool(), "(bool)-0.0 should be false");
zv.set_string("", false).unwrap();
assert!(!zv.coerce_to_bool(), "(bool)'' should be false");
zv.set_string("0", false).unwrap();
assert!(!zv.coerce_to_bool(), "(bool)'0' should be false");
zv.set_bool(false);
assert!(!zv.coerce_to_bool(), "(bool)false should be false");
let null_zv = Zval::null();
assert!(!null_zv.coerce_to_bool(), "(bool)null should be false");
let empty_array = Zval::new_array();
assert!(!empty_array.coerce_to_bool(), "(bool)[] should be false");
});
}
#[test]
fn test_coerce_to_string() {
Embed::run(|| {
let mut zv = Zval::new();
zv.set_long(42);
assert_eq!(zv.coerce_to_string(), Some("42".to_string()));
zv.set_long(-123);
assert_eq!(zv.coerce_to_string(), Some("-123".to_string()));
zv.set_long(0);
assert_eq!(zv.coerce_to_string(), Some("0".to_string()));
zv.set_double(3.14);
assert_eq!(zv.coerce_to_string(), Some("3.14".to_string()));
zv.set_double(-3.14);
assert_eq!(zv.coerce_to_string(), Some("-3.14".to_string()));
zv.set_double(0.0);
assert_eq!(zv.coerce_to_string(), Some("0".to_string()));
zv.set_double(1.0);
assert_eq!(zv.coerce_to_string(), Some("1".to_string()));
zv.set_double(1.5);
assert_eq!(zv.coerce_to_string(), Some("1.5".to_string()));
zv.set_double(100.0);
assert_eq!(zv.coerce_to_string(), Some("100".to_string()));
zv.set_double(f64::INFINITY);
assert_eq!(zv.coerce_to_string(), Some("INF".to_string()));
zv.set_double(f64::NEG_INFINITY);
assert_eq!(zv.coerce_to_string(), Some("-INF".to_string()));
zv.set_double(f64::NAN);
assert_eq!(zv.coerce_to_string(), Some("NAN".to_string()));
zv.set_double(1e13);
assert_eq!(zv.coerce_to_string(), Some("10000000000000".to_string()));
zv.set_double(1e14);
assert_eq!(zv.coerce_to_string(), Some("1.0E+14".to_string()));
zv.set_double(1e52);
assert_eq!(zv.coerce_to_string(), Some("1.0E+52".to_string()));
zv.set_double(0.0001);
assert_eq!(zv.coerce_to_string(), Some("0.0001".to_string()));
zv.set_double(1e-5);
assert_eq!(zv.coerce_to_string(), Some("1.0E-5".to_string()));
zv.set_double(-1e52);
assert_eq!(zv.coerce_to_string(), Some("-1.0E+52".to_string()));
zv.set_double(-1e-10);
assert_eq!(zv.coerce_to_string(), Some("-1.0E-10".to_string()));
zv.set_bool(true);
assert_eq!(zv.coerce_to_string(), Some("1".to_string()));
zv.set_bool(false);
assert_eq!(zv.coerce_to_string(), Some(String::new()));
let null_zv = Zval::null();
assert_eq!(null_zv.coerce_to_string(), Some(String::new()));
zv.set_string("hello", false).unwrap();
assert_eq!(zv.coerce_to_string(), Some("hello".to_string()));
let arr_zv = Zval::new_array();
assert_eq!(arr_zv.coerce_to_string(), None);
});
}
#[test]
fn test_coerce_to_long() {
Embed::run(|| {
let mut zv = Zval::new();
zv.set_long(42);
assert_eq!(zv.coerce_to_long(), Some(42));
zv.set_long(-42);
assert_eq!(zv.coerce_to_long(), Some(-42));
zv.set_long(0);
assert_eq!(zv.coerce_to_long(), Some(0));
zv.set_double(3.14);
assert_eq!(zv.coerce_to_long(), Some(3));
zv.set_double(3.99);
assert_eq!(zv.coerce_to_long(), Some(3));
zv.set_double(-3.14);
assert_eq!(zv.coerce_to_long(), Some(-3));
zv.set_double(-3.99);
assert_eq!(zv.coerce_to_long(), Some(-3));
zv.set_double(0.0);
assert_eq!(zv.coerce_to_long(), Some(0));
zv.set_double(-0.0);
assert_eq!(zv.coerce_to_long(), Some(0));
zv.set_bool(true);
assert_eq!(zv.coerce_to_long(), Some(1));
zv.set_bool(false);
assert_eq!(zv.coerce_to_long(), Some(0));
let null_zv = Zval::null();
assert_eq!(null_zv.coerce_to_long(), Some(0));
zv.set_string("123", false).unwrap();
assert_eq!(zv.coerce_to_long(), Some(123));
zv.set_string(" 123", false).unwrap();
assert_eq!(zv.coerce_to_long(), Some(123));
zv.set_string("123abc", false).unwrap();
assert_eq!(zv.coerce_to_long(), Some(123));
zv.set_string("abc123", false).unwrap();
assert_eq!(zv.coerce_to_long(), Some(0));
zv.set_string("", false).unwrap();
assert_eq!(zv.coerce_to_long(), Some(0));
zv.set_string("0", false).unwrap();
assert_eq!(zv.coerce_to_long(), Some(0));
zv.set_string("-0", false).unwrap();
assert_eq!(zv.coerce_to_long(), Some(0));
zv.set_string("+123", false).unwrap();
assert_eq!(zv.coerce_to_long(), Some(123));
zv.set_string(" -456", false).unwrap();
assert_eq!(zv.coerce_to_long(), Some(-456));
zv.set_string("3.14", false).unwrap();
assert_eq!(zv.coerce_to_long(), Some(3));
zv.set_string("3.99", false).unwrap();
assert_eq!(zv.coerce_to_long(), Some(3));
zv.set_string("-3.99", false).unwrap();
assert_eq!(zv.coerce_to_long(), Some(-3));
zv.set_string("abc", false).unwrap();
assert_eq!(zv.coerce_to_long(), Some(0));
let arr_zv = Zval::new_array();
assert_eq!(arr_zv.coerce_to_long(), None);
});
}
#[test]
fn test_coerce_to_double() {
Embed::run(|| {
let mut zv = Zval::new();
zv.set_double(3.14);
assert!((zv.coerce_to_double().unwrap() - 3.14).abs() < f64::EPSILON);
zv.set_double(-3.14);
assert!((zv.coerce_to_double().unwrap() - (-3.14)).abs() < f64::EPSILON);
zv.set_double(0.0);
assert!((zv.coerce_to_double().unwrap() - 0.0).abs() < f64::EPSILON);
zv.set_long(42);
assert!((zv.coerce_to_double().unwrap() - 42.0).abs() < f64::EPSILON);
zv.set_long(-42);
assert!((zv.coerce_to_double().unwrap() - (-42.0)).abs() < f64::EPSILON);
zv.set_long(0);
assert!((zv.coerce_to_double().unwrap() - 0.0).abs() < f64::EPSILON);
zv.set_bool(true);
assert!((zv.coerce_to_double().unwrap() - 1.0).abs() < f64::EPSILON);
zv.set_bool(false);
assert!((zv.coerce_to_double().unwrap() - 0.0).abs() < f64::EPSILON);
let null_zv = Zval::null();
assert!((null_zv.coerce_to_double().unwrap() - 0.0).abs() < f64::EPSILON);
zv.set_string("3.14", false).unwrap();
assert!((zv.coerce_to_double().unwrap() - 3.14).abs() < f64::EPSILON);
zv.set_string(" 3.14", false).unwrap();
assert!((zv.coerce_to_double().unwrap() - 3.14).abs() < f64::EPSILON);
zv.set_string("3.14abc", false).unwrap();
assert!((zv.coerce_to_double().unwrap() - 3.14).abs() < f64::EPSILON);
zv.set_string("abc3.14", false).unwrap();
assert!((zv.coerce_to_double().unwrap() - 0.0).abs() < f64::EPSILON);
zv.set_string("", false).unwrap();
assert!((zv.coerce_to_double().unwrap() - 0.0).abs() < f64::EPSILON);
zv.set_string("0", false).unwrap();
assert!((zv.coerce_to_double().unwrap() - 0.0).abs() < f64::EPSILON);
zv.set_string("0.0", false).unwrap();
assert!((zv.coerce_to_double().unwrap() - 0.0).abs() < f64::EPSILON);
zv.set_string("+3.14", false).unwrap();
assert!((zv.coerce_to_double().unwrap() - 3.14).abs() < f64::EPSILON);
zv.set_string(" -3.14", false).unwrap();
assert!((zv.coerce_to_double().unwrap() - (-3.14)).abs() < f64::EPSILON);
zv.set_string("1e10", false).unwrap();
assert!((zv.coerce_to_double().unwrap() - 1e10).abs() < 1.0);
zv.set_string("1E10", false).unwrap();
assert!((zv.coerce_to_double().unwrap() - 1e10).abs() < 1.0);
zv.set_string("1.5e-3", false).unwrap();
assert!((zv.coerce_to_double().unwrap() - 0.0015).abs() < f64::EPSILON);
zv.set_string(".5", false).unwrap();
assert!((zv.coerce_to_double().unwrap() - 0.5).abs() < f64::EPSILON);
zv.set_string("5.", false).unwrap();
assert!((zv.coerce_to_double().unwrap() - 5.0).abs() < f64::EPSILON);
zv.set_string("abc", false).unwrap();
assert!((zv.coerce_to_double().unwrap() - 0.0).abs() < f64::EPSILON);
let arr_zv = Zval::new_array();
assert_eq!(arr_zv.coerce_to_double(), None);
});
}
#[test]
fn test_parse_long_from_str() {
use crate::ffi::zend_long as ZendLong;
assert_eq!(parse_long_from_str("42"), 42);
assert_eq!(parse_long_from_str("0"), 0);
assert_eq!(parse_long_from_str("-42"), -42);
assert_eq!(parse_long_from_str("+42"), 42);
assert_eq!(parse_long_from_str("42abc"), 42);
assert_eq!(parse_long_from_str(" 42"), 42);
assert_eq!(parse_long_from_str("\t\n42"), 42);
assert_eq!(parse_long_from_str(" -42"), -42);
assert_eq!(parse_long_from_str("42.5"), 42);
assert_eq!(parse_long_from_str("abc"), 0);
assert_eq!(parse_long_from_str(""), 0);
assert_eq!(parse_long_from_str(" "), 0);
assert_eq!(parse_long_from_str("-"), 0);
assert_eq!(parse_long_from_str("+"), 0);
assert_eq!(parse_long_from_str("abc123"), 0);
assert_eq!(parse_long_from_str("0xFF"), 0);
assert_eq!(parse_long_from_str("0x10"), 0);
assert_eq!(parse_long_from_str("010"), 10);
assert_eq!(parse_long_from_str("0b101"), 0);
assert_eq!(
parse_long_from_str("99999999999999999999999999"),
ZendLong::MAX
);
assert_eq!(
parse_long_from_str("-99999999999999999999999999"),
ZendLong::MIN
);
assert_eq!(parse_long_from_str("9223372036854775807"), ZendLong::MAX); assert_eq!(parse_long_from_str("-9223372036854775808"), ZendLong::MIN); }
#[test]
fn test_parse_double_from_str() {
assert!((parse_double_from_str("3.14") - 3.14).abs() < f64::EPSILON);
assert!((parse_double_from_str("0.0") - 0.0).abs() < f64::EPSILON);
assert!((parse_double_from_str("-3.14") - (-3.14)).abs() < f64::EPSILON);
assert!((parse_double_from_str("+3.14") - 3.14).abs() < f64::EPSILON);
assert!((parse_double_from_str(".5") - 0.5).abs() < f64::EPSILON);
assert!((parse_double_from_str("5.") - 5.0).abs() < f64::EPSILON);
assert!((parse_double_from_str("1e10") - 1e10).abs() < 1.0);
assert!((parse_double_from_str("1E10") - 1e10).abs() < 1.0);
assert!((parse_double_from_str("1.5e-3") - 1.5e-3).abs() < f64::EPSILON);
assert!((parse_double_from_str("1.5E+3") - 1500.0).abs() < f64::EPSILON);
assert!((parse_double_from_str("-1.5e2") - (-150.0)).abs() < f64::EPSILON);
assert!((parse_double_from_str("3.14abc") - 3.14).abs() < f64::EPSILON);
assert!((parse_double_from_str(" 3.14") - 3.14).abs() < f64::EPSILON);
assert!((parse_double_from_str("\t\n3.14") - 3.14).abs() < f64::EPSILON);
assert!((parse_double_from_str("1e10xyz") - 1e10).abs() < 1.0);
assert!((parse_double_from_str("abc") - 0.0).abs() < f64::EPSILON);
assert!((parse_double_from_str("") - 0.0).abs() < f64::EPSILON);
assert!((parse_double_from_str(" ") - 0.0).abs() < f64::EPSILON);
assert!((parse_double_from_str("-") - 0.0).abs() < f64::EPSILON);
assert!((parse_double_from_str("e10") - 0.0).abs() < f64::EPSILON);
assert!((parse_double_from_str("42") - 42.0).abs() < f64::EPSILON);
assert!((parse_double_from_str("-42") - (-42.0)).abs() < f64::EPSILON);
assert!((parse_double_from_str("0xFF") - 0.0).abs() < f64::EPSILON);
assert!((parse_double_from_str("1.2.3") - 1.2).abs() < f64::EPSILON);
assert!((parse_double_from_str("1e2e3") - 100.0).abs() < f64::EPSILON);
}
#[test]
fn test_php_float_to_string() {
assert_eq!(php_float_to_string(3.14), "3.14");
assert_eq!(php_float_to_string(42.0), "42");
assert_eq!(php_float_to_string(-3.14), "-3.14");
assert_eq!(php_float_to_string(0.0), "0");
assert_eq!(php_float_to_string(1e52), "1.0E+52");
assert_eq!(php_float_to_string(1e20), "1.0E+20");
assert_eq!(php_float_to_string(1e14), "1.0E+14");
assert_eq!(php_float_to_string(1e-10), "1.0E-10");
assert_eq!(php_float_to_string(1e-5), "1.0E-5");
assert_eq!(php_float_to_string(0.00001), "1.0E-5");
assert_eq!(php_float_to_string(1e13), "10000000000000");
assert_eq!(php_float_to_string(0.001), "0.001");
assert_eq!(php_float_to_string(0.0001), "0.0001"); }
}