use std::ptr::NonNull;
use libperl_sys::{CV, OP, PADLIST, SV, svtype};
use crate::{Cop, Gv, Op, PadNames, Perl};
#[derive(Clone, Copy)]
#[repr(transparent)]
pub struct Cv(NonNull<CV>);
impl Cv {
#[inline]
pub unsafe fn from_raw_unchecked(p: *mut CV) -> Self {
debug_assert!(!p.is_null(), "Cv::from_raw_unchecked received a null pointer");
Cv(unsafe { NonNull::new_unchecked(p) })
}
#[inline]
pub fn from_raw(p: *mut CV) -> Option<Self> {
NonNull::new(p).map(Cv)
}
#[inline]
pub fn from_coderef(sv: *mut SV) -> Option<Cv> {
if sv.is_null() || unsafe { libperl_sys::SvROK(sv) } == 0 {
return None;
}
let target = unsafe { libperl_sys::SvRV(sv) };
if unsafe { libperl_sys::SvTYPE(target) } != svtype::SVt_PVCV {
return None;
}
Some(unsafe { Cv::from_raw_unchecked(target as *mut CV) })
}
#[inline]
pub fn as_ptr(&self) -> *mut CV {
self.0.as_ptr()
}
#[inline]
pub fn is_xsub(&self) -> bool {
unsafe { libperl_sys::CvISXSUB(self.as_ptr() as *const _) != 0 }
}
#[inline]
pub fn root(&self) -> *const OP {
if self.is_xsub() {
std::ptr::null()
} else {
unsafe { libperl_sys::CvROOT(self.as_ptr() as *const _) }
}
}
#[inline]
pub fn start(&self) -> *const OP {
if self.is_xsub() {
std::ptr::null()
} else {
unsafe { libperl_sys::CvSTART(self.as_ptr() as *const _) }
}
}
#[inline]
pub fn padlist(&self) -> *const PADLIST {
if self.is_xsub() {
std::ptr::null()
} else {
unsafe { libperl_sys::CvPADLIST(self.as_ptr() as *const _) }
}
}
pub fn file(&self) -> Option<String> {
let p = unsafe { libperl_sys::CvFILE(self.as_ptr() as *const _) };
if p.is_null() {
None
} else {
Some(
unsafe { std::ffi::CStr::from_ptr(p) }
.to_string_lossy()
.into_owned(),
)
}
}
#[inline]
pub fn root_op(&self) -> Option<Op> {
Op::from_raw(self.root())
}
#[inline]
pub fn start_op(&self) -> Option<Op> {
Op::from_raw(self.start())
}
#[inline]
pub fn gv(&self, perl: &Perl) -> Option<Gv> {
let gv = unsafe { libperl_sys::thx::CvGV(perl.as_ptr(), self.as_ptr() as *const _) };
Gv::from_raw(gv)
}
pub fn names(&self, perl: &Perl) -> Option<(String, String)> {
let gv = self.gv(perl)?;
let name = gv.name()?;
let full = match gv.stash_name() {
Some(pkg) => format!("{pkg}::{name}"),
None => name.clone(),
};
Some((full, name))
}
pub fn first_cop(&self, perl: &Perl) -> Option<Cop> {
let mut stack: Vec<Op> = self.root_op().into_iter().collect();
while let Some(op) = stack.pop() {
if let Some(cop) = op.as_cop(perl) {
return Some(cop);
}
if let Some(sib) = op.sibling() {
stack.push(sib);
}
if let Some(kid) = op.first() {
stack.push(kid);
}
}
None
}
#[inline]
pub fn pad_names(&self) -> PadNames {
PadNames::from_padlist(self.padlist())
}
pub fn proto(&self) -> Option<String> {
let sv = self.as_ptr() as *mut SV;
unsafe {
if libperl_sys::SvPOK(sv) == 0 {
return None;
}
let pv = libperl_sys::SvPVX_const(sv);
if pv.is_null() {
return None;
}
let len = libperl_sys::SvCUR(sv);
let bytes = std::slice::from_raw_parts(pv as *const u8, len as usize);
Some(String::from_utf8_lossy(bytes).into_owned())
}
}
}