summaryrefslogtreecommitdiff
path: root/vendor/mtdowling
diff options
context:
space:
mode:
authorAndrew Dolgov <[email protected]>2022-11-23 21:14:33 +0300
committerAndrew Dolgov <[email protected]>2022-11-23 21:14:33 +0300
commit0c8af4992cb0f7589dcafaad65ada12753c64594 (patch)
tree18e83d068c3e7dd2499331de977782b382279396 /vendor/mtdowling
initial
Diffstat (limited to 'vendor/mtdowling')
-rw-r--r--vendor/mtdowling/jmespath.php/CHANGELOG.md62
-rw-r--r--vendor/mtdowling/jmespath.php/LICENSE19
-rw-r--r--vendor/mtdowling/jmespath.php/README.rst123
-rwxr-xr-xvendor/mtdowling/jmespath.php/bin/jp.php74
-rwxr-xr-xvendor/mtdowling/jmespath.php/bin/perf.php68
-rw-r--r--vendor/mtdowling/jmespath.php/composer.json39
-rw-r--r--vendor/mtdowling/jmespath.php/src/AstRuntime.php47
-rw-r--r--vendor/mtdowling/jmespath.php/src/CompilerRuntime.php83
-rw-r--r--vendor/mtdowling/jmespath.php/src/DebugRuntime.php109
-rw-r--r--vendor/mtdowling/jmespath.php/src/Env.php91
-rw-r--r--vendor/mtdowling/jmespath.php/src/FnDispatcher.php407
-rw-r--r--vendor/mtdowling/jmespath.php/src/JmesPath.php17
-rw-r--r--vendor/mtdowling/jmespath.php/src/Lexer.php444
-rw-r--r--vendor/mtdowling/jmespath.php/src/Parser.php519
-rw-r--r--vendor/mtdowling/jmespath.php/src/SyntaxErrorException.php36
-rw-r--r--vendor/mtdowling/jmespath.php/src/TreeCompiler.php419
-rw-r--r--vendor/mtdowling/jmespath.php/src/TreeInterpreter.php235
-rw-r--r--vendor/mtdowling/jmespath.php/src/Utils.php258
18 files changed, 3050 insertions, 0 deletions
diff --git a/vendor/mtdowling/jmespath.php/CHANGELOG.md b/vendor/mtdowling/jmespath.php/CHANGELOG.md
new file mode 100644
index 0000000..d97dffb
--- /dev/null
+++ b/vendor/mtdowling/jmespath.php/CHANGELOG.md
@@ -0,0 +1,62 @@
+# CHANGELOG
+
+## 2.6.0 - 2020-07-31
+
+* Support for PHP 8.0.
+
+## 2.5.0 - 2019-12-30
+
+* Full support for PHP 7.0-7.4.
+* Fixed autoloading when run from within vendor folder.
+* Full multibyte (UTF-8) string support.
+
+## 2.4.0 - 2016-12-03
+
+* Added support for floats when interpreting data.
+* Added a function_exists check to work around redeclaration issues.
+
+## 2.3.0 - 2016-01-05
+
+* Added support for [JEP-9](https://github.com/jmespath/jmespath.site/blob/master/docs/proposals/improved-filters.rst),
+ including unary filter expressions, and `&&` filter expressions.
+* Fixed various parsing issues, including not removing escaped single quotes
+ from raw string literals.
+* Added support for the `map` function.
+* Fixed several issues with code generation.
+
+## 2.2.0 - 2015-05-27
+
+* Added support for [JEP-12](https://github.com/jmespath/jmespath.site/blob/master/docs/proposals/raw-string-literals.rst)
+ and raw string literals (e.g., `'foo'`).
+
+## 2.1.0 - 2014-01-13
+
+* Added `JmesPath\Env::cleanCompileDir()` to delete any previously compiled
+ JMESPath expressions.
+
+## 2.0.0 - 2014-01-11
+
+* Moving to a flattened namespace structure.
+* Runtimes are now only PHP callables.
+* Fixed an error in the way empty JSON literals are parsed so that they now
+ return an empty string to match the Python and JavaScript implementations.
+* Removed functions from runtimes. Instead there is now a function dispatcher
+ class, FnDispatcher, that provides function implementations behind a single
+ dispatch function.
+* Removed ExprNode in lieu of just using a PHP callable with bound variables.
+* Removed debug methods from runtimes and instead into a new Debugger class.
+* Heavily cleaned up function argument validation.
+* Slice syntax is now properly validated (i.e., colons are followed by the
+ appropriate value).
+* Lots of code cleanup and performance improvements.
+* Added a convenient `JmesPath\search()` function.
+* **IMPORTANT**: Relocating the project to https://github.com/jmespath/jmespath.php
+
+## 1.1.1 - 2014-10-08
+
+* Added support for using ArrayAccess and Countable as arrays and objects.
+
+## 1.1.0 - 2014-08-06
+
+* Added the ability to search data returned from json_decode() where JSON
+ objects are returned as stdClass objects.
diff --git a/vendor/mtdowling/jmespath.php/LICENSE b/vendor/mtdowling/jmespath.php/LICENSE
new file mode 100644
index 0000000..5c970a4
--- /dev/null
+++ b/vendor/mtdowling/jmespath.php/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2014 Michael Dowling, https://github.com/mtdowling
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/mtdowling/jmespath.php/README.rst b/vendor/mtdowling/jmespath.php/README.rst
new file mode 100644
index 0000000..b65ee46
--- /dev/null
+++ b/vendor/mtdowling/jmespath.php/README.rst
@@ -0,0 +1,123 @@
+============
+jmespath.php
+============
+
+JMESPath (pronounced "jaymz path") allows you to declaratively specify how to
+extract elements from a JSON document. *jmespath.php* allows you to use
+JMESPath in PHP applications with PHP data structures. It requires PHP 5.4 or
+greater and can be installed through `Composer <http://getcomposer.org/doc/00-intro.md>`_
+using the ``mtdowling/jmespath.php`` package.
+
+.. code-block:: php
+
+ require 'vendor/autoload.php';
+
+ $expression = 'foo.*.baz';
+
+ $data = [
+ 'foo' => [
+ 'bar' => ['baz' => 1],
+ 'bam' => ['baz' => 2],
+ 'boo' => ['baz' => 3]
+ ]
+ ];
+
+ JmesPath\search($expression, $data);
+ // Returns: [1, 2, 3]
+
+- `JMESPath Tutorial <http://jmespath.org/tutorial.html>`_
+- `JMESPath Grammar <http://jmespath.org/specification.html#grammar>`_
+- `JMESPath Python library <https://github.com/jmespath/jmespath.py>`_
+
+PHP Usage
+=========
+
+The ``JmesPath\search`` function can be used in most cases when using the
+library. This function utilizes a JMESPath runtime based on your environment.
+The runtime utilized can be configured using environment variables and may at
+some point in the future automatically utilize a C extension if available.
+
+.. code-block:: php
+
+ $result = JmesPath\search($expression, $data);
+
+ // or, if you require PSR-4 compliance.
+ $result = JmesPath\Env::search($expression, $data);
+
+Runtimes
+--------
+
+jmespath.php utilizes *runtimes*. There are currently two runtimes:
+AstRuntime and CompilerRuntime.
+
+AstRuntime is utilized by ``JmesPath\search()`` and ``JmesPath\Env::search()``
+by default.
+
+AstRuntime
+~~~~~~~~~~
+
+The AstRuntime will parse an expression, cache the resulting AST in memory,
+and interpret the AST using an external tree visitor. AstRuntime provides a
+good general approach for interpreting JMESPath expressions that have a low to
+moderate level of reuse.
+
+.. code-block:: php
+
+ $runtime = new JmesPath\AstRuntime();
+ $runtime('foo.bar', ['foo' => ['bar' => 'baz']]);
+ // > 'baz'
+
+CompilerRuntime
+~~~~~~~~~~~~~~~
+
+``JmesPath\CompilerRuntime`` provides the most performance for
+applications that have a moderate to high level of reuse of JMESPath
+expressions. The CompilerRuntime will walk a JMESPath AST and emit PHP source
+code, resulting in anywhere from 7x to 60x speed improvements.
+
+Compiling JMESPath expressions to source code is a slower process than just
+walking and interpreting a JMESPath AST (via the AstRuntime). However,
+running the compiled JMESPath code results in much better performance than
+walking an AST. This essentially means that there is a warm-up period when
+using the ``CompilerRuntime``, but after the warm-up period, it will provide
+much better performance.
+
+Use the CompilerRuntime if you know that you will be executing JMESPath
+expressions more than once or if you can pre-compile JMESPath expressions
+before executing them (for example, server-side applications).
+
+.. code-block:: php
+
+ // Note: The cache directory argument is optional.
+ $runtime = new JmesPath\CompilerRuntime('/path/to/compile/folder');
+ $runtime('foo.bar', ['foo' => ['bar' => 'baz']]);
+ // > 'baz'
+
+Environment Variables
+^^^^^^^^^^^^^^^^^^^^^
+
+You can utilize the CompilerRuntime in ``JmesPath\search()`` by setting
+the ``JP_PHP_COMPILE`` environment variable to "on" or to a directory
+on disk used to store cached expressions.
+
+Testing
+=======
+
+A comprehensive list of test cases can be found at
+https://github.com/jmespath/jmespath.php/tree/master/tests/compliance.
+These compliance tests are utilized by jmespath.php to ensure consistency with
+other implementations, and can serve as examples of the language.
+
+jmespath.php is tested using PHPUnit. In order to run the tests, you need to
+first install the dependencies using Composer as described in the *Installation*
+section. Next you just need to run the tests via make:
+
+.. code-block:: bash
+
+ make test
+
+You can run a suite of performance tests as well:
+
+.. code-block:: bash
+
+ make perf
diff --git a/vendor/mtdowling/jmespath.php/bin/jp.php b/vendor/mtdowling/jmespath.php/bin/jp.php
new file mode 100755
index 0000000..c8433b5
--- /dev/null
+++ b/vendor/mtdowling/jmespath.php/bin/jp.php
@@ -0,0 +1,74 @@
+#!/usr/bin/env php
+<?php
+
+if (file_exists(__DIR__ . '/../vendor/autoload.php')) {
+ require __DIR__ . '/../vendor/autoload.php';
+} elseif (file_exists(__DIR__ . '/../../../autoload.php')) {
+ require __DIR__ . '/../../../autoload.php';
+} elseif (file_exists(__DIR__ . '/../autoload.php')) {
+ require __DIR__ . '/../autoload.php';
+} else {
+ throw new RuntimeException('Unable to locate autoload.php file.');
+}
+
+use JmesPath\Env;
+use JmesPath\DebugRuntime;
+
+$description = <<<EOT
+Runs a JMESPath expression on the provided input or a test case.
+
+Provide the JSON input and expression:
+ echo '{}' | jp.php expression
+
+Or provide the path to a compliance script, a suite, and test case number:
+ jp.php --script path_to_script --suite test_suite_number --case test_case_number [expression]
+
+EOT;
+
+$args = [];
+$currentKey = null;
+for ($i = 1, $total = count($argv); $i < $total; $i++) {
+ if ($i % 2) {
+ if (substr($argv[$i], 0, 2) == '--') {
+ $currentKey = str_replace('--', '', $argv[$i]);
+ } else {
+ $currentKey = trim($argv[$i]);
+ }
+ } else {
+ $args[$currentKey] = $argv[$i];
+ $currentKey = null;
+ }
+}
+
+$expression = $currentKey;
+
+if (isset($args['file']) || isset($args['suite']) || isset($args['case'])) {
+ if (!isset($args['file']) || !isset($args['suite']) || !isset($args['case'])) {
+ die($description);
+ }
+ // Manually run a compliance test
+ $path = realpath($args['file']);
+ file_exists($path) or die('File not found at ' . $path);
+ $json = json_decode(file_get_contents($path), true);
+ $set = $json[$args['suite']];
+ $data = $set['given'];
+ if (!isset($expression)) {
+ $expression = $set['cases'][$args['case']]['expression'];
+ echo "Expects\n=======\n";
+ if (isset($set['cases'][$args['case']]['result'])) {
+ echo json_encode($set['cases'][$args['case']]['result'], JSON_PRETTY_PRINT) . "\n\n";
+ } elseif (isset($set['cases'][$args['case']]['error'])) {
+ echo "{$set['cases'][$argv['case']]['error']} error\n\n";
+ } else {
+ echo "NULL\n\n";
+ }
+ }
+} elseif (isset($expression)) {
+ // Pass in an expression and STDIN as a standalone argument
+ $data = json_decode(stream_get_contents(STDIN), true);
+} else {
+ die($description);
+}
+
+$runtime = new DebugRuntime(Env::createRuntime());
+$runtime($expression, $data);
diff --git a/vendor/mtdowling/jmespath.php/bin/perf.php b/vendor/mtdowling/jmespath.php/bin/perf.php
new file mode 100755
index 0000000..aa93959
--- /dev/null
+++ b/vendor/mtdowling/jmespath.php/bin/perf.php
@@ -0,0 +1,68 @@
+#!/usr/bin/env php
+<?php
+
+if (file_exists(__DIR__ . '/../vendor/autoload.php')) {
+ require __DIR__ . '/../vendor/autoload.php';
+} elseif (file_exists(__DIR__ . '/../../../autoload.php')) {
+ require __DIR__ . '/../../../autoload.php';
+} else {
+ throw new RuntimeException('Unable to locate autoload.php file.');
+}
+
+$xdebug = new \Composer\XdebugHandler\XdebugHandler('perf.php');
+$xdebug->check();
+unset($xdebug);
+
+$dir = isset($argv[1]) ? $argv[1] : __DIR__ . '/../tests/compliance/perf';
+is_dir($dir) or die('Dir not found: ' . $dir);
+// Warm up the runner
+\JmesPath\Env::search('foo', []);
+
+$total = 0;
+foreach (glob($dir . '/*.json') as $file) {
+ $total += runSuite($file);
+}
+echo "\nTotal time: {$total}\n";
+
+function runSuite($file)
+{
+ $contents = file_get_contents($file);
+ $json = json_decode($contents, true);
+ $total = 0;
+ foreach ($json as $suite) {
+ foreach ($suite['cases'] as $case) {
+ $total += runCase(
+ $suite['given'],
+ $case['expression'],
+ $case['name']
+ );
+ }
+ }
+ return $total;
+}
+
+function runCase($given, $expression, $name)
+{
+ $best = 99999;
+ $runtime = \JmesPath\Env::createRuntime();
+
+ for ($i = 0; $i < 100; $i++) {
+ $t = microtime(true);
+ $runtime($expression, $given);
+ $tryTime = (microtime(true) - $t) * 1000;
+ if ($tryTime < $best) {
+ $best = $tryTime;
+ }
+ if (!getenv('CACHE')) {
+ $runtime = \JmesPath\Env::createRuntime();
+ // Delete compiled scripts if not caching.
+ if ($runtime instanceof \JmesPath\CompilerRuntime) {
+ array_map('unlink', glob(sys_get_temp_dir() . '/jmespath_*.php'));
+ }
+ }
+ }
+
+ printf("time: %07.4fms name: %s\n", $best, $name);
+
+ return $best;
+}
diff --git a/vendor/mtdowling/jmespath.php/composer.json b/vendor/mtdowling/jmespath.php/composer.json
new file mode 100644
index 0000000..6b70068
--- /dev/null
+++ b/vendor/mtdowling/jmespath.php/composer.json
@@ -0,0 +1,39 @@
+{
+ "name": "mtdowling/jmespath.php",
+ "description": "Declaratively specify how to extract elements from a JSON document",
+ "keywords": ["json", "jsonpath"],
+ "license": "MIT",
+
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "[email protected]",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+
+ "require": {
+ "php": "^5.4 || ^7.0 || ^8.0",
+ "symfony/polyfill-mbstring": "^1.17"
+ },
+
+ "require-dev": {
+ "composer/xdebug-handler": "^1.4 || ^2.0",
+ "phpunit/phpunit": "^4.8.36 || ^7.5.15"
+ },
+
+ "autoload": {
+ "psr-4": {
+ "JmesPath\\": "src/"
+ },
+ "files": ["src/JmesPath.php"]
+ },
+
+ "bin": ["bin/jp.php"],
+
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.6-dev"
+ }
+ }
+}
diff --git a/vendor/mtdowling/jmespath.php/src/AstRuntime.php b/vendor/mtdowling/jmespath.php/src/AstRuntime.php
new file mode 100644
index 0000000..03f5f1a
--- /dev/null
+++ b/vendor/mtdowling/jmespath.php/src/AstRuntime.php
@@ -0,0 +1,47 @@
+<?php
+namespace JmesPath;
+
+/**
+ * Uses an external tree visitor to interpret an AST.
+ */
+class AstRuntime
+{
+ private $parser;
+ private $interpreter;
+ private $cache = [];
+ private $cachedCount = 0;
+
+ public function __construct(
+ Parser $parser = null,
+ callable $fnDispatcher = null
+ ) {
+ $fnDispatcher = $fnDispatcher ?: FnDispatcher::getInstance();
+ $this->interpreter = new TreeInterpreter($fnDispatcher);
+ $this->parser = $parser ?: new Parser();
+ }
+
+ /**
+ * Returns data from the provided input that matches a given JMESPath
+ * expression.
+ *
+ * @param string $expression JMESPath expression to evaluate
+ * @param mixed $data Data to search. This data should be data that
+ * is similar to data returned from json_decode
+ * using associative arrays rather than objects.
+ *
+ * @return mixed Returns the matching data or null
+ */
+ public function __invoke($expression, $data)
+ {
+ if (!isset($this->cache[$expression])) {
+ // Clear the AST cache when it hits 1024 entries
+ if (++$this->cachedCount > 1024) {
+ $this->cache = [];
+ $this->cachedCount = 0;
+ }
+ $this->cache[$expression] = $this->parser->parse($expression);
+ }
+
+ return $this->interpreter->visit($this->cache[$expression], $data);
+ }
+}
diff --git a/vendor/mtdowling/jmespath.php/src/CompilerRuntime.php b/vendor/mtdowling/jmespath.php/src/CompilerRuntime.php
new file mode 100644
index 0000000..c26b09c
--- /dev/null
+++ b/vendor/mtdowling/jmespath.php/src/CompilerRuntime.php
@@ -0,0 +1,83 @@
+<?php
+namespace JmesPath;
+
+/**
+ * Compiles JMESPath expressions to PHP source code and executes it.
+ *
+ * JMESPath file names are stored in the cache directory using the following
+ * logic to determine the filename:
+ *
+ * 1. Start with the string "jmespath_"
+ * 2. Append the MD5 checksum of the expression.
+ * 3. Append ".php"
+ */
+class CompilerRuntime
+{
+ private $parser;
+ private $compiler;
+ private $cacheDir;
+ private $interpreter;
+
+ /**
+ * @param string|null $dir Directory used to store compiled PHP files.
+ * @param Parser|null $parser JMESPath parser to utilize
+ * @throws \RuntimeException if the cache directory cannot be created
+ */
+ public function __construct($dir = null, Parser $parser = null)
+ {
+ $this->parser = $parser ?: new Parser();
+ $this->compiler = new TreeCompiler();
+ $dir = $dir ?: sys_get_temp_dir();
+
+ if (!is_dir($dir) && !mkdir($dir, 0755, true)) {
+ throw new \RuntimeException("Unable to create cache directory: $dir");
+ }
+
+ $this->cacheDir = realpath($dir);
+ $this->interpreter = new TreeInterpreter();
+ }
+
+ /**
+ * Returns data from the provided input that matches a given JMESPath
+ * expression.
+ *
+ * @param string $expression JMESPath expression to evaluate
+ * @param mixed $data Data to search. This data should be data that
+ * is similar to data returned from json_decode
+ * using associative arrays rather than objects.
+ *
+ * @return mixed Returns the matching data or null
+ * @throws \RuntimeException
+ */
+ public function __invoke($expression, $data)
+ {
+ $functionName = 'jmespath_' . md5($expression);
+
+ if (!function_exists($functionName)) {
+ $filename = "{$this->cacheDir}/{$functionName}.php";
+ if (!file_exists($filename)) {
+ $this->compile($filename, $expression, $functionName);
+ }
+ require $filename;
+ }
+
+ return $functionName($this->interpreter, $data);
+ }
+
+ private function compile($filename, $expression, $functionName)
+ {
+ $code = $this->compiler->visit(
+ $this->parser->parse($expression),
+ $functionName,
+ $expression
+ );
+
+ if (!file_put_contents($filename, $code)) {
+ throw new \RuntimeException(sprintf(
+ 'Unable to write the compiled PHP code to: %s (%s)',
+ $filename,
+ var_export(error_get_last(), true)
+ ));
+ }
+ }
+}
diff --git a/vendor/mtdowling/jmespath.php/src/DebugRuntime.php b/vendor/mtdowling/jmespath.php/src/DebugRuntime.php
new file mode 100644
index 0000000..4052561
--- /dev/null
+++ b/vendor/mtdowling/jmespath.php/src/DebugRuntime.php
@@ -0,0 +1,109 @@
+<?php
+namespace JmesPath;
+
+/**
+ * Provides CLI debugging information for the AST and Compiler runtimes.
+ */
+class DebugRuntime
+{
+ private $runtime;
+ private $out;
+ private $lexer;
+ private $parser;
+
+ public function __construct(callable $runtime, $output = null)
+ {
+ $this->runtime = $runtime;
+ $this->out = $output ?: STDOUT;
+ $this->lexer = new Lexer();
+ $this->parser = new Parser($this->lexer);
+ }
+
+ public function __invoke($expression, $data)
+ {
+ if ($this->runtime instanceof CompilerRuntime) {
+ return $this->debugCompiled($expression, $data);
+ }
+
+ return $this->debugInterpreted($expression, $data);
+ }
+
+ private function debugInterpreted($expression, $data)
+ {
+ return $this->debugCallback(
+ function () use ($expression, $data) {
+ $runtime = $this->runtime;
+ return $runtime($expression, $data);
+ },
+ $expression,
+ $data
+ );
+ }
+
+ private function debugCompiled($expression, $data)
+ {
+ $result = $this->debugCallback(
+ function () use ($expression, $data) {
+ $runtime = $this->runtime;
+ return $runtime($expression, $data);
+ },
+ $expression,
+ $data
+ );
+ $this->dumpCompiledCode($expression);
+
+ return $result;
+ }
+
+ private function dumpTokens($expression)
+ {
+ $lexer = new Lexer();
+ fwrite($this->out, "Tokens\n======\n\n");
+ $tokens = $lexer->tokenize($expression);
+
+ foreach ($tokens as $t) {
+ fprintf(
+ $this->out,
+ "%3d %-13s %s\n", $t['pos'], $t['type'],
+ json_encode($t['value'])
+ );
+ }
+
+ fwrite($this->out, "\n");
+ }
+
+ private function dumpAst($expression)
+ {
+ $parser = new Parser();
+ $ast = $parser->parse($expression);
+ fwrite($this->out, "AST\n========\n\n");
+ fwrite($this->out, json_encode($ast, JSON_PRETTY_PRINT) . "\n");
+ }
+
+ private function dumpCompiledCode($expression)
+ {
+ fwrite($this->out, "Code\n========\n\n");
+ $dir = sys_get_temp_dir();
+ $hash = md5($expression);
+ $functionName = "jmespath_{$hash}";
+ $filename = "{$dir}/{$functionName}.php";
+ fwrite($this->out, "File: {$filename}\n\n");
+ fprintf($this->out, file_get_contents($filename));
+ }
+
+ private function debugCallback(callable $debugFn, $expression, $data)
+ {
+ fprintf($this->out, "Expression\n==========\n\n%s\n\n", $expression);
+ $this->dumpTokens($expression);
+ $this->dumpAst($expression);
+ fprintf($this->out, "\nData\n====\n\n%s\n\n", json_encode($data, JSON_PRETTY_PRINT));
+ $startTime = microtime(true);
+ $result = $debugFn();
+ $total = microtime(true) - $startTime;
+ fprintf($this->out, "\nResult\n======\n\n%s\n\n", json_encode($result, JSON_PRETTY_PRINT));
+ fwrite($this->out, "Time\n====\n\n");
+ fprintf($this->out, "Total time: %f ms\n\n", $total);
+
+ return $result;
+ }
+}
diff --git a/vendor/mtdowling/jmespath.php/src/Env.php b/vendor/mtdowling/jmespath.php/src/Env.php
new file mode 100644
index 0000000..b22cf25
--- /dev/null
+++ b/vendor/mtdowling/jmespath.php/src/Env.php
@@ -0,0 +1,91 @@
+<?php
+namespace JmesPath;
+
+/**
+ * Provides a simple environment based search.
+ *
+ * The runtime utilized by the Env class can be customized via environment
+ * variables. If the JP_PHP_COMPILE environment variable is specified, then the
+ * CompilerRuntime will be utilized. If set to "on", JMESPath expressions will
+ * be cached to the system's temp directory. Set the environment variable to
+ * a string to cache expressions to a specific directory.
+ */
+final class Env
+{
+ const COMPILE_DIR = 'JP_PHP_COMPILE';
+
+ /**
+ * Returns data from the input array that matches a JMESPath expression.
+ *
+ * @param string $expression JMESPath expression to evaluate
+ * @param mixed $data JSON-like data to search
+ *
+ * @return mixed Returns the matching data or null
+ */
+ public static function search($expression, $data)
+ {
+ static $runtime;
+
+ if (!$runtime) {
+ $runtime = Env::createRuntime();
+ }
+
+ return $runtime($expression, $data);
+ }
+
+ /**
+ * Creates a JMESPath runtime based on environment variables and extensions
+ * available on a system.
+ *
+ * @return callable
+ */
+ public static function createRuntime()
+ {
+ switch ($compileDir = self::getEnvVariable(self::COMPILE_DIR)) {
+ case false: return new AstRuntime();
+ case 'on': return new CompilerRuntime();
+ default: return new CompilerRuntime($compileDir);
+ }
+ }
+
+ /**
+ * Delete all previously compiled JMESPath files from the JP_COMPILE_DIR
+ * directory or sys_get_temp_dir().
+ *
+ * @return int Returns the number of deleted files.
+ */
+ public static function cleanCompileDir()
+ {
+ $total = 0;
+ $compileDir = self::getEnvVariable(self::COMPILE_DIR) ?: sys_get_temp_dir();
+
+ foreach (glob("{$compileDir}/jmespath_*.php") as $file) {
+ $total++;
+ unlink($file);
+ }
+
+ return $total;
+ }
+
+ /**
+ * Reads an environment variable from $_SERVER, $_ENV or via getenv().
+ *
+ * @param string $name
+ *
+ * @return string|null
+ */
+ private static function getEnvVariable($name)
+ {
+ if (array_key_exists($name, $_SERVER)) {
+ return $_SERVER[$name];
+ }
+
+ if (array_key_exists($name, $_ENV)) {
+ return $_ENV[$name];
+ }
+
+ $value = getenv($name);
+
+ return $value === false ? null : $value;
+ }
+}
diff --git a/vendor/mtdowling/jmespath.php/src/FnDispatcher.php b/vendor/mtdowling/jmespath.php/src/FnDispatcher.php
new file mode 100644
index 0000000..0af3ca7
--- /dev/null
+++ b/vendor/mtdowling/jmespath.php/src/FnDispatcher.php
@@ -0,0 +1,407 @@
+<?php
+namespace JmesPath;
+
+/**
+ * Dispatches to named JMESPath functions using a single function that has the
+ * following signature:
+ *
+ * mixed $result = fn(string $function_name, array $args)
+ */
+class FnDispatcher
+{
+ /**
+ * Gets a cached instance of the default function implementations.
+ *
+ * @return FnDispatcher
+ */
+ public static function getInstance()
+ {
+ static $instance = null;
+ if (!$instance) {
+ $instance = new self();
+ }
+
+ return $instance;
+ }
+
+ /**
+ * @param string $fn Function name.
+ * @param array $args Function arguments.
+ *
+ * @return mixed
+ */
+ public function __invoke($fn, array $args)
+ {
+ return $this->{'fn_' . $fn}($args);
+ }
+
+ private function fn_abs(array $args)
+ {
+ $this->validate('abs', $args, [['number']]);
+ return abs($args[0]);
+ }
+
+ private function fn_avg(array $args)
+ {
+ $this->validate('avg', $args, [['array']]);
+ $sum = $this->reduce('avg:0', $args[0], ['number'], function ($a, $b) {
+ return Utils::add($a, $b);
+ });
+ return $args[0] ? ($sum / count($args[0])) : null;
+ }
+
+ private function fn_ceil(array $args)
+ {
+ $this->validate('ceil', $args, [['number']]);
+ return ceil($args[0]);
+ }
+
+ private function fn_contains(array $args)
+ {
+ $this->validate('contains', $args, [['string', 'array'], ['any']]);
+ if (is_array($args[0])) {
+ return in_array($args[1], $args[0]);
+ } elseif (is_string($args[1])) {
+ return mb_strpos($args[0], $args[1], 0, 'UTF-8') !== false;
+ } else {
+ return null;
+ }
+ }
+
+ private function fn_ends_with(array $args)
+ {
+ $this->validate('ends_with', $args, [['string'], ['string']]);
+ list($search, $suffix) = $args;
+ return $suffix === '' || mb_substr($search, -mb_strlen($suffix, 'UTF-8'), null, 'UTF-8') === $suffix;
+ }
+
+ private function fn_floor(array $args)
+ {
+ $this->validate('floor', $args, [['number']]);
+ return floor($args[0]);
+ }
+
+ private function fn_not_null(array $args)
+ {
+ if (!$args) {
+ throw new \RuntimeException(
+ "not_null() expects 1 or more arguments, 0 were provided"
+ );
+ }
+
+ return array_reduce($args, function ($carry, $item) {
+ return $carry !== null ? $carry : $item;
+ });
+ }
+
+ private function fn_join(array $args)
+ {
+ $this->validate('join', $args, [['string'], ['array']]);
+ $fn = function ($a, $b, $i) use ($args) {
+ return $i ? ($a . $args[0] . $b) : $b;
+ };
+ return $this->reduce('join:0', $args[1], ['string'], $fn);
+ }
+
+ private function fn_keys(array $args)
+ {
+ $this->validate('keys', $args, [['object']]);
+ return array_keys((array) $args[0]);
+ }
+
+ private function fn_length(array $args)
+ {
+ $this->validate('length', $args, [['string', 'array', 'object']]);
+ return is_string($args[0]) ? mb_strlen($args[0], 'UTF-8') : count((array) $args[0]);
+ }
+
+ private function fn_max(array $args)
+ {
+ $this->validate('max', $args, [['array']]);
+ $fn = function ($a, $b) {
+ return $a >= $b ? $a : $b;
+ };
+ return $this->reduce('max:0', $args[0], ['number', 'string'], $fn);
+ }
+
+ private function fn_max_by(array $args)
+ {
+ $this->validate('max_by', $args, [['array'], ['expression']]);
+ $expr = $this->wrapExpression('max_by:1', $args[1], ['number', 'string']);
+ $fn = function ($carry, $item, $index) use ($expr) {
+ return $index
+ ? ($expr($carry) >= $expr($item) ? $carry : $item)
+ : $item;
+ };
+ return $this->reduce('max_by:1', $args[0], ['any'], $fn);
+ }
+
+ private function fn_min(array $args)
+ {
+ $this->validate('min', $args, [['array']]);
+ $fn = function ($a, $b, $i) {
+ return $i && $a <= $b ? $a : $b;
+ };
+ return $this->reduce('min:0', $args[0], ['number', 'string'], $fn);
+ }
+
+ private function fn_min_by(array $args)
+ {
+ $this->validate('min_by', $args, [['array'], ['expression']]);
+ $expr = $this->wrapExpression('min_by:1', $args[1], ['number', 'string']);
+ $i = -1;
+ $fn = function ($a, $b) use ($expr, &$i) {
+ return ++$i ? ($expr($a) <= $expr($b) ? $a : $b) : $b;
+ };
+ return $this->reduce('min_by:1', $args[0], ['any'], $fn);
+ }
+
+ private function fn_reverse(array $args)
+ {
+ $this->validate('reverse', $args, [['array', 'string']]);
+ if (is_array($args[0])) {
+ return array_reverse($args[0]);
+ } elseif (is_string($args[0])) {
+ return strrev($args[0]);
+ } else {
+ throw new \RuntimeException('Cannot reverse provided argument');
+ }
+ }
+
+ private function fn_sum(array $args)
+ {
+ $this->validate('sum', $args, [['array']]);
+ $fn = function ($a, $b) {
+ return Utils::add($a, $b);
+ };
+ return $this->reduce('sum:0', $args[0], ['number'], $fn);
+ }
+
+ private function fn_sort(array $args)
+ {
+ $this->validate('sort', $args, [['array']]);
+ $valid = ['string', 'number'];
+ return Utils::stableSort($args[0], function ($a, $b) use ($valid) {
+ $this->validateSeq('sort:0', $valid, $a, $b);
+ return strnatcmp($a, $b);
+ });
+ }
+
+ private function fn_sort_by(array $args)
+ {
+ $this->validate('sort_by', $args, [['array'], ['expression']]);
+ $expr = $args[1];
+ $valid = ['string', 'number'];
+ return Utils::stableSort(
+ $args[0],
+ function ($a, $b) use ($expr, $valid) {
+ $va = $expr($a);
+ $vb = $expr($b);
+ $this->validateSeq('sort_by:0', $valid, $va, $vb);
+ return strnatcmp($va, $vb);
+ }
+ );
+ }
+
+ private function fn_starts_with(array $args)
+ {
+ $this->validate('starts_with', $args, [['string'], ['string']]);
+ list($search, $prefix) = $args;
+ return $prefix === '' || mb_strpos($search, $prefix, 0, 'UTF-8') === 0;
+ }
+
+ private function fn_type(array $args)
+ {
+ $this->validateArity('type', count($args), 1);
+ return Utils::type($args[0]);
+ }
+
+ private function fn_to_string(array $args)
+ {
+ $this->validateArity('to_string', count($args), 1);
+ $v = $args[0];
+ if (is_string($v)) {
+ return $v;
+ } elseif (is_object($v)
+ && !($v instanceof \JsonSerializable)
+ && method_exists($v, '__toString')
+ ) {
+ return (string) $v;
+ }
+
+ return json_encode($v);
+ }
+
+ private function fn_to_number(array $args)
+ {
+ $this->validateArity('to_number', count($args), 1);
+ $value = $args[0];
+ $type = Utils::type($value);
+ if ($type == 'number') {
+ return $value;
+ } elseif ($type == 'string' && is_numeric($value)) {
+ return mb_strpos($value, '.', 0, 'UTF-8') ? (float) $value : (int) $value;
+ } else {
+ return null;
+ }
+ }
+
+ private function fn_values(array $args)
+ {
+ $this->validate('values', $args, [['array', 'object']]);
+ return array_values((array) $args[0]);
+ }
+
+ private function fn_merge(array $args)
+ {
+ if (!$args) {
+ throw new \RuntimeException(
+ "merge() expects 1 or more arguments, 0 were provided"
+ );
+ }
+
+ return call_user_func_array('array_replace', $args);
+ }
+
+ private function fn_to_array(array $args)
+ {
+ $this->validate('to_array', $args, [['any']]);
+
+ return Utils::isArray($args[0]) ? $args[0] : [$args[0]];
+ }
+
+ private function fn_map(array $args)
+ {
+ $this->validate('map', $args, [['expression'], ['any']]);
+ $result = [];
+ foreach ($args[1] as $a) {
+ $result[] = $args[0]($a);
+ }
+ return $result;
+ }
+
+ private function typeError($from, $msg)
+ {
+ if (mb_strpos($from, ':', 0, 'UTF-8')) {
+ list($fn, $pos) = explode(':', $from);
+ throw new \RuntimeException(
+ sprintf('Argument %d of %s %s', $pos, $fn, $msg)
+ );
+ } else {
+ throw new \RuntimeException(
+ sprintf('Type error: %s %s', $from, $msg)
+ );
+ }
+ }
+
+ private function validateArity($from, $given, $expected)
+ {
+ if ($given != $expected) {
+ $err = "%s() expects {$expected} arguments, {$given} were provided";
+ throw new \RuntimeException(sprintf($err, $from));
+ }
+ }
+
+ private function validate($from, $args, $types = [])
+ {
+ $this->validateArity($from, count($args), count($types));
+ foreach ($args as $index => $value) {
+ if (!isset($types[$index]) || !$types[$index]) {
+ continue;
+ }
+ $this->validateType("{$from}:{$index}", $value, $types[$index]);
+ }
+ }
+
+ private function validateType($from, $value, array $types)
+ {
+ if ($types[0] == 'any'
+ || in_array(Utils::type($value), $types)
+ || ($value === [] && in_array('object', $types))
+ ) {
+ return;
+ }
+ $msg = 'must be one of the following types: ' . implode(', ', $types)
+ . '. ' . Utils::type($value) . ' found';
+ $this->typeError($from, $msg);
+ }
+
+ /**
+ * Validates value A and B, ensures they both are correctly typed, and of
+ * the same type.
+ *
+ * @param string $from String of function:argument_position
+ * @param array $types Array of valid value types.
+ * @param mixed $a Value A
+ * @param mixed $b Value B
+ */
+ private function validateSeq($from, array $types, $a, $b)
+ {
+ $ta = Utils::type($a);
+ $tb = Utils::type($b);
+
+ if ($ta !== $tb) {
+ $msg = "encountered a type mismatch in sequence: {$ta}, {$tb}";
+ $this->typeError($from, $msg);
+ }
+
+ $typeMatch = ($types && $types[0] == 'any') || in_array($ta, $types);
+ if (!$typeMatch) {
+ $msg = 'encountered a type error in sequence. The argument must be '
+ . 'an array of ' . implode('|', $types) . ' types. '
+ . "Found {$ta}, {$tb}.";
+ $this->typeError($from, $msg);
+ }
+ }
+
+ /**
+ * Reduces and validates an array of values to a single value using a fn.
+ *
+ * @param string $from String of function:argument_position
+ * @param array $values Values to reduce.
+ * @param array $types Array of valid value types.
+ * @param callable $reduce Reduce function that accepts ($carry, $item).
+ *
+ * @return mixed
+ */
+ private function reduce($from, array $values, array $types, callable $reduce)
+ {
+ $i = -1;
+ return array_reduce(
+ $values,
+ function ($carry, $item) use ($from, $types, $reduce, &$i) {
+ if (++$i > 0) {
+ $this->validateSeq($from, $types, $carry, $item);
+ }
+ return $reduce($carry, $item, $i);
+ }
+ );
+ }
+
+ /**
+ * Validates the return values of expressions as they are applied.
+ *
+ * @param string $from Function name : position
+ * @param callable $expr Expression function to validate.
+ * @param array $types Array of acceptable return type values.
+ *
+ * @return callable Returns a wrapped function
+ */
+ private function wrapExpression($from, callable $expr, array $types)
+ {
+ list($fn, $pos) = explode(':', $from);
+ $from = "The expression return value of argument {$pos} of {$fn}";
+ return function ($value) use ($from, $expr, $types) {
+ $value = $expr($value);
+ $this->validateType($from, $value, $types);
+ return $value;
+ };
+ }
+
+ /** @internal Pass function name validation off to runtime */
+ public function __call($name, $args)
+ {
+ $name = str_replace('fn_', '', $name);
+ throw new \RuntimeException("Call to undefined function {$name}");
+ }
+}
diff --git a/vendor/mtdowling/jmespath.php/src/JmesPath.php b/vendor/mtdowling/jmespath.php/src/JmesPath.php
new file mode 100644
index 0000000..d24e516
--- /dev/null
+++ b/vendor/mtdowling/jmespath.php/src/JmesPath.php
@@ -0,0 +1,17 @@
+<?php
+namespace JmesPath;
+
+/**
+ * Returns data from the input array that matches a JMESPath expression.
+ *
+ * @param string $expression Expression to search.
+ * @param mixed $data Data to search.
+ *
+ * @return mixed
+ */
+if (!function_exists(__NAMESPACE__ . '\search')) {
+ function search($expression, $data)
+ {
+ return Env::search($expression, $data);
+ }
+}
diff --git a/vendor/mtdowling/jmespath.php/src/Lexer.php b/vendor/mtdowling/jmespath.php/src/Lexer.php
new file mode 100644
index 0000000..c98ffb6
--- /dev/null
+++ b/vendor/mtdowling/jmespath.php/src/Lexer.php
@@ -0,0 +1,444 @@
+<?php
+namespace JmesPath;
+
+/**
+ * Tokenizes JMESPath expressions
+ */
+class Lexer
+{
+ const T_DOT = 'dot';
+ const T_STAR = 'star';
+ const T_COMMA = 'comma';
+ const T_COLON = 'colon';
+ const T_CURRENT = 'current';
+ const T_EXPREF = 'expref';
+ const T_LPAREN = 'lparen';
+ const T_RPAREN = 'rparen';
+ const T_LBRACE = 'lbrace';
+ const T_RBRACE = 'rbrace';
+ const T_LBRACKET = 'lbracket';
+ const T_RBRACKET = 'rbracket';
+ const T_FLATTEN = 'flatten';
+ const T_IDENTIFIER = 'identifier';
+ const T_NUMBER = 'number';
+ const T_QUOTED_IDENTIFIER = 'quoted_identifier';
+ const T_UNKNOWN = 'unknown';
+ const T_PIPE = 'pipe';
+ const T_OR = 'or';
+ const T_AND = 'and';
+ const T_NOT = 'not';
+ const T_FILTER = 'filter';
+ const T_LITERAL = 'literal';
+ const T_EOF = 'eof';
+ const T_COMPARATOR = 'comparator';
+
+ const STATE_IDENTIFIER = 0;
+ const STATE_NUMBER = 1;
+ const STATE_SINGLE_CHAR = 2;
+ const STATE_WHITESPACE = 3;
+ const STATE_STRING_LITERAL = 4;
+ const STATE_QUOTED_STRING = 5;
+ const STATE_JSON_LITERAL = 6;
+ const STATE_LBRACKET = 7;
+ const STATE_PIPE = 8;
+ const STATE_LT = 9;
+ const STATE_GT = 10;
+ const STATE_EQ = 11;
+ const STATE_NOT = 12;
+ const STATE_AND = 13;
+
+ /** @var array We know what token we are consuming based on each char */
+ private static $transitionTable = [
+ '<' => self::STATE_LT,
+ '>' => self::STATE_GT,
+ '=' => self::STATE_EQ,
+ '!' => self::STATE_NOT,
+ '[' => self::STATE_LBRACKET,
+ '|' => self::STATE_PIPE,
+ '&' => self::STATE_AND,
+ '`' => self::STATE_JSON_LITERAL,
+ '"' => self::STATE_QUOTED_STRING,
+ "'" => self::STATE_STRING_LITERAL,
+ '-' => self::STATE_NUMBER,
+ '0' => self::STATE_NUMBER,
+ '1' => self::STATE_NUMBER,
+ '2' => self::STATE_NUMBER,
+ '3' => self::STATE_NUMBER,
+ '4' => self::STATE_NUMBER,
+ '5' => self::STATE_NUMBER,
+ '6' => self::STATE_NUMBER,
+ '7' => self::STATE_NUMBER,
+ '8' => self::STATE_NUMBER,
+ '9' => self::STATE_NUMBER,
+ ' ' => self::STATE_WHITESPACE,
+ "\t" => self::STATE_WHITESPACE,
+ "\n" => self::STATE_WHITESPACE,
+ "\r" => self::STATE_WHITESPACE,
+ '.' => self::STATE_SINGLE_CHAR,
+ '*' => self::STATE_SINGLE_CHAR,
+ ']' => self::STATE_SINGLE_CHAR,
+ ',' => self::STATE_SINGLE_CHAR,
+ ':' => self::STATE_SINGLE_CHAR,
+ '@' => self::STATE_SINGLE_CHAR,
+ '(' => self::STATE_SINGLE_CHAR,
+ ')' => self::STATE_SINGLE_CHAR,
+ '{' => self::STATE_SINGLE_CHAR,
+ '}' => self::STATE_SINGLE_CHAR,
+ '_' => self::STATE_IDENTIFIER,
+ 'A' => self::STATE_IDENTIFIER,
+ 'B' => self::STATE_IDENTIFIER,
+ 'C' => self::STATE_IDENTIFIER,
+ 'D' => self::STATE_IDENTIFIER,
+ 'E' => self::STATE_IDENTIFIER,
+ 'F' => self::STATE_IDENTIFIER,
+ 'G' => self::STATE_IDENTIFIER,
+ 'H' => self::STATE_IDENTIFIER,
+ 'I' => self::STATE_IDENTIFIER,
+ 'J' => self::STATE_IDENTIFIER,
+ 'K' => self::STATE_IDENTIFIER,
+ 'L' => self::STATE_IDENTIFIER,
+ 'M' => self::STATE_IDENTIFIER,
+ 'N' => self::STATE_IDENTIFIER,
+ 'O' => self::STATE_IDENTIFIER,
+ 'P' => self::STATE_IDENTIFIER,
+ 'Q' => self::STATE_IDENTIFIER,
+ 'R' => self::STATE_IDENTIFIER,
+ 'S' => self::STATE_IDENTIFIER,
+ 'T' => self::STATE_IDENTIFIER,
+ 'U' => self::STATE_IDENTIFIER,
+ 'V' => self::STATE_IDENTIFIER,
+ 'W' => self::STATE_IDENTIFIER,
+ 'X' => self::STATE_IDENTIFIER,
+ 'Y' => self::STATE_IDENTIFIER,
+ 'Z' => self::STATE_IDENTIFIER,
+ 'a' => self::STATE_IDENTIFIER,
+ 'b' => self::STATE_IDENTIFIER,
+ 'c' => self::STATE_IDENTIFIER,
+ 'd' => self::STATE_IDENTIFIER,
+ 'e' => self::STATE_IDENTIFIER,
+ 'f' => self::STATE_IDENTIFIER,
+ 'g' => self::STATE_IDENTIFIER,
+ 'h' => self::STATE_IDENTIFIER,
+ 'i' => self::STATE_IDENTIFIER,
+ 'j' => self::STATE_IDENTIFIER,
+ 'k' => self::STATE_IDENTIFIER,
+ 'l' => self::STATE_IDENTIFIER,
+ 'm' => self::STATE_IDENTIFIER,
+ 'n' => self::STATE_IDENTIFIER,
+ 'o' => self::STATE_IDENTIFIER,
+ 'p' => self::STATE_IDENTIFIER,
+ 'q' => self::STATE_IDENTIFIER,
+ 'r' => self::STATE_IDENTIFIER,
+ 's' => self::STATE_IDENTIFIER,
+ 't' => self::STATE_IDENTIFIER,
+ 'u' => self::STATE_IDENTIFIER,
+ 'v' => self::STATE_IDENTIFIER,
+ 'w' => self::STATE_IDENTIFIER,
+ 'x' => self::STATE_IDENTIFIER,
+ 'y' => self::STATE_IDENTIFIER,
+ 'z' => self::STATE_IDENTIFIER,
+ ];
+
+ /** @var array Valid identifier characters after first character */
+ private $validIdentifier = [
+ 'A' => true, 'B' => true, 'C' => true, 'D' => true, 'E' => true,
+ 'F' => true, 'G' => true, 'H' => true, 'I' => true, 'J' => true,
+ 'K' => true, 'L' => true, 'M' => true, 'N' => true, 'O' => true,
+ 'P' => true, 'Q' => true, 'R' => true, 'S' => true, 'T' => true,
+ 'U' => true, 'V' => true, 'W' => true, 'X' => true, 'Y' => true,
+ 'Z' => true, 'a' => true, 'b' => true, 'c' => true, 'd' => true,
+ 'e' => true, 'f' => true, 'g' => true, 'h' => true, 'i' => true,
+ 'j' => true, 'k' => true, 'l' => true, 'm' => true, 'n' => true,
+ 'o' => true, 'p' => true, 'q' => true, 'r' => true, 's' => true,
+ 't' => true, 'u' => true, 'v' => true, 'w' => true, 'x' => true,
+ 'y' => true, 'z' => true, '_' => true, '0' => true, '1' => true,
+ '2' => true, '3' => true, '4' => true, '5' => true, '6' => true,
+ '7' => true, '8' => true, '9' => true,
+ ];
+
+ /** @var array Valid number characters after the first character */
+ private $numbers = [
+ '0' => true, '1' => true, '2' => true, '3' => true, '4' => true,
+ '5' => true, '6' => true, '7' => true, '8' => true, '9' => true
+ ];
+
+ /** @var array Map of simple single character tokens */
+ private $simpleTokens = [
+ '.' => self::T_DOT,
+ '*' => self::T_STAR,
+ ']' => self::T_RBRACKET,
+ ',' => self::T_COMMA,
+ ':' => self::T_COLON,
+ '@' => self::T_CURRENT,
+ '(' => self::T_LPAREN,
+ ')' => self::T_RPAREN,
+ '{' => self::T_LBRACE,
+ '}' => self::T_RBRACE,
+ ];
+
+ /**
+ * Tokenize the JMESPath expression into an array of tokens hashes that
+ * contain a 'type', 'value', and 'key'.
+ *
+ * @param string $input JMESPath input
+ *
+ * @return array
+ * @throws SyntaxErrorException
+ */
+ public function tokenize($input)
+ {
+ $tokens = [];
+
+ if ($input === '') {
+ goto eof;
+ }
+
+ $chars = str_split($input);
+
+ while (false !== ($current = current($chars))) {
+
+ // Every character must be in the transition character table.
+ if (!isset(self::$transitionTable[$current])) {
+ $tokens[] = [
+ 'type' => self::T_UNKNOWN,
+ 'pos' => key($chars),
+ 'value' => $current
+ ];
+ next($chars);
+ continue;
+ }
+
+ $state = self::$transitionTable[$current];
+
+ if ($state === self::STATE_SINGLE_CHAR) {
+
+ // Consume simple tokens like ".", ",", "@", etc.
+ $tokens[] = [
+ 'type' => $this->simpleTokens[$current],
+ 'pos' => key($chars),
+ 'value' => $current
+ ];
+ next($chars);
+
+ } elseif ($state === self::STATE_IDENTIFIER) {
+
+ // Consume identifiers
+ $start = key($chars);
+ $buffer = '';
+ do {
+ $buffer .= $current;
+ $current = next($chars);
+ } while ($current !== false && isset($this->validIdentifier[$current]));
+ $tokens[] = [
+ 'type' => self::T_IDENTIFIER,
+ 'value' => $buffer,
+ 'pos' => $start
+ ];
+
+ } elseif ($state === self::STATE_WHITESPACE) {
+
+ // Skip whitespace
+ next($chars);
+
+ } elseif ($state === self::STATE_LBRACKET) {
+
+ // Consume "[", "[?", and "[]"
+ $position = key($chars);
+ $actual = next($chars);
+ if ($actual === ']') {
+ next($chars);
+ $tokens[] = [
+ 'type' => self::T_FLATTEN,
+ 'pos' => $position,
+ 'value' => '[]'
+ ];
+ } elseif ($actual === '?') {
+ next($chars);
+ $tokens[] = [
+ 'type' => self::T_FILTER,
+ 'pos' => $position,
+ 'value' => '[?'
+ ];
+ } else {
+ $tokens[] = [
+ 'type' => self::T_LBRACKET,
+ 'pos' => $position,
+ 'value' => '['
+ ];
+ }
+
+ } elseif ($state === self::STATE_STRING_LITERAL) {
+
+ // Consume raw string literals
+ $t = $this->inside($chars, "'", self::T_LITERAL);
+ $t['value'] = str_replace("\\'", "'", $t['value']);
+ $tokens[] = $t;
+
+ } elseif ($state === self::STATE_PIPE) {
+
+ // Consume pipe and OR
+ $tokens[] = $this->matchOr($chars, '|', '|', self::T_OR, self::T_PIPE);
+
+ } elseif ($state == self::STATE_JSON_LITERAL) {
+
+ // Consume JSON literals
+ $token = $this->inside($chars, '`', self::T_LITERAL);
+ if ($token['type'] === self::T_LITERAL) {
+ $token['value'] = str_replace('\\`', '`', $token['value']);
+ $token = $this->parseJson($token);
+ }
+ $tokens[] = $token;
+
+ } elseif ($state == self::STATE_NUMBER) {
+
+ // Consume numbers
+ $start = key($chars);
+ $buffer = '';
+ do {
+ $buffer .= $current;
+ $current = next($chars);
+ } while ($current !== false && isset($this->numbers[$current]));
+ $tokens[] = [
+ 'type' => self::T_NUMBER,
+ 'value' => (int)$buffer,
+ 'pos' => $start
+ ];
+
+ } elseif ($state === self::STATE_QUOTED_STRING) {
+
+ // Consume quoted identifiers
+ $token = $this->inside($chars, '"', self::T_QUOTED_IDENTIFIER);
+ if ($token['type'] === self::T_QUOTED_IDENTIFIER) {
+ $token['value'] = '"' . $token['value'] . '"';
+ $token = $this->parseJson($token);
+ }
+ $tokens[] = $token;
+
+ } elseif ($state === self::STATE_EQ) {
+
+ // Consume equals
+ $tokens[] = $this->matchOr($chars, '=', '=', self::T_COMPARATOR, self::T_UNKNOWN);
+
+ } elseif ($state == self::STATE_AND) {
+
+ $tokens[] = $this->matchOr($chars, '&', '&', self::T_AND, self::T_EXPREF);
+
+ } elseif ($state === self::STATE_NOT) {
+
+ // Consume not equal
+ $tokens[] = $this->matchOr($chars, '!', '=', self::T_COMPARATOR, self::T_NOT);
+
+ } else {
+
+ // either '<' or '>'
+ // Consume less than and greater than
+ $tokens[] = $this->matchOr($chars, $current, '=', self::T_COMPARATOR, self::T_COMPARATOR);
+
+ }
+ }
+
+ eof:
+ $tokens[] = [
+ 'type' => self::T_EOF,
+ 'pos' => mb_strlen($input, 'UTF-8'),
+ 'value' => null
+ ];
+
+ return $tokens;
+ }
+
+ /**
+ * Returns a token based on whether or not the next token matches the
+ * expected value. If it does, a token of "$type" is returned. Otherwise,
+ * a token of "$orElse" type is returned.
+ *
+ * @param array $chars Array of characters by reference.
+ * @param string $current The current character.
+ * @param string $expected Expected character.
+ * @param string $type Expected result type.
+ * @param string $orElse Otherwise return a token of this type.
+ *
+ * @return array Returns a conditional token.
+ */
+ private function matchOr(array &$chars, $current, $expected, $type, $orElse)
+ {
+ if (next($chars) === $expected) {
+ next($chars);
+ return [
+ 'type' => $type,
+ 'pos' => key($chars) - 1,
+ 'value' => $current . $expected
+ ];
+ }
+
+ return [
+ 'type' => $orElse,
+ 'pos' => key($chars) - 1,
+ 'value' => $current
+ ];
+ }
+
+ /**
+ * Returns a token the is the result of consuming inside of delimiter
+ * characters. Escaped delimiters will be adjusted before returning a
+ * value. If the token is not closed, "unknown" is returned.
+ *
+ * @param array $chars Array of characters by reference.
+ * @param string $delim The delimiter character.
+ * @param string $type Token type.
+ *
+ * @return array Returns the consumed token.
+ */
+ private function inside(array &$chars, $delim, $type)
+ {
+ $position = key($chars);
+ $current = next($chars);
+ $buffer = '';
+
+ while ($current !== $delim) {
+ if ($current === '\\') {
+ $buffer .= '\\';
+ $current = next($chars);
+ }
+ if ($current === false) {
+ // Unclosed delimiter
+ return [
+ 'type' => self::T_UNKNOWN,
+ 'value' => $buffer,
+ 'pos' => $position
+ ];
+ }
+ $buffer .= $current;
+ $current = next($chars);
+ }
+
+ next($chars);
+
+ return ['type' => $type, 'value' => $buffer, 'pos' => $position];
+ }
+
+ /**
+ * Parses a JSON token or sets the token type to "unknown" on error.
+ *
+ * @param array $token Token that needs parsing.
+ *
+ * @return array Returns a token with a parsed value.
+ */
+ private function parseJson(array $token)
+ {
+ $value = json_decode($token['value'], true);
+
+ if ($error = json_last_error()) {
+ // Legacy support for elided quotes. Try to parse again by adding
+ // quotes around the bad input value.
+ $value = json_decode('"' . $token['value'] . '"', true);
+ if ($error = json_last_error()) {
+ $token['type'] = self::T_UNKNOWN;
+ return $token;
+ }
+ }
+
+ $token['value'] = $value;
+ return $token;
+ }
+}
diff --git a/vendor/mtdowling/jmespath.php/src/Parser.php b/vendor/mtdowling/jmespath.php/src/Parser.php
new file mode 100644
index 0000000..0733f20
--- /dev/null
+++ b/vendor/mtdowling/jmespath.php/src/Parser.php
@@ -0,0 +1,519 @@
+<?php
+namespace JmesPath;
+
+use JmesPath\Lexer as T;
+
+/**
+ * JMESPath Pratt parser
+ * @link http://hall.org.ua/halls/wizzard/pdf/Vaughan.Pratt.TDOP.pdf
+ */
+class Parser
+{
+ /** @var Lexer */
+ private $lexer;
+ private $tokens;
+ private $token;
+ private $tpos;
+ private $expression;
+ private static $nullToken = ['type' => T::T_EOF];
+ private static $currentNode = ['type' => T::T_CURRENT];
+
+ private static $bp = [
+ T::T_EOF => 0,
+ T::T_QUOTED_IDENTIFIER => 0,
+ T::T_IDENTIFIER => 0,
+ T::T_RBRACKET => 0,
+ T::T_RPAREN => 0,
+ T::T_COMMA => 0,
+ T::T_RBRACE => 0,
+ T::T_NUMBER => 0,
+ T::T_CURRENT => 0,
+ T::T_EXPREF => 0,
+ T::T_COLON => 0,
+ T::T_PIPE => 1,
+ T::T_OR => 2,
+ T::T_AND => 3,
+ T::T_COMPARATOR => 5,
+ T::T_FLATTEN => 9,
+ T::T_STAR => 20,
+ T::T_FILTER => 21,
+ T::T_DOT => 40,
+ T::T_NOT => 45,
+ T::T_LBRACE => 50,
+ T::T_LBRACKET => 55,
+ T::T_LPAREN => 60,
+ ];
+
+ /** @var array Acceptable tokens after a dot token */
+ private static $afterDot = [
+ T::T_IDENTIFIER => true, // foo.bar
+ T::T_QUOTED_IDENTIFIER => true, // foo."bar"
+ T::T_STAR => true, // foo.*
+ T::T_LBRACE => true, // foo[1]
+ T::T_LBRACKET => true, // foo{a: 0}
+ T::T_FILTER => true, // foo.[?bar==10]
+ ];
+
+ /**
+ * @param Lexer|null $lexer Lexer used to tokenize expressions
+ */
+ public function __construct(Lexer $lexer = null)
+ {
+ $this->lexer = $lexer ?: new Lexer();
+ }
+
+ /**
+ * Parses a JMESPath expression into an AST
+ *
+ * @param string $expression JMESPath expression to compile
+ *
+ * @return array Returns an array based AST
+ * @throws SyntaxErrorException
+ */
+ public function parse($expression)
+ {
+ $this->expression = $expression;
+ $this->tokens = $this->lexer->tokenize($expression);
+ $this->tpos = -1;
+ $this->next();
+ $result = $this->expr();
+
+ if ($this->token['type'] === T::T_EOF) {
+ return $result;
+ }
+
+ throw $this->syntax('Did not reach the end of the token stream');
+ }
+
+ /**
+ * Parses an expression while rbp < lbp.
+ *
+ * @param int $rbp Right bound precedence
+ *
+ * @return array
+ */
+ private function expr($rbp = 0)
+ {
+ $left = $this->{"nud_{$this->token['type']}"}();
+ while ($rbp < self::$bp[$this->token['type']]) {
+ $left = $this->{"led_{$this->token['type']}"}($left);
+ }
+
+ return $left;
+ }
+
+ private function nud_identifier()
+ {
+ $token = $this->token;
+ $this->next();
+ return ['type' => 'field', 'value' => $token['value']];
+ }
+
+ private function nud_quoted_identifier()
+ {
+ $token = $this->token;
+ $this->next();
+ $this->assertNotToken(T::T_LPAREN);
+ return ['type' => 'field', 'value' => $token['value']];
+ }
+
+ private function nud_current()
+ {
+ $this->next();
+ return self::$currentNode;
+ }
+
+ private function nud_literal()
+ {
+ $token = $this->token;
+ $this->next();
+ return ['type' => 'literal', 'value' => $token['value']];
+ }
+
+ private function nud_expref()
+ {
+ $this->next();
+ return ['type' => T::T_EXPREF, 'children' => [$this->expr(self::$bp[T::T_EXPREF])]];
+ }
+
+ private function nud_not()
+ {
+ $this->next();
+ return ['type' => T::T_NOT, 'children' => [$this->expr(self::$bp[T::T_NOT])]];
+ }
+
+ private function nud_lparen()
+ {
+ $this->next();
+ $result = $this->expr(0);
+ if ($this->token['type'] !== T::T_RPAREN) {
+ throw $this->syntax('Unclosed `(`');
+ }
+ $this->next();
+ return $result;
+ }
+
+ private function nud_lbrace()
+ {
+ static $validKeys = [T::T_QUOTED_IDENTIFIER => true, T::T_IDENTIFIER => true];
+ $this->next($validKeys);
+ $pairs = [];
+
+ do {
+ $pairs[] = $this->parseKeyValuePair();
+ if ($this->token['type'] == T::T_COMMA) {
+ $this->next($validKeys);
+ }
+ } while ($this->token['type'] !== T::T_RBRACE);
+
+ $this->next();
+
+ return['type' => 'multi_select_hash', 'children' => $pairs];
+ }
+
+ private function nud_flatten()
+ {
+ return $this->led_flatten(self::$currentNode);
+ }
+
+ private function nud_filter()
+ {
+ return $this->led_filter(self::$currentNode);
+ }
+
+ private function nud_star()
+ {
+ return $this->parseWildcardObject(self::$currentNode);
+ }
+
+ private function nud_lbracket()
+ {
+ $this->next();
+ $type = $this->token['type'];
+ if ($type == T::T_NUMBER || $type == T::T_COLON) {
+ return $this->parseArrayIndexExpression();
+ } elseif ($type == T::T_STAR && $this->lookahead() == T::T_RBRACKET) {
+ return $this->parseWildcardArray();
+ } else {
+ return $this->parseMultiSelectList();
+ }
+ }
+
+ private function led_lbracket(array $left)
+ {
+ static $nextTypes = [T::T_NUMBER => true, T::T_COLON => true, T::T_STAR => true];
+ $this->next($nextTypes);
+ switch ($this->token['type']) {
+ case T::T_NUMBER:
+ case T::T_COLON:
+ return [
+ 'type' => 'subexpression',
+ 'children' => [$left, $this->parseArrayIndexExpression()]
+ ];
+ default:
+ return $this->parseWildcardArray($left);
+ }
+ }
+
+ private function led_flatten(array $left)
+ {
+ $this->next();
+
+ return [
+ 'type' => 'projection',
+ 'from' => 'array',
+ 'children' => [
+ ['type' => T::T_FLATTEN, 'children' => [$left]],
+ $this->parseProjection(self::$bp[T::T_FLATTEN])
+ ]
+ ];
+ }
+
+ private function led_dot(array $left)
+ {
+ $this->next(self::$afterDot);
+
+ if ($this->token['type'] == T::T_STAR) {
+ return $this->parseWildcardObject($left);
+ }
+
+ return [
+ 'type' => 'subexpression',
+ 'children' => [$left, $this->parseDot(self::$bp[T::T_DOT])]
+ ];
+ }
+
+ private function led_or(array $left)
+ {
+ $this->next();
+ return [
+ 'type' => T::T_OR,
+ 'children' => [$left, $this->expr(self::$bp[T::T_OR])]
+ ];
+ }
+
+ private function led_and(array $left)
+ {
+ $this->next();
+ return [
+ 'type' => T::T_AND,
+ 'children' => [$left, $this->expr(self::$bp[T::T_AND])]
+ ];
+ }
+
+ private function led_pipe(array $left)
+ {
+ $this->next();
+ return [
+ 'type' => T::T_PIPE,
+ 'children' => [$left, $this->expr(self::$bp[T::T_PIPE])]
+ ];
+ }
+
+ private function led_lparen(array $left)
+ {
+ $args = [];
+ $this->next();
+
+ while ($this->token['type'] != T::T_RPAREN) {
+ $args[] = $this->expr(0);
+ if ($this->token['type'] == T::T_COMMA) {
+ $this->next();
+ }
+ }
+
+ $this->next();
+
+ return [
+ 'type' => 'function',
+ 'value' => $left['value'],
+ 'children' => $args
+ ];
+ }
+
+ private function led_filter(array $left)
+ {
+ $this->next();
+ $expression = $this->expr();
+ if ($this->token['type'] != T::T_RBRACKET) {
+ throw $this->syntax('Expected a closing rbracket for the filter');
+ }
+
+ $this->next();
+ $rhs = $this->parseProjection(self::$bp[T::T_FILTER]);
+
+ return [
+ 'type' => 'projection',
+ 'from' => 'array',
+ 'children' => [
+ $left ?: self::$currentNode,
+ [
+ 'type' => 'condition',
+ 'children' => [$expression, $rhs]
+ ]
+ ]
+ ];
+ }
+
+ private function led_comparator(array $left)
+ {
+ $token = $this->token;
+ $this->next();
+
+ return [
+ 'type' => T::T_COMPARATOR,
+ 'value' => $token['value'],
+ 'children' => [$left, $this->expr(self::$bp[T::T_COMPARATOR])]
+ ];
+ }
+
+ private function parseProjection($bp)
+ {
+ $type = $this->token['type'];
+ if (self::$bp[$type] < 10) {
+ return self::$currentNode;
+ } elseif ($type == T::T_DOT) {
+ $this->next(self::$afterDot);
+ return $this->parseDot($bp);
+ } elseif ($type == T::T_LBRACKET || $type == T::T_FILTER) {
+ return $this->expr($bp);
+ }
+
+ throw $this->syntax('Syntax error after projection');
+ }
+
+ private function parseDot($bp)
+ {
+ if ($this->token['type'] == T::T_LBRACKET) {
+ $this->next();
+ return $this->parseMultiSelectList();
+ }
+
+ return $this->expr($bp);
+ }
+
+ private function parseKeyValuePair()
+ {
+ static $validColon = [T::T_COLON => true];
+ $key = $this->token['value'];
+ $this->next($validColon);
+ $this->next();
+
+ return [
+ 'type' => 'key_val_pair',
+ 'value' => $key,
+ 'children' => [$this->expr()]
+ ];
+ }
+
+ private function parseWildcardObject(array $left = null)
+ {
+ $this->next();
+
+ return [
+ 'type' => 'projection',
+ 'from' => 'object',
+ 'children' => [
+ $left ?: self::$currentNode,
+ $this->parseProjection(self::$bp[T::T_STAR])
+ ]
+ ];
+ }
+
+ private function parseWildcardArray(array $left = null)
+ {
+ static $getRbracket = [T::T_RBRACKET => true];
+ $this->next($getRbracket);
+ $this->next();
+
+ return [
+ 'type' => 'projection',
+ 'from' => 'array',
+ 'children' => [
+ $left ?: self::$currentNode,
+ $this->parseProjection(self::$bp[T::T_STAR])
+ ]
+ ];
+ }
+
+ /**
+ * Parses an array index expression (e.g., [0], [1:2:3]
+ */
+ private function parseArrayIndexExpression()
+ {
+ static $matchNext = [
+ T::T_NUMBER => true,
+ T::T_COLON => true,
+ T::T_RBRACKET => true
+ ];
+
+ $pos = 0;
+ $parts = [null, null, null];
+ $expected = $matchNext;
+
+ do {
+ if ($this->token['type'] == T::T_COLON) {
+ $pos++;
+ $expected = $matchNext;
+ } elseif ($this->token['type'] == T::T_NUMBER) {
+ $parts[$pos] = $this->token['value'];
+ $expected = [T::T_COLON => true, T::T_RBRACKET => true];
+ }
+ $this->next($expected);
+ } while ($this->token['type'] != T::T_RBRACKET);
+
+ // Consume the closing bracket
+ $this->next();
+
+ if ($pos === 0) {
+ // No colons were found so this is a simple index extraction
+ return ['type' => 'index', 'value' => $parts[0]];
+ }
+
+ if ($pos > 2) {
+ throw $this->syntax('Invalid array slice syntax: too many colons');
+ }
+
+ // Sliced array from start (e.g., [2:])
+ return [
+ 'type' => 'projection',
+ 'from' => 'array',
+ 'children' => [
+ ['type' => 'slice', 'value' => $parts],
+ $this->parseProjection(self::$bp[T::T_STAR])
+ ]
+ ];
+ }
+
+ private function parseMultiSelectList()
+ {
+ $nodes = [];
+
+ do {
+ $nodes[] = $this->expr();
+ if ($this->token['type'] == T::T_COMMA) {
+ $this->next();
+ $this->assertNotToken(T::T_RBRACKET);
+ }
+ } while ($this->token['type'] !== T::T_RBRACKET);
+ $this->next();
+
+ return ['type' => 'multi_select_list', 'children' => $nodes];
+ }
+
+ private function syntax($msg)
+ {
+ return new SyntaxErrorException($msg, $this->token, $this->expression);
+ }
+
+ private function lookahead()
+ {
+ return (!isset($this->tokens[$this->tpos + 1]))
+ ? T::T_EOF
+ : $this->tokens[$this->tpos + 1]['type'];
+ }
+
+ private function next(array $match = null)
+ {
+ if (!isset($this->tokens[$this->tpos + 1])) {
+ $this->token = self::$nullToken;
+ } else {
+ $this->token = $this->tokens[++$this->tpos];
+ }
+
+ if ($match && !isset($match[$this->token['type']])) {
+ throw $this->syntax($match);
+ }
+ }
+
+ private function assertNotToken($type)
+ {
+ if ($this->token['type'] == $type) {
+ throw $this->syntax("Token {$this->tpos} not allowed to be $type");
+ }
+ }
+
+ /**
+ * @internal Handles undefined tokens without paying the cost of validation
+ */
+ public function __call($method, $args)
+ {
+ $prefix = substr($method, 0, 4);
+ if ($prefix == 'nud_' || $prefix == 'led_') {
+ $token = substr($method, 4);
+ $message = "Unexpected \"$token\" token ($method). Expected one of"
+ . " the following tokens: "
+ . implode(', ', array_map(function ($i) {
+ return '"' . substr($i, 4) . '"';
+ }, array_filter(
+ get_class_methods($this),
+ function ($i) use ($prefix) {
+ return strpos($i, $prefix) === 0;
+ }
+ )));
+ throw $this->syntax($message);
+ }
+
+ throw new \BadMethodCallException("Call to undefined method $method");
+ }
+}
diff --git a/vendor/mtdowling/jmespath.php/src/SyntaxErrorException.php b/vendor/mtdowling/jmespath.php/src/SyntaxErrorException.php
new file mode 100644
index 0000000..68683d0
--- /dev/null
+++ b/vendor/mtdowling/jmespath.php/src/SyntaxErrorException.php
@@ -0,0 +1,36 @@
+<?php
+namespace JmesPath;
+
+/**
+ * Syntax errors raise this exception that gives context
+ */
+class SyntaxErrorException extends \InvalidArgumentException
+{
+ /**
+ * @param string $expectedTypesOrMessage Expected array of tokens or message
+ * @param array $token Current token
+ * @param string $expression Expression input
+ */
+ public function __construct(
+ $expectedTypesOrMessage,
+ array $token,
+ $expression
+ ) {
+ $message = "Syntax error at character {$token['pos']}\n"
+ . $expression . "\n" . str_repeat(' ', max($token['pos'], 0)) . "^\n";
+ $message .= !is_array($expectedTypesOrMessage)
+ ? $expectedTypesOrMessage
+ : $this->createTokenMessage($token, $expectedTypesOrMessage);
+ parent::__construct($message);
+ }
+
+ private function createTokenMessage(array $token, array $valid)
+ {
+ return sprintf(
+ 'Expected one of the following: %s; found %s "%s"',
+ implode(', ', array_keys($valid)),
+ $token['type'],
+ $token['value']
+ );
+ }
+}
diff --git a/vendor/mtdowling/jmespath.php/src/TreeCompiler.php b/vendor/mtdowling/jmespath.php/src/TreeCompiler.php
new file mode 100644
index 0000000..fe27f41
--- /dev/null
+++ b/vendor/mtdowling/jmespath.php/src/TreeCompiler.php
@@ -0,0 +1,419 @@
+<?php
+namespace JmesPath;
+
+/**
+ * Tree visitor used to compile JMESPath expressions into native PHP code.
+ */
+class TreeCompiler
+{
+ private $indentation;
+ private $source;
+ private $vars;
+
+ /**
+ * @param array $ast AST to compile.
+ * @param string $fnName The name of the function to generate.
+ * @param string $expr Expression being compiled.
+ *
+ * @return string
+ */
+ public function visit(array $ast, $fnName, $expr)
+ {
+ $this->vars = [];
+ $this->source = $this->indentation = '';
+ $this->write("<?php\n")
+ ->write('use JmesPath\\TreeInterpreter as Ti;')
+ ->write('use JmesPath\\FnDispatcher as Fd;')
+ ->write('use JmesPath\\Utils;')
+ ->write('')
+ ->write('function %s(Ti $interpreter, $value) {', $fnName)
+ ->indent()
+ ->dispatch($ast)
+ ->write('')
+ ->write('return $value;')
+ ->outdent()
+ ->write('}');
+
+ return $this->source;
+ }
+
+ /**
+ * @param array $node
+ * @return mixed
+ */
+ private function dispatch(array $node)
+ {
+ return $this->{"visit_{$node['type']}"}($node);
+ }
+
+ /**
+ * Creates a monotonically incrementing unique variable name by prefix.
+ *
+ * @param string $prefix Variable name prefix
+ *
+ * @return string
+ */
+ private function makeVar($prefix)
+ {
+ if (!isset($this->vars[$prefix])) {
+ $this->vars[$prefix] = 0;
+ return '$' . $prefix;
+ }
+
+ return '$' . $prefix . ++$this->vars[$prefix];
+ }
+
+ /**
+ * Writes the given line of source code. Pass positional arguments to write
+ * that match the format of sprintf.
+ *
+ * @param string $str String to write
+ * @return $this
+ */
+ private function write($str)
+ {
+ $this->source .= $this->indentation;
+ if (func_num_args() == 1) {
+ $this->source .= $str . "\n";
+ return $this;
+ }
+ $this->source .= vsprintf($str, array_slice(func_get_args(), 1)) . "\n";
+ return $this;
+ }
+
+ /**
+ * Decreases the indentation level of code being written
+ * @return $this
+ */
+ private function outdent()
+ {
+ $this->indentation = substr($this->indentation, 0, -4);
+ return $this;
+ }
+
+ /**
+ * Increases the indentation level of code being written
+ * @return $this
+ */
+ private function indent()
+ {
+ $this->indentation .= ' ';
+ return $this;
+ }
+
+ private function visit_or(array $node)
+ {
+ $a = $this->makeVar('beforeOr');
+ return $this
+ ->write('%s = $value;', $a)
+ ->dispatch($node['children'][0])
+ ->write('if (!$value && $value !== "0" && $value !== 0) {')
+ ->indent()
+ ->write('$value = %s;', $a)
+ ->dispatch($node['children'][1])
+ ->outdent()
+ ->write('}');
+ }
+
+ private function visit_and(array $node)
+ {
+ $a = $this->makeVar('beforeAnd');
+ return $this
+ ->write('%s = $value;', $a)
+ ->dispatch($node['children'][0])
+ ->write('if ($value || $value === "0" || $value === 0) {')
+ ->indent()
+ ->write('$value = %s;', $a)
+ ->dispatch($node['children'][1])
+ ->outdent()
+ ->write('}');
+ }
+
+ private function visit_not(array $node)
+ {
+ return $this
+ ->write('// Visiting not node')
+ ->dispatch($node['children'][0])
+ ->write('// Applying boolean not to result of not node')
+ ->write('$value = !Utils::isTruthy($value);');
+ }
+
+ private function visit_subexpression(array $node)
+ {
+ return $this
+ ->dispatch($node['children'][0])
+ ->write('if ($value !== null) {')
+ ->indent()
+ ->dispatch($node['children'][1])
+ ->outdent()
+ ->write('}');
+ }
+
+ private function visit_field(array $node)
+ {
+ $arr = '$value[' . var_export($node['value'], true) . ']';
+ $obj = '$value->{' . var_export($node['value'], true) . '}';
+ $this->write('if (is_array($value) || $value instanceof \\ArrayAccess) {')
+ ->indent()
+ ->write('$value = isset(%s) ? %s : null;', $arr, $arr)
+ ->outdent()
+ ->write('} elseif ($value instanceof \\stdClass) {')
+ ->indent()
+ ->write('$value = isset(%s) ? %s : null;', $obj, $obj)
+ ->outdent()
+ ->write("} else {")
+ ->indent()
+ ->write('$value = null;')
+ ->outdent()
+ ->write("}");
+
+ return $this;
+ }
+
+ private function visit_index(array $node)
+ {
+ if ($node['value'] >= 0) {
+ $check = '$value[' . $node['value'] . ']';
+ return $this->write(
+ '$value = (is_array($value) || $value instanceof \\ArrayAccess)'
+ . ' && isset(%s) ? %s : null;',
+ $check, $check
+ );
+ }
+
+ $a = $this->makeVar('count');
+ return $this
+ ->write('if (is_array($value) || ($value instanceof \\ArrayAccess && $value instanceof \\Countable)) {')
+ ->indent()
+ ->write('%s = count($value) + %s;', $a, $node['value'])
+ ->write('$value = isset($value[%s]) ? $value[%s] : null;', $a, $a)
+ ->outdent()
+ ->write('} else {')
+ ->indent()
+ ->write('$value = null;')
+ ->outdent()
+ ->write('}');
+ }
+
+ private function visit_literal(array $node)
+ {
+ return $this->write('$value = %s;', var_export($node['value'], true));
+ }
+
+ private function visit_pipe(array $node)
+ {
+ return $this
+ ->dispatch($node['children'][0])
+ ->dispatch($node['children'][1]);
+ }
+
+ private function visit_multi_select_list(array $node)
+ {
+ return $this->visit_multi_select_hash($node);
+ }
+
+ private function visit_multi_select_hash(array $node)
+ {
+ $listVal = $this->makeVar('list');
+ $value = $this->makeVar('prev');
+ $this->write('if ($value !== null) {')
+ ->indent()
+ ->write('%s = [];', $listVal)
+ ->write('%s = $value;', $value);
+
+ $first = true;
+ foreach ($node['children'] as $child) {
+ if (!$first) {
+ $this->write('$value = %s;', $value);
+ }
+ $first = false;
+ if ($node['type'] == 'multi_select_hash') {
+ $this->dispatch($child['children'][0]);
+ $key = var_export($child['value'], true);
+ $this->write('%s[%s] = $value;', $listVal, $key);
+ } else {
+ $this->dispatch($child);
+ $this->write('%s[] = $value;', $listVal);
+ }
+ }
+
+ return $this
+ ->write('$value = %s;', $listVal)
+ ->outdent()
+ ->write('}');
+ }
+
+ private function visit_function(array $node)
+ {
+ $value = $this->makeVar('val');
+ $args = $this->makeVar('args');
+ $this->write('%s = $value;', $value)
+ ->write('%s = [];', $args);
+
+ foreach ($node['children'] as $arg) {
+ $this->dispatch($arg);
+ $this->write('%s[] = $value;', $args)
+ ->write('$value = %s;', $value);
+ }
+
+ return $this->write(
+ '$value = Fd::getInstance()->__invoke("%s", %s);',
+ $node['value'], $args
+ );
+ }
+
+ private function visit_slice(array $node)
+ {
+ return $this
+ ->write('$value = !is_string($value) && !Utils::isArray($value)')
+ ->write(' ? null : Utils::slice($value, %s, %s, %s);',
+ var_export($node['value'][0], true),
+ var_export($node['value'][1], true),
+ var_export($node['value'][2], true)
+ );
+ }
+
+ private function visit_current(array $node)
+ {
+ return $this->write('// Visiting current node (no-op)');
+ }
+
+ private function visit_expref(array $node)
+ {
+ $child = var_export($node['children'][0], true);
+ return $this->write('$value = function ($value) use ($interpreter) {')
+ ->indent()
+ ->write('return $interpreter->visit(%s, $value);', $child)
+ ->outdent()
+ ->write('};');
+ }
+
+ private function visit_flatten(array $node)
+ {
+ $this->dispatch($node['children'][0]);
+ $merged = $this->makeVar('merged');
+ $val = $this->makeVar('val');
+
+ $this
+ ->write('// Visiting merge node')
+ ->write('if (!Utils::isArray($value)) {')
+ ->indent()
+ ->write('$value = null;')
+ ->outdent()
+ ->write('} else {')
+ ->indent()
+ ->write('%s = [];', $merged)
+ ->write('foreach ($value as %s) {', $val)
+ ->indent()
+ ->write('if (is_array(%s) && isset(%s[0])) {', $val, $val)
+ ->indent()
+ ->write('%s = array_merge(%s, %s);', $merged, $merged, $val)
+ ->outdent()
+ ->write('} elseif (%s !== []) {', $val)
+ ->indent()
+ ->write('%s[] = %s;', $merged, $val)
+ ->outdent()
+ ->write('}')
+ ->outdent()
+ ->write('}')
+ ->write('$value = %s;', $merged)
+ ->outdent()
+ ->write('}');
+
+ return $this;
+ }
+
+ private function visit_projection(array $node)
+ {
+ $val = $this->makeVar('val');
+ $collected = $this->makeVar('collected');
+ $this->write('// Visiting projection node')
+ ->dispatch($node['children'][0])
+ ->write('');
+
+ if (!isset($node['from'])) {
+ $this->write('if (!is_array($value) || !($value instanceof \stdClass)) { $value = null; }');
+ } elseif ($node['from'] == 'object') {
+ $this->write('if (!Utils::isObject($value)) { $value = null; }');
+ } elseif ($node['from'] == 'array') {
+ $this->write('if (!Utils::isArray($value)) { $value = null; }');
+ }
+
+ $this->write('if ($value !== null) {')
+ ->indent()
+ ->write('%s = [];', $collected)
+ ->write('foreach ((array) $value as %s) {', $val)
+ ->indent()
+ ->write('$value = %s;', $val)
+ ->dispatch($node['children'][1])
+ ->write('if ($value !== null) {')
+ ->indent()
+ ->write('%s[] = $value;', $collected)
+ ->outdent()
+ ->write('}')
+ ->outdent()
+ ->write('}')
+ ->write('$value = %s;', $collected)
+ ->outdent()
+ ->write('}');
+
+ return $this;
+ }
+
+ private function visit_condition(array $node)
+ {
+ $value = $this->makeVar('beforeCondition');
+ return $this
+ ->write('%s = $value;', $value)
+ ->write('// Visiting condition node')
+ ->dispatch($node['children'][0])
+ ->write('// Checking result of condition node')
+ ->write('if (Utils::isTruthy($value)) {')
+ ->indent()
+ ->write('$value = %s;', $value)
+ ->dispatch($node['children'][1])
+ ->outdent()
+ ->write('} else {')
+ ->indent()
+ ->write('$value = null;')
+ ->outdent()
+ ->write('}');
+ }
+
+ private function visit_comparator(array $node)
+ {
+ $value = $this->makeVar('val');
+ $a = $this->makeVar('left');
+ $b = $this->makeVar('right');
+
+ $this
+ ->write('// Visiting comparator node')
+ ->write('%s = $value;', $value)
+ ->dispatch($node['children'][0])
+ ->write('%s = $value;', $a)
+ ->write('$value = %s;', $value)
+ ->dispatch($node['children'][1])
+ ->write('%s = $value;', $b);
+
+ if ($node['value'] == '==') {
+ $this->write('$value = Utils::isEqual(%s, %s);', $a, $b);
+ } elseif ($node['value'] == '!=') {
+ $this->write('$value = !Utils::isEqual(%s, %s);', $a, $b);
+ } else {
+ $this->write(
+ '$value = (is_int(%s) || is_float(%s)) && (is_int(%s) || is_float(%s)) && %s %s %s;',
+ $a, $a, $b, $b, $a, $node['value'], $b
+ );
+ }
+
+ return $this;
+ }
+
+ /** @internal */
+ public function __call($method, $args)
+ {
+ throw new \RuntimeException(
+ sprintf('Invalid node encountered: %s', json_encode($args[0]))
+ );
+ }
+}
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");
+ }
+ }
+}
diff --git a/vendor/mtdowling/jmespath.php/src/Utils.php b/vendor/mtdowling/jmespath.php/src/Utils.php
new file mode 100644
index 0000000..9e69fef
--- /dev/null
+++ b/vendor/mtdowling/jmespath.php/src/Utils.php
@@ -0,0 +1,258 @@
+<?php
+namespace JmesPath;
+
+class Utils
+{
+ public static $typeMap = [
+ 'boolean' => 'boolean',
+ 'string' => 'string',
+ 'NULL' => 'null',
+ 'double' => 'number',
+ 'float' => 'number',
+ 'integer' => 'number'
+ ];
+
+ /**
+ * Returns true if the value is truthy
+ *
+ * @param mixed $value Value to check
+ *
+ * @return bool
+ */
+ public static function isTruthy($value)
+ {
+ if (!$value) {
+ return $value === 0 || $value === '0';
+ } elseif ($value instanceof \stdClass) {
+ return (bool) get_object_vars($value);
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * Gets the JMESPath type equivalent of a PHP variable.
+ *
+ * @param mixed $arg PHP variable
+ * @return string Returns the JSON data type
+ * @throws \InvalidArgumentException when an unknown type is given.
+ */
+ public static function type($arg)
+ {
+ $type = gettype($arg);
+ if (isset(self::$typeMap[$type])) {
+ return self::$typeMap[$type];
+ } elseif ($type === 'array') {
+ if (empty($arg)) {
+ return 'array';
+ }
+ reset($arg);
+ return key($arg) === 0 ? 'array' : 'object';
+ } elseif ($arg instanceof \stdClass) {
+ return 'object';
+ } elseif ($arg instanceof \Closure) {
+ return 'expression';
+ } elseif ($arg instanceof \ArrayAccess
+ && $arg instanceof \Countable
+ ) {
+ return count($arg) == 0 || $arg->offsetExists(0)
+ ? 'array'
+ : 'object';
+ } elseif (method_exists($arg, '__toString')) {
+ return 'string';
+ }
+
+ throw new \InvalidArgumentException(
+ 'Unable to determine JMESPath type from ' . get_class($arg)
+ );
+ }
+
+ /**
+ * Determine if the provided value is a JMESPath compatible object.
+ *
+ * @param mixed $value
+ *
+ * @return bool
+ */
+ public static function isObject($value)
+ {
+ if (is_array($value)) {
+ return !$value || array_keys($value)[0] !== 0;
+ }
+
+ // Handle array-like values. Must be empty or offset 0 does not exist
+ return $value instanceof \Countable && $value instanceof \ArrayAccess
+ ? count($value) == 0 || !$value->offsetExists(0)
+ : $value instanceof \stdClass;
+ }
+
+ /**
+ * Determine if the provided value is a JMESPath compatible array.
+ *
+ * @param mixed $value
+ *
+ * @return bool
+ */
+ public static function isArray($value)
+ {
+ if (is_array($value)) {
+ return !$value || array_keys($value)[0] === 0;
+ }
+
+ // Handle array-like values. Must be empty or offset 0 exists.
+ return $value instanceof \Countable && $value instanceof \ArrayAccess
+ ? count($value) == 0 || $value->offsetExists(0)
+ : false;
+ }
+
+ /**
+ * JSON aware value comparison function.
+ *
+ * @param mixed $a First value to compare
+ * @param mixed $b Second value to compare
+ *
+ * @return bool
+ */
+ public static function isEqual($a, $b)
+ {
+ if ($a === $b) {
+ return true;
+ } elseif ($a instanceof \stdClass) {
+ return self::isEqual((array) $a, $b);
+ } elseif ($b instanceof \stdClass) {
+ return self::isEqual($a, (array) $b);
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Safely add together two values.
+ *
+ * @param mixed $a First value to add
+ * @param mixed $b Second value to add
+ *
+ * @return int|float
+ */
+ public static function add($a, $b)
+ {
+ if (is_numeric($a)) {
+ if (is_numeric($b)) {
+ return $a + $b;
+ } else {
+ return $a;
+ }
+ } else {
+ if (is_numeric($b)) {
+ return $b;
+ } else {
+ return 0;
+ }
+ }
+ }
+
+ /**
+ * JMESPath requires a stable sorting algorithm, so here we'll implement
+ * a simple Schwartzian transform that uses array index positions as tie
+ * breakers.
+ *
+ * @param array $data List or map of data to sort
+ * @param callable $sortFn Callable used to sort values
+ *
+ * @return array Returns the sorted array
+ * @link http://en.wikipedia.org/wiki/Schwartzian_transform
+ */
+ public static function stableSort(array $data, callable $sortFn)
+ {
+ // Decorate each item by creating an array of [value, index]
+ array_walk($data, function (&$v, $k) {
+ $v = [$v, $k];
+ });
+ // Sort by the sort function and use the index as a tie-breaker
+ uasort($data, function ($a, $b) use ($sortFn) {
+ return $sortFn($a[0], $b[0]) ?: ($a[1] < $b[1] ? -1 : 1);
+ });
+
+ // Undecorate each item and return the resulting sorted array
+ return array_map(function ($v) {
+ return $v[0];
+ }, array_values($data));
+ }
+
+ /**
+ * Creates a Python-style slice of a string or array.
+ *
+ * @param array|string $value Value to slice
+ * @param int|null $start Starting position
+ * @param int|null $stop Stop position
+ * @param int $step Step (1, 2, -1, -2, etc.)
+ *
+ * @return array|string
+ * @throws \InvalidArgumentException
+ */
+ public static function slice($value, $start = null, $stop = null, $step = 1)
+ {
+ if (!is_array($value) && !is_string($value)) {
+ throw new \InvalidArgumentException('Expects string or array');
+ }
+
+ return self::sliceIndices($value, $start, $stop, $step);
+ }
+
+ private static function adjustEndpoint($length, $endpoint, $step)
+ {
+ if ($endpoint < 0) {
+ $endpoint += $length;
+ if ($endpoint < 0) {
+ $endpoint = $step < 0 ? -1 : 0;
+ }
+ } elseif ($endpoint >= $length) {
+ $endpoint = $step < 0 ? $length - 1 : $length;
+ }
+
+ return $endpoint;
+ }
+
+ private static function adjustSlice($length, $start, $stop, $step)
+ {
+ if ($step === null) {
+ $step = 1;
+ } elseif ($step === 0) {
+ throw new \RuntimeException('step cannot be 0');
+ }
+
+ if ($start === null) {
+ $start = $step < 0 ? $length - 1 : 0;
+ } else {
+ $start = self::adjustEndpoint($length, $start, $step);
+ }
+
+ if ($stop === null) {
+ $stop = $step < 0 ? -1 : $length;
+ } else {
+ $stop = self::adjustEndpoint($length, $stop, $step);
+ }
+
+ return [$start, $stop, $step];
+ }
+
+ private static function sliceIndices($subject, $start, $stop, $step)
+ {
+ $type = gettype($subject);
+ $len = $type == 'string' ? mb_strlen($subject, 'UTF-8') : count($subject);
+ list($start, $stop, $step) = self::adjustSlice($len, $start, $stop, $step);
+
+ $result = [];
+ if ($step > 0) {
+ for ($i = $start; $i < $stop; $i += $step) {
+ $result[] = $subject[$i];
+ }
+ } else {
+ for ($i = $start; $i > $stop; $i += $step) {
+ $result[] = $subject[$i];
+ }
+ }
+
+ return $type == 'string' ? implode('', $result) : $result;
+ }
+}