PluginProbe
SQLite Database Integration / trunk
SQLite Database Integration vtrunk
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 2.2.4 All 31 releases
sqlite-database-integration / wp-includes / database / parser / class-wp-parser.php

class-wp-parser.php in SQLite Database Integration trunk, at wp-includes/database/parser/class-wp-parser.php

127 lines 3.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * A recursive descent parser.
5 *
6 * This is a dynamic recursive descent parser that can parse LL grammars.
7 *
8 * @TODO: Add a detailed description and list the properties that a grammar must
9 * satisfy in order to be supported by this parser (e.g., no left recursion).
10 *
11 * @access private
12 */
13 class WP_Parser {
14 protected $grammar;
15 protected $tokens;
16 protected $position;
17
18 public function __construct( WP_Parser_Grammar $grammar, array $tokens ) {
19 $this->grammar = $grammar;
20 $this->tokens = $tokens;
21 $this->position = 0;
22 }
23
24 public function parse() {
25 // @TODO: Make the starting rule lookup non-grammar-specific.
26 $query_rule_id = $this->grammar->get_rule_id( 'query' );
27 $ast = $this->parse_recursive( $query_rule_id );
28 return false === $ast ? null : $ast;
29 }
30
31 private function parse_recursive( $rule_id ) {
32 $is_terminal = $rule_id <= $this->grammar->highest_terminal_id;
33 if ( $is_terminal ) {
34 if ( $this->position >= count( $this->tokens ) ) {
35 return false;
36 }
37
38 if ( WP_Parser_Grammar::EMPTY_RULE_ID === $rule_id ) {
39 return true;
40 }
41
42 if ( $this->tokens[ $this->position ]->id === $rule_id ) {
43 ++$this->position;
44 return $this->tokens[ $this->position - 1 ];
45 }
46 return false;
47 }
48
49 $branches = $this->grammar->rules[ $rule_id ];
50 if ( ! count( $branches ) ) {
51 return false;
52 }
53
54 // Bail out from processing the current branch if none of its rules can
55 // possibly match the current token.
56 if ( isset( $this->grammar->lookahead_is_match_possible[ $rule_id ] ) ) {
57 $token_id = $this->tokens[ $this->position ]->id;
58 if (
59 ! isset( $this->grammar->lookahead_is_match_possible[ $rule_id ][ $token_id ] ) &&
60 ! isset( $this->grammar->lookahead_is_match_possible[ $rule_id ][ WP_Parser_Grammar::EMPTY_RULE_ID ] )
61 ) {
62 return false;
63 }
64 }
65
66 $rule_name = $this->grammar->rule_names[ $rule_id ];
67 $starting_position = $this->position;
68 foreach ( $branches as $branch ) {
69 $this->position = $starting_position;
70 $node = new WP_Parser_Node( $rule_id, $rule_name );
71 $branch_matches = true;
72 foreach ( $branch as $subrule_id ) {
73 $subnode = $this->parse_recursive( $subrule_id );
74 if ( false === $subnode ) {
75 $branch_matches = false;
76 break;
77 } elseif ( true === $subnode ) {
78 /*
79 * The subrule was matched without actually matching a token.
80 * This means a special empty "ε" (epsilon) rule was matched.
81 * An "ε" rule in a grammar matches an empty input of 0 bytes.
82 * It is used to represent optional grammar productions.
83 */
84 continue;
85 } elseif ( is_array( $subnode ) && 0 === count( $subnode ) ) {
86 continue;
87 }
88 if ( is_array( $subnode ) && ! count( $subnode ) ) {
89 continue;
90 }
91 if ( isset( $this->grammar->fragment_ids[ $subrule_id ] ) ) {
92 $node->merge_fragment( $subnode );
93 } else {
94 $node->append_child( $subnode );
95 }
96 }
97
98 // Negative lookahead for INTO after a valid SELECT statement.
99 // If we match a SELECT statement, but there is an INTO keyword after it,
100 // we're in the wrong branch and need to leave matching to a later rule.
101 // @TODO: Extract this to the "WP_MySQL_Parser" class, or add support
102 // for right-associative rules, which could solve this.
103 // See: https://github.com/mysql/mysql-workbench/blob/8.0.38/library/parsers/grammars/MySQLParser.g4#L994
104 // See: https://github.com/antlr/antlr4/issues/488
105 $la = $this->tokens[ $this->position ] ?? null;
106 if ( $la && 'selectStatement' === $rule_name && WP_MySQL_Lexer::INTO_SYMBOL === $la->id ) {
107 $branch_matches = false;
108 }
109
110 if ( true === $branch_matches ) {
111 break;
112 }
113 }
114
115 if ( ! $branch_matches ) {
116 $this->position = $starting_position;
117 return false;
118 }
119
120 if ( ! $node->has_child() ) {
121 return true;
122 }
123
124 return $node;
125 }
126 }
127