Skip to main content

libperl_rs/
op.rs

1//! `Op` newtype — a non-null handle to a node of a Perl OP tree, plus
2//! the two iterators every OP-tree walker needs: execution order
3//! ([`OpNextIter`], the `op_next` chain) and tree order
4//! ([`OpSiblingIter`], the `OpSIBLING` chain).
5//!
6//! This is the OP-tree slice of "Step 2" in `docs/plan/README.md`,
7//! extracted from the raw layers that grew downstream
8//! (perl-optree-analyzer `analyzer-capture/src/raw.rs` and
9//! perl-LibPerlRs-PartialEval `partial-eval-engine/src/raw.rs`).
10//!
11//! Accessors delegate to macrogen-emitted official API where one
12//! exists (`OpSIBLING`); the remaining hand-written struct reads are
13//! the ones with no public C macro, same set as the `B` module uses:
14//!
15//! - `op_next`: direct member read (`B` does the same),
16//! - `first()`: `cUNOPx(o)->op_first` equivalent behind an
17//!   `OPf_KIDS` guard,
18//! - `name()`: `OP_NAME` is on the macrogen skip list
19//!   (`libperl-sys/skip-codegen.txt`), so it reads the `PL_op_name`
20//!   table instead — again the `B` way.
21
22use std::ffi::CStr;
23use std::ptr::NonNull;
24
25use libperl_sys::{OP, OPclass, OPf_KIDS, PL_op_name, opcode, unop};
26
27use crate::{Cop, Perl};
28
29/// Non-null pointer to a Perl `OP`. Same ABI as `*mut OP`.
30///
31/// Like [`Sv`](crate::Sv), an `Op` does not own its referent —
32/// dropping it is a no-op. OP lifetimes follow their owning CV
33/// (`perl_destruct` / `op_free` invalidate them), which the type does
34/// not track; keep walks inside the scope where the CV is known live.
35#[derive(Clone, Copy)]
36#[repr(transparent)]
37pub struct Op(NonNull<OP>);
38
39impl Op {
40    /// Wrap a raw OP pointer without checking for null.
41    ///
42    /// # Safety
43    /// Caller must guarantee `p` is non-null and points to a valid OP
44    /// for at least the lifetime of the resulting `Op`.
45    #[inline]
46    pub unsafe fn from_raw_unchecked(p: *const OP) -> Self {
47        debug_assert!(!p.is_null(), "Op::from_raw_unchecked received a null pointer");
48        Op(unsafe { NonNull::new_unchecked(p as *mut OP) })
49    }
50
51    /// Wrap a raw OP pointer, returning `None` on null input. Takes
52    /// `*const OP` because that is what tree sources like
53    /// [`Cv::root`](crate::Cv::root) / [`Cv::start`](crate::Cv::start)
54    /// hand out.
55    #[inline]
56    pub fn from_raw(p: *const OP) -> Option<Self> {
57        NonNull::new(p as *mut OP).map(Op)
58    }
59
60    /// Raw pointer for FFI calls.
61    #[inline]
62    pub fn as_ptr(&self) -> *mut OP {
63        self.0.as_ptr()
64    }
65
66    /// The op's type as a plain integer. The underlying bitfield is
67    /// `u16` on modern Perl but `u32` on 5.30 and older — normalising
68    /// to `u32` here keeps callers version-portable.
69    #[inline]
70    pub fn op_type_raw(&self) -> u32 {
71        unsafe { (*self.0.as_ptr()).op_type() as u32 }
72    }
73
74    /// The op's type as the `opcode` enum, or `None` for
75    /// out-of-range values (custom ops).
76    #[inline]
77    pub fn opcode(&self) -> Option<opcode> {
78        opcode::try_from(self.op_type_raw()).ok()
79    }
80
81    /// The op's name (`"nextstate"`, `"add"`, ...) from the
82    /// `PL_op_name` table, or `None` for out-of-range op types.
83    pub fn name(&self) -> Option<&'static str> {
84        // Range-validate through the opcode enum first; indexing the
85        // static table with an arbitrary op_type would walk off the end
86        // for custom ops.
87        self.opcode()?;
88        let p = unsafe { PL_op_name[self.op_type_raw() as usize] };
89        unsafe { CStr::from_ptr(p) }.to_str().ok()
90    }
91
92    /// `op_flags` (`OPf_KIDS` and friends).
93    #[inline]
94    pub fn flags(&self) -> u8 {
95        unsafe { (*self.0.as_ptr()).op_flags }
96    }
97
98    /// Next op in execution order (`op_next`), or `None` at the end
99    /// of the chain.
100    #[inline]
101    pub fn next(&self) -> Option<Op> {
102        Op::from_raw(unsafe { (*self.0.as_ptr()).op_next })
103    }
104
105    /// Next sibling in tree order (official `OpSIBLING`: the
106    /// `op_moresib` check terminates at the parent back-pointer on
107    /// 5.26+ layouts), or `None` for the last sibling.
108    #[inline]
109    pub fn sibling(&self) -> Option<Op> {
110        Op::from_raw(unsafe { libperl_sys::OpSIBLING(self.0.as_ptr()) })
111    }
112
113    /// First child (`cUNOPx(o)->op_first` equivalent), or `None` when
114    /// the op has no kids (`OPf_KIDS` unset). No public C macro exists
115    /// for this — the struct read matches what `B` does.
116    #[inline]
117    pub fn first(&self) -> Option<Op> {
118        if (self.flags() as u32 & OPf_KIDS) == 0 {
119            None
120        } else {
121            Op::from_raw(unsafe { (*(self.0.as_ptr() as *const unop)).op_first })
122        }
123    }
124
125    /// Iterate this op's children in tree order (first child, then
126    /// its siblings). Empty for kid-less ops.
127    #[inline]
128    pub fn kids(&self) -> OpSiblingIter {
129        OpSiblingIter { cur: self.first() }
130    }
131
132    /// Iterate in execution order starting from (and including) this
133    /// op, following `op_next` until null.
134    ///
135    /// Note: the static `op_next` chain of a finished sub is not
136    /// acyclic — loop constructs point back to their condition — so an
137    /// unbounded walk over arbitrary code may not terminate. Cap with
138    /// `.take(n)` unless the code is known to be straight-line.
139    #[inline]
140    pub fn next_iter(&self) -> OpNextIter {
141        OpNextIter { cur: Some(*self) }
142    }
143
144    /// The op's class (`Perl_op_class`, the same classification `B`
145    /// exposes as `B::class`).
146    #[inline]
147    pub fn class(&self, perl: &Perl) -> OPclass {
148        unsafe { crate::thx_call!(perl, Perl_op_class, self.0.as_ptr()) }
149    }
150
151    /// View this op as a [`Cop`] when it is one (`nextstate` /
152    /// `dbstate`), giving access to its file / line.
153    #[inline]
154    pub fn as_cop(&self, perl: &Perl) -> Option<Cop> {
155        if self.class(perl) == OPclass::OPclass_COP {
156            Cop::from_raw(self.0.as_ptr() as *const libperl_sys::COP)
157        } else {
158            None
159        }
160    }
161}
162
163/// Execution-order iterator (`op_next` chain), yielded by
164/// [`Op::next_iter`]. See the cycle caveat there.
165pub struct OpNextIter {
166    cur: Option<Op>,
167}
168
169impl Iterator for OpNextIter {
170    type Item = Op;
171
172    fn next(&mut self) -> Option<Op> {
173        let op = self.cur?;
174        self.cur = op.next();
175        Some(op)
176    }
177}
178
179/// Tree-order sibling iterator (`OpSIBLING` chain), yielded by
180/// [`Op::kids`].
181pub struct OpSiblingIter {
182    cur: Option<Op>,
183}
184
185impl Iterator for OpSiblingIter {
186    type Item = Op;
187
188    fn next(&mut self) -> Option<Op> {
189        let op = self.cur?;
190        self.cur = op.sibling();
191        Some(op)
192    }
193}