summaryrefslogtreecommitdiff
path: root/vendor/mtdowling/jmespath.php/src/TreeInterpreter.php
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/mtdowling/jmespath.php/src/TreeInterpreter.php')
-rw-r--r--vendor/mtdowling/jmespath.php/src/TreeInterpreter.php235
1 files changed, 235 insertions, 0 deletions
diff --git a/vendor/mtdowling/jmespath.php/src/TreeInterpreter.php b/vendor/mtdowling/jmespath.php/src/TreeInterpreter.php
new file mode 100644
index 0000000..934c506
--- /dev/null
+++ b/vendor/mtdowling/jmespath.php/src/TreeInterpreter.php
@@ -0,0 +1,235 @@
+<?php
+namespace JmesPath;
+
+/**
+ * Tree visitor used to evaluates JMESPath AST expressions.
+ */
+class TreeInterpreter
+{
+ /** @var callable */
+ private $fnDispatcher;
+
+ /**
+ * @param callable|null $fnDispatcher Function dispatching function that accepts
+ * a function name argument and an array of
+ * function arguments and returns the result.
+ */
+ public function __construct(callable $fnDispatcher = null)
+ {
+ $this->fnDispatcher = $fnDispatcher ?: FnDispatcher::getInstance();
+ }
+
+ /**
+ * Visits each node in a JMESPath AST and returns the evaluated result.
+ *
+ * @param array $node JMESPath AST node
+ * @param mixed $data Data to evaluate
+ *
+ * @return mixed
+ */
+ public function visit(array $node, $data)
+ {
+ return $this->dispatch($node, $data);
+ }
+
+ /**
+ * Recursively traverses an AST using depth-first, pre-order traversal.
+ * The evaluation logic for each node type is embedded into a large switch
+ * statement to avoid the cost of "double dispatch".
+ * @return mixed
+ */
+ private function dispatch(array $node, $value)
+ {
+ $dispatcher = $this->fnDispatcher;
+
+ switch ($node['type']) {
+
+ case 'field':
+ if (is_array($value) || $value instanceof \ArrayAccess) {
+ return isset($value[$node['value']]) ? $value[$node['value']] : null;
+ } elseif ($value instanceof \stdClass) {
+ return isset($value->{$node['value']}) ? $value->{$node['value']} : null;
+ }
+ return null;
+
+ case 'subexpression':
+ return $this->dispatch(
+ $node['children'][1],
+ $this->dispatch($node['children'][0], $value)
+ );
+
+ case 'index':
+ if (!Utils::isArray($value)) {
+ return null;
+ }
+ $idx = $node['value'] >= 0
+ ? $node['value']
+ : $node['value'] + count($value);
+ return isset($value[$idx]) ? $value[$idx] : null;
+
+ case 'projection':
+ $left = $this->dispatch($node['children'][0], $value);
+ switch ($node['from']) {
+ case 'object':
+ if (!Utils::isObject($left)) {
+ return null;
+ }
+ break;
+ case 'array':
+ if (!Utils::isArray($left)) {
+ return null;
+ }
+ break;
+ default:
+ if (!is_array($left) || !($left instanceof \stdClass)) {
+ return null;
+ }
+ }
+
+ $collected = [];
+ foreach ((array) $left as $val) {
+ $result = $this->dispatch($node['children'][1], $val);
+ if ($result !== null) {
+ $collected[] = $result;
+ }
+ }
+
+ return $collected;
+
+ case 'flatten':
+ static $skipElement = [];
+ $value = $this->dispatch($node['children'][0], $value);
+
+ if (!Utils::isArray($value)) {
+ return null;
+ }
+
+ $merged = [];
+ foreach ($value as $values) {
+ // Only merge up arrays lists and not hashes
+ if (is_array($values) && isset($values[0])) {
+ $merged = array_merge($merged, $values);
+ } elseif ($values !== $skipElement) {
+ $merged[] = $values;
+ }
+ }
+
+ return $merged;
+
+ case 'literal':
+ return $node['value'];
+
+ case 'current':
+ return $value;
+
+ case 'or':
+ $result = $this->dispatch($node['children'][0], $value);
+ return Utils::isTruthy($result)
+ ? $result
+ : $this->dispatch($node['children'][1], $value);
+
+ case 'and':
+ $result = $this->dispatch($node['children'][0], $value);
+ return Utils::isTruthy($result)
+ ? $this->dispatch($node['children'][1], $value)
+ : $result;
+
+ case 'not':
+ return !Utils::isTruthy(
+ $this->dispatch($node['children'][0], $value)
+ );
+
+ case 'pipe':
+ return $this->dispatch(
+ $node['children'][1],
+ $this->dispatch($node['children'][0], $value)
+ );
+
+ case 'multi_select_list':
+ if ($value === null) {
+ return null;
+ }
+
+ $collected = [];
+ foreach ($node['children'] as $node) {
+ $collected[] = $this->dispatch($node, $value);
+ }
+
+ return $collected;
+
+ case 'multi_select_hash':
+ if ($value === null) {
+ return null;
+ }
+
+ $collected = [];
+ foreach ($node['children'] as $node) {
+ $collected[$node['value']] = $this->dispatch(
+ $node['children'][0],
+ $value
+ );
+ }
+
+ return $collected;
+
+ case 'comparator':
+ $left = $this->dispatch($node['children'][0], $value);
+ $right = $this->dispatch($node['children'][1], $value);
+ if ($node['value'] == '==') {
+ return Utils::isEqual($left, $right);
+ } elseif ($node['value'] == '!=') {
+ return !Utils::isEqual($left, $right);
+ } else {
+ return self::relativeCmp($left, $right, $node['value']);
+ }
+
+ case 'condition':
+ return Utils::isTruthy($this->dispatch($node['children'][0], $value))
+ ? $this->dispatch($node['children'][1], $value)
+ : null;
+
+ case 'function':
+ $args = [];
+ foreach ($node['children'] as $arg) {
+ $args[] = $this->dispatch($arg, $value);
+ }
+ return $dispatcher($node['value'], $args);
+
+ case 'slice':
+ return is_string($value) || Utils::isArray($value)
+ ? Utils::slice(
+ $value,
+ $node['value'][0],
+ $node['value'][1],
+ $node['value'][2]
+ ) : null;
+
+ case 'expref':
+ $apply = $node['children'][0];
+ return function ($value) use ($apply) {
+ return $this->visit($apply, $value);
+ };
+
+ default:
+ throw new \RuntimeException("Unknown node type: {$node['type']}");
+ }
+ }
+
+ /**
+ * @return bool
+ */
+ private static function relativeCmp($left, $right, $cmp)
+ {
+ if (!(is_int($left) || is_float($left)) || !(is_int($right) || is_float($right))) {
+ return false;
+ }
+
+ switch ($cmp) {
+ case '>': return $left > $right;
+ case '>=': return $left >= $right;
+ case '<': return $left < $right;
+ case '<=': return $left <= $right;
+ default: throw new \RuntimeException("Invalid comparison: $cmp");
+ }
+ }
+}