PluginProbe
WowAddons – Product Addons and Product Options With Custom Fields / trunk
WowAddons – Product Addons and Product Options With Custom Fields vtrunk
1.7.2 1.7.1 1.7.0 1.6.21 1.6.20 1.6.19 1.6.18 1.6.17 1.6.16 1.6.15 1.6.14 1.6.13 1.6.12 1.6.11 1.6.10 1.6.9 1.6.8 1.6.7 1.6.6 1.5.10 1.5.11 1.5.2 1.5.3 1.5.4 1.5.5 All 57 releases
product-addons / includes / common / formula / parser / class-expression-parser.php

class-expression-parser.php in WowAddons – Product Addons and Product Options With Custom Fields trunk, at includes/common/formula/parser/class-expression-parser.php

289 lines 7.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php // phpcs:ignore
2 /**
3 * Recursive descent expression parser.
4 *
5 * Precedence (high -> low):
6 * - unary + -
7 * - * /
8 * - + -
9 * - comparisons: > < >= <= != =
10 * - & (logical AND)
11 * - || (logical OR)
12 *
13 * @package PRAD
14 * @since 1.5.8
15 */
16
17 namespace PRAD\Includes\Common\Formula\Parser;
18
19 use PRAD\Includes\Common\Formula\Ast\Expression_Binary_Node;
20 use PRAD\Includes\Common\Formula\Ast\Expression_Function_Node;
21 use PRAD\Includes\Common\Formula\Ast\Expression_Number_Node;
22 use PRAD\Includes\Common\Formula\Ast\Expression_Node;
23 use PRAD\Includes\Common\Formula\Ast\Expression_Unary_Node;
24 use PRAD\Includes\Common\Formula\Ast\Expression_Variable_Node;
25 use PRAD\Includes\Common\Formula\Expression_Exception;
26 use PRAD\Includes\Common\Formula\Lexer\Expression_Token;
27
28 defined( 'ABSPATH' ) || exit;
29
30 /**
31 * Parses a token stream into an AST.
32 */
33 final class Expression_Parser {
34 /**
35 * Token stream to be parsed.
36 *
37 * @var Expression_Token[]
38 */
39 private $tokens;
40 /**
41 * Current token index.
42 *
43 * @var int
44 */
45 private $i = 0;
46
47 /**
48 * Constructor for Expression_Parser.
49 *
50 * Initializes the Expression_Parser with a token stream.
51 *
52 * Expression_Parser constructor.
53 *
54 * @param Expression_Token[] $tokens Token stream to be parsed.
55 */
56 public function __construct( array $tokens ) {
57 $this->tokens = $tokens;
58 }
59
60 /**
61 * Parses the token stream and returns the root AST node.
62 *
63 * @return Expression_Node
64 * @throws Expression_Exception If the token stream contains unexpected tokens or invalid syntax.
65 */
66 public function parse() {
67 $node = $this->parse_or();
68 $this->expect( Expression_Token::T_EOF );
69 return $node;
70 }
71
72 /**
73 * Returns the current token in the token stream.
74 *
75 * @return Expression_Token Current token.
76 */
77 private function current() {
78 return $this->tokens[ $this->i ];
79 }
80
81 /**
82 * Advances the current token index and returns the previous token.
83 *
84 * @return Expression_Token The token before advancing.
85 */
86 private function advance() {
87 ++$this->i;
88 return $this->tokens[ $this->i - 1 ];
89 }
90
91 /**
92 * Checks if the current token matches the given type and optional value, advances if matched.
93 *
94 * @param string $type The token type to match.
95 * @param mixed $value Optional token value to match.
96 * @return Expression_Token|false Returns the token if matched, false otherwise.
97 */
98 private function match( $type, $value = null ) {
99 $t = $this->current();
100 if ( $t->type !== $type ) {
101 return false;
102 }
103 if ( null !== $value && $t->value !== $value ) {
104 return false;
105 }
106 $this->advance();
107 return $t;
108 }
109
110 /**
111 * Checks if the current token matches the given type and optional value, throws exception if not matched.
112 *
113 * @param string $type The token type to match.
114 * @param mixed $value Optional token value to match.
115 * @return Expression_Token Returns the token if matched.
116 * @throws Expression_Exception If the token does not match the expected type or value.
117 */
118 private function expect( $type, $value = null ) {
119 $t = $this->current();
120 if ( ! $this->match( $type, $value ) ) {
121 $want = $type . ( null !== $value ? ( ':' . $value ) : '' );
122 $got = $t->type . ( null !== $t->value ? ( ':' . $t->value ) : '' );
123 throw new Expression_Exception(
124 'Expected ' . esc_html( $want ) . ' but got ' . esc_html( $got ) . ' at position ' . esc_html( $t->pos )
125 );
126 }
127 return $t;
128 }
129
130 /**
131 * Parses logical OR expressions (||) in the token stream.
132 *
133 * @return Expression_Node The parsed expression node.
134 */
135 private function parse_or() {
136 $node = $this->parse_and();
137 while ( $this->match( Expression_Token::T_OPERATOR, '||' ) ) {
138 $right = $this->parse_and();
139 $node = new Expression_Binary_Node( '||', $node, $right );
140 }
141 return $node;
142 }
143
144 /**
145 * Parses logical AND expressions (&) in the token stream.
146 *
147 * @return Expression_Node The parsed expression node.
148 */
149 private function parse_and() {
150 $node = $this->parse_compare();
151 while ( $this->match( Expression_Token::T_OPERATOR, '&' ) ) {
152 $right = $this->parse_compare();
153 $node = new Expression_Binary_Node( '&', $node, $right );
154 }
155 return $node;
156 }
157
158 /**
159 * Parses comparison expressions (>, <, >=, <=, !=, =) in the token stream.
160 *
161 * @return Expression_Node The parsed expression node.
162 */
163 private function parse_compare() {
164 $node = $this->parse_add();
165
166 while ( true ) {
167 $t = $this->current();
168 if ( Expression_Token::T_OPERATOR !== $t->type ) {
169 break;
170 }
171 $op = $t->value;
172 if ( ! in_array( $op, array( '>', '<', '>=', '<=', '!=', '=' ), true ) ) {
173 break;
174 }
175 $this->advance();
176 $right = $this->parse_add();
177 $node = new Expression_Binary_Node( $op, $node, $right );
178 }
179
180 return $node;
181 }
182
183 /**
184 * Parses addition and subtraction expressions (+, -) in the token stream.
185 *
186 * @return Expression_Node The parsed expression node.
187 */
188 private function parse_add() {
189 $node = $this->parse_mul();
190 while ( true ) {
191 if ( $this->match( Expression_Token::T_OPERATOR, '+' ) ) {
192 $right = $this->parse_mul();
193 $node = new Expression_Binary_Node( '+', $node, $right );
194 continue;
195 }
196 if ( $this->match( Expression_Token::T_OPERATOR, '-' ) ) {
197 $right = $this->parse_mul();
198 $node = new Expression_Binary_Node( '-', $node, $right );
199 continue;
200 }
201 break;
202 }
203 return $node;
204 }
205
206 /**
207 * Parses multiplication and division expressions (*, /) in the token stream.
208 *
209 * @return Expression_Node The parsed expression node.
210 */
211 private function parse_mul() {
212 $node = $this->parse_unary();
213 while ( true ) {
214 if ( $this->match( Expression_Token::T_OPERATOR, '*' ) ) {
215 $right = $this->parse_unary();
216 $node = new Expression_Binary_Node( '*', $node, $right );
217 continue;
218 }
219 if ( $this->match( Expression_Token::T_OPERATOR, '/' ) ) {
220 $right = $this->parse_unary();
221 $node = new Expression_Binary_Node( '/', $node, $right );
222 continue;
223 }
224 break;
225 }
226 return $node;
227 }
228
229 /**
230 * Parses unary expressions (+, -) in the token stream.
231 *
232 * @return Expression_Node The parsed unary expression node.
233 */
234 private function parse_unary() {
235 if ( $this->match( Expression_Token::T_OPERATOR, '+' ) ) {
236 return new Expression_Unary_Node( '+', $this->parse_unary() );
237 }
238 if ( $this->match( Expression_Token::T_OPERATOR, '-' ) ) {
239 return new Expression_Unary_Node( '-', $this->parse_unary() );
240 }
241 return $this->parse_primary();
242 }
243
244 /**
245 * Parses primary expressions (numbers, variables, function calls, parentheses) in the token stream.
246 *
247 * @return Expression_Node The parsed primary expression node.
248 * @throws Expression_Exception If an unexpected token is encountered.
249 */
250 private function parse_primary() {
251 $t = $this->current();
252
253 if ( $this->match( Expression_Token::T_NUMBER ) ) {
254 return new Expression_Number_Node( $t->value );
255 }
256
257 if ( $this->match( Expression_Token::T_VARIABLE ) ) {
258 return new Expression_Variable_Node( $t->value, $t->pos );
259 }
260
261 if ( $this->match( Expression_Token::T_IDENTIFIER ) ) {
262 $name = $t->value;
263 $pos = $t->pos;
264
265 // Identifier must be a function call.
266 $this->expect( Expression_Token::T_LPAREN );
267 $args = array();
268 if ( ! $this->match( Expression_Token::T_RPAREN ) ) {
269 $args[] = $this->parse_or();
270 while ( $this->match( Expression_Token::T_COMMA ) ) {
271 $args[] = $this->parse_or();
272 }
273 $this->expect( Expression_Token::T_RPAREN );
274 }
275 return new Expression_Function_Node( $name, $args, $pos );
276 }
277
278 if ( $this->match( Expression_Token::T_LPAREN ) ) {
279 $node = $this->parse_or();
280 $this->expect( Expression_Token::T_RPAREN );
281 return $node;
282 }
283
284 throw new Expression_Exception(
285 'Unexpected token ' . esc_html( $t->type ) . ' at position ' . esc_html( $t->pos )
286 );
287 }
288 }
289