Files
hub-insurance/wp-content/mu-plugins/sqlite-database-integration/wp-includes/database/parser/class-wp-parser-node.php
T
2026-07-02 15:54:39 -06:00

385 lines
12 KiB
PHP

<?php
/**
* A node in parse tree.
*
* This class represents a node in the parse tree that is produced by WP_Parser.
* A node corresponds to the related grammar rule that was matched by the parser.
* Each node can contain children, consisting of other nodes and grammar tokens.
* In this way, a parser node constitutes a recursive structure that represents
* a parse (sub)tree at each level of the full grammar tree.
*/
class WP_Parser_Node {
/**
* @TODO: Review and document these properties and their visibility.
*/
public $rule_id;
public $rule_name;
protected $children = array();
public function __construct( $rule_id, $rule_name ) {
$this->rule_id = $rule_id;
$this->rule_name = $rule_name;
}
public function append_child( $node ) {
$this->children[] = $node;
}
/**
* Flatten the matched rule fragments as if their children were direct
* descendants of the current rule.
*
* What are rule fragments?
*
* When we initially parse the grammar file, it has compound rules such
* as this one:
*
* query ::= EOF | ((simpleStatement | beginWork) ((SEMICOLON_SYMBOL EOF?) | EOF))
*
* Building a parser that can understand such rules is way more complex than building
* a parser that only follows simple rules, so we flatten those compound rules into
* simpler ones. The above rule would be flattened to:
*
* query ::= EOF | %query0
* %query0 ::= %%query01 %%query02
* %%query01 ::= simpleStatement | beginWork
* %%query02 ::= SEMICOLON_SYMBOL EOF_zero_or_one | EOF
* EOF_zero_or_one ::= EOF | ε
*
* This factorization happens in "convert-grammar.php".
*
* "Fragments" are intermediate artifacts whose names are not in the original grammar.
* They are extremely useful for the parser, but the API consumer should never have to
* worry about them. Fragment names start with a percent sign ("%").
*
* The code below inlines every fragment back in its parent rule.
*
* We could optimize this. The current $match may be discarded later on so any inlining
* effort here would be wasted. However, inlining seems cheap and doing it bottom-up here
* is **much** easier than reprocessing the parse tree top-down later on.
*
* The following parse tree:
*
* [
* 'query' => [
* [
* '%query01' => [
* [
* 'simpleStatement' => [
* MySQLToken(MySQLLexer::WITH_SYMBOL, 'WITH')
* ],
* '%query02' => [
* [
* 'simpleStatement' => [
* MySQLToken(MySQLLexer::WITH_SYMBOL, 'WITH')
* ]
* ],
* ]
* ]
* ]
* ]
* ]
*
* Would be inlined as:
*
* [
* 'query' => [
* [
* 'simpleStatement' => [
* MySQLToken(MySQLLexer::WITH_SYMBOL, 'WITH')
* ]
* ],
* [
* 'simpleStatement' => [
* MySQLToken(MySQLLexer::WITH_SYMBOL, 'WITH')
* ]
* ]
* ]
* ]
*/
public function merge_fragment( $node ) {
$this->children = array_merge( $this->children, $node->children );
}
/**
* Check if this node has any child nodes or tokens.
*
* @return bool True if this node has any child nodes or tokens, false otherwise.
*/
public function has_child(): bool {
return count( $this->children ) > 0;
}
/**
* Check if this node has any child nodes.
*
* @param string|null $rule_name Optional. A node rule name to check for.
* @return bool True if any child nodes are found, false otherwise.
*/
public function has_child_node( ?string $rule_name = null ): bool {
foreach ( $this->children as $child ) {
if (
$child instanceof WP_Parser_Node
&& ( null === $rule_name || $child->rule_name === $rule_name )
) {
return true;
}
}
return false;
}
/**
* Check if this node has any child tokens.
*
* @param int|null $token_id Optional. A token ID to check for.
* @return bool True if any child tokens are found, false otherwise.
*/
public function has_child_token( ?int $token_id = null ): bool {
foreach ( $this->children as $child ) {
if (
$child instanceof WP_Parser_Token
&& ( null === $token_id || $child->id === $token_id )
) {
return true;
}
}
return false;
}
/**
* Get the first child node or token of this node.
*
* @return WP_Parser_Node|WP_Parser_Token|null The first child node or token;
* null when no children are found.
*/
public function get_first_child() {
return $this->children[0] ?? null;
}
/**
* Get the first child node of this node.
*
* @param string|null $rule_name Optional. A node rule name to check for.
* @return WP_Parser_Node|null The first matching child node; null when no children are found.
*/
public function get_first_child_node( ?string $rule_name = null ): ?WP_Parser_Node {
foreach ( $this->children as $child ) {
if (
$child instanceof WP_Parser_Node
&& ( null === $rule_name || $child->rule_name === $rule_name )
) {
return $child;
}
}
return null;
}
/**
* Get the first child token of this node.
*
* @param int|null $token_id Optional. A token ID to check for.
* @return WP_Parser_Token|null The first matching child token; null when no children are found.
*/
public function get_first_child_token( ?int $token_id = null ): ?WP_Parser_Token {
foreach ( $this->children as $child ) {
if (
$child instanceof WP_Parser_Token
&& ( null === $token_id || $child->id === $token_id )
) {
return $child;
}
}
return null;
}
/**
* Get the first descendant node of this node.
*
* The node children are traversed recursively in a depth-first order until
* a matching descendant node is found, or the entire subtree is searched.
*
* @param string|null $rule_name Optional. A node rule name to check for.
* @return WP_Parser_Node|null The first matching descendant node; null when no descendants are found.
*/
public function get_first_descendant_node( ?string $rule_name = null ): ?WP_Parser_Node {
for ( $i = 0; $i < count( $this->children ); $i++ ) {
$child = $this->children[ $i ];
if ( ! $child instanceof WP_Parser_Node ) {
continue;
}
if ( null === $rule_name || $child->rule_name === $rule_name ) {
return $child;
}
$node = $child->get_first_descendant_node( $rule_name );
if ( $node ) {
return $node;
}
}
return null;
}
/**
* Get the first descendant token of this node.
*
* The node children are traversed recursively in a depth-first order until
* a matching descendant token is found, or the entire subtree is searched.
*
* @param int|null $token_id Optional. A token ID to check for.
* @return WP_Parser_Token|null The first matching descendant token; null when no descendants are found.
*/
public function get_first_descendant_token( ?int $token_id = null ): ?WP_Parser_Token {
for ( $i = 0; $i < count( $this->children ); $i++ ) {
$child = $this->children[ $i ];
if ( $child instanceof WP_Parser_Token ) {
if ( null === $token_id || $child->id === $token_id ) {
return $child;
}
} else {
$token = $child->get_first_descendant_token( $token_id );
if ( $token ) {
return $token;
}
}
}
return null;
}
/**
* Get all children of this node.
*
* @return array<WP_Parser_Node|WP_Parser_Token> An array of all child nodes and tokens of this node.
*/
public function get_children(): array {
return $this->children;
}
/**
* Get all child nodes of this node.
*
* @param string|null $rule_name Optional. A node rule name to check for.
* @return WP_Parser_Node[] An array of all matching child nodes.
*/
public function get_child_nodes( ?string $rule_name = null ): array {
$nodes = array();
foreach ( $this->children as $child ) {
if (
$child instanceof WP_Parser_Node
&& ( null === $rule_name || $child->rule_name === $rule_name )
) {
$nodes[] = $child;
}
}
return $nodes;
}
/**
* Get all child tokens of this node.
*
* @param int|null $token_id Optional. A token ID to check for.
* @return WP_Parser_Token[] An array of all matching child tokens.
*/
public function get_child_tokens( ?int $token_id = null ): array {
$tokens = array();
foreach ( $this->children as $child ) {
if (
$child instanceof WP_Parser_Token
&& ( null === $token_id || $child->id === $token_id )
) {
$tokens[] = $child;
}
}
return $tokens;
}
/**
* Get all descendants of this node.
*
* The descendants are collected using a depth-first pre-order NLR traversal.
* This produces a natural ordering that corresponds to the original input.
*
* @return array<WP_Parser_Node|WP_Parser_Token> An array of all descendant nodes and tokens of this node.
*/
public function get_descendants(): array {
$descendants = array();
foreach ( $this->children as $child ) {
if ( $child instanceof WP_Parser_Node ) {
$descendants[] = $child;
$descendants = array_merge( $descendants, $child->get_descendants() );
} else {
$descendants[] = $child;
}
}
return $descendants;
}
/**
* Get all descendant nodes of this node.
*
* The descendants are collected using a depth-first pre-order NLR traversal.
* This produces a natural ordering that corresponds to the original input.
* All matching nodes are collected during the traversal.
*
* @param string|null $rule_name Optional. A node rule name to check for.
* @return WP_Parser_Node[] An array of all matching descendant nodes.
*/
public function get_descendant_nodes( ?string $rule_name = null ): array {
$nodes = array();
foreach ( $this->children as $child ) {
if ( ! $child instanceof WP_Parser_Node ) {
continue;
}
if ( null === $rule_name || $child->rule_name === $rule_name ) {
$nodes[] = $child;
}
$nodes = array_merge( $nodes, $child->get_descendant_nodes( $rule_name ) );
}
return $nodes;
}
/**
* Get all descendant tokens of this node.
*
* The descendants are collected using a depth-first pre-order NLR traversal.
* This produces a natural ordering that corresponds to the original input.
* All matching tokens are collected during the traversal.
*
* @param int|null $token_id Optional. A token ID to check for.
* @return WP_Parser_Token[] An array of all matching descendant tokens.
*/
public function get_descendant_tokens( ?int $token_id = null ): array {
$tokens = array();
foreach ( $this->children as $child ) {
if ( $child instanceof WP_Parser_Token ) {
if ( null === $token_id || $child->id === $token_id ) {
$tokens[] = $child;
}
} else {
$tokens = array_merge( $tokens, $child->get_descendant_tokens( $token_id ) );
}
}
return $tokens;
}
/**
* Get the byte offset in the input string where this node begins.
*
* @return int The byte offset in the input string where this node begins.
*/
public function get_start(): int {
return $this->get_first_descendant_token()->start;
}
/**
* Get the byte length of this node in the input string.
*
* @return int The byte length of this node in the input string.
*/
public function get_length(): int {
$tokens = $this->get_descendant_tokens();
$first_token = $tokens[0];
$last_token = $tokens[ count( $tokens ) - 1 ];
return $last_token->start + $last_token->length - $first_token->start;
}
}