PluginProbe
SQLite Database Integration / 2.2.0
SQLite Database Integration v2.2.0
3.0.2 3.0.1 trunk 2.1.13 2.1.14 2.1.15 2.1.16 2.2.0 2.2.1 2.2.10 2.2.11 2.2.12 2.2.13 2.2.14 2.2.15 2.2.16 2.2.17 2.2.18 2.2.19 2.2.2 2.2.20 2.2.21 2.2.22 2.2.23 2.2.3 All 32 releases
sqlite-database-integration / wp-includes / parser / class-wp-parser-node.php

class-wp-parser-node.php in SQLite Database Integration 2.2.0, at wp-includes/parser/class-wp-parser-node.php

292 lines 8.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * A node in parse tree.
5 *
6 * This class represents a node in the parse tree that is produced by WP_Parser.
7 * A node corresponds to the related grammar rule that was matched by the parser.
8 * Each node can contain children, consisting of other nodes and grammar tokens.
9 * In this way, a parser node constitutes a recursive structure that represents
10 * a parse (sub)tree at each level of the full grammar tree.
11 */
12 class WP_Parser_Node {
13 /**
14 * @TODO: Review and document these properties and their visibility.
15 */
16 public $rule_id;
17 public $rule_name;
18 private $children = array();
19
20 public function __construct( $rule_id, $rule_name ) {
21 $this->rule_id = $rule_id;
22 $this->rule_name = $rule_name;
23 }
24
25 public function append_child( $node ) {
26 $this->children[] = $node;
27 }
28
29 /**
30 * Flatten the matched rule fragments as if their children were direct
31 * descendants of the current rule.
32 *
33 * What are rule fragments?
34 *
35 * When we initially parse the grammar file, it has compound rules such
36 * as this one:
37 *
38 * query ::= EOF | ((simpleStatement | beginWork) ((SEMICOLON_SYMBOL EOF?) | EOF))
39 *
40 * Building a parser that can understand such rules is way more complex than building
41 * a parser that only follows simple rules, so we flatten those compound rules into
42 * simpler ones. The above rule would be flattened to:
43 *
44 * query ::= EOF | %query0
45 * %query0 ::= %%query01 %%query02
46 * %%query01 ::= simpleStatement | beginWork
47 * %%query02 ::= SEMICOLON_SYMBOL EOF_zero_or_one | EOF
48 * EOF_zero_or_one ::= EOF | ε
49 *
50 * This factorization happens in "convert-grammar.php".
51 *
52 * "Fragments" are intermediate artifacts whose names are not in the original grammar.
53 * They are extremely useful for the parser, but the API consumer should never have to
54 * worry about them. Fragment names start with a percent sign ("%").
55 *
56 * The code below inlines every fragment back in its parent rule.
57 *
58 * We could optimize this. The current $match may be discarded later on so any inlining
59 * effort here would be wasted. However, inlining seems cheap and doing it bottom-up here
60 * is **much** easier than reprocessing the parse tree top-down later on.
61 *
62 * The following parse tree:
63 *
64 * [
65 * 'query' => [
66 * [
67 * '%query01' => [
68 * [
69 * 'simpleStatement' => [
70 * MySQLToken(MySQLLexer::WITH_SYMBOL, 'WITH')
71 * ],
72 * '%query02' => [
73 * [
74 * 'simpleStatement' => [
75 * MySQLToken(MySQLLexer::WITH_SYMBOL, 'WITH')
76 * ]
77 * ],
78 * ]
79 * ]
80 * ]
81 * ]
82 * ]
83 *
84 * Would be inlined as:
85 *
86 * [
87 * 'query' => [
88 * [
89 * 'simpleStatement' => [
90 * MySQLToken(MySQLLexer::WITH_SYMBOL, 'WITH')
91 * ]
92 * ],
93 * [
94 * 'simpleStatement' => [
95 * MySQLToken(MySQLLexer::WITH_SYMBOL, 'WITH')
96 * ]
97 * ]
98 * ]
99 * ]
100 */
101 public function merge_fragment( $node ) {
102 $this->children = array_merge( $this->children, $node->children );
103 }
104
105 public function has_child(): bool {
106 return count( $this->children ) > 0;
107 }
108
109 public function has_child_node( ?string $rule_name = null ): bool {
110 foreach ( $this->children as $child ) {
111 if (
112 $child instanceof WP_Parser_Node
113 && ( null === $rule_name || $child->rule_name === $rule_name )
114 ) {
115 return true;
116 }
117 }
118 return false;
119 }
120
121 public function has_child_token( ?int $token_id = null ): bool {
122 foreach ( $this->children as $child ) {
123 if (
124 $child instanceof WP_Parser_Token
125 && ( null === $token_id || $child->id === $token_id )
126 ) {
127 return true;
128 }
129 }
130 return false;
131 }
132
133
134 public function get_first_child() {
135 return $this->children[0] ?? null;
136 }
137
138 public function get_first_child_node( ?string $rule_name = null ): ?WP_Parser_Node {
139 foreach ( $this->children as $child ) {
140 if (
141 $child instanceof WP_Parser_Node
142 && ( null === $rule_name || $child->rule_name === $rule_name )
143 ) {
144 return $child;
145 }
146 }
147 return null;
148 }
149
150 public function get_first_child_token( ?int $token_id = null ): ?WP_Parser_Token {
151 foreach ( $this->children as $child ) {
152 if (
153 $child instanceof WP_Parser_Token
154 && ( null === $token_id || $child->id === $token_id )
155 ) {
156 return $child;
157 }
158 }
159 return null;
160 }
161
162 public function get_first_descendant_node( ?string $rule_name = null ): ?WP_Parser_Node {
163 $nodes = array( $this );
164 while ( count( $nodes ) ) {
165 $node = array_shift( $nodes );
166 $child = $node->get_first_child_node( $rule_name );
167 if ( $child ) {
168 return $child;
169 }
170 $children = $node->get_child_nodes();
171 if ( count( $children ) > 0 ) {
172 array_push( $nodes, ...$children );
173 }
174 }
175 return null;
176 }
177
178 public function get_first_descendant_token( ?int $token_id = null ): ?WP_Parser_Token {
179 $nodes = array( $this );
180 while ( count( $nodes ) ) {
181 $node = array_shift( $nodes );
182 $child = $node->get_first_child_token( $token_id );
183 if ( $child ) {
184 return $child;
185 }
186 $children = $node->get_child_nodes();
187 if ( count( $children ) > 0 ) {
188 array_push( $nodes, ...$children );
189 }
190 }
191 return null;
192 }
193
194 public function get_children(): array {
195 return $this->children;
196 }
197
198 public function get_child_nodes( ?string $rule_name = null ): array {
199 $nodes = array();
200 foreach ( $this->children as $child ) {
201 if (
202 $child instanceof WP_Parser_Node
203 && ( null === $rule_name || $child->rule_name === $rule_name )
204 ) {
205 $nodes[] = $child;
206 }
207 }
208 return $nodes;
209 }
210
211 public function get_child_tokens( ?int $token_id = null ): array {
212 $tokens = array();
213 foreach ( $this->children as $child ) {
214 if (
215 $child instanceof WP_Parser_Token
216 && ( null === $token_id || $child->id === $token_id )
217 ) {
218 $tokens[] = $child;
219 }
220 }
221 return $tokens;
222 }
223
224 public function get_descendants(): array {
225 $nodes = array( $this );
226 $all_descendants = array();
227 while ( count( $nodes ) ) {
228 $node = array_shift( $nodes );
229 $all_descendants = array_merge( $all_descendants, $node->get_children() );
230 $children = $node->get_child_nodes();
231 if ( count( $children ) > 0 ) {
232 array_push( $nodes, ...$children );
233 }
234 }
235 return $all_descendants;
236 }
237
238 public function get_descendant_nodes( ?string $rule_name = null ): array {
239 $nodes = array( $this );
240 $all_descendants = array();
241 while ( count( $nodes ) ) {
242 $node = array_shift( $nodes );
243 $all_descendants = array_merge( $all_descendants, $node->get_child_nodes( $rule_name ) );
244 $children = $node->get_child_nodes();
245 if ( count( $children ) > 0 ) {
246 array_push( $nodes, ...$children );
247 }
248 }
249 return $all_descendants;
250 }
251
252 public function get_descendant_tokens( ?int $token_id = null ): array {
253 $nodes = array( $this );
254 $all_descendants = array();
255 while ( count( $nodes ) ) {
256 $node = array_shift( $nodes );
257 $all_descendants = array_merge( $all_descendants, $node->get_child_tokens( $token_id ) );
258 $children = $node->get_child_nodes();
259 if ( count( $children ) > 0 ) {
260 array_push( $nodes, ...$children );
261 }
262 }
263 return $all_descendants;
264 }
265
266 /**
267 * Get the byte offset in the input SQL string where this node begins.
268 *
269 * @return int
270 */
271 public function get_start(): int {
272 return $this->get_first_descendant_token()->start;
273 }
274
275 /**
276 * Get the byte length of this node in the input SQL string.
277 *
278 * @return int
279 */
280 public function get_length(): int {
281 $tokens = $this->get_descendant_tokens();
282 $last_token = end( $tokens );
283 $start = $this->get_start();
284 return $last_token->start + $last_token->length - $start;
285 }
286
287 /*
288 * @TODO: Let's implement a more powerful AST-querying API.
289 * See: https://github.com/WordPress/sqlite-database-integration/pull/164#discussion_r1855230501
290 */
291 }
292