88 * caller actually walks the tree. On the first mutation (append_child or
99 * merge_fragment), the node materializes its children into the inherited
1010 * `$children` array and behaves like a plain WP_Parser_Node from then on.
11- *
12- * Wrappers returned by accessors are interned through a per-AST identity
13- * map (WP_MySQL_Native_AST_Cache) so two reads of the same logical node
14- * yield the same PHP instance. This preserves the WP_Parser_Node contract
15- * that mutations performed on a child via `get_first_child_node()` remain
16- * visible when the same child is reached again, including after the parent
17- * has materialized.
1811 */
1912class WP_MySQL_Native_Parser_Node extends WP_Parser_Node {
2013 private $ native_ast = null ;
2114 private $ native_node_index = null ;
2215 private $ was_mutated = false ;
2316
24- /**
25- * Per-AST identity map shared between every interned wrapper.
26- *
27- * Created lazily on the first child access; the root wrapper is the
28- * first entry. Children inherit the same cache instance by reference.
29- *
30- * @var WP_MySQL_Native_AST_Cache|null
31- */
32- private $ cache = null ;
33-
3417 public function __construct ( $ rule_id , $ rule_name , $ native_ast = null , $ native_node_index = null ) {
3518 parent ::__construct ( $ rule_id , $ rule_name );
3619
3720 $ this ->native_ast = $ native_ast ;
3821 $ this ->native_node_index = $ native_node_index ;
3922 }
4023
41- /**
42- * Native node index in the Rust-owned arena.
43- *
44- * Exposed so the identity cache can key on it. Returns null after
45- * the wrapper has materialized — at that point the node is detached
46- * from the native arena and behaves like a plain WP_Parser_Node.
47- *
48- * @return int|null
49- */
50- public function get_native_node_index (): ?int {
51- return $ this ->native_node_index ;
52- }
53-
5424 /** @inheritDoc */
5525 public function append_child ( $ node ) {
5626 $ this ->materialize_native_children ();
@@ -95,15 +65,15 @@ public function get_first_child() {
9565 if ( $ this ->was_mutated () ) {
9666 return parent ::get_first_child ();
9767 }
98- return $ this -> intern ( wp_sqlite_mysql_native_ast_get_first_child ( $ this ->native_ast , $ this ->native_node_index ) );
68+ return wp_sqlite_mysql_native_ast_get_first_child ( $ this ->native_ast , $ this ->native_node_index );
9969 }
10070
10171 /** @inheritDoc */
10272 public function get_first_child_node ( ?string $ rule_name = null ): ?WP_Parser_Node {
10373 if ( $ this ->was_mutated () ) {
10474 return parent ::get_first_child_node ( $ rule_name );
10575 }
106- return $ this -> intern ( wp_sqlite_mysql_native_ast_get_first_child_node ( $ this ->native_ast , $ this ->native_node_index , $ rule_name ) );
76+ return wp_sqlite_mysql_native_ast_get_first_child_node ( $ this ->native_ast , $ this ->native_node_index , $ rule_name );
10777 }
10878
10979 /** @inheritDoc */
@@ -119,7 +89,7 @@ public function get_first_descendant_node( ?string $rule_name = null ): ?WP_Pars
11989 if ( $ this ->was_mutated () ) {
12090 return parent ::get_first_descendant_node ( $ rule_name );
12191 }
122- return $ this -> intern ( wp_sqlite_mysql_native_ast_get_first_descendant_node ( $ this ->native_ast , $ this ->native_node_index , $ rule_name ) );
92+ return wp_sqlite_mysql_native_ast_get_first_descendant_node ( $ this ->native_ast , $ this ->native_node_index , $ rule_name );
12393 }
12494
12595 /** @inheritDoc */
@@ -135,15 +105,15 @@ public function get_children(): array {
135105 if ( $ this ->was_mutated () ) {
136106 return parent ::get_children ();
137107 }
138- return $ this -> intern_all ( wp_sqlite_mysql_native_ast_get_children ( $ this ->native_ast , $ this ->native_node_index ) );
108+ return wp_sqlite_mysql_native_ast_get_children ( $ this ->native_ast , $ this ->native_node_index );
139109 }
140110
141111 /** @inheritDoc */
142112 public function get_child_nodes ( ?string $ rule_name = null ): array {
143113 if ( $ this ->was_mutated () ) {
144114 return parent ::get_child_nodes ( $ rule_name );
145115 }
146- return $ this -> intern_nodes ( wp_sqlite_mysql_native_ast_get_child_nodes ( $ this ->native_ast , $ this ->native_node_index , $ rule_name ) );
116+ return wp_sqlite_mysql_native_ast_get_child_nodes ( $ this ->native_ast , $ this ->native_node_index , $ rule_name );
147117 }
148118
149119 /** @inheritDoc */
@@ -159,15 +129,15 @@ public function get_descendants(): array {
159129 if ( $ this ->was_mutated () ) {
160130 return parent ::get_descendants ();
161131 }
162- return $ this -> intern_all ( wp_sqlite_mysql_native_ast_get_descendants ( $ this ->native_ast , $ this ->native_node_index ) );
132+ return wp_sqlite_mysql_native_ast_get_descendants ( $ this ->native_ast , $ this ->native_node_index );
163133 }
164134
165135 /** @inheritDoc */
166136 public function get_descendant_nodes ( ?string $ rule_name = null ): array {
167137 if ( $ this ->was_mutated () ) {
168138 return parent ::get_descendant_nodes ( $ rule_name );
169139 }
170- return $ this -> intern_nodes ( wp_sqlite_mysql_native_ast_get_descendant_nodes ( $ this ->native_ast , $ this ->native_node_index , $ rule_name ) );
140+ return wp_sqlite_mysql_native_ast_get_descendant_nodes ( $ this ->native_ast , $ this ->native_node_index , $ rule_name );
171141 }
172142
173143 /** @inheritDoc */
@@ -198,128 +168,12 @@ private function was_mutated(): bool {
198168 return $ this ->was_mutated ;
199169 }
200170
201- /**
202- * Intern a single accessor return value through the per-AST cache.
203- *
204- * Tokens and nulls pass through untouched. Native node wrappers are
205- * keyed on their `native_node_index`: on cache miss, the freshly
206- * constructed wrapper is stored and given the cache reference; on
207- * cache hit, the canonical instance is returned and the new wrapper
208- * is discarded so callers see stable identity and surviving mutations.
209- *
210- * @param mixed $value Return value from the Rust bridge.
211- * @return mixed
212- */
213- private function intern ( $ value ) {
214- if ( ! $ value instanceof WP_MySQL_Native_Parser_Node ) {
215- return $ value ;
216- }
217-
218- $ cache = $ this ->ensure_cache ();
219- $ index = $ value ->native_node_index ;
220- if ( null === $ index ) {
221- return $ value ;
222- }
223- if ( isset ( $ cache ->nodes [ $ index ] ) ) {
224- return $ cache ->nodes [ $ index ];
225- }
226- $ value ->cache = $ cache ;
227- $ cache ->nodes [ $ index ] = $ value ;
228- return $ value ;
229- }
230-
231- /**
232- * Intern every entry in an accessor return array.
233- *
234- * Hot path: this runs once per descendant when a caller walks the tree,
235- * so cache lookup and the cache-miss write are inlined and the cache
236- * reference is hoisted out of the loop.
237- *
238- * @param array $values
239- * @return array
240- */
241- private function intern_all ( array $ values ): array {
242- if ( ! $ values ) {
243- return $ values ;
244- }
245- $ cache = $ this ->cache ?? $ this ->ensure_cache ();
246- $ nodes = &$ cache ->nodes ;
247- foreach ( $ values as $ i => $ value ) {
248- if ( ! $ value instanceof WP_MySQL_Native_Parser_Node ) {
249- continue ;
250- }
251- $ index = $ value ->native_node_index ;
252- if ( null === $ index ) {
253- continue ;
254- }
255- if ( isset ( $ nodes [ $ index ] ) ) {
256- $ values [ $ i ] = $ nodes [ $ index ];
257- } else {
258- $ value ->cache = $ cache ;
259- $ nodes [ $ index ] = $ value ;
260- }
261- }
262- return $ values ;
263- }
264-
265- /**
266- * Intern array of guaranteed-node results (no token/null mixing).
267- *
268- * Used by `get_child_nodes()` / `get_descendant_nodes()` whose Rust
269- * bridge returns only WP_MySQL_Native_Parser_Node instances. Skips
270- * the per-item `instanceof` check that intern_all() must do for the
271- * mixed `get_children()` / `get_descendants()` arrays.
272- *
273- * @param array $values
274- * @return array
275- */
276- private function intern_nodes ( array $ values ): array {
277- if ( ! $ values ) {
278- return $ values ;
279- }
280- $ cache = $ this ->cache ?? $ this ->ensure_cache ();
281- $ nodes = &$ cache ->nodes ;
282- foreach ( $ values as $ i => $ value ) {
283- $ index = $ value ->native_node_index ;
284- if ( isset ( $ nodes [ $ index ] ) ) {
285- $ values [ $ i ] = $ nodes [ $ index ];
286- } else {
287- $ value ->cache = $ cache ;
288- $ nodes [ $ index ] = $ value ;
289- }
290- }
291- return $ values ;
292- }
293-
294- /**
295- * Lazily build (or reuse) the per-AST identity map.
296- *
297- * The root wrapper is constructed without a cache, so the first time
298- * any accessor needs to intern a child, it creates the cache and
299- * registers itself as the root entry. Subsequent interns on this
300- * wrapper or any descendant share the same cache by reference.
301- *
302- * @return WP_MySQL_Native_AST_Cache
303- */
304- private function ensure_cache (): WP_MySQL_Native_AST_Cache {
305- if ( null === $ this ->cache ) {
306- $ this ->cache = new WP_MySQL_Native_AST_Cache ();
307- if ( null !== $ this ->native_node_index ) {
308- $ this ->cache ->nodes [ $ this ->native_node_index ] = $ this ;
309- }
310- }
311- return $ this ->cache ;
312- }
313-
314171 private function materialize_native_children (): void {
315172 if ( $ this ->was_mutated ) {
316173 return ;
317174 }
318175
319- // Pull children through the cache so any wrapper a caller already
320- // mutated via get_first_child_node() etc. survives the transition
321- // into $this->children — same instance, same mutations.
322- $ this ->children = $ this ->intern_all ( wp_sqlite_mysql_native_ast_get_children ( $ this ->native_ast , $ this ->native_node_index ) );
176+ $ this ->children = wp_sqlite_mysql_native_ast_get_children ( $ this ->native_ast , $ this ->native_node_index );
323177 $ this ->native_ast = null ;
324178 $ this ->native_node_index = null ;
325179 $ this ->was_mutated = true ;
0 commit comments