Skip to content

Commit da3822a

Browse files
committed
Parser and AST cleanup/normalization
1 parent d9039b3 commit da3822a

File tree

4 files changed

+78
-118
lines changed

4 files changed

+78
-118
lines changed

src/Parser.php

Lines changed: 49 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,13 @@ class Parser
99
{
1010
/** @var Lexer */
1111
private $lexer;
12-
13-
/** @var array Array of parsed tokens */
1412
private $tokens;
15-
16-
/** @var array Current token */
1713
private $token;
18-
19-
/** @var int Current token position */
2014
private $tpos;
21-
22-
/** @var string Expression being parsed */
2315
private $expression;
16+
private static $nullToken = ['type' => 'eof'];
17+
private static $currentNode = ['type' => 'current'];
2418

25-
/** @var array Null token that is reused over and over */
26-
private static $nullToken = ['type' => 'eof', 'value' => ''];
27-
28-
/** @var array Token binding power */
2919
private static $bp = [
3020
'eof' => 0,
3121
'quoted_identifier' => 0,
@@ -49,9 +39,6 @@ class Parser
4939
'lparen' => 60,
5040
];
5141

52-
/** @var array Cached current AST node */
53-
private static $currentNode = ['type' => 'current'];
54-
5542
/** @var array Acceptable tokens after a dot token */
5643
private static $afterDot = [
5744
'identifier' => true, // foo.bar
@@ -87,13 +74,11 @@ public function parse($expression)
8774
$this->next();
8875
$result = $this->expr();
8976

90-
if ($this->token['type'] != 'eof') {
91-
$this->throwSyntax('Encountered an unexpected "'
92-
. $this->token['type'] . '" token and did not reach'
93-
. ' the end of the token stream');
77+
if ($this->token['type'] === 'eof') {
78+
return $result;
9479
}
9580

96-
return $result;
81+
throw $this->syntax('Did not reach the end of the token stream');
9782
}
9883

9984
/**
@@ -117,20 +102,15 @@ private function nud_identifier()
117102
{
118103
$token = $this->token;
119104
$this->next();
120-
return ['type' => 'field', 'key' => $token['value']];
105+
return ['type' => 'field', 'value' => $token['value']];
121106
}
122107

123108
private function nud_quoted_identifier()
124109
{
125110
$token = $this->token;
126111
$this->next();
127-
if ($this->token['type'] == 'lparen') {
128-
$this->throwSyntax(
129-
'Quoted identifiers are not allowed for function names.'
130-
);
131-
}
132-
133-
return ['type' => 'field', 'key' => $token['value']];
112+
$this->assertNotToken('lparen');
113+
return ['type' => 'field', 'value' => $token['value']];
134114
}
135115

136116
private function nud_current()
@@ -149,8 +129,7 @@ private function nud_literal()
149129
private function nud_expref()
150130
{
151131
$this->next();
152-
153-
return ['type' => 'expression', 'children' => [$this->expr(2)]];
132+
return ['type' => 'expref', 'children' => [$this->expr(2)]];
154133
}
155134

156135
private function nud_lbrace()
@@ -192,7 +171,7 @@ private function nud_lbracket()
192171
$type = $this->token['type'];
193172
if ($type == 'number' || $type == 'colon') {
194173
return $this->parseArrayIndexExpression();
195-
} elseif ($type == 'star' && $this->lookahead()['type'] == 'rbracket') {
174+
} elseif ($type == 'star' && $this->lookahead() == 'rbracket') {
196175
return $this->parseWildcardArray();
197176
} else {
198177
return $this->parseMultiSelectList();
@@ -203,16 +182,16 @@ private function led_lbracket(array $left)
203182
{
204183
static $nextTypes = ['number' => true, 'colon' => true, 'star' => true];
205184
$this->next($nextTypes);
206-
$type = $this->token['type'];
207-
208-
if ($type == 'number' || $type == 'colon') {
209-
return [
210-
'type' => 'subexpression',
211-
'children' => [$left, $this->parseArrayIndexExpression()]
212-
];
185+
switch ($this->token['type']) {
186+
case 'number':
187+
case 'colon':
188+
return [
189+
'type' => 'subexpression',
190+
'children' => [$left, $this->parseArrayIndexExpression()]
191+
];
192+
default:
193+
return $this->parseWildcardArray($left);
213194
}
214-
215-
return $this->parseWildcardArray($left);
216195
}
217196

218197
private function led_flatten(array $left)
@@ -264,7 +243,6 @@ private function led_pipe(array $left)
264243
private function led_lparen(array $left)
265244
{
266245
$args = [];
267-
$name = $left['key'];
268246
$this->next();
269247

270248
while ($this->token['type'] != 'rparen') {
@@ -276,15 +254,19 @@ private function led_lparen(array $left)
276254

277255
$this->next();
278256

279-
return ['type' => 'function', 'fn' => $name, 'children' => $args];
257+
return [
258+
'type' => 'function',
259+
'value' => $left['value'],
260+
'children' => $args
261+
];
280262
}
281263

282264
private function led_filter(array $left)
283265
{
284266
$this->next();
285267
$expression = $this->expr();
286268
if ($this->token['type'] != 'rbracket') {
287-
$this->throwSyntax('Expected a closing rbracket for the filter');
269+
throw $this->syntax('Expected a closing rbracket for the filter');
288270
}
289271

290272
$this->next();
@@ -294,7 +276,7 @@ private function led_filter(array $left)
294276
'type' => 'projection',
295277
'from' => 'array',
296278
'children' => [
297-
$left ?:self::$currentNode,
279+
$left ?: self::$currentNode,
298280
[
299281
'type' => 'condition',
300282
'children' => [$expression, $rhs]
@@ -310,7 +292,7 @@ private function led_comparator(array $left)
310292

311293
return [
312294
'type' => 'comparator',
313-
'relation' => $token['value'],
295+
'value' => $token['value'],
314296
'children' => [$left, $this->expr()]
315297
];
316298
}
@@ -327,7 +309,7 @@ private function parseProjection($bp)
327309
return $this->expr($bp);
328310
}
329311

330-
$this->throwSyntax('Syntax error after projection');
312+
throw $this->syntax('Syntax error after projection');
331313
}
332314

333315
private function parseDot($bp)
@@ -348,8 +330,8 @@ private function parseKeyValuePair()
348330
$this->next();
349331

350332
return [
351-
'type' => 'key_value_pair',
352-
'key' => $key,
333+
'type' => 'key_val_pair',
334+
'value' => $key,
353335
'children' => [$this->expr()]
354336
];
355337
}
@@ -415,19 +397,15 @@ private function parseArrayIndexExpression()
415397

416398
if ($pos == 0) {
417399
// No colons were found so this is a simple index extraction
418-
return ['type' => 'index', 'index' => $parts[0]];
400+
return ['type' => 'index', 'value' => $parts[0]];
419401
} elseif ($pos > 2) {
420-
$this->throwSyntax('Invalid array slice syntax: too many colons');
402+
throw $this->syntax('Invalid array slice syntax: too many colons');
403+
} else {
404+
// Sliced array from start (e.g., [2:])
405+
return ['type' => 'slice', 'value' => $parts];
421406
}
422-
423-
// Sliced array from start (e.g., [2:])
424-
return ['type' => 'slice', 'args' => $parts];
425407
}
426408

427-
/**
428-
* Parses a multi-select-list expression:
429-
* multi-select-list = "[" ( expression *( "," expression ) ) "]"
430-
*/
431409
private function parseMultiSelectList()
432410
{
433411
$nodes = [];
@@ -436,47 +414,26 @@ private function parseMultiSelectList()
436414
$nodes[] = $this->expr();
437415
if ($this->token['type'] == 'comma') {
438416
$this->next();
439-
if ($this->token['type'] == 'rbracket') {
440-
$this->throwSyntax('Expected expression, found rbracket');
441-
}
417+
$this->assertNotToken('rbracket');
442418
}
443419
} while ($this->token['type'] != 'rbracket');
444-
445420
$this->next();
446421

447422
return ['type' => 'multi_select_list', 'children' => $nodes];
448423
}
449424

450-
/**
451-
* Throws a SyntaxErrorException for the current token
452-
*
453-
* @param string $msg Error message
454-
* @throws SyntaxErrorException
455-
*/
456-
private function throwSyntax($msg)
425+
private function syntax($msg)
457426
{
458-
throw new SyntaxErrorException($msg, $this->token, $this->expression);
427+
return new SyntaxErrorException($msg, $this->token, $this->expression);
459428
}
460429

461-
/**
462-
* Lookahead at the next token.
463-
*
464-
* @return array
465-
*/
466430
private function lookahead()
467431
{
468432
return (!isset($this->tokens[$this->tpos + 1]))
469-
? self::$nullToken
470-
: $this->tokens[$this->tpos + 1];
433+
? 'eof'
434+
: $this->tokens[$this->tpos + 1]['type'];
471435
}
472436

473-
/**
474-
* Move the token stream cursor to the next token
475-
*
476-
* @param array $match Associative array of acceptable next tokens
477-
*
478-
* @throws SyntaxErrorException if the next token is not acceptable
479-
*/
480437
private function next(array $match = null)
481438
{
482439
if (!isset($this->tokens[$this->tpos + 1])) {
@@ -486,11 +443,14 @@ private function next(array $match = null)
486443
}
487444

488445
if ($match && !isset($match[$this->token['type']])) {
489-
throw new SyntaxErrorException(
490-
$match,
491-
$this->token,
492-
$this->expression
493-
);
446+
throw $this->syntax($match);
447+
}
448+
}
449+
450+
private function assertNotToken($type)
451+
{
452+
if ($this->token['type'] == $type) {
453+
throw $this->syntax("Token {$this->tpos} not allowed to be $type");
494454
}
495455
}
496456

@@ -512,7 +472,7 @@ function ($i) use ($prefix) {
512472
return strpos($i, $prefix) === 0;
513473
}
514474
)));
515-
$this->throwSyntax($message);
475+
throw $this->syntax($message);
516476
}
517477

