#![cfg(phper_enum_supported)]
use crate::{
classes::{
ClassEntry, ConstantEntity, InnerClassEntry, Interface, Visibility, add_class_constant,
},
errors::Throwable,
functions::{Function, FunctionEntry, HandlerMap, MethodEntity},
objects::ZObj,
strings::ZString,
sys::*,
types::Scalar,
utils::ensure_end_with_zero,
values::ZVal,
};
use sealed::sealed;
use std::{
cell::RefCell,
ffi::{CStr, CString},
marker::PhantomData,
mem::{ManuallyDrop, zeroed},
ptr::{null, null_mut},
rc::Rc,
};
#[sealed]
pub trait EnumBackingType: Into<Scalar> {
fn enum_type() -> EnumType;
}
#[sealed]
impl EnumBackingType for () {
fn enum_type() -> EnumType {
EnumType::Pure
}
}
#[sealed]
impl EnumBackingType for i64 {
fn enum_type() -> EnumType {
EnumType::IntBacked
}
}
#[sealed]
impl EnumBackingType for String {
fn enum_type() -> EnumType {
EnumType::StringBacked
}
}
pub enum EnumType {
Pure,
IntBacked,
StringBacked,
}
struct EnumCaseEntity {
name: CString,
value: Scalar,
}
#[derive(Clone)]
pub struct EnumCase {
r#enum: Enum,
case_name: String,
}
impl EnumCase {
fn new(enum_obj: Enum, case_name: impl Into<String>) -> Self {
Self {
r#enum: enum_obj,
case_name: case_name.into(),
}
}
pub fn get_case<'a>(&self) -> &'a ZObj {
unsafe { self.r#enum.get_case(&self.case_name).unwrap() }
}
pub fn get_mut_case<'a>(&mut self) -> &'a mut ZObj {
unsafe { self.r#enum.get_mut_case(&self.case_name).unwrap() }
}
pub fn name(&self) -> &str {
&self.case_name
}
pub fn as_enum(&self) -> &Enum {
&self.r#enum
}
}
#[derive(Clone)]
pub struct Enum {
inner: Rc<RefCell<InnerClassEntry>>,
}
impl Enum {
fn null() -> Self {
Self {
inner: Rc::new(RefCell::new(InnerClassEntry::Ptr(null()))),
}
}
pub fn from_name(name: impl Into<String>) -> Self {
Self {
inner: Rc::new(RefCell::new(InnerClassEntry::Name(name.into()))),
}
}
fn bind(&self, ptr: *mut zend_class_entry) {
match &mut *self.inner.borrow_mut() {
InnerClassEntry::Ptr(p) => {
*p = ptr;
}
InnerClassEntry::Name(_) => {
unreachable!("Cannot bind() an Enum created with from_name()");
}
}
}
pub fn as_class_entry(&self) -> &ClassEntry {
let inner = self.inner.borrow().clone();
match inner {
InnerClassEntry::Ptr(ptr) => unsafe { ClassEntry::from_ptr(ptr) },
InnerClassEntry::Name(name) => {
let entry = ClassEntry::from_globals(name).unwrap();
*self.inner.borrow_mut() = InnerClassEntry::Ptr(entry.as_ptr());
entry
}
}
}
pub unsafe fn get_case<'a>(&self, case_name: impl AsRef<str>) -> crate::Result<&'a ZObj> {
unsafe {
let ce = self.as_class_entry().as_ptr() as *mut _;
let case_name_str = case_name.as_ref();
let mut name_zstr = ZString::new(case_name_str);
let case_obj = zend_enum_get_case(ce, name_zstr.as_mut_ptr());
Ok(ZObj::from_ptr(case_obj))
}
}
pub unsafe fn get_mut_case<'a>(
&mut self, case_name: impl AsRef<str>,
) -> crate::Result<&'a mut ZObj> {
unsafe {
let ce = self.as_class_entry().as_ptr() as *mut _;
let case_name_str = case_name.as_ref();
let mut name_zstr = ZString::new(case_name_str);
let case_obj = zend_enum_get_case(ce, name_zstr.as_mut_ptr());
Ok(ZObj::from_mut_ptr(case_obj as *mut _))
}
}
}
pub struct EnumEntity<B: EnumBackingType = ()> {
enum_name: CString,
enum_type: EnumType,
method_entities: Vec<MethodEntity>,
cases: Vec<EnumCaseEntity>,
constants: Vec<ConstantEntity>,
interfaces: Vec<Interface>,
bound_enum: Enum,
_p: PhantomData<(B, *mut ())>,
}
impl<B: EnumBackingType> EnumEntity<B> {
pub fn new(enum_name: impl Into<String>) -> Self {
Self {
enum_name: ensure_end_with_zero(enum_name),
enum_type: B::enum_type(),
method_entities: Vec::new(),
cases: Vec::new(),
constants: Vec::new(),
interfaces: Vec::new(),
bound_enum: Enum::null(),
_p: PhantomData,
}
}
pub fn add_case(&mut self, name: impl Into<String>, value: B) -> EnumCase {
let case_name_str = name.into();
let case_name = ensure_end_with_zero(&case_name_str);
self.cases.push(EnumCaseEntity {
name: case_name,
value: value.into(),
});
EnumCase::new(self.bound_enum(), case_name_str)
}
pub fn add_static_method<F, Z, E>(
&mut self, name: impl Into<String>, vis: Visibility, handler: F,
) -> &mut MethodEntity
where
F: Fn(&mut [ZVal]) -> Result<Z, E> + 'static,
Z: Into<ZVal> + 'static,
E: Throwable + 'static,
{
let mut entity = MethodEntity::new(name, Some(Rc::new(Function::new(handler))), vis);
entity.set_vis_static();
self.method_entities.push(entity);
self.method_entities.last_mut().unwrap()
}
pub fn add_constant(&mut self, name: impl Into<String>, value: impl Into<Scalar>) {
let constant = ConstantEntity::new(name, value);
self.constants.push(constant);
}
pub fn implements(&mut self, interface: Interface) {
self.interfaces.push(interface);
}
#[inline]
pub fn bound_enum(&self) -> Enum {
self.bound_enum.clone()
}
unsafe fn function_entries(&self) -> *const zend_function_entry {
unsafe {
let mut methods = self
.method_entities
.iter()
.map(|method| FunctionEntry::from_method_entity(method))
.collect::<Vec<_>>();
methods.push(zeroed::<zend_function_entry>());
Box::into_raw(methods.into_boxed_slice()).cast()
}
}
pub(crate) fn handler_map(&self) -> HandlerMap {
self.method_entities
.iter()
.filter_map(|method| {
method.handler.as_ref().map(|handler| {
(
(Some(self.enum_name.clone()), method.name.clone()),
handler.clone(),
)
})
})
.collect()
}
#[allow(clippy::useless_conversion)]
pub(crate) unsafe fn init(&self) -> *mut zend_class_entry {
unsafe {
let backing_type = match self.enum_type {
EnumType::Pure => IS_NULL,
EnumType::IntBacked => IS_LONG,
EnumType::StringBacked => IS_STRING,
} as u8;
let class_ce = zend_register_internal_enum(
self.enum_name.as_ptr().cast(),
backing_type,
self.function_entries(),
);
self.bound_enum.bind(class_ce);
for interface in &self.interfaces {
let interface_ce = interface.as_class_entry().as_ptr();
zend_class_implements(class_ce, 1, interface_ce);
}
for constant in &self.constants {
add_class_constant(class_ce, constant);
}
for case in &self.cases {
register_enum_case(class_ce, &case.name, &case.value);
}
class_ce
}
}
}
unsafe fn register_enum_case(
class_ce: *mut zend_class_entry, case_name: &CStr, case_value: &Scalar,
) {
unsafe {
match case_value {
Scalar::I64(value) => {
zend_enum_add_case_cstr(
class_ce,
case_name.as_ptr(),
ZVal::from(*value).as_mut_ptr(),
);
}
Scalar::String(value) => {
let value = ZString::new_persistent(value);
let mut value = ManuallyDrop::new(ZVal::from(value));
zend_enum_add_case_cstr(class_ce, case_name.as_ptr(), value.as_mut_ptr());
}
Scalar::Null => {
zend_enum_add_case_cstr(class_ce, case_name.as_ptr(), null_mut());
}
_ => unreachable!(),
};
}
}