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, OPf_KIDS, PL_op_name, opcode, unop};
26
27// `OPclass` (and `Perl_op_class`) first appeared in perl 5.26.
28#[cfg(perlapi_ver26)]
29use libperl_sys::OPclass;
30
31use crate::{Cop, Perl};
32
33/// Non-null pointer to a Perl `OP`. Same ABI as `*mut OP`.
34///
35/// Like [`Sv`](crate::Sv), an `Op` does not own its referent —
36/// dropping it is a no-op. OP lifetimes follow their owning CV
37/// (`perl_destruct` / `op_free` invalidate them), which the type does
38/// not track; keep walks inside the scope where the CV is known live.
39#[derive(Clone, Copy)]
40#[repr(transparent)]
41pub struct Op(NonNull<OP>);
42
43impl Op {
44    /// Wrap a raw OP pointer without checking for null.
45    ///
46    /// # Safety
47    /// Caller must guarantee `p` is non-null and points to a valid OP
48    /// for at least the lifetime of the resulting `Op`.
49    #[inline]
50    pub unsafe fn from_raw_unchecked(p: *const OP) -> Self {
51        debug_assert!(!p.is_null(), "Op::from_raw_unchecked received a null pointer");
52        Op(unsafe { NonNull::new_unchecked(p as *mut OP) })
53    }
54
55    /// Wrap a raw OP pointer, returning `None` on null input. Takes
56    /// `*const OP` because that is what tree sources like
57    /// [`Cv::root`](crate::Cv::root) / [`Cv::start`](crate::Cv::start)
58    /// hand out.
59    #[inline]
60    pub fn from_raw(p: *const OP) -> Option<Self> {
61        NonNull::new(p as *mut OP).map(Op)
62    }
63
64    /// Raw pointer for FFI calls.
65    #[inline]
66    pub fn as_ptr(&self) -> *mut OP {
67        self.0.as_ptr()
68    }
69
70    /// The op's type as a plain integer. The underlying bitfield is
71    /// `u16` on modern Perl but `u32` on 5.30 and older — normalising
72    /// to `u32` here keeps callers version-portable.
73    #[inline]
74    pub fn op_type_raw(&self) -> u32 {
75        unsafe { (*self.0.as_ptr()).op_type() as u32 }
76    }
77
78    /// The op's type as the `opcode` enum, or `None` for
79    /// out-of-range values (custom ops).
80    #[inline]
81    pub fn opcode(&self) -> Option<opcode> {
82        opcode::try_from(self.op_type_raw()).ok()
83    }
84
85    /// The op's name (`"nextstate"`, `"add"`, ...) from the
86    /// `PL_op_name` table, or `None` for out-of-range op types.
87    pub fn name(&self) -> Option<&'static str> {
88        // Range-validate through the opcode enum first; indexing the
89        // static table with an arbitrary op_type would walk off the end
90        // for custom ops.
91        self.opcode()?;
92        let p = unsafe { PL_op_name[self.op_type_raw() as usize] };
93        unsafe { CStr::from_ptr(p) }.to_str().ok()
94    }
95
96    /// `op_flags` (`OPf_KIDS` and friends).
97    #[inline]
98    pub fn flags(&self) -> u8 {
99        unsafe { (*self.0.as_ptr()).op_flags }
100    }
101
102    /// Next op in execution order (`op_next`), or `None` at the end
103    /// of the chain.
104    #[inline]
105    pub fn next(&self) -> Option<Op> {
106        Op::from_raw(unsafe { (*self.0.as_ptr()).op_next })
107    }
108
109    /// Next sibling in tree order (official `OpSIBLING`: the
110    /// `op_moresib` check terminates at the parent back-pointer on
111    /// 5.26+ layouts), or `None` for the last sibling.
112    #[inline]
113    pub fn sibling(&self) -> Option<Op> {
114        Op::from_raw(unsafe { libperl_sys::OpSIBLING(self.0.as_ptr()) })
115    }
116
117    /// First child (`cUNOPx(o)->op_first` equivalent), or `None` when
118    /// the op has no kids (`OPf_KIDS` unset). No public C macro exists
119    /// for this — the struct read matches what `B` does.
120    #[inline]
121    pub fn first(&self) -> Option<Op> {
122        if (self.flags() as u32 & OPf_KIDS) == 0 {
123            None
124        } else {
125            Op::from_raw(unsafe { (*(self.0.as_ptr() as *const unop)).op_first })
126        }
127    }
128
129    /// Iterate this op's children in tree order (first child, then
130    /// its siblings). Empty for kid-less ops.
131    #[inline]
132    pub fn kids(&self) -> OpSiblingIter {
133        OpSiblingIter { cur: self.first() }
134    }
135
136    /// Iterate in execution order starting from (and including) this
137    /// op, following `op_next` until null.
138    ///
139    /// Note: the static `op_next` chain of a finished sub is not
140    /// acyclic — loop constructs point back to their condition — so an
141    /// unbounded walk over arbitrary code may not terminate. Cap with
142    /// `.take(n)` unless the code is known to be straight-line.
143    #[inline]
144    pub fn next_iter(&self) -> OpNextIter {
145        OpNextIter { cur: Some(*self) }
146    }
147
148    /// The op's class (`Perl_op_class`, the same classification `B`
149    /// exposes as `B::class`).
150    ///
151    /// Only on perl 5.26+ — `OPclass` and `op_class()` were both born
152    /// there. Version-portable callers that only need COP detection
153    /// should use [`Op::as_cop`] instead, which works on every
154    /// supported perl.
155    #[cfg(perlapi_ver26)]
156    #[inline]
157    pub fn class(&self, perl: &Perl) -> OPclass {
158        unsafe { crate::thx_call!(perl, Perl_op_class, self.0.as_ptr()) }
159    }
160
161    /// Whether this op is a COP (`nextstate` / `dbstate`), including
162    /// an optimized-away ex-COP (`OP_NULL` whose `op_targ` records the
163    /// original type — the same mapping `op_class` applies).
164    fn is_cop(&self, perl: &Perl) -> bool {
165        #[cfg(perlapi_ver26)]
166        {
167            self.class(perl) == OPclass::OPclass_COP
168        }
169        #[cfg(not(perlapi_ver26))]
170        {
171            let _ = perl;
172            let mut t = self.op_type_raw();
173            if t == opcode::OP_NULL as u32 {
174                t = unsafe { (*self.0.as_ptr()).op_targ } as u32;
175            }
176            t == opcode::OP_NEXTSTATE as u32 || t == opcode::OP_DBSTATE as u32
177        }
178    }
179
180    /// View this op as a [`Cop`] when it is one (`nextstate` /
181    /// `dbstate`), giving access to its file / line.
182    #[inline]
183    pub fn as_cop(&self, perl: &Perl) -> Option<Cop> {
184        if self.is_cop(perl) {
185            Cop::from_raw(self.0.as_ptr() as *const libperl_sys::COP)
186        } else {
187            None
188        }
189    }
190}
191
192/// Execution-order iterator (`op_next` chain), yielded by
193/// [`Op::next_iter`]. See the cycle caveat there.
194pub struct OpNextIter {
195    cur: Option<Op>,
196}
197
198impl Iterator for OpNextIter {
199    type Item = Op;
200
201    fn next(&mut self) -> Option<Op> {
202        let op = self.cur?;
203        self.cur = op.next();
204        Some(op)
205    }
206}
207
208/// Tree-order sibling iterator (`OpSIBLING` chain), yielded by
209/// [`Op::kids`].
210pub struct OpSiblingIter {
211    cur: Option<Op>,
212}
213
214impl Iterator for OpSiblingIter {
215    type Item = Op;
216
217    fn next(&mut self) -> Option<Op> {
218        let op = self.cur?;
219        self.cur = op.sibling();
220        Some(op)
221    }
222}