Skip to content

Commit e9b1d5d

Browse files
committed
Syncing with latest compliance tests
Implementing and, not, and unary expressions Implemented map function Fixing various bugs in the parser and lexer Cleaning up some code-gen that was causing new filter issues Closes #24
1 parent f4e2ad2 commit e9b1d5d

17 files changed

+766
-178
lines changed

src/FnDispatcher.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,16 @@ private function fn_to_array(array $args)
264264
return Utils::isArray($args[0]) ? $args[0] : [$args[0]];
265265
}
266266

267+
private function fn_map(array $args)
268+
{
269+
$this->validate('map', $args, [['expression'], ['any']]);
270+
$result = [];
271+
foreach ($args[1] as $a) {
272+
$result[] = $args[0]($a);
273+
}
274+
return $result;
275+
}
276+
267277
private function typeError($from, $msg)
268278
{
269279
if (strpos($from, ':')) {

src/Lexer.php

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ class Lexer
2525
const T_UNKNOWN = 'unknown';
2626
const T_PIPE = 'pipe';
2727
const T_OR = 'or';
28+
const T_AND = 'and';
29+
const T_NOT = 'not';
2830
const T_FILTER = 'filter';
2931
const T_LITERAL = 'literal';
3032
const T_EOF = 'eof';
@@ -43,6 +45,7 @@ class Lexer
4345
const STATE_GT = 10;
4446
const STATE_EQ = 11;
4547
const STATE_NOT = 12;
48+
const STATE_AND = 13;
4649

4750
/** @var array We know what token we are consuming based on each char */
4851
private static $transitionTable = [
@@ -52,6 +55,7 @@ class Lexer
5255
'!' => self::STATE_NOT,
5356
'[' => self::STATE_LBRACKET,
5457
'|' => self::STATE_PIPE,
58+
'&' => self::STATE_AND,
5559
'`' => self::STATE_JSON_LITERAL,
5660
'"' => self::STATE_QUOTED_STRING,
5761
"'" => self::STATE_STRING_LITERAL,
@@ -76,7 +80,6 @@ class Lexer
7680
',' => self::STATE_SINGLE_CHAR,
7781
':' => self::STATE_SINGLE_CHAR,
7882
'@' => self::STATE_SINGLE_CHAR,
79-
'&' => self::STATE_SINGLE_CHAR,
8083
'(' => self::STATE_SINGLE_CHAR,
8184
')' => self::STATE_SINGLE_CHAR,
8285
'{' => self::STATE_SINGLE_CHAR,
@@ -167,7 +170,6 @@ class Lexer
167170
',' => self::T_COMMA,
168171
':' => self::T_COLON,
169172
'@' => self::T_CURRENT,
170-
'&' => self::T_EXPREF,
171173
'(' => self::T_LPAREN,
172174
')' => self::T_RPAREN,
173175
'{' => self::T_LBRACE,
@@ -268,7 +270,9 @@ public function tokenize($input)
268270
} elseif ($state === self::STATE_STRING_LITERAL) {
269271

270272
// Consume raw string literals
271-
$tokens[] = $this->inside($chars, "'", self::T_LITERAL);
273+
$t = $this->inside($chars, "'", self::T_LITERAL);
274+
$t['value'] = str_replace("\\'", "'", $t['value']);
275+
$tokens[] = $t;
272276

273277
} elseif ($state === self::STATE_PIPE) {
274278

@@ -315,10 +319,14 @@ public function tokenize($input)
315319
// Consume equals
316320
$tokens[] = $this->matchOr($chars, '=', '=', self::T_COMPARATOR, self::T_UNKNOWN);
317321

322+
} elseif ($state == self::STATE_AND) {
323+
324+
$tokens[] = $this->matchOr($chars, '&', '&', self::T_AND, self::T_EXPREF);
325+
318326
} elseif ($state === self::STATE_NOT) {
319327

320328
// Consume not equal
321-
$tokens[] = $this->matchOr($chars, '!', '=', self::T_COMPARATOR, self::T_UNKNOWN);
329+
$tokens[] = $this->matchOr($chars, '!', '=', self::T_COMPARATOR, self::T_NOT);
322330

323331
} else {
324332

src/Parser.php

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,14 @@ class Parser
3131
T::T_EXPREF => 0,
3232
T::T_COLON => 0,
3333
T::T_PIPE => 1,
34-
T::T_COMPARATOR => 2,
35-
T::T_OR => 5,
36-
T::T_FLATTEN => 6,
34+
T::T_OR => 2,
35+
T::T_AND => 3,
36+
T::T_COMPARATOR => 5,
37+
T::T_FLATTEN => 9,
3738
T::T_STAR => 20,
3839
T::T_FILTER => 21,
3940
T::T_DOT => 40,
41+
T::T_NOT => 45,
4042
T::T_LBRACE => 50,
4143
T::T_LBRACKET => 55,
4244
T::T_LPAREN => 60,
@@ -134,6 +136,22 @@ private function nud_expref()
134136
return ['type' => T::T_EXPREF, 'children' => [$this->expr(self::$bp[T::T_EXPREF])]];
135137
}
136138

139+
private function nud_not()
140+
{
141+
$this->next();
142+
return ['type' => T::T_NOT, 'children' => [$this->expr(self::$bp[T::T_NOT])]];
143+
}
144+
145+
private function nud_lparen() {
146+
$this->next();
147+
$result = $this->expr(0);
148+
if ($this->token['type'] !== T::T_RPAREN) {
149+
throw $this->syntax('Unclosed `(`');
150+
}
151+
$this->next();
152+
return $result;
153+
}
154+
137155
private function nud_lbrace()
138156
{
139157
static $validKeys = [T::T_QUOTED_IDENTIFIER => true, T::T_IDENTIFIER => true];
@@ -233,6 +251,15 @@ private function led_or(array $left)
233251
];
234252
}
235253

254+
private function led_and(array $left)
255+
{
256+
$this->next();
257+
return [
258+
'type' => T::T_AND,
259+
'children' => [$left, $this->expr(self::$bp[T::T_AND])]
260+
];
261+
}
262+
236263
private function led_pipe(array $left)
237264
{
238265
$this->next();
@@ -295,7 +322,7 @@ private function led_comparator(array $left)
295322
return [
296323
'type' => T::T_COMPARATOR,
297324
'value' => $token['value'],
298-
'children' => [$left, $this->expr()]
325+
'children' => [$left, $this->expr(self::$bp[T::T_COMPARATOR])]
299326
];
300327
}
301328

src/TreeCompiler.php

Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ public function visit(array $ast, $fnName, $expr)
2828
->write('')
2929
->write('function %s(Ti $interpreter, $value) {', $fnName)
3030
->indent()
31-
->write('$current = $value;')
3231
->dispatch($ast)
3332
->write('')
3433
->write('return $value;')
@@ -116,6 +115,29 @@ private function visit_or(array $node)
116115
->write('}');
117116
}
118117

