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}