Skip to content

Commit 1f0d130

Browse files
committed
Adding a utils class and some tests
1 parent 9dd65ba commit 1f0d130

File tree

5 files changed

+266
-130
lines changed

5 files changed

+266
-130
lines changed

src/FnDispatcher.php

Lines changed: 8 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -24,35 +24,6 @@ public static function getInstance()
2424
return $instance;
2525
}
2626

27-
/**
28-
* Gets the JMESPath type equivalent of a PHP variable.
29-
*
30-
* @param mixed $arg PHP variable
31-
* @return string Returns the JSON data type
32-
*/
33-
public static function type($arg)
34-
{
35-
static $map = [
36-
'boolean' => 'boolean',
37-
'string' => 'string',
38-
'NULL' => 'null',
39-
'double' => 'number',
40-
'integer' => 'number',
41-
'object' => 'object'
42-
];
43-
44-
if (is_callable($arg)) {
45-
return 'expression';
46-
}
47-
48-
$type = gettype($arg);
49-
if (isset($map[$type])) {
50-
return $map[$type];
51-
}
52-
53-
return !$arg || array_keys($arg)[0] === 0 ? 'array' : 'object';
54-
}
55-
5627
/**
5728
* @param string $fn Function name.
5829
* @param array $args Function arguments.
@@ -204,7 +175,7 @@ private function fn_sort(array $args)
204175
{
205176
$this->validate('sort', $args, [['array']]);
206177
$valid = ['string', 'number'];
207-
return self::stableSort($args[0], function ($a, $b) use ($valid) {
178+
return Utils::stableSort($args[0], function ($a, $b) use ($valid) {
208179
$this->validateSeq('sort:0', $valid, $a, $b);
209180
return strnatcmp($a, $b);
210181
});
@@ -215,7 +186,7 @@ private function fn_sort_by(array $args)
215186
$this->validate('sort_by', $args, [['array'], ['expression']]);
216187
$expr = $args[1];
217188
$valid = ['string', 'number'];
218-
return self::stableSort(
189+
return Utils::stableSort(
219190
$args[0],
220191
function ($a, $b) use ($expr, $valid) {
221192
$va = $expr($a);
@@ -226,29 +197,6 @@ function ($a, $b) use ($expr, $valid) {
226197
);
227198
}
228199

229-
/**
230-
* JMESPath requires a stable sorting algorithm, so here we'll implement
231-
* a simple Schwartzian transform that uses array index positions as tie
232-
* breakers.
233-
*
234-
* @param array $data List or map of data to sort
235-
* @param callable $sortFn Callable used to sort values
236-
*
237-
* @return array Returns the sorted array
238-
* @link http://en.wikipedia.org/wiki/Schwartzian_transform
239-
*/
240-
private function stableSort(array $data, callable $sortFn)
241-
{
242-
// Decorate each item by creating an array of [value, index]
243-
array_walk($data, function (&$v, $k) { $v = [$v, $k]; });
244-
// Sort by the sort function and use the index as a tie-breaker
245-
uasort($data, function ($a, $b) use ($sortFn) {
246-
return $sortFn($a[0], $b[0]) ?: ($a[1] < $b[1] ? -1 : 1);
247-
});
248-
// Undecorate each item and return the resulting sorted array
249-
return array_map(function ($v) { return $v[0]; }, array_values($data));
250-
}
251-
252200
private function fn_starts_with(array $args)
253201
{
254202
$this->validate('starts_with', $args, [['string'], ['string']]);
@@ -259,7 +207,7 @@ private function fn_starts_with(array $args)
259207
private function fn_type(array $args)
260208
{
261209
$this->validateArity('type', count($args), 1);
262-
return self::type($args[0]);
210+
return Utils::type($args[0]);
263211
}
264212

265213
private function fn_to_string(array $args)
@@ -272,7 +220,7 @@ private function fn_to_number(array $args)
272220
{
273221
$this->validateArity('to_number', count($args), 1);
274222
$value = $args[0];
275-
$type = self::type($value);
223+
$type = Utils::type($value);
276224
if ($type == 'number') {
277225
return $value;
278226
} elseif ($type == 'string' && is_numeric($value)) {
@@ -398,13 +346,13 @@ private function validate($from, $args, $types = [])
398346
private function validateType($from, $value, array $types)
399347
{
400348
if ($types[0] == 'any'
401-
|| in_array(self::type($value), $types)
349+
|| in_array(Utils::type($value), $types)
402350
|| ($value === [] && in_array('object', $types))
403351
) {
404352
return;
405353
}
406354
$msg = 'must be one of the following types: ' . implode(', ', $types)
407-
. '. ' . self::type($value) . ' found';
355+
. '. ' . Utils::type($value) . ' found';
408356
$this->typeError($from, $msg);
409357
}
410358

@@ -419,8 +367,8 @@ private function validateType($from, $value, array $types)
419367
*/
420368
private function validateSeq($from, array $types, $a, $b)
421369
{
422-
$ta = self::type($a);
423-
$tb = self::type($b);
370+
$ta = Utils::type($a);
371+
$tb = Utils::type($b);
424372

425373
if ($ta != $tb) {
426374
$msg = "encountered a type mismatch in sequence: {$ta}, {$tb}";

src/TreeCompiler.php

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ public function visit(array $ast, $fnName, $expr)
2424
$this->write("<?php\n")
2525
->write('use JmesPath\\TreeInterpreter as Ti;')
2626
->write('use JmesPath\\FnDispatcher as Fn;')
27+
->write('use JmesPath\\Utils;')
2728
->write("// {$expr}")
2829
->write('function %s(Ti $interpreter, $value) {', [$fnName])
2930
->indent()
@@ -287,7 +288,7 @@ private function visit_flatten(array $node)
287288

288289
$this
289290
->write('// Visiting merge node')
290-
->write('if (!Ti::isArray($value)) {')
291+
->write('if (!Utils::isArray($value)) {')
291292
->indent()
292293
->write('$value = null;')
293294
->outdent()
@@ -325,9 +326,9 @@ private function visit_projection(array $node)
325326
if (!isset($node['from'])) {
326327
$this->write('if (!is_array($value) || !($value instanceof \stdClass)) $value = null;');
327328
} elseif ($node['from'] == 'object') {
328-
$this->write('if (!Ti::isObject($value)) $value = null;');
329+
$this->write('if (!Utils::isObject($value)) $value = null;');
329330
} elseif ($node['from'] == 'array') {
330-
$this->write('if (!Ti::isArray($value)) $value = null;');
331+
$this->write('if (!Utils::isArray($value)) $value = null;');
331332
}
332333

333334
$this->write('if ($value !== null) {')
@@ -381,9 +382,9 @@ private function visit_comparator(array $node)
381382
->write('%s = $value;', [$b]);
382383

383384
if ($node['value'] == '==') {
384-
$this->write('$result = Ti::valueCmp(%s, %s);', [$a, $b]);
385+
$this->write('$result = Utils::valueCmp(%s, %s);', [$a, $b]);
385386
} elseif ($node['value'] == '!=') {
386-
$this->write('$result = !Ti::valueCmp(%s, %s);', [$a, $b]);
387+
$this->write('$result = !Utils::valueCmp(%s, %s);', [$a, $b]);
387388
} else {
388389
$this->write(
389390
'$result = is_int(%s) && is_int(%s) && %s %s %s;',

src/TreeInterpreter.php

Lines changed: 6 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -32,65 +32,6 @@ public function visit(array $node, $data)
3232
return $this->dispatch($node, $data);
3333
}
3434

35-
/**
36-
* Determine if the provided value is a JMESPath compatible object.
37-
*
38-
* @param mixed $value
39-
*
40-
* @return bool
41-
*/
42-
public static function isObject($value)
43-
{
44-
if (is_array($value)) {
45-
return !$value || array_keys($value)[0] !== 0;
46-
}
47-
48-
// Handle array-like values. Must be empty or offset 0 does not exist
49-
return $value instanceof \Countable && $value instanceof \ArrayAccess
50-
? count($value) == 0 || !$value->offsetExists(0)
51-
: $value instanceof \stdClass;
52-
}
53-
54-
/**
55-
* Determine if the provided value is a JMESPath compatible array.
56-
*
57-
* @param mixed $value
58-
*
59-
* @return bool
60-
*/
61-
public static function isArray($value)
62-
{
63-
if (is_array($value)) {
64-
return !$value || array_keys($value)[0] === 0;
65-
}
66-
67-
// Handle array-like values. Must be empty or offset 0 exists.
68-
return $value instanceof \Countable && $value instanceof \ArrayAccess
69-
? count($value) == 0 || $value->offsetExists(0)
70-
: false;
71-
}
72-
73-
/**
74-
* JSON aware value comparison function.
75-
*
76-
* @param mixed $a First value to compare
77-
* @param mixed $b Second value to compare
78-
*
79-
* @return bool
80-
*/
81-
public static function valueCmp($a, $b)
82-
{
83-
if ($a === $b) {
84-
return true;
85-
} elseif ($a instanceof \stdClass) {
86-
return self::valueCmp((array) $a, $b);
87-
} elseif ($b instanceof \stdClass) {
88-
return self::valueCmp($a, (array) $b);
89-
} else {
90-
return false;
91-
}
92-
}
93-
9435
/**
9536
* Recursively traverses an AST using depth-first, pre-order traversal.
9637
* The evaluation logic for each node type is embedded into a large switch
@@ -117,7 +58,7 @@ private function dispatch(array $node, $value)
11758
);
11859

11960
case 'index':
120-
if (!self::isArray($value)) {
61+
if (!Utils::isArray($value)) {
12162
return null;
12263
}
12364
$idx = $node['value'] >= 0
@@ -129,12 +70,12 @@ private function dispatch(array $node, $value)
12970
$left = $this->dispatch($node['children'][0], $value);
13071
switch ($node['from']) {
13172
case 'object':
132-
if (!self::isObject($left)) {
73+
if (!Utils::isObject($left)) {
13374
return null;
13475
}
13576
break;
13677
case 'array':
137-
if (!self::isArray($left)) {
78+
if (!Utils::isArray($left)) {
13879
return null;
13980
}
14081
break;
@@ -158,7 +99,7 @@ private function dispatch(array $node, $value)
15899
static $skipElement = [];
159100
$value = $this->dispatch($node['children'][0], $value);
160101

161-
if (!self::isArray($value)) {
102+
if (!Utils::isArray($value)) {
162103
return null;
163104
}
164105

@@ -225,9 +166,9 @@ private function dispatch(array $node, $value)
225166
$left = $this->dispatch($node['children'][0], $value);
226167
$right = $this->dispatch($node['children'][1], $value);
227168
if ($node['value'] == '==') {
228-
return self::valueCmp($left, $right);
169+
return Utils::valueCmp($left, $right);
229170
} elseif ($node['value'] == '!=') {
230-
return !self::valueCmp($left, $right);
171+
return !Utils::valueCmp($left, $right);
231172
} else {
232173
return self::relativeCmp($left, $right, $node['value']);
233174
}

0 commit comments

Comments
 (0)