118+
private function visit_and(array $node)
119+
{
120+
$a = $this->makeVar('beforeAnd');
121+
return $this
122+
->write('%s = $value;', $a)
123+
->dispatch($node['children'][0])
124+
->write('if ($value || $value === "0" || $value === 0) {')
125+
->indent()
126+
->write('$value = %s;', $a)
127+
->dispatch($node['children'][1])
128+
->outdent()
129+
->write('}');
130+
}
131+
132+
private function visit_not(array $node)
133+
{
134+
return $this
135+
->write('// Visiting not node')
136+
->dispatch($node['children'][0])
137+
->write('// Applying boolean not to result of not node')
138+
->write('$value = !Utils::isTruthy($value);');
139+
}
140+
119141
private function visit_subexpression(array $node)
120142
{
121143
return $this
@@ -182,7 +204,6 @@ private function visit_pipe(array $node)
182204
{
183205
return $this
184206
->dispatch($node['children'][0])
185-
->write('$current = $value;')
186207
->dispatch($node['children'][1]);
187208
}
188209

@@ -193,13 +214,11 @@ private function visit_multi_select_list(array $node)
193214

194215
private function visit_multi_select_hash(array $node)
195216
{
196-
$tmpCurrent = $this->makeVar('cur');
197217
$listVal = $this->makeVar('list');
198218
$value = $this->makeVar('prev');
199219
$this->write('if ($value !== null) {')
200220
->indent()
201221
->write('%s = [];', $listVal)
202-
->write('%s = $current;', $tmpCurrent)
203222
->write('%s = $value;', $value);
204223

205224
$first = true;
@@ -220,24 +239,20 @@ private function visit_multi_select_hash(array $node)
220239

221240
return $this
222241
->write('$value = %s;', $listVal)
223-
->write('$current = %s;', $tmpCurrent)
224242
->outdent()
225243
->write('}');
226244
}
227245

228246
private function visit_function(array $node)
229247
{
230248
$value = $this->makeVar('val');
231-
$current = $this->makeVar('current');
232249
$args = $this->makeVar('args');
233250
$this->write('%s = $value;', $value)
234-
->write('%s = $current;', $current)
235251
->write('%s = [];', $args);
236252

237253
foreach ($node['children'] as $arg) {
238254
$this->dispatch($arg);
239255
$this->write('%s[] = $value;', $args)
240-
->write('$current = %s;', $current)
241256
->write('$value = %s;', $value);
242257
}
243258

@@ -347,47 +362,51 @@ private function visit_projection(array $node)
347362

348363
private function visit_condition(array $node)
349364
{
365+
$value = $this->makeVar('beforeCondition');
350366
return $this
351-
->write('// Visiting projection node')
367+
->write('%s = $value;', $value)
368+
->write('// Visiting condition node')
352369
->dispatch($node['children'][0])
353-
->write('if ($value !== null) {')
370+
->write('// Checking result of condition node')
371+
->write('if (Utils::isTruthy($value)) {')
354372
->indent()
373+
->write('$value = %s;', $value)
355374
->dispatch($node['children'][1])
356375
->outdent()
376+
->write('} else {')
377+
->indent()
378+
->write('$value = null;')
379+
->outdent()
357380
->write('}');
358381
}
359382

360383
private function visit_comparator(array $node)
361384
{
362385
$value = $this->makeVar('val');
363-
$tmpCurrent = $this->makeVar('cur');
364386
$a = $this->makeVar('left');
365387
$b = $this->makeVar('right');
366388

367389
$this
368390
->write('// Visiting comparator node')
369391
->write('%s = $value;', $value)
370-
->write('%s = $current;', $tmpCurrent)
371392
->dispatch($node['children'][0])
372393
->write('%s = $value;', $a)
373394
->write('$value = %s;', $value)
374395
->dispatch($node['children'][1])
375396
->write('%s = $value;', $b);
376397

377398
if ($node['value'] == '==') {
378-
$this->write('$result = Utils::isEqual(%s, %s);', $a, $b);
399+
$this->write('$value = Utils::isEqual(%s, %s);', $a, $b);
379400
} elseif ($node['value'] == '!=') {
380-
$this->write('$result = !Utils::isEqual(%s, %s);', $a, $b);
401+
$this->write('$value = !Utils::isEqual(%s, %s);', $a, $b);
381402
} else {
382403
$this->write(
383-
'$result = is_int(%s) && is_int(%s) && %s %s %s;',
404+
'$value = is_int(%s) && is_int(%s) && %s %s %s;',
384405
$a, $b, $a, $node['value'], $b
385406
);
386407
}
387408

388-
return $this
389-
->write('$value = $result === true ? %s : null;', $value)
390-
->write('$current = %s;', $tmpCurrent);
409+
return $this;
391410
}
392411

393412
/** @internal */

src/TreeInterpreter.php

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -124,11 +124,20 @@ private function dispatch(array $node, $value)
124124

125125
case 'or':
126126
$result = $this->dispatch($node['children'][0], $value);
127-
if (!$result && $result !== '0' && $result !== 0) {
128-
$result = $this->dispatch($node['children'][1], $value);
129-
}
127+
return Utils::isTruthy($result)
128+
? $result
129+
: $this->dispatch($node['children'][1], $value);
130+
131+
case 'and':
132+
$result = $this->dispatch($node['children'][0], $value);
133+
return Utils::isTruthy($result)
134+
? $this->dispatch($node['children'][1], $value)
135+
: $result;
130136

131-
return $result;
137+
case 'not':
138+
return !Utils::isTruthy(
139+
$this->dispatch($node['children'][0], $value)
140+
);
132141

133142
case 'pipe':
134143
return $this->dispatch(
@@ -175,7 +184,7 @@ private function dispatch(array $node, $value)
175184
}
176185

177186
case 'condition':
178-
return true === $this->dispatch($node['children'][0], $value)
187+
return Utils::isTruthy($this->dispatch($node['children'][0], $value))
179188
? $this->dispatch($node['children'][1], $value)
180189
: null;
181190

src/Utils.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,24 @@ class Utils
1212
'integer' => 'number'
1313
];
1414

15+
/**
16+
* Returns true if the value is truthy
17+
*
18+
* @param mixed $value Value to check
19+
*
20+
* @return bool
21+
*/
22+
public static function isTruthy($value)
23+
{
24+
if (!$value) {
25+
return $value === 0 || $value === '0';
26+
} elseif ($value instanceof \stdClass) {
27+
return (bool) get_object_vars($value);
28+
} else {
29+
return true;
30+
}
31+
}
32+
1533
/**
1634
* Gets the JMESPath type equivalent of a PHP variable.
1735
*

0 commit comments

Comments
 (0)