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}