518478
throw new \BadMethodCallException("Call to undefined method $method");

src/TreeCompiler.php

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,8 @@ private function visit_subexpression(array $node)
118118
*/
119119
private function visit_field(array $node)
120120
{
121-
$arrCheck = '$value[' . var_export($node['key'], true) . ']';
122-
$objCheck = '$value->{' . var_export($node['key'], true) . '}';
121+
$arrCheck = '$value[' . var_export($node['value'], true) . ']';
122+
$objCheck = '$value->{' . var_export($node['value'], true) . '}';
123123

124124
$this->write("if (is_array(\$value) || \$value instanceof \\ArrayAccess) {")
125125
->indent()
@@ -143,8 +143,8 @@ private function visit_field(array $node)
143143
*/
144144
private function visit_index(array $node)
145145
{
146-
if ($node['index'] >= 0) {
147-
$check = '$value[' . $node['index'] . ']';
146+
if ($node['value'] >= 0) {
147+
$check = '$value[' . $node['value'] . ']';
148148
$this->write("\$value = (is_array(\$value) || \$value instanceof \\ArrayAccess) && isset($check) ? $check : null;");
149149
return $this;
150150
}
@@ -155,7 +155,7 @@ private function visit_index(array $node)
155155
$this
156156
->write('if (is_array($value) || ($value instanceof \ArrayAccess && $value instanceof \Countable)) {')
157157
->indent()
158-
->write("\${$a} = count(\$value) + {$node['index']};")
158+
->write("\${$a} = count(\$value) + {$node['value']};")
159159
->write("\$value = isset(\$value[\${$a}]) ? \$value[\${$a}] : null;")
160160
->outdent()
161161
->write('} else {')
@@ -206,7 +206,7 @@ private function visit_multi_select_hash(array $node)
206206
$first = false;
207207
if ($node['type'] == 'multi_select_hash') {
208208
$this->dispatch($child['children'][0]);
209-
$key = var_export($child['key'], true);
209+
$key = var_export($child['value'], true);
210210
$this->write("\${$listVal}[{$key}] = \$value;");
211211
} else {
212212
$this->dispatch($child);
@@ -238,7 +238,7 @@ private function visit_function(array $node)
238238
->write("\$value = \${$value};");
239239
}
240240

241-
return $this->write("\$value = JmesPath\\FnDispatcher::getInstance()->__invoke('{$node['fn']}', \${$args});");
241+
return $this->write("\$value = JmesPath\\FnDispatcher::getInstance()->__invoke('{$node['value']}', \${$args});");
242242
}
243243

244244
private function visit_slice(array $node)
@@ -248,9 +248,9 @@ private function visit_slice(array $node)
248248
->indent()
249249
->write(sprintf(
250250
'$value, %s, %s, %s',
251-
var_export($node['args'][0], true),
252-
var_export($node['args'][1], true),
253-
var_export($node['args'][2], true)
251+
var_export($node['value'][0], true),
252+
var_export($node['value'][1], true),
253+
var_export($node['value'][2], true)
254254
))
255255
->outdent()
256256
->write('));');
@@ -261,7 +261,7 @@ private function visit_current(array $node)
261261
return $this->write('// Visiting current node (no-op)');
262262
}
263263

264-
private function visit_expression(array $node)
264+
private function visit_expref(array $node)
265265
{
266266
$child = var_export($node['children'][0], true);
267267
return $this->write("\$value = function (\$value) use (\$interpreter) {")
@@ -373,12 +373,12 @@ private function visit_comparator(array $node)
373373
->dispatch($node['children'][1])
374374
->write("\${$tmpB} = \$value;");
375375

376-
if ($node['relation'] == '==') {
376+
if ($node['value'] == '==') {
377377
$this->write("\$result = \\JmesPath\\TreeInterpreter::valueCmp(\${$tmpA}, \${$tmpB});");
378-
} elseif ($node['relation'] == '!=') {
378+
} elseif ($node['value'] == '!=') {
379379
$this->write("\$result = !\\JmesPath\\TreeInterpreter::valueCmp(\${$tmpA}, \${$tmpB});");
380380
} else {
381-
$this->write("\$result = is_int(\${$tmpA}) && is_int(\${$tmpB}) && \${$tmpA} {$node['relation']} \${$tmpB};");
381+
$this->write("\$result = is_int(\${$tmpA}) && is_int(\${$tmpB}) && \${$tmpA} {$node['value']} \${$tmpB};");
382382
}
383383

384384
return $this

0 commit comments

Comments
 (0)