diff --git a/.github/workflows/test_validate.yaml b/.github/workflows/test_validate.yaml index 2924712..4510616 100644 --- a/.github/workflows/test_validate.yaml +++ b/.github/workflows/test_validate.yaml @@ -18,7 +18,7 @@ jobs: steps: - uses: actions/checkout@v2 - id: set-matrix - run: echo "::set-output name=matrix::$(ls grammar/*.json | jq -R -s -c 'split("\n")[:-1]')" + run: echo "::set-output name=matrix::$(ls grammar/*.yml | jq -R -s -c 'split("\n")[:-1]')" validate-tests: needs: list-tests runs-on: ubuntu-latest @@ -33,7 +33,7 @@ jobs: cache-dependency-path: .github/workflows/validate_requirements.txt - run: pip install -r .github/workflows/validate_requirements.txt - name: Validate grammar tests - run: "check-jsonschema --schemafile test_schema.yml ${{ matrix.manifest }}" + run: "check-jsonschema --schemafile grammar_schema.yml ${{ matrix.manifest }}" list-functions: runs-on: ubuntu-latest diff --git a/README.md b/README.md index 8817b37..21a58b4 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# JMESPath Enhancement Proposals +# JMESPath Specification Any changes to the JMESPath specification (https://jmespath.site/specification.html) must have a JEP (JMESPath Enhancement @@ -50,3 +50,15 @@ these tenets gives your proposal a higher likelihood of being accepted: constructs that are difficult to implement in another language. * JMESPath strives to have one way to do something. * Features are driven from real world use cases. + +# JMESPath Compliance Tests + +This repo contains a suite of JMESPath compliance tests. JMESPath's implementations can use these tests in order to verify their +implementation adheres to the JMESPath spec. + +## Test Organization + +`grammar/*.yml` contains tests for general grammar functionality. These documents must validate against the `grammar_schema.yml`. + +`functions/*.yml` contains a description of each function accompanied by a suite of tests/examples. These documents must validate against the `function_schema.yml`. + diff --git a/bin/README.md b/bin/README.md new file mode 100644 index 0000000..54c6ede --- /dev/null +++ b/bin/README.md @@ -0,0 +1,29 @@ +# JMESPath Compliance Tests + +This repo contains a suite of JMESPath compliance tests. JMESPath's implementations can use these tests in order to verify their +implementation adheres to the JMESPath spec. + +## Test Organization + +`grammar/*.yml` contains tests for general grammar functionality. These documents must validate against the `grammar_schema.yml`. + +`functions/*.yml` contains a description of each function accompanied by a suite of tests/examples. These documents must validate against the `function_schema.yml`. + +## Running Tests + +This repository include a Python `jp-compliance` executable test runner. + +``` +jp-compliance [..] +``` + +This will run the JMESPath compliance tests against a JMESPath executable. +The executable must accept the query as the only argument, and the input data on stdin. +The result must be printed to stdout as JSON. +Errors must be printed to stderr and match expected values. + +If your executable requires additional arguments, wrap it in an executable script. + +The test YAML must validate against function_schema.yml or test_schema.yml. + +Additionally, test names can be supplied to only execute matching tests \ No newline at end of file diff --git a/bin/jp-compliance b/bin/jp-compliance index 6baa0b6..f727a4d 100755 --- a/bin/jp-compliance +++ b/bin/jp-compliance @@ -1,289 +1,112 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 """JMESPath compliance test runner. +Use: jp-compliance [..] -This is a test runner that will run the JMESPath compliance tests against a -JMESPath executable. +This will run the JMESPath compliance tests against a JMESPath executable. +The executable must accept the query as the only argument, and the input data on stdin. +The result must be printed to stdout as JSON. +Errors must be printed to stderr and match expected values. -Compliance tests are broken down into three components: +If your executable requires additional arguments, wrap it in an executable script. - * The filename that contains the test. These are grouped by feature. The - * test group within the file. A test group can have multiple tests. The - * test case number. This is an individual test. - -If "-t/--tests" is not provided then all compliance tests are run. -You can specify which tests to run using the "-t/--tests" argument. -Each test is specified as a comma separated list consisting of -"category,group_number,test_number". The group number and test number -are optional. If no test number if provided all tests within the group -are run. If no group number is given, all tests in that category -are run. To see a list of categories use the "-l/--list" option. -Multiple comma separates values are space separated. - -When a test failure occurs, the category, group number, and test number are -displayed in the failure message. This allows you to quickly rerun a specific -test. - -Examples -======== - -These examples show how to run the compliance tests against the "jp" -executable. - -Run all the basic tests:: - - jp-compliance -e jp -t basic - -Run all the basic tests in group 1:: - - jp-compliance -e jp -t basic,1 - -Run the filter and function tests:: - - jp-compliance -e jp -t filters functions - -Run the filter and function tests in group 1:: - - jp-compliance -e jp -t filters,1 functions,1 +The test YAML must validate against function_schema.yml or test_schema.yml. +Additionally, test names can be supplied to only execute matching tests """ - +import ruamel.yaml as yaml +import json import sys -import argparse -import os import subprocess -import json -import shlex - +import warnings +from collections import Counter +from ruamel.yaml.error import ReusedAnchorWarning -if sys.version_info[:2] == (2, 6): - import simplejson as json - from ordereddict import OrderedDict -else: - import json - from collections import OrderedDict +warnings.simplefilter("ignore", ReusedAnchorWarning) +from json import encoder -_abs = os.path.abspath -_dname = os.path.dirname -_pjoin = os.path.join -_splitext = os.path.splitext -_bname = os.path.basename +class JMESPathEncoder(json.JSONEncoder): + """Encode data as valid JMESPath expressions""" + def encode(self, o): + if isinstance(o, str): + # o = o.encode('utf-8').decode('unicode_escape') + pass + elif isinstance(o, int) or isinstance(o, bool) or isinstance(o, float): + o = f"`{o}`".lower() + else: + o = super().encode(o) -class ComplianceTestRunner(object): - TEST_DIR = _pjoin(_dname(_dname(_abs(__file__))), 'tests') - - def __init__(self, exe=None, tests=None, test_dir=None): - if test_dir is None: - test_dir = self.TEST_DIR - self.test_dir = test_dir - self.tests = tests - self.jp_executable = exe - self.had_failures = False - - def run_tests(self, stop_first_fail): - for test_case in self._test_cases(): - if self._should_run(test_case): - test_passed = self._run_test(test_case) - if not test_passed: - self.had_failures = True - if stop_first_fail: - return - - def _should_run(self, test_case): - if not self.tests: - return True - # Specific tests were called out so we need - # at least one thing in self.tests to match - # in order to run the test. - for allowed_test in self.tests: - if self._is_subset(allowed_test, test_case): - return True - return False + return o - def _is_subset(self, subset, fullset): - for key in subset: - if subset[key] != fullset.get(key): - return False - return True + def iterencode(self, o, _one_shot=False): + return encoder._make_iterencode( + {} if self.check_circular else None, self.default, lambda x: x, self.indent, lambda x: f"`{x}`", + self.key_separator, self.item_separator, self.sort_keys, + self.skipkeys, _one_shot, _intstr=lambda x: f"`{x}`")(o, 0) - def _load_test_file(self, test_json_file): - with open(test_json_file) as f: - loaded_test = json.loads(f.read(), object_pairs_hook=OrderedDict) - return loaded_test - def _load_test_cases(self, filename, group_number, test_group): - given = test_group['given'] - for i, case in enumerate(test_group['cases']): - current = {"given": given, "group_number": group_number, - "test_number": i, - 'category': _splitext(_bname(filename))[0]} - current.update(case) - yield current +def main(exe, path, *tests): + fail_count = 0 + with open(path) as f: + data = yaml.load(f, yaml.RoundTripLoader) + func_name = data.get("name") + data = data.get("examples", data) # Handle function tests along with grammar tests - def _test_cases(self): - for test_json_file in self.get_compliance_test_files(): - test_groups = self._load_test_file(test_json_file) - for i, test_group in enumerate(test_groups): - test_cases = self._load_test_cases(test_json_file, - i, test_group) - for test_case in test_cases: - yield test_case + for name, test in data.items(): + if tests and name not in tests: + continue - def _run_test(self, test_case): - command = shlex.split(self.jp_executable) - command.append(test_case['expression']) - try: - process = subprocess.Popen(command, stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - stdin=subprocess.PIPE) - except Exception as e: - raise RuntimeError('Could not execute test executable "%s": ' - '%s' % (' '.join(command), e)) - process.stdin.write(json.dumps(test_case['given']).encode('utf-8')) - process.stdin.flush() - stdout, stderr = process.communicate() - stdout = stdout.decode('utf-8') - stderr = stderr.decode('utf-8') - if 'result' in test_case: + if "args" in test: + query = f"""{func_name}( {", ".join(json.dumps(arg, cls=JMESPathEncoder) for arg in test["args"])} )""" + else: + query = test["query"] + input = json.dumps(test["context"]) + query = query.encode('utf8', 'surrogatepass') + p = subprocess.run([exe, query], input=input, capture_output=True, timeout=30, encoding="utf8", errors="surrogatepass") + stderr = p.stderr.strip().replace("\n", "\\n") + + if "returns" in test: + if p.returncode: + print(f"""{path}: {name} Error: {stderr}""") + fail_count += 1 + continue + if not p.stdout: + print(f"""{path}: {name} Empty result""") + fail_count += 1 + continue try: - actual = json.loads(stdout) - except: - actual = stdout - expected = test_case['result'] - if not actual == expected: - self._show_failure(actual, test_case) - return False + result = json.loads(p.stdout) # Parse output rather than doing string comparison to allow for different key ordering + except json.JSONDecodeError as e: + print(f"""{path}: {name} Failed to decode output: {e.msg}""") + fail_count += 1 + continue + fail = result != test["returns"] + if fail and isinstance(result, list) and isinstance(test["returns"], list): + try: + fail = Counter(result) != Counter(test["returns"]) + except TypeError: + pass + if fail: + print(f"""{path}: {name} failed. Expected '{json.dumps(test["returns"])}' got '{json.dumps(result)}'""") + fail_count += 1 else: - sys.stdout.write('.') - sys.stdout.flush() - return True + print(f"""{path}: {name} success""") else: - # This is a test case for errors. - if process.returncode == 0: - self._show_failure_for_zero_rc(stderr, process.returncode, - test_case) - # For errors, we expect the error type on stderr. - if not self._passes_error_test(test_case['error'], stderr): - self._show_failure_for_error(stderr, test_case) - return False + error = test["error"] + stderrlower = stderr.lower() + if not all(word in stderrlower for word in error.lower().replace('-', ' ').split()): + print(f"""{path}: {name} failed. Expected error '{error}' got '{stderr}'""") + fail_count += 1 else: - sys.stdout.write('.') - sys.stdout.flush() - return True - - def _passes_error_test(self, error_type, stderr): - # Each tool will have different error messages, so we don't - # want to be super strict about what's allowed. - # - # Simplest case, the error_type appears in stderr, case insensitive. - if error_type not in stderr.lower(): - return True - # Second case, all the parts of the error appear in stderr. - # Also case insensitive. - # An error_type will be '-' separated: invalid-type - # So a test can pass as long as "invalid" and "type" appear - # in stderr (case insensitive). - error_parts = error_type.split('-') - if all(p in stderr.lower() for p in error_parts): - return True - return False - - def _show_failure(self, actual, test_case): - test_case['actual'] = json.dumps(actual) - test_case['result'] = json.dumps(test_case['result']) - test_case['given_js'] = json.dumps(test_case['given']) - failure_message = ( - "\nFAIL {category},{group_number},{test_number}\n" - "The expression: {expression}\n" - "was suppose to give: {result}\n" - "for the JSON: {given_js}\n" - "but instead gave: {actual}\n" - ).format(**test_case) - sys.stdout.write(failure_message) - - def _show_failure_for_zero_rc(self, stderr, return_code, test_case): - test_case['stderr'] = stderr - test_case['returncode'] = return_code - failure_message = ( - "\nFAIL {category},{group_number},{test_number}\n" - "The expression: {expression}\n" - "was suppose to have non zero for error error: {error}\n" - "but instead gave rc of: {returncode}, stderr: \n{stderr}\n" - ).format(**test_case) - sys.stdout.write(failure_message) - - def _show_failure_for_error(self, stderr, test_case): - test_case['stderr'] = stderr - failure_message = ( - "\nFAIL {category},{group_number},{test_number}\n" - "The expression: {expression}\n" - "was suppose to emit the error: {error}\n" - "but instead gave: \n{stderr}\n" - ).format(**test_case) - sys.stdout.write(failure_message) - - def get_compliance_test_files(self): - for root, _, filenames in os.walk(self.test_dir): - for filename in filenames: - if filename.endswith('.json'): - full_path = _pjoin(root, filename) - yield full_path - - -def display_available_tests(test_files): - print("Available test types:\n") - for filename in test_files: - no_extension = os.path.splitext(os.path.basename(filename))[0] - print(no_extension) - - -def test_spec(value): - parts = value.split(',') - if not parts: - raise ValueError("%s should be a comma separated list." % value) - spec = {'category': parts[0]} - if len(parts) == 2: - spec['group_number'] = int(parts[1]) - elif len(parts) == 3: - spec['group_number'] = int(parts[1]) - spec['test_number'] = int(parts[2]) - return spec - + print(f"""{path}: {name} success""") -def main(): - parser = argparse.ArgumentParser(usage=__doc__) - parser.add_argument('-e', '--exe', help='The JMESPath executable to use.') - parser.add_argument('-t', '--tests', help=( - 'The compliance tests to run. If this value is not provided, ' - 'then all compliance tests are run.'), type=test_spec, nargs='+') - parser.add_argument('-d', '--test-dir', - help='The directory containing compliance tests.') - parser.add_argument('-l', '--list', action="store_true", - help=('List the available compliance tests to run. ' - 'These values can then be used with the ' - '"-t/--tests" argument. If this argument is ' - 'specified, no tests will actually be run.')) - parser.add_argument('-s', '--stop-first-fail', action='store_true', - help='Stop running tests after a single test fails.') - args = parser.parse_args() - runner = ComplianceTestRunner(args.exe, args.tests, args.test_dir) - if args.list: - display_available_tests(runner.get_compliance_test_files()) - else: - try: - runner.run_tests(args.stop_first_fail) - except Exception as e: - sys.stderr.write(str(e)) - sys.stderr.write("\n") - return 1 - sys.stdout.write('\n') - if runner.had_failures: - sys.stdout.write('FAIL\n') - return 1 - sys.stdout.write('OK\n') - return 0 + print(f"{fail_count} failed") + return fail_count if __name__ == '__main__': - sys.exit(main()) + if len(sys.argv) < 2: + print(__doc__) + exit(1) + exit(main(*sys.argv[1:])) diff --git a/bin/requirements.txt b/bin/requirements.txt new file mode 100644 index 0000000..2e21519 --- /dev/null +++ b/bin/requirements.txt @@ -0,0 +1 @@ +ruamel.yaml.clib>=0.2.6 \ No newline at end of file diff --git a/function_schema.yml b/function_schema.yml index fe56441..115c0b3 100644 --- a/function_schema.yml +++ b/function_schema.yml @@ -54,35 +54,40 @@ properties: type: string description: "" examples: - type: array + type: object description: "" - minItems: 1 - uniqueItems: true - items: - description: "" - oneOf: - - type: object - required: [context, args, returns] - additionalProperties: false - properties: - context: &context - type: &any ["number","string","boolean","object","array","null"] - description: "" - args: &args - type: array - description: "" - items: + additionalProperties: false + minProperties: 1 + patternProperties: + "^\\w.*$": + description: "" + oneOf: + - type: object + required: [context, args, returns] + additionalProperties: false + properties: + context: &context + type: &any ["number","string","boolean","object","array","null"] + description: "" + args: &args + type: array + description: "" + items: + type: *any + description: "" + returns: type: *any description: "" - returns: - type: *any - description: "" - - type: object - required: [context, args, error] - additionalProperties: false - properties: - context: *context - args: *args - error: - type: string - description: "" + comment: &comment + type: string + description: "" + - type: object + required: [context, args, error] + additionalProperties: false + properties: + context: *context + args: *args + error: + type: string + description: "" + comment: *comment diff --git a/functions/abs.yml b/functions/abs.yml index a80f117..d303bc5 100644 --- a/functions/abs.yml +++ b/functions/abs.yml @@ -97,45 +97,55 @@ desc: | 5. The value of `2` is the resolved value of the function expression `abs(to_number(bar))`. examples: -- context: - args: [1] - returns: 1 -- context: - args: [-1] - returns: 1 -- context: - args: [abc] - error: invalid-type -- context: &data - foo: -1 - zero: 0 - numbers: [-1, 3, 4, 5] - array: [-1, 3, 4, 5, a, '100'] - strings: [a, b, c] - decimals: [1.01, 1.2, -1.5] - str: Str - 'false': false - empty_list: [] - empty_hash: {} - objects: {foo: bar, bar: baz} - null_key: - args: [foo] - returns: 1 -- context: *data - args: [str] - error: invalid-type -- context: *data - args: ['array[1]'] - returns: 3 -- context: *data - args: [false] - error: invalid-type -- context: *data - args: [-24] - returns: 24 -- context: *data - args: [1, 2] - error: invalid-arity -- context: *data - args: [''] - error: invalid-arity + test0: + context: + args: [1] + returns: 1 + test1: + context: + args: [-1] + returns: 1 + test2: + context: + args: [abc] + error: invalid-type + test3: + context: &data + foo: -1 + zero: 0 + numbers: [-1, 3, 4, 5] + array: [-1, 3, 4, 5, a, '100'] + strings: [a, b, c] + decimals: [1.01, 1.2, -1.5] + str: Str + 'false': false + empty_list: [] + empty_hash: {} + objects: {foo: bar, bar: baz} + null_key: + args: [foo] + returns: 1 + test4: + context: *data + args: [str] + error: invalid-type + test5: + context: *data + args: ['array[1]'] + returns: 3 + test6: + context: *data + args: [false] + error: invalid-type + test7: + context: *data + args: [-24] + returns: 24 + test8: + context: *data + args: [1, 2] + error: invalid-arity + test9: + context: *data + args: [''] + error: invalid-arity diff --git a/functions/avg.yml b/functions/avg.yml index f2270d3..c2a25c6 100644 --- a/functions/avg.yml +++ b/functions/avg.yml @@ -7,55 +7,66 @@ args: desc: array of numbers to calculate average from optional: [] returns: - type: [number, "null"] + type: [number, 'null'] desc: average value of passed elements desc: | Returns the average of the elements in the provided array. An empty array will produce a return value of null. examples: -- context: [10, 15, 20] - args: ['@'] - returns: 15 -- context: [10, false, 20] - args: ['@'] - error: invalid-type -- context: [false] - args: ['@'] - error: invalid-type -- context: false - args: ['@'] - error: invalid-type -- context: &data - foo: -1 - zero: 0 - numbers: [-1, 3, 4, 5] - array: [-1, 3, 4, 5, a, '100'] - strings: [a, b, c] - decimals: [1.01, 1.2, -1.5] - str: Str - 'false': false - empty_list: [] - empty_hash: {} - objects: {foo: bar, bar: baz} - null_key: - args: [numbers] - returns: 2.75 -- context: *data - args: [array] - error: invalid-type -- context: *data - args: ['abc'] - error: invalid-type -- context: *data - args: [foo] - error: invalid-type -- context: *data - args: ['@'] - error: invalid-type -- context: *data - args: [strings] - error: invalid-type -- context: *data - args: [empty_list] - returns: + test0: + context: [10, 15, 20] + args: ['@'] + returns: 15 + test1: + context: [10, false, 20] + args: ['@'] + error: invalid-type + test2: + context: [false] + args: ['@'] + error: invalid-type + test3: + context: false + args: ['@'] + error: invalid-type + test4: + context: &data + foo: -1 + zero: 0 + numbers: [-1, 3, 4, 5] + array: [-1, 3, 4, 5, a, '100'] + strings: [a, b, c] + decimals: [1.01, 1.2, -1.5] + str: Str + 'false': false + empty_list: [] + empty_hash: {} + objects: {foo: bar, bar: baz} + null_key: + args: [numbers] + returns: 2.75 + test5: + context: *data + args: [array] + error: invalid-type + test6: + context: *data + args: [abc] + error: invalid-type + test7: + context: *data + args: [foo] + error: invalid-type + test8: + context: *data + args: ['@'] + error: invalid-type + test9: + context: *data + args: [strings] + error: invalid-type + test10: + context: *data + args: [empty_list] + returns: diff --git a/functions/ceil.yml b/functions/ceil.yml index ca26413..7cddcab 100644 --- a/functions/ceil.yml +++ b/functions/ceil.yml @@ -4,50 +4,59 @@ args: required: - name: value type: [number] - desc: "" + desc: '' optional: [] returns: type: number - desc: "" + desc: '' desc: | Returns the next highest integer value by rounding up if necessary. examples: -- context: - args: [1.001] - returns: 2 -- context: - args: [1.9] - returns: 2 -- context: - args: [1] - returns: 1 -- context: - args: [abc] - returns: -- context: &data - foo: -1 - zero: 0 - numbers: [-1, 3, 4, 5] - array: [-1, 3, 4, 5, a, '100'] - strings: [a, b, c] - decimals: [1.01, 1.2, -1.5] - str: Str - 'false': false - empty_list: [] - empty_hash: {} - objects: {foo: bar, bar: baz} - null_key: - args: [1.2] - returns: 2 -- context: *data - args: ['decimals[0]'] - returns: 2 -- context: *data - args: ['decimals[1]'] - returns: 2 -- context: *data - args: ['decimals[2]'] - returns: -1 -- context: *data - args: ['string'] - error: invalid-type + test0: + context: + args: [1.001] + returns: 2 + test1: + context: + args: [1.9] + returns: 2 + test2: + context: + args: [1] + returns: 1 + test3: + context: + args: [abc] + error: invalid-type + test4: + context: &data + foo: -1 + zero: 0 + numbers: [-1, 3, 4, 5] + array: [-1, 3, 4, 5, a, '100'] + strings: [a, b, c] + decimals: [1.01, 1.2, -1.5] + str: Str + 'false': false + empty_list: [] + empty_hash: {} + objects: {foo: bar, bar: baz} + null_key: + args: [1.2] + returns: 2 + test5: + context: *data + args: ['decimals[0]'] + returns: 2 + test6: + context: *data + args: ['decimals[1]'] + returns: 2 + test7: + context: *data + args: ['decimals[2]'] + returns: -1 + test8: + context: *data + args: ["'string'"] + error: invalid-type diff --git a/functions/contains.yml b/functions/contains.yml index e1648c9..00bb492 100644 --- a/functions/contains.yml +++ b/functions/contains.yml @@ -4,14 +4,14 @@ args: required: - name: subject type: [array, string] - desc: "" + desc: '' - name: search type: [any] - desc: "" + desc: '' optional: [] returns: type: boolean - desc: "" + desc: '' desc: | Returns `true` if the given `$subject` contains the provided `$search` string. @@ -22,63 +22,79 @@ desc: | If the provided `$subject` is a string, this function returns true if the string contains the provided `$search` argument. examples: -- context: - args: [foobar, foo] - returns: true -- context: - args: [foobar, not] - returns: false -- context: - args: [foobar, bar] - returns: true -- context: - args: [false, bar] - error: invalid-type -- context: - args: [foobar, 123] - returns: false -- context: [a, b] - args: ['@', a] - returns: true -- context: [a] - args: ['@', a] - returns: true -- context: [a] - args: ['@', b] - returns: false -- context: [foo, bar] - args: ['@', foo] - returns: true -- context: [foo, bar] - args: ['@', b] - returns: false -- context: &data - foo: -1 - zero: 0 - numbers: [-1, 3, 4, 5] - array: [-1, 3, 4, 5, a, '100'] - strings: [a, b, c] - decimals: [1.01, 1.2, -1.5] - str: Str - 'false': false - empty_list: [] - empty_hash: {} - objects: {foo: bar, bar: baz} - null_key: - args: ['abc', 'a'] - returns: true -- context: *data - args: ['abc', 'd'] - returns: false -- context: *data - args: [false, 'd'] - error: invalid-type -- context: *data - args: [strings, 'a'] - returns: true -- context: *data - args: [decimals, 1.2] - returns: true -- context: *data - args: [decimals, false] - returns: false + test0: + context: + args: ["'foobar'", "'foo'"] + returns: true + test1: + context: + args: ["'foobar'", "'not'"] + returns: false + test2: + context: + args: ["'foobar'", "'bar'"] + returns: true + test3: + context: + args: [false, "'bar'"] + error: invalid-type + test4: + context: + args: ["'foobar'", "'123'"] + returns: false + test5: + context: [a, b] + args: ['@', "'a'"] + returns: true + test6: + context: [a] + args: ['@', "'a'"] + returns: true + test7: + context: [a] + args: ['@', "'b'"] + returns: false + test8: + context: [foo, bar] + args: ['@', "'foo'"] + returns: true + test9: + context: [foo, bar] + args: ['@', "'b'"] + returns: false + test10: + context: &data + foo: -1 + zero: 0 + numbers: [-1, 3, 4, 5] + array: [-1, 3, 4, 5, a, '100'] + strings: [a, b, c] + decimals: [1.01, 1.2, -1.5] + str: Str + 'false': false + empty_list: [] + empty_hash: {} + objects: {foo: bar, bar: baz} + null_key: + args: ["'abc'", "'a'"] + returns: true + test11: + context: *data + args: ["'abc'", "'d'"] + returns: false + test12: + context: *data + args: [false, "'d'"] + error: invalid-type + test13: + context: *data + args: [strings, "'a'"] + returns: true + test14: + context: *data + args: [decimals, 1.2] + returns: true + test15: + context: *data + args: [decimals, false] + returns: false diff --git a/functions/ends_with.yml b/functions/ends_with.yml index 4ae5477..a431bde 100644 --- a/functions/ends_with.yml +++ b/functions/ends_with.yml @@ -4,54 +4,63 @@ args: required: - name: subject type: [string] - desc: "" + desc: '' - name: prefix type: [string] - desc: "" + desc: '' optional: [] returns: type: boolean - desc: "" + desc: '' desc: | Returns `true` if the `$subject` ends with the `$prefix`, otherwise this function returns `false`. examples: -- context: foobarbaz - args: ['@', baz] - returns: true -- context: foobarbaz - args: ['@', foo] - returns: false -- context: foobarbaz - args: ['@', z] - returns: true -- context: &data - foo: -1 - zero: 0 - numbers: [-1, 3, 4, 5] - array: [-1, 3, 4, 5, a, '100'] - strings: [a, b, c] - decimals: [1.01, 1.2, -1.5] - str: Str - 'false': false - empty_list: [] - empty_hash: {} - objects: {foo: bar, bar: baz} - null_key: - args: [str, 'r'] - returns: true -- context: *data - args: [str, 'tr'] - returns: true -- context: *data - args: [str, 'Str'] - returns: true -- context: *data - args: [str, 'SStr'] - returns: false -- context: *data - args: [str, 'foo'] - returns: false -- context: *data - args: [str, 0] - error: invalid-type + test0: + context: foobarbaz + args: ['@', "'baz'"] + returns: true + test1: + context: foobarbaz + args: ['@', "'foo'"] + returns: false + test2: + context: foobarbaz + args: ['@', "'z'"] + returns: true + test3: + context: &data + foo: -1 + zero: 0 + numbers: [-1, 3, 4, 5] + array: [-1, 3, 4, 5, a, '100'] + strings: [a, b, c] + decimals: [1.01, 1.2, -1.5] + str: Str + 'false': false + empty_list: [] + empty_hash: {} + objects: {foo: bar, bar: baz} + null_key: + args: [str, "'r'"] + returns: true + test4: + context: *data + args: [str, "'tr'"] + returns: true + test5: + context: *data + args: [str, "'Str'"] + returns: true + test6: + context: *data + args: [str, "'SStr'"] + returns: false + test7: + context: *data + args: [str, "'foo'"] + returns: false + test8: + context: *data + args: [str, 0] + error: invalid-type diff --git a/functions/floor.yml b/functions/floor.yml index 014cc16..7753fe1 100644 --- a/functions/floor.yml +++ b/functions/floor.yml @@ -4,47 +4,55 @@ args: required: - name: value type: [number] - desc: "" + desc: '' optional: [] returns: type: number - desc: "" + desc: '' desc: | Returns the next lowest integer value by rounding down if necessary. examples: -- context: - args: [1.001] - returns: 1 -- context: - args: [1.9] - returns: 1 -- context: - args: [1] - returns: 1 -- context: &data - foo: -1 - zero: 0 - numbers: [-1, 3, 4, 5] - array: [-1, 3, 4, 5, a, '100'] - strings: [a, b, c] - decimals: [1.01, 1.2, -1.5] - str: Str - 'false': false - empty_list: [] - empty_hash: {} - objects: {foo: bar, bar: baz} - null_key: - args: [1.2] - returns: 1 -- context: *data - args: ['string'] - error: invalid-type -- context: *data - args: ['decimals[0]'] - returns: 1 -- context: *data - args: [foo] - returns: -1 -- context: *data - args: [str] - error: invalid-type + test0: + context: + args: [1.001] + returns: 1 + test1: + context: + args: [1.9] + returns: 1 + test2: + context: + args: [1] + returns: 1 + test3: + context: &data + foo: -1 + zero: 0 + numbers: [-1, 3, 4, 5] + array: [-1, 3, 4, 5, a, '100'] + strings: [a, b, c] + decimals: [1.01, 1.2, -1.5] + str: Str + 'false': false + empty_list: [] + empty_hash: {} + objects: {foo: bar, bar: baz} + null_key: + args: [1.2] + returns: 1 + test4: + context: *data + args: [string] + error: invalid-type + test5: + context: *data + args: ['decimals[0]'] + returns: 1 + test6: + context: *data + args: [foo] + returns: -1 + test7: + context: *data + args: [str] + error: invalid-type diff --git a/functions/join.yml b/functions/join.yml index db7ab03..54dbf2e 100644 --- a/functions/join.yml +++ b/functions/join.yml @@ -4,66 +4,79 @@ args: required: - name: glue type: [string] - desc: "" + desc: '' - name: stringsarray type: ['array[string]'] - desc: "" + desc: '' optional: [] returns: type: string - desc: "" + desc: '' desc: | Returns all of the elements from the provided `$stringsarray` array joined together using the `$glue` argument as a separator between each. examples: -- context: [a, b] - args: [', ', '@'] - returns: a, b -- context: [a, b] - args: ['', '@'] - returns: ab -- context: [a, false, b] - args: [', ', '@'] - error: invalid-type -- context: [false] - args: [', ', '@'] - error: invalid-type -- context: &data - foo: -1 - zero: 0 - numbers: [-1, 3, 4, 5] - array: [-1, 3, 4, 5, a, '100'] - strings: [a, b, c] - decimals: [1.01, 1.2, -1.5] - str: Str - 'false': false - empty_list: [] - empty_hash: {} - objects: {foo: bar, bar: baz} - null_key: - args: [', ', strings] - returns: a, b, c -- context: *data - args: [', ', ["a', 'b"]] - returns: a,b -- context: *data - args: [', ', ["a", 0]] - error: invalid-type -- context: *data - args: [', ', str] - error: invalid-type -- context: *data - args: ['|', strings] - returns: a|b|c -- context: *data - args: [2, strings] - error: invalid-type -- context: *data - args: ['|', decimals] - error: invalid-type -- context: *data - args: ['|', 'decimals[].to_string(@)'] - returns: '1.01|1.2|-1.5' -- context: *data - args: ['|', empty_list] - returns: '' + test0: + context: [a, b] + args: ["', '", '@'] + returns: a, b + test1: + context: [a, b] + args: ["''", '@'] + returns: ab + test2: + context: [a, false, b] + args: ["', '", '@'] + error: invalid-type + test3: + context: [false] + args: ["', '", '@'] + error: invalid-type + test4: + context: &data + foo: -1 + zero: 0 + numbers: [-1, 3, 4, 5] + array: [-1, 3, 4, 5, a, '100'] + strings: [a, b, c] + decimals: [1.01, 1.2, -1.5] + str: Str + 'false': false + empty_list: [] + empty_hash: {} + objects: {foo: bar, bar: baz} + null_key: + args: ["', '", strings] + returns: a, b, c + test5: + context: *data + args: ["', '", ["'a'", "'b'"]] + returns: a, b + test6: + context: *data + args: ["', '", ["'a'", 0]] + error: invalid-type + test7: + context: *data + args: ["', '", str] + error: invalid-type + test8: + context: *data + args: ["'|'", strings] + returns: a|b|c + test9: + context: *data + args: [2, strings] + error: invalid-type + test10: + context: *data + args: ["'|'", decimals] + error: invalid-type + test11: + context: *data + args: ["'|'", 'decimals[].to_string(@)'] + returns: 1.01|1.2|-1.5 + test12: + context: *data + args: ["'|'", empty_list] + returns: '' diff --git a/functions/keys.yml b/functions/keys.yml index 7678ad6..4010345 100644 --- a/functions/keys.yml +++ b/functions/keys.yml @@ -4,11 +4,11 @@ args: required: - name: obj type: [object] - desc: "" + desc: '' optional: [] returns: type: array - desc: "" + desc: '' desc: | Returns an array containing the keys of the provided object. Note that because JSON hashes are inheritently unordered, the @@ -16,39 +16,47 @@ desc: | unordered. Implementations are not required to return keys in any specific order. examples: -- context: {foo: baz, bar: bam} - args: ['@'] - returns: [foo, bar] -- context: {} - args: ['@'] - returns: [] -- context: false - args: ['@'] - error: invalid-type -- context: [b, a, c] - args: ['@'] - error: invalid-type -- context: &data - foo: -1 - zero: 0 - numbers: [-1, 3, 4, 5] - array: [-1, 3, 4, 5, a, '100'] - strings: [a, b, c] - decimals: [1.01, 1.2, -1.5] - str: Str - 'false': false - empty_list: [] - empty_hash: {} - objects: {foo: bar, bar: baz} - null_key: - args: [foo] - error: invalid-type -- context: *data - args: [strings] - error: invalid-type -- context: *data - args: [false] - error: invalid-type -- context: *data - args: [empty_hash] - returns: [] + test0: + context: {foo: baz, bar: bam} + args: ['@'] + returns: [foo, bar] + test1: + context: {} + args: ['@'] + returns: [] + test2: + context: false + args: ['@'] + error: invalid-type + test3: + context: [b, a, c] + args: ['@'] + error: invalid-type + test4: + context: &data + foo: -1 + zero: 0 + numbers: [-1, 3, 4, 5] + array: [-1, 3, 4, 5, a, '100'] + strings: [a, b, c] + decimals: [1.01, 1.2, -1.5] + str: Str + 'false': false + empty_list: [] + empty_hash: {} + objects: {foo: bar, bar: baz} + null_key: + args: [foo] + error: invalid-type + test5: + context: *data + args: [strings] + error: invalid-type + test6: + context: *data + args: [false] + error: invalid-type + test7: + context: *data + args: [empty_hash] + returns: [] diff --git a/functions/length.yml b/functions/length.yml index 6f3b6a3..f763c9f 100644 --- a/functions/length.yml +++ b/functions/length.yml @@ -4,11 +4,11 @@ args: required: - name: subject type: [string, array, object] - desc: "" + desc: '' optional: [] returns: type: number - desc: "" + desc: '' desc: | Returns the length of the given argument using the following types rules: @@ -21,66 +21,83 @@ desc: | 3. object: returns the number of key-value pairs in the object examples: -- context: - args: [abc] - returns: 3 -- context: - args: ['@'] - returns: 7 -- context: - args: [not_there] - error: invalid-type -- context: [a, b, c] - args: ['@'] - returns: 3 -- context: [] - args: ['@'] - returns: 0 -- context: {} - args: ['@'] - returns: 0 -- context: {foo: bar, baz: bam} - args: ['@'] - returns: 2 -- context: &data - foo: -1 - zero: 0 - numbers: [-1, 3, 4, 5] - array: [-1, 3, 4, 5, a, '100'] - strings: [a, b, c] - decimals: [1.01, 1.2, -1.5] - str: Str - 'false': false - empty_list: [] - empty_hash: {} - objects: {foo: bar, bar: baz} - null_key: - args: ['abc'] - returns: 3 -- context: *data - args: ['\u2713foo'] - returns: 4 -- context: *data - args: [''] - returns: 0 -- context: *data - args: ['@'] - returns: 12 -- context: *data - args: ['strings[0]'] - returns: 1 -- context: *data - args: [str] - returns: 3 -- context: *data - args: [array] - returns: 6 -- context: *data - args: [objects] - returns: 2 -- context: *data - args: [false] - error: invalid-type -- context: *data - args: [foo] - error: invalid-type \ No newline at end of file + test0: + context: + args: ["'abc'"] + returns: 3 + test1: + context: [1, 2, 3, 4, 5, 6, 7] + args: ['@'] + returns: 7 + test2: + context: + args: [not_there] + error: invalid-type + test3: + context: [a, b, c] + args: ['@'] + returns: 3 + test4: + context: [] + args: ['@'] + returns: 0 + test5: + context: {} + args: ['@'] + returns: 0 + test6: + context: {foo: bar, baz: bam} + args: ['@'] + returns: 2 + test7: + context: &data + foo: -1 + zero: 0 + numbers: [-1, 3, 4, 5] + array: [-1, 3, 4, 5, a, '100'] + strings: [a, b, c] + decimals: [1.01, 1.2, -1.5] + str: Str + 'false': false + empty_list: [] + empty_hash: {} + objects: {foo: bar, bar: baz} + null_key: + args: ["'abc'"] + returns: 3 + test8: + context: *data + args: ["'\u2713foo'"] + returns: 4 + test9: + context: *data + args: ["''"] + returns: 0 + test10: + context: *data + args: ['@'] + returns: 12 + test11: + context: *data + args: ['strings[0]'] + returns: 1 + test12: + context: *data + args: [str] + returns: 3 + test13: + context: *data + args: [array] + returns: 6 + test14: + context: *data + args: [objects] + returns: 2 + test15: + context: *data + args: [false] + error: invalid-type + test16: + context: *data + args: [foo] + error: invalid-type diff --git a/functions/map.yml b/functions/map.yml index c7ad925..07c6812 100644 --- a/functions/map.yml +++ b/functions/map.yml @@ -4,14 +4,14 @@ args: required: - name: expr type: [expression] - desc: "" + desc: '' - name: elements type: ['array[any]'] - desc: "" + desc: '' optional: [] returns: type: array[any] - desc: "" + desc: '' desc: | Apply the `expr` to every element in the `elements` array and return the array of results. An `elements` of length @@ -21,39 +21,49 @@ desc: | the result of applying the `expr` for every element in the `elements` array, even if the result if `null`. examples: -- context: {array: [foo: a, foo: b, {}, [], foo: f]} - args: ['&foo', array] - returns: [a, b, null, null, f] -- context: [[1, 2, 3, [4]], [5, 6, 7, [8, 9]]] - args: ['&[]', '@'] - returns: [[1, 2, 3, 4], [5, 6, 7, 8, 9]] -- context: &data1 - people: [{a: 10, b: 1, c: z}, {a: 10, b: 2, c: null}, {a: 10, b: 3}, {a: 10, b: 4, - c: z}, {a: 10, b: 5, c: null}, {a: 10, b: 6}, {a: 10, b: 7, c: z}, {a: 10, - b: 8, c: null}, {a: 10, b: 9}] - empty: [] - args: ['&a', people] - returns: [10, 10, 10, 10, 10, 10, 10, 10, 10] -- context: *data1 - args: ['&c', people] - returns: [z, null, null, z, null, null, z, null, null] -- context: *data1 - args: ['&a', badkey] - error: invalid-type -- context: *data1 - args: ['&foo', empty] - returns: [] -- context: &data2 - array: [foo: {bar: yes1}, foo: {bar: yes2}, foo1: {bar: no}] - args: ['&foo.bar', array] - returns: [yes1, yes2, null] -- context: *data2 - args: ['&foo1.bar', array] - returns: [null, null, no] -- context: *data2 - args: ['&foo.bar.baz', array] - returns: [null, null, null] -- context: - array: [[1, 2, 3, [4]], [5, 6, 7, [8, 9]]] - args: ['&[]', array] - returns: [[1, 2, 3, 4], [5, 6, 7, 8, 9]] + test0: + context: {array: [foo: a, foo: b, {}, [], foo: f]} + args: ['&foo', array] + returns: [a, b, !!null '', !!null '', f] + test1: + context: [[1, 2, 3, [4]], [5, 6, 7, [8, 9]]] + args: ['&[]', '@'] + returns: [[1, 2, 3, 4], [5, 6, 7, 8, 9]] + test2: + context: &data1 + people: [{a: 10, b: 1, c: z}, {a: 10, b: 2, c: !!null ''}, {a: 10, b: 3}, { + a: 10, b: 4, c: z}, {a: 10, b: 5, c: !!null ''}, {a: 10, b: 6}, {a: 10, + b: 7, c: z}, {a: 10, b: 8, c: !!null ''}, {a: 10, b: 9}] + empty: [] + args: ['&a', people] + returns: [10, 10, 10, 10, 10, 10, 10, 10, 10] + test3: + context: *data1 + args: ['&c', people] + returns: [z, !!null '', !!null '', z, !!null '', !!null '', z, !!null '', !!null ''] + test4: + context: *data1 + args: ['&a', badkey] + error: invalid-type + test5: + context: *data1 + args: ['&foo', empty] + returns: [] + test6: + context: &data2 + array: [foo: {bar: yes1}, foo: {bar: yes2}, foo1: {bar: no}] + args: ['&foo.bar', array] + returns: [yes1, yes2, !!null ''] + test7: + context: *data2 + args: ['&foo1.bar', array] + returns: [!!null '', !!null '', no] + test8: + context: *data2 + args: ['&foo.bar.baz', array] + returns: [!!null '', !!null '', !!null ''] + test9: + context: + array: [[1, 2, 3, [4]], [5, 6, 7, [8, 9]]] + args: ['&[]', array] + returns: [[1, 2, 3, 4], [5, 6, 7, 8, 9]] diff --git a/functions/max.yml b/functions/max.yml index d436b21..0488f42 100644 --- a/functions/max.yml +++ b/functions/max.yml @@ -4,55 +4,65 @@ args: required: - name: collection type: ['array[number]', 'array[string]'] - desc: "" + desc: '' optional: [] returns: type: number - desc: "" + desc: '' desc: | Returns the highest found number in the provided array argument. An empty array will produce a return value of null. examples: -- context: [10, 15] - args: ['@'] - returns: 15 -- context: [a, b] - args: ['@'] - returns: b -- context: [a, 2, b] - args: ['@'] - error: invalid-type -- context: [10, false, 20] - args: ['@'] - error: invalid-type -- context: &data - foo: -1 - zero: 0 - numbers: [-1, 3, 4, 5] - array: [-1, 3, 4, 5, a, '100'] - strings: [a, b, c] - decimals: [1.01, 1.2, -1.5] - str: Str - 'false': false - empty_list: [] - empty_hash: {} - objects: {foo: bar, bar: baz} - null_key: - args: [numbers] - returns: 5 -- context: *data - args: [decimals] - returns: 1.2 -- context: *data - args: [strings] - returns: c -- context: *data - args: [abc] - error: invalid-type -- context: *data - args: [array] - error: invalid-type -- context: *data - args: [empty_list] - returns: + test0: + context: [10, 15] + args: ['@'] + returns: 15 + test1: + context: [a, b] + args: ['@'] + returns: b + test2: + context: [a, 2, b] + args: ['@'] + error: invalid-type + test3: + context: [10, false, 20] + args: ['@'] + error: invalid-type + test4: + context: &data + foo: -1 + zero: 0 + numbers: [-1, 3, 4, 5] + array: [-1, 3, 4, 5, a, '100'] + strings: [a, b, c] + decimals: [1.01, 1.2, -1.5] + str: Str + 'false': false + empty_list: [] + empty_hash: {} + objects: {foo: bar, bar: baz} + null_key: + args: [numbers] + returns: 5 + test5: + context: *data + args: [decimals] + returns: 1.2 + test6: + context: *data + args: [strings] + returns: c + test7: + context: *data + args: [abc] + error: invalid-type + test8: + context: *data + args: [array] + error: invalid-type + test9: + context: *data + args: [empty_list] + returns: diff --git a/functions/max_by.yml b/functions/max_by.yml index 3c7b054..87f1155 100644 --- a/functions/max_by.yml +++ b/functions/max_by.yml @@ -4,51 +4,51 @@ args: required: - name: elements type: [array] - desc: "" + desc: '' - name: expr type: [expression->number, expression->string] - desc: "" + desc: '' optional: [] returns: type: any - desc: "" + desc: '' desc: | Return the maximum element in an array using the expression `expr` as the comparison key. The entire maximum element is returned. Below are several examples using the `people` array (defined above) as the given input. examples: -- context: - args: [people, '&age'] - returns: {age: 50, age_str: '50', bool: false, name: d} -- context: - args: [people, '&age'] - returns: 50 -- context: - args: [people, '&to_number(age_str)'] - returns: {age: 50, age_str: '50', bool: false, name: d} -- context: - args: [people, '&age_str'] - error: invalid-type -- context: - args: [people, age] - error: invalid-type -- context: &data - people: [{age: 20, age_str: '20', bool: true, name: a, extra: foo}, {age: 40, - age_str: '40', bool: false, name: b, extra: bar}, {age: 30, age_str: '30', - bool: true, name: c}, {age: 50, age_str: '50', bool: false, name: d}, {age: 10, - age_str: '10', bool: true, name: 3}] - args: [people, '&age'] - returns: {age: 50, age_str: '50', bool: false, name: d} -- context: *data - args: [people, '&age_str'] - returns: {age: 50, age_str: '50', bool: false, name: d} -- context: *data - args: [people, '&bool'] - error: invalid-type -- context: *data - args: [people, '&extra'] - error: invalid-type -- context: *data - args: [people, '&to_number(age_str)'] - returns: {age: 50, age_str: '50', bool: false, name: d} + test5: + context: &data + people: + - {age: 20, age_str: '20', bool: true, name: a, extra: foo} + - {age: 40, age_str: '40', bool: false, name: b, extra: bar} + - {age: 30, age_str: '30', bool: true, name: c} + - {age: 50, age_str: '50', bool: false, name: d} + - {age: 10, age_str: '10', bool: true, name: 3} + args: [people, '&age'] + returns: {age: 50, age_str: '50', bool: false, name: d} + test6: + context: *data + args: [people, '&age_str'] + returns: {age: 50, age_str: '50', bool: false, name: d} + test7: + context: *data + args: [people, '&bool'] + error: invalid-type + test8: + context: *data + args: [people, '&extra'] + error: invalid-type + test9: + context: *data + args: [people, '&to_number(age_str)'] + returns: {age: 50, age_str: '50', bool: false, name: d} + test3: + context: *data + args: [people, '&age_str'] + error: invalid-type + test4: + context: *data + args: [people, age] + error: invalid-type diff --git a/functions/merge.yml b/functions/merge.yml index 1385d43..f43d5b5 100644 --- a/functions/merge.yml +++ b/functions/merge.yml @@ -4,14 +4,14 @@ args: required: - name: argument type: [object] - desc: "" + desc: '' optional: - - name: ".." + - name: .. type: [object] - desc: "" + desc: '' returns: type: object - desc: "" + desc: '' desc: | Accepts 0 or more objects as arguments, and returns a single object with subsequent objects merged. Each subsequent object’s key/value @@ -20,39 +20,47 @@ desc: | the first object being the base object, and each subsequent argument being overrides that are applied to the base object. examples: -- context: - args: [a: b, c: d] - returns: {a: b, c: d} -- context: - args: [a: b, a: override] - returns: {a: override} -- context: - args: [{a: x, b: y}, {b: override, c: z}] - returns: {a: x, b: override, c: z} -- context: &data - foo: -1 - zero: 0 - numbers: [-1, 3, 4, 5] - array: [-1, 3, 4, 5, a, '100'] - strings: [a, b, c] - decimals: [1.01, 1.2, -1.5] - str: Str - 'false': false - empty_list: [] - empty_hash: {} - objects: {foo: bar, bar: baz} - null_key: - args: [{}] - returns: {} -- context: *data - args: [{}, {}] - returns: {} -- context: *data - args: [{"a": 1}, {"b": 2}] - returns: {a: 1, b: 2} -- context: *data - args: [{"a": 1}, {"a": 2}] - returns: {a: 2} -- context: *data - args: [{"a": 1, "b": 2}, {"a": 2, "c": 3}, {"d": 4}] - returns: {a: 2, b: 2, c: 3, d: 4} + test0: + context: + args: [a: "'b'", c: "'d'"] + returns: {a: b, c: d} + test1: + context: + args: [a: "'b'", a: "'override'"] + returns: {a: override} + test2: + context: + args: [{a: "'x'", b: "'y'"}, {b: "'override'", c: "'z'"}] + returns: {a: x, b: override, c: z} + test3: + context: &data + foo: -1 + zero: 0 + numbers: [-1, 3, 4, 5] + array: [-1, 3, 4, 5, a, '100'] + strings: [a, b, c] + decimals: [1.01, 1.2, -1.5] + str: Str + 'false': false + empty_list: [] + empty_hash: {} + objects: {foo: bar, bar: baz} + null_key: + args: [empty_hash] + returns: {} + test4: + context: *data + args: [empty_hash, empty_hash] + returns: {} + test5: + context: *data + args: [a: 1, b: 2] + returns: {a: 1, b: 2} + test6: + context: *data + args: [a: 1, a: 2] + returns: {a: 2} + test7: + context: *data + args: [{a: 1, b: 2}, {a: 2, c: 3}, d: 4] + returns: {a: 2, b: 2, c: 3, d: 4} diff --git a/functions/min.yml b/functions/min.yml index bb8ec68..7db770f 100644 --- a/functions/min.yml +++ b/functions/min.yml @@ -4,53 +4,63 @@ args: required: - name: collection type: ['array[number]', 'array[string]'] - desc: "" + desc: '' optional: [] returns: type: number - desc: "" + desc: '' desc: | Returns the lowest found number in the provided `$collection` argument. examples: -- context: [10, 15] - args: ['@'] - returns: 10 -- context: [a, b] - args: ['@'] - returns: a -- context: [a, 2, b] - args: ['@'] - error: invalid-type -- context: [10, false, 20] - args: ['@'] - error: invalid-type -- context: &data - foo: -1 - zero: 0 - numbers: [-1, 3, 4, 5] - array: [-1, 3, 4, 5, a, '100'] - strings: [a, b, c] - decimals: [1.01, 1.2, -1.5] - str: Str - 'false': false - empty_list: [] - empty_hash: {} - objects: {foo: bar, bar: baz} - null_key: - args: [numbers] - returns: -1 -- context: *data - args: [decimals] - returns: -1.5 -- context: *data - args: [abc] - error: invalid-type -- context: *data - args: [array] - error: invalid-type -- context: *data - args: [empty_list] - returns: -- context: *data - args: [strings] - returns: a + test0: + context: [10, 15] + args: ['@'] + returns: 10 + test1: + context: [a, b] + args: ['@'] + returns: a + test2: + context: [a, 2, b] + args: ['@'] + error: invalid-type + test3: + context: [10, false, 20] + args: ['@'] + error: invalid-type + test4: + context: &data + foo: -1 + zero: 0 + numbers: [-1, 3, 4, 5] + array: [-1, 3, 4, 5, a, '100'] + strings: [a, b, c] + decimals: [1.01, 1.2, -1.5] + str: Str + 'false': false + empty_list: [] + empty_hash: {} + objects: {foo: bar, bar: baz} + null_key: + args: [numbers] + returns: -1 + test5: + context: *data + args: [decimals] + returns: -1.5 + test6: + context: *data + args: [abc] + error: invalid-type + test7: + context: *data + args: [array] + error: invalid-type + test8: + context: *data + args: [empty_list] + returns: + test9: + context: *data + args: [strings] + returns: a diff --git a/functions/min_by.yml b/functions/min_by.yml index e9a7803..2a76b08 100644 --- a/functions/min_by.yml +++ b/functions/min_by.yml @@ -4,51 +4,49 @@ args: required: - name: elements type: [array] - desc: "" + desc: '' - name: expr type: [expression->number, expression->string] - desc: "" + desc: '' optional: [] returns: type: any - desc: "" + desc: '' desc: | Return the minimum element in an array using the expression `expr` as the comparison key. The entire maximum element is returned. Below are several examples using the `people` array (defined above) as the given input. examples: -- context: - args: [people, '&age'] - returns: {age: 10, age_str: '10', bool: true, name: 3} -- context: - args: [people, '&age'] - returns: 10 -- context: - args: [people, '&to_number(age_str)'] - returns: {age: 10, age_str: '10', bool: true, name: 3} -- context: - args: [people, '&age_str'] - error: invalid-type -- context: - args: [people, age] - error: invalid-type -- context: &data - people: [{age: 20, age_str: '20', bool: true, name: a, extra: foo}, {age: 40, - age_str: '40', bool: false, name: b, extra: bar}, {age: 30, age_str: '30', - bool: true, name: c}, {age: 50, age_str: '50', bool: false, name: d}, {age: 10, - age_str: '10', bool: true, name: 3}] - args: [people, '&age'] - returns: {age: 10, age_str: '10', bool: true, name: 3} -- context: *data - args: [people, '&age_str'] - returns: {age: 10, age_str: '10', bool: true, name: 3} -- context: *data - args: [people, '&bool'] - error: invalid-type -- context: *data - args: [people, '&extra'] - error: invalid-type -- context: *data - args: [people, '&to_number(age_str)'] - returns: {age: 10, age_str: '10', bool: true, name: 3} + test5: + context: &data + people: [{age: 20, age_str: '20', bool: true, name: a, extra: foo}, {age: 40, + age_str: '40', bool: false, name: b, extra: bar}, {age: 30, age_str: '30', + bool: true, name: c}, {age: 50, age_str: '50', bool: false, name: d}, { + age: 10, age_str: '10', bool: true, name: 3}] + args: [people, '&age'] + returns: {age: 10, age_str: '10', bool: true, name: 3} + test6: + context: *data + args: [people, '&age_str'] + returns: {age: 10, age_str: '10', bool: true, name: 3} + test7: + context: *data + args: [people, '&bool'] + error: invalid-type + test8: + context: *data + args: [people, '&extra'] + error: invalid-type + test9: + context: *data + args: [people, '&to_number(age_str)'] + returns: {age: 10, age_str: '10', bool: true, name: 3} + test3: + context: *data + args: [people, '&age_str'] + error: invalid-type + test4: + context: *data + args: [people, age] + error: invalid-type \ No newline at end of file diff --git a/functions/not_null.yml b/functions/not_null.yml index 2bf608d..a998d26 100644 --- a/functions/not_null.yml +++ b/functions/not_null.yml @@ -4,53 +4,61 @@ args: required: - name: argument type: [any] - desc: "" + desc: '' optional: - - name: ".." + - name: .. type: [any] - desc: "" + desc: '' returns: type: any - desc: "" + desc: '' desc: | Returns the first argument that does not resolve to `null`. This function accepts one or more arguments, and will evaluate them in order until a non null argument is encounted. If all arguments values resolve to `null`, then a value of `null` is returned. examples: -- context: {a: null, b: null, c: [], d: foo} - args: [no_exist, a, b, c, d] - returns: [] -- context: {a: null, b: null, c: [], d: foo} - args: [a, b, null, d, c] - returns: foo -- context: {a: null, b: null, c: [], d: foo} - args: [a, b] - returns: -- context: &data - foo: -1 - zero: 0 - numbers: [-1, 3, 4, 5] - array: [-1, 3, 4, 5, a, '100'] - strings: [a, b, c] - decimals: [1.01, 1.2, -1.5] - str: Str - 'false': false - empty_list: [] - empty_hash: {} - objects: {foo: bar, bar: baz} - null_key: - args: [unknown_key, str] - returns: Str -- context: *data - args: [unknown_key, foo.bar, empty_list, str] - returns: [] -- context: *data - args: [unknown_key, null_key, empty_list, str] - returns: [] -- context: *data - args: [all, expressions, are_null] - returns: -- context: *data - args: [''] - error: invalid-arity + test0: + context: {a: !!null '', b: !!null '', c: [], d: foo} + args: [no_exist, a, b, c, d] + returns: [] + test1: + context: {a: !!null '', b: !!null '', c: [], d: foo} + args: [a, b, !!null '', d, c] + returns: foo + test2: + context: {a: !!null '', b: !!null '', c: [], d: foo} + args: [a, b] + returns: + test3: + context: &data + foo: -1 + zero: 0 + numbers: [-1, 3, 4, 5] + array: [-1, 3, 4, 5, a, '100'] + strings: [a, b, c] + decimals: [1.01, 1.2, -1.5] + str: Str + 'false': false + empty_list: [] + empty_hash: {} + objects: {foo: bar, bar: baz} + null_key: + args: [unknown_key, str] + returns: Str + test4: + context: *data + args: [unknown_key, foo.bar, empty_list, str] + returns: [] + test5: + context: *data + args: [unknown_key, null_key, empty_list, str] + returns: [] + test6: + context: *data + args: [all, expressions, are_null] + returns: + test7: + context: *data + args: [''] + error: invalid-arity diff --git a/functions/reverse.yml b/functions/reverse.yml index 7c90a3a..1f6e3bb 100644 --- a/functions/reverse.yml +++ b/functions/reverse.yml @@ -4,50 +4,59 @@ args: required: - name: argument type: [string, array] - desc: "" + desc: '' optional: [] returns: type: array - desc: "" + desc: '' desc: | Reverses the order of the `$argument`. examples: -- context: [0, 1, 2, 3, 4] - args: ['@'] - returns: [4, 3, 2, 1, 0] -- context: [] - args: ['@'] - returns: [] -- context: [a, b, c, 1, 2, 3] - args: ['@'] - returns: [3, 2, 1, c, b, a] -- context: abcd - args: ['@'] - returns: dcba -- context: &data - foo: -1 - zero: 0 - numbers: [-1, 3, 4, 5] - array: [-1, 3, 4, 5, a, '100'] - strings: [a, b, c] - decimals: [1.01, 1.2, -1.5] - str: Str - 'false': false - empty_list: [] - empty_hash: {} - objects: {foo: bar, bar: baz} - null_key: - args: [numbers] - returns: [5, 4, 3, -1] -- context: *data - args: [array] - returns: ['100', a, 5, 4, 3, -1] -- context: *data - args: [[]] - returns: [] -- context: *data - args: [''] - returns: '' -- context: *data - args: ['hello world'] - returns: dlrow olleh + test0: + context: [0, 1, 2, 3, 4] + args: ['@'] + returns: [4, 3, 2, 1, 0] + test1: + context: [] + args: ['@'] + returns: [] + test2: + context: [a, b, c, 1, 2, 3] + args: ['@'] + returns: [3, 2, 1, c, b, a] + test3: + context: abcd + args: ['@'] + returns: dcba + test4: + context: &data + foo: -1 + zero: 0 + numbers: [-1, 3, 4, 5] + array: [-1, 3, 4, 5, a, '100'] + strings: [a, b, c] + decimals: [1.01, 1.2, -1.5] + str: Str + 'false': false + empty_list: [] + empty_hash: {} + objects: {foo: bar, bar: baz} + null_key: + args: [numbers] + returns: [5, 4, 3, -1] + test5: + context: *data + args: [array] + returns: ['100', a, 5, 4, 3, -1] + test6: + context: *data + args: [empty_list] + returns: [] + test7: + context: *data + args: ["''"] + returns: '' + test8: + context: *data + args: ["'hello world'"] + returns: dlrow olleh diff --git a/functions/sort.yml b/functions/sort.yml index b733d30..e82abce 100644 --- a/functions/sort.yml +++ b/functions/sort.yml @@ -4,11 +4,11 @@ args: required: - name: list type: ['array[number]', 'array[string]'] - desc: "" + desc: '' optional: [] returns: type: array - desc: "" + desc: '' desc: | This function accepts an array `$list` argument and returns the sorted elements of the `$list` as an array. @@ -16,60 +16,75 @@ desc: | The array must be a list of strings or numbers. Sorting strings is based on code points. Locale is not taken into account. examples: -- context: [b, a, c] - args: ['@'] - returns: [a, b, c] -- context: [1, a, c] - args: ['@'] - returns: [1, a, c] -- context: [false, [], null] - args: ['@'] - returns: [[], null, false] -- context: [[], {}, false] - args: ['@'] - returns: [{}, [], false] -- context: {a: 1, b: 2} - args: ['@'] - returns: -- context: false - args: ['@'] - returns: -- context: &data - foo: -1 - zero: 0 - numbers: [-1, 3, 4, 5] - array: [-1, 3, 4, 5, a, '100'] - strings: [a, b, c] - decimals: [1.01, 1.2, -1.5] - str: Str - 'false': false - empty_list: [] - empty_hash: {} - objects: {foo: bar, bar: baz} - null_key: - args: [keys(objects)] - returns: [bar, foo] -- context: *data - args: [values(objects)] - returns: [bar, baz] -- context: *data - args: [numbers] - returns: [-1, 3, 4, 5] -- context: *data - args: [strings] - returns: [a, b, c] -- context: *data - args: [decimals] - returns: [-1.5, 1.01, 1.2] -- context: *data - args: [array] - error: invalid-type -- context: *data - args: [abc] - error: invalid-type -- context: *data - args: [empty_list] - returns: [] -- context: *data - args: ['@'] - error: invalid-type + test0: + context: [b, a, c] + args: ['@'] + returns: [a, b, c] + test1: + context: [1, a, c] + args: ['@'] + returns: [1, a, c] + test2: + context: [false, [], !!null ''] + args: ['@'] + returns: [[], !!null '', false] + test3: + context: [[], {}, false] + args: ['@'] + returns: [{}, [], false] + test4: + context: {a: 1, b: 2} + args: ['@'] + returns: + test5: + context: false + args: ['@'] + returns: + test6: + context: &data + foo: -1 + zero: 0 + numbers: [-1, 3, 4, 5] + array: [-1, 3, 4, 5, a, '100'] + strings: [a, b, c] + decimals: [1.01, 1.2, -1.5] + str: Str + 'false': false + empty_list: [] + empty_hash: {} + objects: {foo: bar, bar: baz} + null_key: + args: [keys(objects)] + returns: [bar, foo] + test7: + context: *data + args: [values(objects)] + returns: [bar, baz] + test8: + context: *data + args: [numbers] + returns: [-1, 3, 4, 5] + test9: + context: *data + args: [strings] + returns: [a, b, c] + test10: + context: *data + args: [decimals] + returns: [-1.5, 1.01, 1.2] + test11: + context: *data + args: [array] + error: invalid-type + test12: + context: *data + args: [abc] + error: invalid-type + test13: + context: *data + args: [empty_list] + returns: [] + test14: + context: *data + args: ['@'] + error: invalid-type diff --git a/functions/sort_by.yml b/functions/sort_by.yml index 54f52dc..f63ec06 100644 --- a/functions/sort_by.yml +++ b/functions/sort_by.yml @@ -4,14 +4,14 @@ args: required: - name: elements type: [array] - desc: "" + desc: '' - name: expr type: [expression->number, expression->string] - desc: "" + desc: '' optional: [] returns: type: array - desc: "" + desc: '' desc: | Sort an array using an expression `expr` as the sort key. For each element in the array of `elements`, the `expr` expression is applied and the @@ -25,65 +25,60 @@ desc: | given input. `sort_by` follows the same sorting logic as the `sort` function. examples: -- context: - args: [people, '&age'] - returns: [10, 20, 30, 40, 50] -- context: - args: [people, '&age'] - returns: {age: 10, age_str: '10', bool: true, name: 3} -- context: - args: [people, '&to_number(age_str)'] - returns: {age: 10, age_str: '10', bool: true, name: 3} -- context: &data - people: [{age: 20, age_str: '20', bool: true, name: a, extra: foo}, {age: 40, - age_str: '40', bool: false, name: b, extra: bar}, {age: 30, age_str: '30', - bool: true, name: c}, {age: 50, age_str: '50', bool: false, name: d}, {age: 10, - age_str: '10', bool: true, name: 3}] - args: [people, '&age'] - returns: [{age: 10, age_str: '10', bool: true, name: 3}, {age: 20, age_str: '20', - bool: true, name: a, extra: foo}, {age: 30, age_str: '30', bool: true, name: c}, - {age: 40, age_str: '40', bool: false, name: b, extra: bar}, {age: 50, age_str: '50', - bool: false, name: d}] -- context: *data - args: [people, '&age_str'] - returns: [{age: 10, age_str: '10', bool: true, name: 3}, {age: 20, age_str: '20', - bool: true, name: a, extra: foo}, {age: 30, age_str: '30', bool: true, name: c}, - {age: 40, age_str: '40', bool: false, name: b, extra: bar}, {age: 50, age_str: '50', - bool: false, name: d}] -- context: *data - args: [people, '&to_number(age_str)'] - returns: [{age: 10, age_str: '10', bool: true, name: 3}, {age: 20, age_str: '20', - bool: true, name: a, extra: foo}, {age: 30, age_str: '30', bool: true, name: c}, - {age: 40, age_str: '40', bool: false, name: b, extra: bar}, {age: 50, age_str: '50', - bool: false, name: d}] -- context: *data - args: [people, '&age)[].nam'] - returns: [3, a, c, b, d] -- context: *data - args: [people, '&extra'] - error: invalid-type -- context: *data - args: [people, '&bool'] - error: invalid-type -- context: *data - args: [people, '&name'] - error: invalid-type -- context: *data - args: [people, name] - error: invalid-type -- context: *data - args: [people, '&age)[].extr'] - returns: [foo, bar] -- context: *data - args: [[], '&age'] - returns: [] -- context: - people: [{age: 10, order: '1'}, {age: 10, order: '2'}, {age: 10, order: '3'}, + test3: + context: &data + people: [{age: 20, age_str: '20', bool: true, name: a, extra: foo}, {age: 40, + age_str: '40', bool: false, name: b, extra: bar}, {age: 30, age_str: '30', + bool: true, name: c}, {age: 50, age_str: '50', bool: false, name: d}, { + age: 10, age_str: '10', bool: true, name: 3}] + empty_list: [] + args: [people, '&age'] + returns: [{age: 10, age_str: '10', bool: true, name: 3}, {age: 20, age_str: '20', + bool: true, name: a, extra: foo}, {age: 30, age_str: '30', bool: true, name: c}, + {age: 40, age_str: '40', bool: false, name: b, extra: bar}, {age: 50, age_str: '50', + bool: false, name: d}] + test4: + context: *data + args: [people, '&age_str'] + returns: [{age: 10, age_str: '10', bool: true, name: 3}, {age: 20, age_str: '20', + bool: true, name: a, extra: foo}, {age: 30, age_str: '30', bool: true, name: c}, + {age: 40, age_str: '40', bool: false, name: b, extra: bar}, {age: 50, age_str: '50', + bool: false, name: d}] + test5: + context: *data + args: [people, '&to_number(age_str)'] + returns: [{age: 10, age_str: '10', bool: true, name: 3}, {age: 20, age_str: '20', + bool: true, name: a, extra: foo}, {age: 30, age_str: '30', bool: true, name: c}, + {age: 40, age_str: '40', bool: false, name: b, extra: bar}, {age: 50, age_str: '50', + bool: false, name: d}] + test7: + context: *data + args: [people, '&extra'] + error: invalid-type + test8: + context: *data + args: [people, '&bool'] + error: invalid-type + test9: + context: *data + args: [people, '&name'] + error: invalid-type + test10: + context: *data + args: [people, name] + error: invalid-type + test12: + context: *data + args: [empty_list, '&age'] + returns: [] + test13: + context: + people: [{age: 10, order: '1'}, {age: 10, order: '2'}, {age: 10, order: '3'}, + {age: 10, order: '4'}, {age: 10, order: '5'}, {age: 10, order: '6'}, {age: 10, + order: '7'}, {age: 10, order: '8'}, {age: 10, order: '9'}, {age: 10, order: '10'}, + {age: 10, order: '11'}] + args: [people, '&age'] + returns: [{age: 10, order: '1'}, {age: 10, order: '2'}, {age: 10, order: '3'}, {age: 10, order: '4'}, {age: 10, order: '5'}, {age: 10, order: '6'}, {age: 10, order: '7'}, {age: 10, order: '8'}, {age: 10, order: '9'}, {age: 10, order: '10'}, {age: 10, order: '11'}] - args: [people, '&age'] - returns: [{age: 10, order: '1'}, {age: 10, order: '2'}, {age: 10, order: '3'}, { - age: 10, order: '4'}, {age: 10, order: '5'}, {age: 10, order: '6'}, {age: 10, - order: '7'}, {age: 10, order: '8'}, {age: 10, order: '9'}, {age: 10, order: '10'}, - {age: 10, order: '11'}] diff --git a/functions/starts_with.yml b/functions/starts_with.yml index a6a5e16..ba50011 100644 --- a/functions/starts_with.yml +++ b/functions/starts_with.yml @@ -4,51 +4,59 @@ args: required: - name: subject type: [string] - desc: "" + desc: '' - name: prefix type: [string] - desc: "" + desc: '' optional: [] returns: type: boolean - desc: "" + desc: '' desc: | Returns `true` if the `$subject` starts with the `$prefix`, otherwise this function returns `false`. examples: -- context: foobarbaz - args: ['@', foo] - returns: true -- context: foobarbaz - args: ['@', baz] - returns: false -- context: foobarbaz - args: ['@', f] - returns: true -- context: &data - foo: -1 - zero: 0 - numbers: [-1, 3, 4, 5] - array: [-1, 3, 4, 5, a, '100'] - strings: [a, b, c] - decimals: [1.01, 1.2, -1.5] - str: Str - 'false': false - empty_list: [] - empty_hash: {} - objects: {foo: bar, bar: baz} - null_key: - args: [str, 'S'] - returns: true -- context: *data - args: [str, 'St'] - returns: true -- context: *data - args: [str, 'Str'] - returns: true -- context: *data - args: [str, 'String'] - returns: false -- context: *data - args: [str, 0] - error: invalid-type + test0: + context: foobarbaz + args: ['@', "'foo'"] + returns: true + test1: + context: foobarbaz + args: ['@', "'baz'"] + returns: false + test2: + context: foobarbaz + args: ['@', "'f'"] + returns: true + test3: + context: &data + foo: -1 + zero: 0 + numbers: [-1, 3, 4, 5] + array: [-1, 3, 4, 5, a, '100'] + strings: [a, b, c] + decimals: [1.01, 1.2, -1.5] + str: Str + 'false': false + empty_list: [] + empty_hash: {} + objects: {foo: bar, bar: baz} + null_key: + args: [str, "'S'"] + returns: true + test4: + context: *data + args: [str, "'St'"] + returns: true + test5: + context: *data + args: [str, "'Str'"] + returns: true + test6: + context: *data + args: [str, "'String'"] + returns: false + test7: + context: *data + args: [str, 0] + error: invalid-type diff --git a/functions/sum.yml b/functions/sum.yml index 19cf8d9..d4bca7a 100644 --- a/functions/sum.yml +++ b/functions/sum.yml @@ -4,52 +4,61 @@ args: required: - name: collection type: ['array[number]'] - desc: "" + desc: '' optional: [] returns: type: number - desc: "" + desc: '' desc: | Returns the sum of the provided array argument. An empty array will produce a return value of 0. examples: -- context: [10, 15] - args: ['@'] - returns: 25 -- context: [10, false, 20] - args: ['@'] - error: invalid-type -- context: [10, false, 20] - args: ['[].to_number(@)'] - returns: 30 -- context: [] - args: ['@'] - returns: 0 -- context: &data - foo: -1 - zero: 0 - numbers: [-1, 3, 4, 5] - array: [-1, 3, 4, 5, a, '100'] - strings: [a, b, c] - decimals: [1.01, 1.2, -1.5] - str: Str - 'false': false - empty_list: [] - empty_hash: {} - objects: {foo: bar, bar: baz} - null_key: - args: [numbers] - returns: 11 -- context: *data - args: [decimals] - returns: 0.71 -- context: *data - args: [array] - error: invalid-type -- context: *data - args: ['array[].to_number(@)'] - returns: 111 -- context: *data - args: [[]] - returns: 0 + test0: + context: [10, 15] + args: ['@'] + returns: 25 + test1: + context: [10, false, 20] + args: ['@'] + error: invalid-type + test2: + context: [10, false, 20] + args: ['[].to_number(@)'] + returns: 30 + test3: + context: [] + args: ['@'] + returns: 0 + test4: + context: &data + foo: -1 + zero: 0 + numbers: [-1, 3, 4, 5] + array: [-1, 3, 4, 5, a, '100'] + strings: [a, b, c] + decimals: [1.01, 1.2, -1.5] + str: Str + 'false': false + empty_list: [] + empty_hash: {} + objects: {foo: bar, bar: baz} + null_key: + args: [numbers] + returns: 11 + test5: + context: *data + args: [decimals] + returns: 0.71 + test6: + context: *data + args: [array] + error: invalid-type + test7: + context: *data + args: ['array[].to_number(@)'] + returns: 111 + test8: + context: *data + args: [empty_list] + returns: 0 diff --git a/functions/to_array.yml b/functions/to_array.yml index ef95e38..6e9da4e 100644 --- a/functions/to_array.yml +++ b/functions/to_array.yml @@ -4,56 +4,66 @@ args: required: - name: arg type: [any] - desc: "" + desc: '' optional: [] returns: type: array - desc: "" + desc: '' desc: | * array - Returns the passed in value. * number/string/object/boolean - Returns a one element array containing the passed in argument. examples: -- context: - args: [[1, 2]] - returns: [1, 2] -- context: - args: [string] - returns: [string] -- context: - args: [0] - returns: [0] -- context: - args: [true] - returns: [true] -- context: - args: [foo: bar] - returns: [foo: bar] -- context: &data - foo: -1 - zero: 0 - numbers: [-1, 3, 4, 5] - array: [-1, 3, 4, 5, a, '100'] - strings: [a, b, c] - decimals: [1.01, 1.2, -1.5] - str: Str - 'false': false - empty_list: [] - empty_hash: {} - objects: {foo: bar, bar: baz} - null_key: - args: ['foo'] - returns: [foo] -- context: *data - args: [0] - returns: [0] -- context: *data - args: [objects] - returns: [{foo: bar, bar: baz}] -- context: *data - args: [[1, 2, 3]] - returns: [1, 2, 3] -- context: *data - args: [false] - returns: [false] + test0: + context: + args: [[1, 2]] + returns: [1, 2] + test1: + context: + args: ["'string'"] + returns: [string] + test2: + context: + args: [0] + returns: [0] + test3: + context: + args: [true] + returns: [true] + test4: + context: + args: [foo: "'bar'"] + returns: [foo: bar] + test5: + context: &data + foo: -1 + zero: 0 + numbers: [-1, 3, 4, 5] + array: [-1, 3, 4, 5, a, '100'] + strings: [a, b, c] + decimals: [1.01, 1.2, -1.5] + str: Str + 'false': false + empty_list: [] + empty_hash: {} + objects: {foo: bar, bar: baz} + null_key: + args: ["'foo'"] + returns: [foo] + test6: + context: *data + args: [0] + returns: [0] + test7: + context: *data + args: [objects] + returns: [{foo: bar, bar: baz}] + test8: + context: *data + args: [[1, 2, 3]] + returns: [1, 2, 3] + test9: + context: *data + args: [false] + returns: [false] diff --git a/functions/to_number.yml b/functions/to_number.yml index 5beeeeb..90ccac2 100644 --- a/functions/to_number.yml +++ b/functions/to_number.yml @@ -4,11 +4,11 @@ args: required: - name: arg type: [any] - desc: "" + desc: '' optional: [] returns: type: number - desc: "" + desc: '' desc: | * string - Returns the parsed number. Any string that conforms to the `json-number` production is supported. Note that the floating number @@ -31,39 +31,47 @@ desc: | * null - null examples: -- context: &data - foo: -1 - zero: 0 - numbers: [-1, 3, 4, 5] - array: [-1, 3, 4, 5, a, '100'] - strings: [a, b, c] - decimals: [1.01, 1.2, -1.5] - str: Str - 'false': false - empty_list: [] - empty_hash: {} - objects: {foo: bar, bar: baz} - null_key: - args: ['1.0'] - returns: 1.0 -- context: *data - args: ['1.1'] - returns: 1.1 -- context: *data - args: ['4'] - returns: 4 -- context: *data - args: ['notanumber'] - returns: -- context: *data - args: [false] - returns: -- context: *data - args: [null] - returns: -- context: *data - args: [[0]] - returns: -- context: *data - args: [{"foo": 0}] - returns: + test0: + context: &data + foo: -1 + zero: 0 + numbers: [-1, 3, 4, 5] + array: [-1, 3, 4, 5, a, '100'] + strings: [a, b, c] + decimals: [1.01, 1.2, -1.5] + str: Str + 'false': false + empty_list: [] + empty_hash: {} + objects: {foo: bar, bar: baz} + null_key: + args: ["'1.0'"] + returns: 1.0 + test1: + context: *data + args: ["'1.1'"] + returns: 1.1 + test2: + context: *data + args: ["'4'"] + returns: 4 + test3: + context: *data + args: [notanumber] + returns: + test4: + context: *data + args: [false] + returns: + test5: + context: *data + args: [!!null ''] + returns: + test6: + context: *data + args: [[0]] + returns: + test7: + context: *data + args: [foo: 0] + returns: diff --git a/functions/to_string.yml b/functions/to_string.yml index ee3916f..e042150 100644 --- a/functions/to_string.yml +++ b/functions/to_string.yml @@ -4,11 +4,11 @@ args: required: - name: arg type: [any] - desc: "" + desc: '' optional: [] returns: type: string - desc: "" + desc: '' desc: |2 * string - Returns the passed in value. @@ -18,27 +18,31 @@ desc: |2 JSON encoder should emit the encoded JSON value without adding any additional new lines. examples: -- context: - args: [2] - returns: '2' -- context: &data - foo: -1 - zero: 0 - numbers: [-1, 3, 4, 5] - array: [-1, 3, 4, 5, a, '100'] - strings: [a, b, c] - decimals: [1.01, 1.2, -1.5] - str: Str - 'false': false - empty_list: [] - empty_hash: {} - objects: {foo: bar, bar: baz} - null_key: - args: ['foo'] - returns: foo -- context: *data - args: [1.2] - returns: '1.2' -- context: *data - args: [[0, 1]] - returns: '[0,1]' + test0: + context: + args: [2] + returns: '2' + test1: + context: &data + foo: -1 + zero: 0 + numbers: [-1, 3, 4, 5] + array: [-1, 3, 4, 5, a, '100'] + strings: [a, b, c] + decimals: [1.01, 1.2, -1.5] + str: Str + 'false': false + empty_list: [] + empty_hash: {} + objects: {foo: bar, bar: baz} + null_key: + args: ["'foo'"] + returns: foo + test2: + context: *data + args: [1.2] + returns: '1.2' + test3: + context: *data + args: [[0, 1]] + returns: '[0,1]' diff --git a/functions/type.yml b/functions/type.yml index 9d595f6..503b10e 100644 --- a/functions/type.yml +++ b/functions/type.yml @@ -4,11 +4,11 @@ args: required: - name: subject type: [array, object, string, number, boolean, 'null'] - desc: "" + desc: '' optional: [] returns: type: string - desc: "" + desc: '' desc: | Returns the JavaScript type of the given `$subject` argument as a string value. @@ -22,66 +22,83 @@ desc: | * object * null examples: -- context: foo - args: ['@'] - returns: string -- context: true - args: ['@'] - returns: boolean -- context: false - args: ['@'] - returns: boolean -- context: - args: ['@'] - returns: 'null' -- context: 123 - args: ['@'] - returns: number -- context: 123.05 - args: ['@'] - returns: number -- context: [abc] - args: ['@'] - returns: array -- context: {abc: '123'} - args: ['@'] - returns: object -- context: &data - foo: -1 - zero: 0 - numbers: [-1, 3, 4, 5] - array: [-1, 3, 4, 5, a, '100'] - strings: [a, b, c] - decimals: [1.01, 1.2, -1.5] - str: Str - 'false': false - empty_list: [] - empty_hash: {} - objects: {foo: bar, bar: baz} - null_key: - args: ['abc'] - returns: string -- context: *data - args: [1.0] - returns: number -- context: *data - args: [2] - returns: number -- context: *data - args: [true] - returns: boolean -- context: *data - args: [false] - returns: boolean -- context: *data - args: [null] - returns: 'null' -- context: *data - args: [[0]] - returns: array -- context: *data - args: [{"a": "b"}] - returns: object -- context: *data - args: ['@'] - returns: object + test0: + context: foo + args: ['@'] + returns: string + test1: + context: true + args: ['@'] + returns: boolean + test2: + context: false + args: ['@'] + returns: boolean + test3: + context: + args: ['@'] + returns: 'null' + test4: + context: 123 + args: ['@'] + returns: number + test5: + context: 123.05 + args: ['@'] + returns: number + test6: + context: [abc] + args: ['@'] + returns: array + test7: + context: {abc: '123'} + args: ['@'] + returns: object + test8: + context: &data + foo: -1 + zero: 0 + numbers: [-1, 3, 4, 5] + array: [-1, 3, 4, 5, a, '100'] + strings: [a, b, c] + decimals: [1.01, 1.2, -1.5] + str: Str + 'false': false + empty_list: [] + empty_hash: {} + objects: {foo: bar, bar: baz} + null_key: + args: ["'abc'"] + returns: string + test9: + context: *data + args: [1.0] + returns: number + test10: + context: *data + args: [2] + returns: number + test11: + context: *data + args: [true] + returns: boolean + test12: + context: *data + args: [false] + returns: boolean + test13: + context: *data + args: [!!null ''] + returns: 'null' + test14: + context: *data + args: [[0]] + returns: array + test15: + context: *data + args: [a: b] + returns: object + test16: + context: *data + args: ['@'] + returns: object diff --git a/functions/values.yml b/functions/values.yml index 0e3cfc0..edcff60 100644 --- a/functions/values.yml +++ b/functions/values.yml @@ -4,11 +4,11 @@ args: required: - name: obj type: [object] - desc: "" + desc: '' optional: [] returns: type: array - desc: "" + desc: '' desc: | Returns the values of the provided object. Note that because JSON hashes are inheritently unordered, the @@ -43,27 +43,31 @@ desc: | If you would like a specific order, consider using the `sort` or `sort_by` functions. examples: -- context: {foo: baz, bar: bam} - args: ['@'] - returns: [baz, bam] -- context: [a, b] - args: ['@'] - error: invalid-type -- context: false - args: ['@'] - error: invalid-type -- context: - foo: -1 - zero: 0 - numbers: [-1, 3, 4, 5] - array: [-1, 3, 4, 5, a, '100'] - strings: [a, b, c] - decimals: [1.01, 1.2, -1.5] - str: Str - 'false': false - empty_list: [] - empty_hash: {} - objects: {foo: bar, bar: baz} - null_key: - args: [foo] - error: invalid-type + test0: + context: {foo: baz, bar: bam} + args: ['@'] + returns: [baz, bam] + test1: + context: [a, b] + args: ['@'] + error: invalid-type + test2: + context: false + args: ['@'] + error: invalid-type + test3: + context: + foo: -1 + zero: 0 + numbers: [-1, 3, 4, 5] + array: [-1, 3, 4, 5, a, '100'] + strings: [a, b, c] + decimals: [1.01, 1.2, -1.5] + str: Str + 'false': false + empty_list: [] + empty_hash: {} + objects: {foo: bar, bar: baz} + null_key: + args: [foo] + error: invalid-type diff --git a/grammar/README.md b/grammar/README.md deleted file mode 100644 index 605c07f..0000000 --- a/grammar/README.md +++ /dev/null @@ -1,129 +0,0 @@ -# JMESPath Compliance Tests - -This repo contains a suite of JMESPath compliance tests. JMESPath -implementations can use these tests in order to verify their -implementation adheres to the JMESPath spec. - -# Test Organization - -The `test/` directory contains JSON files containing the JMESPath -testcase. Each JSON file represents a JMESPath feature. Each JSON file -is a JSON list containing one or more tests suites: - - [ - , - , - ] - -Each test suite is a JSON object that has the following keys: - - - `given` - The input data from which the JMESPath expression is - evaluated. - - `cases` - A list of test cases. - - `comment` - An optional field containing a description of the test - suite. - -Each JMESPath test case can have the following keys: - - - `expression` - The JMESPath expression being tested. - - `result` - The expected result from evaluating the JMESPath - expression against the `given` input. - - `error` - The type of error that should be raised as a result of - evaluating the JMESPath expression. The valid values for an error - are: - - `syntax` - Syntax error from an invalid JMESPath expression. - - `invalid-arity` - Wrong number of arguments passed to a - function. - - `invalid-type` - Invalid argument type for a function. - - `invalid-value` - Semantically incorrect value (used in slice - tests) - - `unknown-function` - Attempting to invoke an unknown function. - - `bench` - If the case is a benchmark, `bench` contains the type of - benchmark. Available `bench` types are as follows: - - `parse` - Benchmark only the parsing of an expression. - - `interpret` - Benchmark only the interpreting of an expression. - - `full` - Benchmark both parsing and interepreting an expression. - - `comment` - An optional comment containing a description of the - specific test case. - -For each test case, either `result`, `error`, or `bench` must be -specified. Only one of these keys can be present in a single test case. - -The error type (if the `error` key is present) indicates the type of -error that an implementation should raise, but it does not indicate -**when** this error should be raised. For example, a value of `"error": -"syntax"` does not require that the syntax error be raised when the -expression is compiled. If an implementation does not have a separate -compilation step this won't even be possible. Similar for type errors, -implementations are free to check for type errors during compilation or -at run time (when the parsed expression is evaluated). As long as an -implementation can detect that this error occurred at any point during -the evaluation of a JMESPath expression, this is considered sufficient. - -Below are a few examples: - - [{ - "given": - {"foo": {"bar": {"baz": "correct"}}}, - "cases": [ - { - "expression": "foo", - "result": {"bar": {"baz": "correct"}} - }, - { - "expression": "foo.1", - "error": "syntax" - }, - ] - }] - -This above JSON document specifies 1 test suite that contains 2 test -cases. The two test cases are: - - - Given the input `{"foo": {"bar": {"baz": "correct"}}}`, the - expression `foo` should have a result of `{"bar": {"baz": - "correct"}}`. - - Given the input `{"foo": {"bar": {"baz": "correct"}}}`, the - expression `foo.1` should generate a syntax error. - -# Utility Tools - -Most languages have test frameworks that are capable of reading the JSON -test descriptions and generating testcases. However, a `jp-compliance` -tool is provided to help with any implementation that does not have an -available test framework to generate test cases. The `jp-compliance` -tool takes the name of a jmespath executable and will evaluate all the -compliance tests using this provided executable. This way all that's -needed to verify your JMESPath implementation is for you to write a -basic executable. This executable must have the following interface: - - - Accept the input JSON data on stdin. - - Accept the jmespath expression as an argument. - - Print the jmespath result as JSON on stdout. - - If an error occurred, it must write the error name to sys.stderr. - This check is case-insensitive. The error types in the compliance - tests are hyphenated, but each individual component may appear in - stderr (again case-insensitive). - -Here are a few examples of error messages that would pass -`jp-compliance`: - - - Error type: `unknown-function` - - Valid error messages: - - `unknown-function: somefunction()` - - `error: unknown function 'somefunction()` - - `Unknown function: somefunction()` - - Error type: `syntax` - - Valid error messages: - - `syntax: Unknown token '$'` - - `syntax-error: Unknown token '$'` - - `Syntax error: Unknown token '$'` - - `An error occurred: Syntax error, unknown token '$'` - -## Note - -This will be substantially slower than using a test framework. Using -`jp-compliance` each test case is evaluated by executing a new process. - -You can run the `/bin/jp-compliance --help` for more information and for -examples on how to use this tool. \ No newline at end of file diff --git a/grammar/basic.json b/grammar/basic.json deleted file mode 100644 index d550e96..0000000 --- a/grammar/basic.json +++ /dev/null @@ -1,96 +0,0 @@ -[{ - "given": - {"foo": {"bar": {"baz": "correct"}}}, - "cases": [ - { - "expression": "foo", - "result": {"bar": {"baz": "correct"}} - }, - { - "expression": "foo.bar", - "result": {"baz": "correct"} - }, - { - "expression": "foo.bar.baz", - "result": "correct" - }, - { - "expression": "foo\n.\nbar\n.baz", - "result": "correct" - }, - { - "expression": "foo.bar.baz.bad", - "result": null - }, - { - "expression": "foo.bar.bad", - "result": null - }, - { - "expression": "foo.bad", - "result": null - }, - { - "expression": "bad", - "result": null - }, - { - "expression": "bad.morebad.morebad", - "result": null - } - ] -}, -{ - "given": - {"foo": {"bar": ["one", "two", "three"]}}, - "cases": [ - { - "expression": "foo", - "result": {"bar": ["one", "two", "three"]} - }, - { - "expression": "foo.bar", - "result": ["one", "two", "three"] - } - ] -}, -{ - "given": ["one", "two", "three"], - "cases": [ - { - "expression": "one", - "result": null - }, - { - "expression": "two", - "result": null - }, - { - "expression": "three", - "result": null - }, - { - "expression": "one.two", - "result": null - } - ] -}, -{ - "given": - {"foo": {"1": ["one", "two", "three"], "-1": "bar"}}, - "cases": [ - { - "expression": "foo.\"1\"", - "result": ["one", "two", "three"] - }, - { - "expression": "foo.\"1\"[0]", - "result": "one" - }, - { - "expression": "foo.\"-1\"", - "result": "bar" - } - ] -} -] diff --git a/grammar/basic.yml b/grammar/basic.yml new file mode 100644 index 0000000..dc9c4d6 --- /dev/null +++ b/grammar/basic.yml @@ -0,0 +1,82 @@ +test0: + context: &data + foo: {bar: {baz: correct}} + query: foo + returns: {bar: {baz: correct}} +test1: + context: *data + query: foo.bar + returns: {baz: correct} +test2: + context: *data + query: foo.bar.baz + returns: correct +test3: + context: *data + query: "foo\n.\nbar\n.baz" + returns: correct +test4: + context: *data + query: foo.bar.baz.bad + returns: +test5: + context: *data + query: foo.bar.bad + returns: +test6: + context: *data + query: foo.bad + returns: +test7: + context: *data + query: bad + returns: +test8: + context: *data + query: bad.morebad.morebad + returns: +test9: + context: &data + foo: {bar: [one, two, three]} + query: foo + returns: {bar: [one, two, three]} +test10: + context: *data + query: foo.bar + returns: [one, two, three] +test11: + context: &data + - one + - two + - three + query: one + returns: +test12: + context: *data + query: two + returns: +test13: + context: *data + query: three + returns: +test14: + context: *data + query: one.two + returns: +test15: + context: &data + foo: {'1': [one, two, three], '-1': bar} + query: foo."1" + returns: [one, two, three] +test16: + context: *data + query: foo."1"[0] + returns: one +test17: + context: *data + query: foo."-1" + returns: bar +null_input: + context: + query: foo + returns: \ No newline at end of file diff --git a/grammar/benchmarks.json b/grammar/benchmarks.json deleted file mode 100644 index 024a590..0000000 --- a/grammar/benchmarks.json +++ /dev/null @@ -1,138 +0,0 @@ -[ - { - "given": { - "long_name_for_a_field": true, - "a": { - "b": { - "c": { - "d": { - "e": { - "f": { - "g": { - "h": { - "i": { - "j": { - "k": { - "l": { - "m": { - "n": { - "o": { - "p": true - } - } - } - } - } - } - } - } - } - } - } - } - } - } - }, - "b": true, - "c": { - "d": true - } - }, - "cases": [ - { - "comment": "simple field", - "expression": "b", - "bench": "full" - }, - { - "comment": "simple subexpression", - "expression": "c.d", - "bench": "full" - }, - { - "comment": "deep field selection no match", - "expression": "a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s", - "bench": "full" - }, - { - "comment": "deep field selection", - "expression": "a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p", - "bench": "full" - }, - { - "comment": "simple or", - "expression": "not_there || b", - "bench": "full" - } - ] - }, - { - "given": { - "a":0,"b":1,"c":2,"d":3,"e":4,"f":5,"g":6,"h":7,"i":8,"j":9,"k":10, - "l":11,"m":12,"n":13,"o":14,"p":15,"q":16,"r":17,"s":18,"t":19,"u":20, - "v":21,"w":22,"x":23,"y":24,"z":25 - }, - "cases": [ - { - "comment": "deep ands", - "expression": "a && b && c && d && e && f && g && h && i && j && k && l && m && n && o && p && q && r && s && t && u && v && w && x && y && z", - "bench": "full" - }, - { - "comment": "deep ors", - "expression": "z || y || x || w || v || u || t || s || r || q || p || o || n || m || l || k || j || i || h || g || f || e || d || c || b || a", - "bench": "full" - }, - { - "comment": "lots of summing", - "expression": "sum([z, y, x, w, v, u, t, s, r, q, p, o, n, m, l, k, j, i, h, g, f, e, d, c, b, a])", - "bench": "full" - }, - { - "comment": "lots of function application", - "expression": "sum([z, sum([y, sum([x, sum([w, sum([v, sum([u, sum([t, sum([s, sum([r, sum([q, sum([p, sum([o, sum([n, sum([m, sum([l, sum([k, sum([j, sum([i, sum([h, sum([g, sum([f, sum([e, sum([d, sum([c, sum([b, a])])])])])])])])])])])])])])])])])])])])])])])])])", - "bench": "full" - }, - { - "comment": "lots of multi list", - "expression": "[z, y, x, w, v, u, t, s, r, q, p, o, n, m, l, k, j, i, h, g, f, e, d, c, b, a]", - "bench": "full" - } - ] - }, - { - "given": {}, - "cases": [ - { - "comment": "field 50", - "expression": "j49.j48.j47.j46.j45.j44.j43.j42.j41.j40.j39.j38.j37.j36.j35.j34.j33.j32.j31.j30.j29.j28.j27.j26.j25.j24.j23.j22.j21.j20.j19.j18.j17.j16.j15.j14.j13.j12.j11.j10.j9.j8.j7.j6.j5.j4.j3.j2.j1.j0", - "bench": "parse" - }, - { - "comment": "pipe 50", - "expression": "j49|j48|j47|j46|j45|j44|j43|j42|j41|j40|j39|j38|j37|j36|j35|j34|j33|j32|j31|j30|j29|j28|j27|j26|j25|j24|j23|j22|j21|j20|j19|j18|j17|j16|j15|j14|j13|j12|j11|j10|j9|j8|j7|j6|j5|j4|j3|j2|j1|j0", - "bench": "parse" - }, - { - "comment": "index 50", - "expression": "[49][48][47][46][45][44][43][42][41][40][39][38][37][36][35][34][33][32][31][30][29][28][27][26][25][24][23][22][21][20][19][18][17][16][15][14][13][12][11][10][9][8][7][6][5][4][3][2][1][0]", - "bench": "parse" - }, - { - "comment": "long raw string literal", - "expression": "'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz'", - "bench": "parse" - }, - { - "comment": "deep projection 104", - "expression": "a[*].b[*].c[*].d[*].e[*].f[*].g[*].h[*].i[*].j[*].k[*].l[*].m[*].n[*].o[*].p[*].q[*].r[*].s[*].t[*].u[*].v[*].w[*].x[*].y[*].z[*].a[*].b[*].c[*].d[*].e[*].f[*].g[*].h[*].i[*].j[*].k[*].l[*].m[*].n[*].o[*].p[*].q[*].r[*].s[*].t[*].u[*].v[*].w[*].x[*].y[*].z[*].a[*].b[*].c[*].d[*].e[*].f[*].g[*].h[*].i[*].j[*].k[*].l[*].m[*].n[*].o[*].p[*].q[*].r[*].s[*].t[*].u[*].v[*].w[*].x[*].y[*].z[*].a[*].b[*].c[*].d[*].e[*].f[*].g[*].h[*].i[*].j[*].k[*].l[*].m[*].n[*].o[*].p[*].q[*].r[*].s[*].t[*].u[*].v[*].w[*].x[*].y[*].z[*]", - "bench": "parse" - }, - { - "comment": "filter projection", - "expression": "foo[?bar > baz][?qux > baz]", - "bench": "parse" - } - ] - } -] diff --git a/grammar/benchmarks.yml b/grammar/benchmarks.yml new file mode 100644 index 0000000..7a52278 --- /dev/null +++ b/grammar/benchmarks.yml @@ -0,0 +1,116 @@ +test0: + context: &data + long_name_for_a_field: true + a: {b: {c: {d: {e: {f: {g: {h: {i: {j: {k: {l: {m: {n: {o: {p: true}}}}}}}}}}}}}}} + b: true + c: {d: true} + query: b + returns: true + comment: simple field +test1: + context: *data + query: c.d + returns: true + comment: simple subexpression +test2: + context: *data + query: a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s + returns: + comment: deep field selection no match +test3: + context: *data + query: a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p + returns: true + comment: deep field selection +test4: + context: *data + query: not_there || b + returns: true + comment: simple or +test5: + context: &data + a: 0 + b: 1 + c: 2 + d: 3 + e: 4 + f: 5 + g: 6 + h: 7 + i: 8 + j: 9 + k: 10 + l: 11 + m: 12 + n: 13 + o: 14 + p: 15 + q: 16 + r: 17 + s: 18 + t: 19 + u: 20 + v: 21 + w: 22 + x: 23 + y: 24 + z: 25 + query: a && b && c && d && e && f && g && h && i && j && k && l && m && n && o && + p && q && r && s && t && u && v && w && x && y && z + returns: 25 + comment: deep ands +test6: + context: *data + query: z || y || x || w || v || u || t || s || r || q || p || o || n || m || l || + k || j || i || h || g || f || e || d || c || b || a + returns: 25 + comment: deep ors +test7: + context: *data + query: sum([z, y, x, w, v, u, t, s, r, q, p, o, n, m, l, k, j, i, h, g, f, e, d, + c, b, a]) + returns: 325 + comment: lots of summing +test8: + context: *data + query: sum([z, sum([y, sum([x, sum([w, sum([v, sum([u, sum([t, sum([s, sum([r, sum([q, + sum([p, sum([o, sum([n, sum([m, sum([l, sum([k, sum([j, sum([i, sum([h, sum([g, + sum([f, sum([e, sum([d, sum([c, sum([b, a])])])])])])])])])])])])])])])])])])])])])])])])]) + returns: 325 + comment: lots of function application +test9: + context: *data + query: '[z, y, x, w, v, u, t, s, r, q, p, o, n, m, l, k, j, i, h, g, f, e, d, c, + b, a]' + returns: [25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0] + comment: lots of multi list +test10: + context: &data {} + query: j49.j48.j47.j46.j45.j44.j43.j42.j41.j40.j39.j38.j37.j36.j35.j34.j33.j32.j31.j30.j29.j28.j27.j26.j25.j24.j23.j22.j21.j20.j19.j18.j17.j16.j15.j14.j13.j12.j11.j10.j9.j8.j7.j6.j5.j4.j3.j2.j1.j0 + returns: + comment: field 50 +test11: + context: *data + query: j49|j48|j47|j46|j45|j44|j43|j42|j41|j40|j39|j38|j37|j36|j35|j34|j33|j32|j31|j30|j29|j28|j27|j26|j25|j24|j23|j22|j21|j20|j19|j18|j17|j16|j15|j14|j13|j12|j11|j10|j9|j8|j7|j6|j5|j4|j3|j2|j1|j0 + returns: + comment: pipe 50 +test12: + context: *data + query: '[49][48][47][46][45][44][43][42][41][40][39][38][37][36][35][34][33][32][31][30][29][28][27][26][25][24][23][22][21][20][19][18][17][16][15][14][13][12][11][10][9][8][7][6][5][4][3][2][1][0]' + returns: + comment: index 50 +test13: + context: *data + query: "'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz'" + returns: abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz + comment: long raw string literal +test14: + context: *data + query: a[*].b[*].c[*].d[*].e[*].f[*].g[*].h[*].i[*].j[*].k[*].l[*].m[*].n[*].o[*].p[*].q[*].r[*].s[*].t[*].u[*].v[*].w[*].x[*].y[*].z[*].a[*].b[*].c[*].d[*].e[*].f[*].g[*].h[*].i[*].j[*].k[*].l[*].m[*].n[*].o[*].p[*].q[*].r[*].s[*].t[*].u[*].v[*].w[*].x[*].y[*].z[*].a[*].b[*].c[*].d[*].e[*].f[*].g[*].h[*].i[*].j[*].k[*].l[*].m[*].n[*].o[*].p[*].q[*].r[*].s[*].t[*].u[*].v[*].w[*].x[*].y[*].z[*].a[*].b[*].c[*].d[*].e[*].f[*].g[*].h[*].i[*].j[*].k[*].l[*].m[*].n[*].o[*].p[*].q[*].r[*].s[*].t[*].u[*].v[*].w[*].x[*].y[*].z[*] + returns: null + comment: deep projection 104 +test15: + context: *data + query: foo[?bar > baz][?qux > baz] + returns: null + comment: filter projection diff --git a/grammar/boolean.json b/grammar/boolean.json deleted file mode 100644 index 60635ac..0000000 --- a/grammar/boolean.json +++ /dev/null @@ -1,275 +0,0 @@ -[ - { - "given": { - "outer": { - "foo": "foo", - "bar": "bar", - "baz": "baz" - } - }, - "cases": [ - { - "expression": "outer.foo || outer.bar", - "result": "foo" - }, - { - "expression": "outer.foo||outer.bar", - "result": "foo" - }, - { - "expression": "outer.bar || outer.baz", - "result": "bar" - }, - { - "expression": "outer.bar||outer.baz", - "result": "bar" - }, - { - "expression": "outer.bad || outer.foo", - "result": "foo" - }, - { - "expression": "outer.bad||outer.foo", - "result": "foo" - }, - { - "expression": "outer.foo || outer.bad", - "result": "foo" - }, - { - "expression": "outer.foo||outer.bad", - "result": "foo" - }, - { - "expression": "outer.bad || outer.alsobad", - "result": null - }, - { - "expression": "outer.bad||outer.alsobad", - "result": null - } - ] - }, - { - "given": { - "outer": { - "foo": "foo", - "bool": false, - "empty_list": [], - "empty_string": "" - } - }, - "cases": [ - { - "expression": "outer.empty_string || outer.foo", - "result": "foo" - }, - { - "expression": "outer.nokey || outer.bool || outer.empty_list || outer.empty_string || outer.foo", - "result": "foo" - } - ] - }, - { - "given": { - "True": true, - "False": false, - "Number": 5, - "EmptyList": [], - "Zero": 0 - }, - "cases": [ - { - "expression": "True && False", - "result": false - }, - { - "expression": "False && True", - "result": false - }, - { - "expression": "True && True", - "result": true - }, - { - "expression": "False && False", - "result": false - }, - { - "expression": "True && Number", - "result": 5 - }, - { - "expression": "Number && True", - "result": true - }, - { - "expression": "Number && False", - "result": false - }, - { - "expression": "Number && EmptyList", - "result": [] - }, - { - "expression": "Number && True", - "result": true - }, - { - "expression": "EmptyList && True", - "result": [] - }, - { - "expression": "EmptyList && False", - "result": [] - }, - { - "expression": "True || False", - "result": true - }, - { - "expression": "True || True", - "result": true - }, - { - "expression": "False || True", - "result": true - }, - { - "expression": "False || False", - "result": false - }, - { - "expression": "Number || EmptyList", - "result": 5 - }, - { - "expression": "Number || True", - "result": 5 - }, - { - "expression": "Number || True && False", - "result": 5 - }, - { - "expression": "(Number || True) && False", - "result": false - }, - { - "expression": "Number || (True && False)", - "result": 5 - }, - { - "expression": "!True", - "result": false - }, - { - "expression": "!False", - "result": true - }, - { - "expression": "!Number", - "result": false - }, - { - "expression": "!EmptyList", - "result": true - }, - { - "expression": "True && !False", - "result": true - }, - { - "expression": "True && !EmptyList", - "result": true - }, - { - "expression": "!False && !EmptyList", - "result": true - }, - { - "expression": "!(True && False)", - "result": true - }, - { - "expression": "!Zero", - "result": false - }, - { - "expression": "!!Zero", - "result": true - } - ] - }, - { - "given": { - "one": 1, - "two": 2, - "three": 3, - "emptylist": [], - "boolvalue": false - }, - "cases": [ - { - "expression": "one < two", - "result": true - }, - { - "expression": "one <= two", - "result": true - }, - { - "expression": "one == one", - "result": true - }, - { - "expression": "one == two", - "result": false - }, - { - "expression": "one > two", - "result": false - }, - { - "expression": "one >= two", - "result": false - }, - { - "expression": "one != two", - "result": true - }, - { - "expression": "emptylist < one", - "result": null - }, - { - "expression": "emptylist < nullvalue", - "result": null - }, - { - "expression": "emptylist < boolvalue", - "result": null - }, - { - "expression": "one < boolvalue", - "result": null - }, - { - "expression": "one < two && three > one", - "result": true - }, - { - "expression": "one < two || three > one", - "result": true - }, - { - "expression": "one < two || three < one", - "result": true - }, - { - "expression": "two < one || three < one", - "result": false - } - ] - } -] diff --git a/grammar/boolean.yml b/grammar/boolean.yml new file mode 100644 index 0000000..d36ae90 --- /dev/null +++ b/grammar/boolean.yml @@ -0,0 +1,240 @@ +test0: + context: &data + outer: {foo: foo, bar: bar, baz: baz} + query: outer.foo || outer.bar + returns: foo +test1: + context: *data + query: outer.foo||outer.bar + returns: foo +test2: + context: *data + query: outer.bar || outer.baz + returns: bar +test3: + context: *data + query: outer.bar||outer.baz + returns: bar +test4: + context: *data + query: outer.bad || outer.foo + returns: foo +test5: + context: *data + query: outer.bad||outer.foo + returns: foo +test6: + context: *data + query: outer.foo || outer.bad + returns: foo +test7: + context: *data + query: outer.foo||outer.bad + returns: foo +test8: + context: *data + query: outer.bad || outer.alsobad + returns: +test9: + context: *data + query: outer.bad||outer.alsobad + returns: +test10: + context: &data + outer: {foo: foo, bool: false, empty_list: [], empty_string: ''} + query: outer.empty_string || outer.foo + returns: foo +test11: + context: *data + query: outer.nokey || outer.bool || outer.empty_list || outer.empty_string || outer.foo + returns: foo +test12: + context: &data + 'True': true + 'False': false + Number: 5 + EmptyList: [] + Zero: 0 + query: True && False + returns: false +test13: + context: *data + query: False && True + returns: false +test14: + context: *data + query: True && True + returns: true +test15: + context: *data + query: False && False + returns: false +test16: + context: *data + query: True && Number + returns: 5 +test17: + context: *data + query: Number && True + returns: true +test18: + context: *data + query: Number && False + returns: false +test19: + context: *data + query: Number && EmptyList + returns: [] +test20: + context: *data + query: Number && True + returns: true +test21: + context: *data + query: EmptyList && True + returns: [] +test22: + context: *data + query: EmptyList && False + returns: [] +test23: + context: *data + query: True || False + returns: true +test24: + context: *data + query: True || True + returns: true +test25: + context: *data + query: False || True + returns: true +test26: + context: *data + query: False || False + returns: false +test27: + context: *data + query: Number || EmptyList + returns: 5 +test28: + context: *data + query: Number || True + returns: 5 +test29: + context: *data + query: Number || True && False + returns: 5 +test30: + context: *data + query: (Number || True) && False + returns: false +test31: + context: *data + query: Number || (True && False) + returns: 5 +test32: + context: *data + query: '!True' + returns: false +test33: + context: *data + query: '!False' + returns: true +test34: + context: *data + query: '!Number' + returns: false +test35: + context: *data + query: '!EmptyList' + returns: true +test36: + context: *data + query: True && !False + returns: true +test37: + context: *data + query: True && !EmptyList + returns: true +test38: + context: *data + query: '!False && !EmptyList' + returns: true +test39: + context: *data + query: '!(True && False)' + returns: true +test40: + context: *data + query: '!Zero' + returns: false +test41: + context: *data + query: '!!Zero' + returns: true +test42: + context: &data + one: 1 + two: 2 + three: 3 + emptylist: [] + boolvalue: false + query: one < two + returns: true +test43: + context: *data + query: one <= two + returns: true +test44: + context: *data + query: one == one + returns: true +test45: + context: *data + query: one == two + returns: false +test46: + context: *data + query: one > two + returns: false +test47: + context: *data + query: one >= two + returns: false +test48: + context: *data + query: one != two + returns: true +test49: + context: *data + query: emptylist < one + returns: +test50: + context: *data + query: emptylist < nullvalue + returns: +test51: + context: *data + query: emptylist < boolvalue + returns: +test52: + context: *data + query: one < boolvalue + returns: +test53: + context: *data + query: one < two && three > one + returns: true +test54: + context: *data + query: one < two || three > one + returns: true +test55: + context: *data + query: one < two || three < one + returns: true +test56: + context: *data + query: two < one || three < one + returns: false diff --git a/grammar/current.json b/grammar/current.json deleted file mode 100644 index 0c26248..0000000 --- a/grammar/current.json +++ /dev/null @@ -1,25 +0,0 @@ -[ - { - "given": { - "foo": [{"name": "a"}, {"name": "b"}], - "bar": {"baz": "qux"} - }, - "cases": [ - { - "expression": "@", - "result": { - "foo": [{"name": "a"}, {"name": "b"}], - "bar": {"baz": "qux"} - } - }, - { - "expression": "@.bar", - "result": {"baz": "qux"} - }, - { - "expression": "@.foo[0]", - "result": {"name": "a"} - } - ] - } -] diff --git a/grammar/current.yml b/grammar/current.yml new file mode 100644 index 0000000..c263038 --- /dev/null +++ b/grammar/current.yml @@ -0,0 +1,14 @@ +test0: + context: &data + foo: [name: a, name: b] + bar: {baz: qux} + query: '@' + returns: {foo: [name: a, name: b], bar: {baz: qux}} +test1: + context: *data + query: '@.bar' + returns: {baz: qux} +test2: + context: *data + query: '@.foo[0]' + returns: {name: a} diff --git a/grammar/escape.json b/grammar/escape.json deleted file mode 100644 index 4a62d95..0000000 --- a/grammar/escape.json +++ /dev/null @@ -1,46 +0,0 @@ -[{ - "given": { - "foo.bar": "dot", - "foo bar": "space", - "foo\nbar": "newline", - "foo\"bar": "doublequote", - "c:\\\\windows\\path": "windows", - "/unix/path": "unix", - "\"\"\"": "threequotes", - "bar": {"baz": "qux"} - }, - "cases": [ - { - "expression": "\"foo.bar\"", - "result": "dot" - }, - { - "expression": "\"foo bar\"", - "result": "space" - }, - { - "expression": "\"foo\\nbar\"", - "result": "newline" - }, - { - "expression": "\"foo\\\"bar\"", - "result": "doublequote" - }, - { - "expression": "\"c:\\\\\\\\windows\\\\path\"", - "result": "windows" - }, - { - "expression": "\"/unix/path\"", - "result": "unix" - }, - { - "expression": "\"\\\"\\\"\\\"\"", - "result": "threequotes" - }, - { - "expression": "\"bar\".\"baz\"", - "result": "qux" - } - ] -}] diff --git a/grammar/escape.yml b/grammar/escape.yml new file mode 100644 index 0000000..82adadd --- /dev/null +++ b/grammar/escape.yml @@ -0,0 +1,41 @@ +test0: + context: &data + foo.bar: dot + foo bar: space + ? "foo\nbar" + : newline + foo"bar: doublequote + c:\\windows\path: windows + /unix/path: unix + '"""': threequotes + bar: {baz: qux} + query: '"foo.bar"' + returns: dot +test1: + context: *data + query: '"foo bar"' + returns: space +test2: + context: *data + query: '"foo\nbar"' + returns: newline +test3: + context: *data + query: '"foo\"bar"' + returns: doublequote +test4: + context: *data + query: '"c:\\\\windows\\path"' + returns: windows +test5: + context: *data + query: '"/unix/path"' + returns: unix +test6: + context: *data + query: '"\"\"\""' + returns: threequotes +test7: + context: *data + query: '"bar"."baz"' + returns: qux diff --git a/grammar/filters.json b/grammar/filters.json deleted file mode 100644 index 5b9f52b..0000000 --- a/grammar/filters.json +++ /dev/null @@ -1,468 +0,0 @@ -[ - { - "given": {"foo": [{"name": "a"}, {"name": "b"}]}, - "cases": [ - { - "comment": "Matching a literal", - "expression": "foo[?name == 'a']", - "result": [{"name": "a"}] - } - ] - }, - { - "given": {"foo": [0, 1], "bar": [2, 3]}, - "cases": [ - { - "comment": "Matching a literal", - "expression": "*[?[0] == `0`]", - "result": [[], []] - } - ] - }, - { - "given": {"foo": [{"first": "foo", "last": "bar"}, - {"first": "foo", "last": "foo"}, - {"first": "foo", "last": "baz"}]}, - "cases": [ - { - "comment": "Matching an expression", - "expression": "foo[?first == last]", - "result": [{"first": "foo", "last": "foo"}] - }, - { - "comment": "Verify projection created from filter", - "expression": "foo[?first == last].first", - "result": ["foo"] - } - ] - }, - { - "given": {"foo": [{"age": 20}, - {"age": 25}, - {"age": 30}]}, - "cases": [ - { - "comment": "Greater than with a number", - "expression": "foo[?age > `25`]", - "result": [{"age": 30}] - }, - { - "expression": "foo[?age >= `25`]", - "result": [{"age": 25}, {"age": 30}] - }, - { - "comment": "Greater than with a number", - "expression": "foo[?age > `30`]", - "result": [] - }, - { - "comment": "Greater than with a number", - "expression": "foo[?age < `25`]", - "result": [{"age": 20}] - }, - { - "comment": "Greater than with a number", - "expression": "foo[?age <= `25`]", - "result": [{"age": 20}, {"age": 25}] - }, - { - "comment": "Greater than with a number", - "expression": "foo[?age < `20`]", - "result": [] - }, - { - "expression": "foo[?age == `20`]", - "result": [{"age": 20}] - }, - { - "expression": "foo[?age != `20`]", - "result": [{"age": 25}, {"age": 30}] - } - ] - }, - { - "given": {"foo": [{"top": {"name": "a"}}, - {"top": {"name": "b"}}]}, - "cases": [ - { - "comment": "Filter with subexpression", - "expression": "foo[?top.name == 'a']", - "result": [{"top": {"name": "a"}}] - } - ] - }, - { - "given": {"foo": [{"top": {"first": "foo", "last": "bar"}}, - {"top": {"first": "foo", "last": "foo"}}, - {"top": {"first": "foo", "last": "baz"}}]}, - "cases": [ - { - "comment": "Matching an expression", - "expression": "foo[?top.first == top.last]", - "result": [{"top": {"first": "foo", "last": "foo"}}] - }, - { - "comment": "Matching a JSON array", - "expression": "foo[?top == `{\"first\": \"foo\", \"last\": \"bar\"}`]", - "result": [{"top": {"first": "foo", "last": "bar"}}] - } - ] - }, - { - "given": {"foo": [ - {"key": true}, - {"key": false}, - {"key": 0}, - {"key": 1}, - {"key": [0]}, - {"key": {"bar": [0]}}, - {"key": null}, - {"key": [1]}, - {"key": {"a":2}} - ]}, - "cases": [ - { - "expression": "foo[?key == `true`]", - "result": [{"key": true}] - }, - { - "expression": "foo[?key == `false`]", - "result": [{"key": false}] - }, - { - "expression": "foo[?key == `0`]", - "result": [{"key": 0}] - }, - { - "expression": "foo[?key == `1`]", - "result": [{"key": 1}] - }, - { - "expression": "foo[?key == `[0]`]", - "result": [{"key": [0]}] - }, - { - "expression": "foo[?key == `{\"bar\": [0]}`]", - "result": [{"key": {"bar": [0]}}] - }, - { - "expression": "foo[?key == `null`]", - "result": [{"key": null}] - }, - { - "expression": "foo[?key == `[1]`]", - "result": [{"key": [1]}] - }, - { - "expression": "foo[?key == `{\"a\":2}`]", - "result": [{"key": {"a":2}}] - }, - { - "expression": "foo[?`true` == key]", - "result": [{"key": true}] - }, - { - "expression": "foo[?`false` == key]", - "result": [{"key": false}] - }, - { - "expression": "foo[?`0` == key]", - "result": [{"key": 0}] - }, - { - "expression": "foo[?`1` == key]", - "result": [{"key": 1}] - }, - { - "expression": "foo[?`[0]` == key]", - "result": [{"key": [0]}] - }, - { - "expression": "foo[?`{\"bar\": [0]}` == key]", - "result": [{"key": {"bar": [0]}}] - }, - { - "expression": "foo[?`null` == key]", - "result": [{"key": null}] - }, - { - "expression": "foo[?`[1]` == key]", - "result": [{"key": [1]}] - }, - { - "expression": "foo[?`{\"a\":2}` == key]", - "result": [{"key": {"a":2}}] - }, - { - "expression": "foo[?key != `true`]", - "result": [{"key": false}, {"key": 0}, {"key": 1}, {"key": [0]}, - {"key": {"bar": [0]}}, {"key": null}, {"key": [1]}, {"key": {"a":2}}] - }, - { - "expression": "foo[?key != `false`]", - "result": [{"key": true}, {"key": 0}, {"key": 1}, {"key": [0]}, - {"key": {"bar": [0]}}, {"key": null}, {"key": [1]}, {"key": {"a":2}}] - }, - { - "expression": "foo[?key != `0`]", - "result": [{"key": true}, {"key": false}, {"key": 1}, {"key": [0]}, - {"key": {"bar": [0]}}, {"key": null}, {"key": [1]}, {"key": {"a":2}}] - }, - { - "expression": "foo[?key != `1`]", - "result": [{"key": true}, {"key": false}, {"key": 0}, {"key": [0]}, - {"key": {"bar": [0]}}, {"key": null}, {"key": [1]}, {"key": {"a":2}}] - }, - { - "expression": "foo[?key != `null`]", - "result": [{"key": true}, {"key": false}, {"key": 0}, {"key": 1}, {"key": [0]}, - {"key": {"bar": [0]}}, {"key": [1]}, {"key": {"a":2}}] - }, - { - "expression": "foo[?key != `[1]`]", - "result": [{"key": true}, {"key": false}, {"key": 0}, {"key": 1}, {"key": [0]}, - {"key": {"bar": [0]}}, {"key": null}, {"key": {"a":2}}] - }, - { - "expression": "foo[?key != `{\"a\":2}`]", - "result": [{"key": true}, {"key": false}, {"key": 0}, {"key": 1}, {"key": [0]}, - {"key": {"bar": [0]}}, {"key": null}, {"key": [1]}] - }, - { - "expression": "foo[?`true` != key]", - "result": [{"key": false}, {"key": 0}, {"key": 1}, {"key": [0]}, - {"key": {"bar": [0]}}, {"key": null}, {"key": [1]}, {"key": {"a":2}}] - }, - { - "expression": "foo[?`false` != key]", - "result": [{"key": true}, {"key": 0}, {"key": 1}, {"key": [0]}, - {"key": {"bar": [0]}}, {"key": null}, {"key": [1]}, {"key": {"a":2}}] - }, - { - "expression": "foo[?`0` != key]", - "result": [{"key": true}, {"key": false}, {"key": 1}, {"key": [0]}, - {"key": {"bar": [0]}}, {"key": null}, {"key": [1]}, {"key": {"a":2}}] - }, - { - "expression": "foo[?`1` != key]", - "result": [{"key": true}, {"key": false}, {"key": 0}, {"key": [0]}, - {"key": {"bar": [0]}}, {"key": null}, {"key": [1]}, {"key": {"a":2}}] - }, - { - "expression": "foo[?`null` != key]", - "result": [{"key": true}, {"key": false}, {"key": 0}, {"key": 1}, {"key": [0]}, - {"key": {"bar": [0]}}, {"key": [1]}, {"key": {"a":2}}] - }, - { - "expression": "foo[?`[1]` != key]", - "result": [{"key": true}, {"key": false}, {"key": 0}, {"key": 1}, {"key": [0]}, - {"key": {"bar": [0]}}, {"key": null}, {"key": {"a":2}}] - }, - { - "expression": "foo[?`{\"a\":2}` != key]", - "result": [{"key": true}, {"key": false}, {"key": 0}, {"key": 1}, {"key": [0]}, - {"key": {"bar": [0]}}, {"key": null}, {"key": [1]}] - } - ] - }, - { - "given": {"reservations": [ - {"instances": [ - {"foo": 1, "bar": 2}, {"foo": 1, "bar": 3}, - {"foo": 1, "bar": 2}, {"foo": 2, "bar": 1}]}]}, - "cases": [ - { - "expression": "reservations[].instances[?bar==`1`]", - "result": [[{"foo": 2, "bar": 1}]] - }, - { - "expression": "reservations[*].instances[?bar==`1`]", - "result": [[{"foo": 2, "bar": 1}]] - }, - { - "expression": "reservations[].instances[?bar==`1`][]", - "result": [{"foo": 2, "bar": 1}] - } - ] - }, - { - "given": { - "baz": "other", - "foo": [ - {"bar": 1}, {"bar": 2}, {"bar": 3}, {"bar": 4}, {"bar": 1, "baz": 2} - ] - }, - "cases": [ - { - "expression": "foo[?bar==`1`].bar[0]", - "result": [] - } - ] - }, - { - "given": { - "foo": [ - {"a": 1, "b": {"c": "x"}}, - {"a": 1, "b": {"c": "y"}}, - {"a": 1, "b": {"c": "z"}}, - {"a": 2, "b": {"c": "z"}}, - {"a": 1, "baz": 2} - ] - }, - "cases": [ - { - "expression": "foo[?a==`1`].b.c", - "result": ["x", "y", "z"] - } - ] - }, - { - "given": {"foo": [{"name": "a"}, {"name": "b"}, {"name": "c"}]}, - "cases": [ - { - "comment": "Filter with or expression", - "expression": "foo[?name == 'a' || name == 'b']", - "result": [{"name": "a"}, {"name": "b"}] - }, - { - "expression": "foo[?name == 'a' || name == 'e']", - "result": [{"name": "a"}] - }, - { - "expression": "foo[?name == 'a' || name == 'b' || name == 'c']", - "result": [{"name": "a"}, {"name": "b"}, {"name": "c"}] - } - ] - }, - { - "given": {"foo": [{"a": 1, "b": 2}, {"a": 1, "b": 3}]}, - "cases": [ - { - "comment": "Filter with and expression", - "expression": "foo[?a == `1` && b == `2`]", - "result": [{"a": 1, "b": 2}] - }, - { - "expression": "foo[?a == `1` && b == `4`]", - "result": [] - } - ] - }, - { - "given": {"foo": [{"a": 1, "b": 2, "c": 3}, {"a": 3, "b": 4}]}, - "cases": [ - { - "comment": "Filter with Or and And expressions", - "expression": "foo[?c == `3` || a == `1` && b == `4`]", - "result": [{"a": 1, "b": 2, "c": 3}] - }, - { - "expression": "foo[?b == `2` || a == `3` && b == `4`]", - "result": [{"a": 1, "b": 2, "c": 3}, {"a": 3, "b": 4}] - }, - { - "expression": "foo[?a == `3` && b == `4` || b == `2`]", - "result": [{"a": 1, "b": 2, "c": 3}, {"a": 3, "b": 4}] - }, - { - "expression": "foo[?(a == `3` && b == `4`) || b == `2`]", - "result": [{"a": 1, "b": 2, "c": 3}, {"a": 3, "b": 4}] - }, - { - "expression": "foo[?((a == `3` && b == `4`)) || b == `2`]", - "result": [{"a": 1, "b": 2, "c": 3}, {"a": 3, "b": 4}] - }, - { - "expression": "foo[?a == `3` && (b == `4` || b == `2`)]", - "result": [{"a": 3, "b": 4}] - }, - { - "expression": "foo[?a == `3` && ((b == `4` || b == `2`))]", - "result": [{"a": 3, "b": 4}] - } - ] - }, - { - "given": {"foo": [{"a": 1, "b": 2, "c": 3}, {"a": 3, "b": 4}]}, - "cases": [ - { - "comment": "Verify precedence of or/and expressions", - "expression": "foo[?a == `1` || b ==`2` && c == `5`]", - "result": [{"a": 1, "b": 2, "c": 3}] - }, - { - "comment": "Parentheses can alter precedence", - "expression": "foo[?(a == `1` || b ==`2`) && c == `5`]", - "result": [] - }, - { - "comment": "Not expressions combined with and/or", - "expression": "foo[?!(a == `1` || b ==`2`)]", - "result": [{"a": 3, "b": 4}] - } - ] - }, - { - "given": { - "foo": [ - {"key": true}, - {"key": false}, - {"key": []}, - {"key": {}}, - {"key": [0]}, - {"key": {"a": "b"}}, - {"key": 0}, - {"key": 1}, - {"key": null}, - {"notkey": true} - ] - }, - "cases": [ - { - "comment": "Unary filter expression", - "expression": "foo[?key]", - "result": [ - {"key": true}, {"key": [0]}, {"key": {"a": "b"}}, - {"key": 0}, {"key": 1} - ] - }, - { - "comment": "Unary not filter expression", - "expression": "foo[?!key]", - "result": [ - {"key": false}, {"key": []}, {"key": {}}, - {"key": null}, {"notkey": true} - ] - }, - { - "comment": "Equality with null RHS", - "expression": "foo[?key == `null`]", - "result": [ - {"key": null}, {"notkey": true} - ] - } - ] - }, - { - "given": { - "foo": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - }, - "cases": [ - { - "comment": "Using @ in a filter expression", - "expression": "foo[?@ < `5`]", - "result": [0, 1, 2, 3, 4] - }, - { - "comment": "Using @ in a filter expression", - "expression": "foo[?`5` > @]", - "result": [0, 1, 2, 3, 4] - }, - { - "comment": "Using @ in a filter expression", - "expression": "foo[?@ == @]", - "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - } - ] - } -] diff --git a/grammar/filters.yml b/grammar/filters.yml new file mode 100644 index 0000000..bb9b755 --- /dev/null +++ b/grammar/filters.yml @@ -0,0 +1,353 @@ +test0: + context: + foo: [name: a, name: b] + query: foo[?name == 'a'] + returns: [name: a] + comment: Matching a literal +test1: + context: + foo: [0, 1] + bar: [2, 3] + query: '*[?[0] == `0`]' + returns: [[], []] + comment: Matching a literal +test2: + context: &data + foo: [{first: foo, last: bar}, {first: foo, last: foo}, {first: foo, last: baz}] + query: foo[?first == last] + returns: [{first: foo, last: foo}] + comment: Matching an expression +test3: + context: *data + query: foo[?first == last].first + returns: [foo] + comment: Verify projection created from filter +test4: + context: &data + foo: [age: 20, age: 25, age: 30] + query: foo[?age > `25`] + returns: [age: 30] + comment: Greater than with a number +test5: + context: *data + query: foo[?age >= `25`] + returns: [age: 25, age: 30] +test6: + context: *data + query: foo[?age > `30`] + returns: [] + comment: Greater than with a number +test7: + context: *data + query: foo[?age < `25`] + returns: [age: 20] + comment: Greater than with a number +test8: + context: *data + query: foo[?age <= `25`] + returns: [age: 20, age: 25] + comment: Greater than with a number +test9: + context: *data + query: foo[?age < `20`] + returns: [] + comment: Greater than with a number +test10: + context: *data + query: foo[?age == `20`] + returns: [age: 20] +test11: + context: *data + query: foo[?age != `20`] + returns: [age: 25, age: 30] +test12: + context: + foo: [top: {name: a}, top: {name: b}] + query: foo[?top.name == 'a'] + returns: [top: {name: a}] + comment: Filter with subexpression +test13: + context: &data + foo: [top: {first: foo, last: bar}, top: {first: foo, last: foo}, top: {first: foo, + last: baz}] + query: foo[?top.first == top.last] + returns: [top: {first: foo, last: foo}] + comment: Matching an expression +test14: + context: *data + query: 'foo[?top == `{"first": "foo", "last": "bar"}`]' + returns: [top: {first: foo, last: bar}] + comment: Matching a JSON array +test15: + context: &data + foo: [key: true, key: false, key: 0, key: 1, key: [0], key: {bar: [0]}, key: !!null '', + key: [1], key: {a: 2}] + query: foo[?key == `true`] + returns: [key: true] +test16: + context: *data + query: foo[?key == `false`] + returns: [key: false] +test17: + context: *data + query: foo[?key == `0`] + returns: [key: 0] +test18: + context: *data + query: foo[?key == `1`] + returns: [key: 1] +test19: + context: *data + query: foo[?key == `[0]`] + returns: [key: [0]] +test20: + context: *data + query: 'foo[?key == `{"bar": [0]}`]' + returns: [key: {bar: [0]}] +test21: + context: *data + query: foo[?key == `null`] + returns: [key: !!null ''] +test22: + context: *data + query: foo[?key == `[1]`] + returns: [key: [1]] +test23: + context: *data + query: foo[?key == `{"a":2}`] + returns: [key: {a: 2}] +test24: + context: *data + query: foo[?`true` == key] + returns: [key: true] +test25: + context: *data + query: foo[?`false` == key] + returns: [key: false] +test26: + context: *data + query: foo[?`0` == key] + returns: [key: 0] +test27: + context: *data + query: foo[?`1` == key] + returns: [key: 1] +test28: + context: *data + query: foo[?`[0]` == key] + returns: [key: [0]] +test29: + context: *data + query: 'foo[?`{"bar": [0]}` == key]' + returns: [key: {bar: [0]}] +test30: + context: *data + query: foo[?`null` == key] + returns: [key: !!null ''] +test31: + context: *data + query: foo[?`[1]` == key] + returns: [key: [1]] +test32: + context: *data + query: foo[?`{"a":2}` == key] + returns: [key: {a: 2}] +test33: + context: *data + query: foo[?key != `true`] + returns: [key: false, key: 0, key: 1, key: [0], key: {bar: [0]}, key: !!null '', + key: [1], key: {a: 2}] +test34: + context: *data + query: foo[?key != `false`] + returns: [key: true, key: 0, key: 1, key: [0], key: {bar: [0]}, key: !!null '', + key: [1], key: {a: 2}] +test35: + context: *data + query: foo[?key != `0`] + returns: [key: true, key: false, key: 1, key: [0], key: {bar: [0]}, key: !!null '', + key: [1], key: {a: 2}] +test36: + context: *data + query: foo[?key != `1`] + returns: [key: true, key: false, key: 0, key: [0], key: {bar: [0]}, key: !!null '', + key: [1], key: {a: 2}] +test37: + context: *data + query: foo[?key != `null`] + returns: [key: true, key: false, key: 0, key: 1, key: [0], key: {bar: [0]}, key: [ + 1], key: {a: 2}] +test38: + context: *data + query: foo[?key != `[1]`] + returns: [key: true, key: false, key: 0, key: 1, key: [0], key: {bar: [0]}, key: !!null '', + key: {a: 2}] +test39: + context: *data + query: foo[?key != `{"a":2}`] + returns: [key: true, key: false, key: 0, key: 1, key: [0], key: {bar: [0]}, key: !!null '', + key: [1]] +test40: + context: *data + query: foo[?`true` != key] + returns: [key: false, key: 0, key: 1, key: [0], key: {bar: [0]}, key: !!null '', + key: [1], key: {a: 2}] +test41: + context: *data + query: foo[?`false` != key] + returns: [key: true, key: 0, key: 1, key: [0], key: {bar: [0]}, key: !!null '', + key: [1], key: {a: 2}] +test42: + context: *data + query: foo[?`0` != key] + returns: [key: true, key: false, key: 1, key: [0], key: {bar: [0]}, key: !!null '', + key: [1], key: {a: 2}] +test43: + context: *data + query: foo[?`1` != key] + returns: [key: true, key: false, key: 0, key: [0], key: {bar: [0]}, key: !!null '', + key: [1], key: {a: 2}] +test44: + context: *data + query: foo[?`null` != key] + returns: [key: true, key: false, key: 0, key: 1, key: [0], key: {bar: [0]}, key: [ + 1], key: {a: 2}] +test45: + context: *data + query: foo[?`[1]` != key] + returns: [key: true, key: false, key: 0, key: 1, key: [0], key: {bar: [0]}, key: !!null '', + key: {a: 2}] +test46: + context: *data + query: foo[?`{"a":2}` != key] + returns: [key: true, key: false, key: 0, key: 1, key: [0], key: {bar: [0]}, key: !!null '', + key: [1]] +test47: + context: &data + reservations: [instances: [{foo: 1, bar: 2}, {foo: 1, bar: 3}, {foo: 1, bar: 2}, + {foo: 2, bar: 1}]] + query: reservations[].instances[?bar==`1`] + returns: [[{foo: 2, bar: 1}]] +test48: + context: *data + query: reservations[*].instances[?bar==`1`] + returns: [[{foo: 2, bar: 1}]] +test49: + context: *data + query: reservations[].instances[?bar==`1`][] + returns: [{foo: 2, bar: 1}] +test50: + context: + baz: other + foo: [bar: 1, bar: 2, bar: 3, bar: 4, {bar: 1, baz: 2}] + query: foo[?bar==`1`].bar[0] + returns: [] +test51: + context: + foo: [{a: 1, b: {c: x}}, {a: 1, b: {c: y}}, {a: 1, b: {c: z}}, {a: 2, b: {c: z}}, + {a: 1, baz: 2}] + query: foo[?a==`1`].b.c + returns: [x, y, z] +test52: + context: &data + foo: [name: a, name: b, name: c] + query: foo[?name == 'a' || name == 'b'] + returns: [name: a, name: b] + comment: Filter with or expression +test53: + context: *data + query: foo[?name == 'a' || name == 'e'] + returns: [name: a] +test54: + context: *data + query: foo[?name == 'a' || name == 'b' || name == 'c'] + returns: [name: a, name: b, name: c] +test55: + context: &data + foo: [{a: 1, b: 2}, {a: 1, b: 3}] + query: foo[?a == `1` && b == `2`] + returns: [{a: 1, b: 2}] + comment: Filter with and expression +test56: + context: *data + query: foo[?a == `1` && b == `4`] + returns: [] +test57: + context: &data + foo: [{a: 1, b: 2, c: 3}, {a: 3, b: 4}] + query: foo[?c == `3` || a == `1` && b == `4`] + returns: [{a: 1, b: 2, c: 3}] + comment: Filter with Or and And expressions +test58: + context: *data + query: foo[?b == `2` || a == `3` && b == `4`] + returns: [{a: 1, b: 2, c: 3}, {a: 3, b: 4}] +test59: + context: *data + query: foo[?a == `3` && b == `4` || b == `2`] + returns: [{a: 1, b: 2, c: 3}, {a: 3, b: 4}] +test60: + context: *data + query: foo[?(a == `3` && b == `4`) || b == `2`] + returns: [{a: 1, b: 2, c: 3}, {a: 3, b: 4}] +test61: + context: *data + query: foo[?((a == `3` && b == `4`)) || b == `2`] + returns: [{a: 1, b: 2, c: 3}, {a: 3, b: 4}] +test62: + context: *data + query: foo[?a == `3` && (b == `4` || b == `2`)] + returns: [{a: 3, b: 4}] +test63: + context: *data + query: foo[?a == `3` && ((b == `4` || b == `2`))] + returns: [{a: 3, b: 4}] +test64: + context: &data + foo: [{a: 1, b: 2, c: 3}, {a: 3, b: 4}] + query: foo[?a == `1` || b ==`2` && c == `5`] + returns: [{a: 1, b: 2, c: 3}] + comment: Verify precedence of or/and expressions +test65: + context: *data + query: foo[?(a == `1` || b ==`2`) && c == `5`] + returns: [] + comment: Parentheses can alter precedence +test66: + context: *data + query: foo[?!(a == `1` || b ==`2`)] + returns: [{a: 3, b: 4}] + comment: Not expressions combined with and/or +test67: + context: &data + foo: [key: true, key: false, key: [], key: {}, key: [0], key: {a: b}, key: 0, + key: 1, key: !!null '', notkey: true] + query: foo[?key] + returns: [key: true, key: [0], key: {a: b}, key: 0, key: 1] + comment: Unary filter expression +test68: + context: *data + query: foo[?!key] + returns: [key: false, key: [], key: {}, key: !!null '', notkey: true] + comment: Unary not filter expression +test69: + context: *data + query: foo[?key == `null`] + returns: [key: !!null '', notkey: true] + comment: Equality with null RHS +test70: + context: &data + foo: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + query: foo[?@ < `5`] + returns: [0, 1, 2, 3, 4] + comment: Using @ in a filter expression +test71: + context: *data + query: foo[?`5` > @] + returns: [0, 1, 2, 3, 4] + comment: Using @ in a filter expression +test72: + context: *data + query: foo[?@ == @] + returns: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + comment: Using @ in a filter expression diff --git a/grammar/functions.json b/grammar/functions.json deleted file mode 100644 index 7a78f8e..0000000 --- a/grammar/functions.json +++ /dev/null @@ -1,53 +0,0 @@ -[{ - "given": - { - "foo": -1, - "zero": 0, - "numbers": [-1, 3, 4, 5], - "array": [-1, 3, 4, 5, "a", "100"], - "strings": ["a", "b", "c"], - "decimals": [1.01, 1.2, -1.5], - "str": "Str", - "false": false, - "empty_list": [], - "empty_hash": {}, - "objects": {"foo": "bar", "bar": "baz"}, - "null_key": null - }, - "cases": [ { - "expression": "unknown_function(`1`, `2`)", - "error": "unknown-function" - }, { - "expression": "\"to_string\"(`1.0`)", - "error": "syntax" - }, - { - "comment": "function projection on single arg function", - "expression": "numbers[].to_string(@)", - "result": ["-1", "3", "4", "5"] - }, - { - "comment": "function projection on single arg function", - "expression": "array[].to_number(@)", - "result": [-1, 3, 4, 5, 100] - } - ] -}, { - "given": - { - "foo": [ - {"b": "b", "a": "a"}, - {"c": "c", "b": "b"}, - {"d": "d", "c": "c"}, - {"e": "e", "d": "d"}, - {"f": "f", "e": "e"} - ] - }, - "cases": [ - { - "comment": "function projection on variadic function", - "expression": "foo[].not_null(f, e, d, c, b, a)", - "result": ["b", "c", "d", "e", "f"] - } - ] -}] diff --git a/grammar/functions.yml b/grammar/functions.yml new file mode 100644 index 0000000..f841b23 --- /dev/null +++ b/grammar/functions.yml @@ -0,0 +1,36 @@ +test0: + context: &data + foo: -1 + zero: 0 + numbers: [-1, 3, 4, 5] + array: [-1, 3, 4, 5, a, '100'] + strings: [a, b, c] + decimals: [1.01, 1.2, -1.5] + str: Str + 'false': false + empty_list: [] + empty_hash: {} + objects: {foo: bar, bar: baz} + null_key: + query: unknown_function(`1`, `2`) + error: unknown-function +test1: + context: *data + query: '"to_string"(`1.0`)' + error: syntax +test2: + context: *data + query: numbers[].to_string(@) + returns: ['-1', '3', '4', '5'] + comment: function projection on single arg function +test3: + context: *data + query: array[].to_number(@) + returns: [-1, 3, 4, 5, 100] + comment: function projection on single arg function +test4: + context: + foo: [{b: b, a: a}, {c: c, b: b}, {d: d, c: c}, {e: e, d: d}, {f: f, e: e}] + query: foo[].not_null(f, e, d, c, b, a) + returns: [b, c, d, e, f] + comment: function projection on variadic function diff --git a/grammar/identifiers.json b/grammar/identifiers.json deleted file mode 100644 index 7998a41..0000000 --- a/grammar/identifiers.json +++ /dev/null @@ -1,1377 +0,0 @@ -[ - { - "given": { - "__L": true - }, - "cases": [ - { - "expression": "__L", - "result": true - } - ] - }, - { - "given": { - "!\r": true - }, - "cases": [ - { - "expression": "\"!\\r\"", - "result": true - } - ] - }, - { - "given": { - "Y_1623": true - }, - "cases": [ - { - "expression": "Y_1623", - "result": true - } - ] - }, - { - "given": { - "x": true - }, - "cases": [ - { - "expression": "x", - "result": true - } - ] - }, - { - "given": { - "\tF\uCebb": true - }, - "cases": [ - { - "expression": "\"\\tF\\uCebb\"", - "result": true - } - ] - }, - { - "given": { - " \t": true - }, - "cases": [ - { - "expression": "\" \\t\"", - "result": true - } - ] - }, - { - "given": { - " ": true - }, - "cases": [ - { - "expression": "\" \"", - "result": true - } - ] - }, - { - "given": { - "v2": true - }, - "cases": [ - { - "expression": "v2", - "result": true - } - ] - }, - { - "given": { - "\t": true - }, - "cases": [ - { - "expression": "\"\\t\"", - "result": true - } - ] - }, - { - "given": { - "_X": true - }, - "cases": [ - { - "expression": "_X", - "result": true - } - ] - }, - { - "given": { - "\t4\ud9da\udd15": true - }, - "cases": [ - { - "expression": "\"\\t4\\ud9da\\udd15\"", - "result": true - } - ] - }, - { - "given": { - "v24_W": true - }, - "cases": [ - { - "expression": "v24_W", - "result": true - } - ] - }, - { - "given": { - "H": true - }, - "cases": [ - { - "expression": "\"H\"", - "result": true - } - ] - }, - { - "given": { - "\f": true - }, - "cases": [ - { - "expression": "\"\\f\"", - "result": true - } - ] - }, - { - "given": { - "E4": true - }, - "cases": [ - { - "expression": "\"E4\"", - "result": true - } - ] - }, - { - "given": { - "!": true - }, - "cases": [ - { - "expression": "\"!\"", - "result": true - } - ] - }, - { - "given": { - "tM": true - }, - "cases": [ - { - "expression": "tM", - "result": true - } - ] - }, - { - "given": { - " [": true - }, - "cases": [ - { - "expression": "\" [\"", - "result": true - } - ] - }, - { - "given": { - "R!": true - }, - "cases": [ - { - "expression": "\"R!\"", - "result": true - } - ] - }, - { - "given": { - "_6W": true - }, - "cases": [ - { - "expression": "_6W", - "result": true - } - ] - }, - { - "given": { - "\uaBA1\r": true - }, - "cases": [ - { - "expression": "\"\\uaBA1\\r\"", - "result": true - } - ] - }, - { - "given": { - "tL7": true - }, - "cases": [ - { - "expression": "tL7", - "result": true - } - ] - }, - { - "given": { - "<": true - }, - "cases": [ - { - "expression": "\">\"", - "result": true - } - ] - }, - { - "given": { - "hvu": true - }, - "cases": [ - { - "expression": "hvu", - "result": true - } - ] - }, - { - "given": { - "; !": true - }, - "cases": [ - { - "expression": "\"; !\"", - "result": true - } - ] - }, - { - "given": { - "hU": true - }, - "cases": [ - { - "expression": "hU", - "result": true - } - ] - }, - { - "given": { - "!I\n\/": true - }, - "cases": [ - { - "expression": "\"!I\\n\\/\"", - "result": true - } - ] - }, - { - "given": { - "\uEEbF": true - }, - "cases": [ - { - "expression": "\"\\uEEbF\"", - "result": true - } - ] - }, - { - "given": { - "U)\t": true - }, - "cases": [ - { - "expression": "\"U)\\t\"", - "result": true - } - ] - }, - { - "given": { - "fa0_9": true - }, - "cases": [ - { - "expression": "fa0_9", - "result": true - } - ] - }, - { - "given": { - "/": true - }, - "cases": [ - { - "expression": "\"/\"", - "result": true - } - ] - }, - { - "given": { - "Gy": true - }, - "cases": [ - { - "expression": "Gy", - "result": true - } - ] - }, - { - "given": { - "\b": true - }, - "cases": [ - { - "expression": "\"\\b\"", - "result": true - } - ] - }, - { - "given": { - "<": true - }, - "cases": [ - { - "expression": "\"<\"", - "result": true - } - ] - }, - { - "given": { - "\t": true - }, - "cases": [ - { - "expression": "\"\\t\"", - "result": true - } - ] - }, - { - "given": { - "\t&\\\r": true - }, - "cases": [ - { - "expression": "\"\\t&\\\\\\r\"", - "result": true - } - ] - }, - { - "given": { - "#": true - }, - "cases": [ - { - "expression": "\"#\"", - "result": true - } - ] - }, - { - "given": { - "B__": true - }, - "cases": [ - { - "expression": "B__", - "result": true - } - ] - }, - { - "given": { - "\nS \n": true - }, - "cases": [ - { - "expression": "\"\\nS \\n\"", - "result": true - } - ] - }, - { - "given": { - "Bp": true - }, - "cases": [ - { - "expression": "Bp", - "result": true - } - ] - }, - { - "given": { - ",\t;": true - }, - "cases": [ - { - "expression": "\",\\t;\"", - "result": true - } - ] - }, - { - "given": { - "B_q": true - }, - "cases": [ - { - "expression": "B_q", - "result": true - } - ] - }, - { - "given": { - "\/+\t\n\b!Z": true - }, - "cases": [ - { - "expression": "\"\\/+\\t\\n\\b!Z\"", - "result": true - } - ] - }, - { - "given": { - "\udadd\udfc7\\ueFAc": true - }, - "cases": [ - { - "expression": "\"\udadd\udfc7\\\\ueFAc\"", - "result": true - } - ] - }, - { - "given": { - ":\f": true - }, - "cases": [ - { - "expression": "\":\\f\"", - "result": true - } - ] - }, - { - "given": { - "\/": true - }, - "cases": [ - { - "expression": "\"\\/\"", - "result": true - } - ] - }, - { - "given": { - "_BW_6Hg_Gl": true - }, - "cases": [ - { - "expression": "_BW_6Hg_Gl", - "result": true - } - ] - }, - { - "given": { - "\udbcf\udc02": true - }, - "cases": [ - { - "expression": "\"\udbcf\udc02\"", - "result": true - } - ] - }, - { - "given": { - "zs1DC": true - }, - "cases": [ - { - "expression": "zs1DC", - "result": true - } - ] - }, - { - "given": { - "__434": true - }, - "cases": [ - { - "expression": "__434", - "result": true - } - ] - }, - { - "given": { - "\udb94\udd41": true - }, - "cases": [ - { - "expression": "\"\udb94\udd41\"", - "result": true - } - ] - }, - { - "given": { - "Z_5": true - }, - "cases": [ - { - "expression": "Z_5", - "result": true - } - ] - }, - { - "given": { - "z_M_": true - }, - "cases": [ - { - "expression": "z_M_", - "result": true - } - ] - }, - { - "given": { - "YU_2": true - }, - "cases": [ - { - "expression": "YU_2", - "result": true - } - ] - }, - { - "given": { - "_0": true - }, - "cases": [ - { - "expression": "_0", - "result": true - } - ] - }, - { - "given": { - "\b+": true - }, - "cases": [ - { - "expression": "\"\\b+\"", - "result": true - } - ] - }, - { - "given": { - "\"": true - }, - "cases": [ - { - "expression": "\"\\\"\"", - "result": true - } - ] - }, - { - "given": { - "D7": true - }, - "cases": [ - { - "expression": "D7", - "result": true - } - ] - }, - { - "given": { - "_62L": true - }, - "cases": [ - { - "expression": "_62L", - "result": true - } - ] - }, - { - "given": { - "\tK\t": true - }, - "cases": [ - { - "expression": "\"\\tK\\t\"", - "result": true - } - ] - }, - { - "given": { - "\n\\\f": true - }, - "cases": [ - { - "expression": "\"\\n\\\\\\f\"", - "result": true - } - ] - }, - { - "given": { - "I_": true - }, - "cases": [ - { - "expression": "I_", - "result": true - } - ] - }, - { - "given": { - "W_a0_": true - }, - "cases": [ - { - "expression": "W_a0_", - "result": true - } - ] - }, - { - "given": { - "BQ": true - }, - "cases": [ - { - "expression": "BQ", - "result": true - } - ] - }, - { - "given": { - "\tX$\uABBb": true - }, - "cases": [ - { - "expression": "\"\\tX$\\uABBb\"", - "result": true - } - ] - }, - { - "given": { - "Z9": true - }, - "cases": [ - { - "expression": "Z9", - "result": true - } - ] - }, - { - "given": { - "\b%\"\uda38\udd0f": true - }, - "cases": [ - { - "expression": "\"\\b%\\\"\uda38\udd0f\"", - "result": true - } - ] - }, - { - "given": { - "_F": true - }, - "cases": [ - { - "expression": "_F", - "result": true - } - ] - }, - { - "given": { - "!,": true - }, - "cases": [ - { - "expression": "\"!,\"", - "result": true - } - ] - }, - { - "given": { - "\"!": true - }, - "cases": [ - { - "expression": "\"\\\"!\"", - "result": true - } - ] - }, - { - "given": { - "Hh": true - }, - "cases": [ - { - "expression": "Hh", - "result": true - } - ] - }, - { - "given": { - "&": true - }, - "cases": [ - { - "expression": "\"&\"", - "result": true - } - ] - }, - { - "given": { - "9\r\\R": true - }, - "cases": [ - { - "expression": "\"9\\r\\\\R\"", - "result": true - } - ] - }, - { - "given": { - "M_k": true - }, - "cases": [ - { - "expression": "M_k", - "result": true - } - ] - }, - { - "given": { - "!\b\n\udb06\ude52\"\"": true - }, - "cases": [ - { - "expression": "\"!\\b\\n\udb06\ude52\\\"\\\"\"", - "result": true - } - ] - }, - { - "given": { - "6": true - }, - "cases": [ - { - "expression": "\"6\"", - "result": true - } - ] - }, - { - "given": { - "_7": true - }, - "cases": [ - { - "expression": "_7", - "result": true - } - ] - }, - { - "given": { - "0": true - }, - "cases": [ - { - "expression": "\"0\"", - "result": true - } - ] - }, - { - "given": { - "\\8\\": true - }, - "cases": [ - { - "expression": "\"\\\\8\\\\\"", - "result": true - } - ] - }, - { - "given": { - "b7eo": true - }, - "cases": [ - { - "expression": "b7eo", - "result": true - } - ] - }, - { - "given": { - "xIUo9": true - }, - "cases": [ - { - "expression": "xIUo9", - "result": true - } - ] - }, - { - "given": { - "5": true - }, - "cases": [ - { - "expression": "\"5\"", - "result": true - } - ] - }, - { - "given": { - "?": true - }, - "cases": [ - { - "expression": "\"?\"", - "result": true - } - ] - }, - { - "given": { - "sU": true - }, - "cases": [ - { - "expression": "sU", - "result": true - } - ] - }, - { - "given": { - "VH2&H\\\/": true - }, - "cases": [ - { - "expression": "\"VH2&H\\\\\\/\"", - "result": true - } - ] - }, - { - "given": { - "_C": true - }, - "cases": [ - { - "expression": "_C", - "result": true - } - ] - }, - { - "given": { - "_": true - }, - "cases": [ - { - "expression": "_", - "result": true - } - ] - }, - { - "given": { - "<\t": true - }, - "cases": [ - { - "expression": "\"<\\t\"", - "result": true - } - ] - }, - { - "given": { - "\uD834\uDD1E": true - }, - "cases": [ - { - "expression": "\"\\uD834\\uDD1E\"", - "result": true - } - ] - } -] diff --git a/grammar/identifiers.yml b/grammar/identifiers.yml new file mode 100644 index 0000000..6c5f780 --- /dev/null +++ b/grammar/identifiers.yml @@ -0,0 +1,631 @@ +test0: + context: + __L: true + query: __L + returns: true +test1: + context: + "!\r": true + query: '"!\r"' + returns: true +test2: + context: + Y_1623: true + query: Y_1623 + returns: true +test3: + context: + x: true + query: x + returns: true +test4: + context: + "\tF\uCEBB": true + query: '"\tF\uCebb"' + returns: true +test5: + context: + " \t": true + query: '" \t"' + returns: true +test6: + context: + ' ': true + query: '" "' + returns: true +test7: + context: + v2: true + query: v2 + returns: true +test8: + context: + "\t": true + query: '"\t"' + returns: true +test9: + context: + _X: true + query: _X + returns: true +test10: + context: + "\t4\uD9DA\uDD15": true + query: '"\t4\ud9da\udd15"' + returns: true +test11: + context: + v24_W: true + query: v24_W + returns: true +test12: + context: + H: true + query: '"H"' + returns: true +test13: + context: + "\f": true + query: '"\f"' + returns: true +test14: + context: + E4: true + query: '"E4"' + returns: true +test15: + context: + '!': true + query: '"!"' + returns: true +test16: + context: + tM: true + query: tM + returns: true +test17: + context: + ' [': true + query: '" ["' + returns: true +test18: + context: + R!: true + query: '"R!"' + returns: true +test19: + context: + _6W: true + query: _6W + returns: true +test20: + context: + "\uABA1\r": true + query: '"\uaBA1\r"' + returns: true +test21: + context: + tL7: true + query: tL7 + returns: true +test22: + context: + "<': true + query: '">"' + returns: true +test59: + context: + hvu: true + query: hvu + returns: true +test60: + context: + ; !: true + query: '"; !"' + returns: true +test61: + context: + hU: true + query: hU + returns: true +test62: + context: + ? "!I\n/" + : true + query: '"!I\n\/"' + returns: true +test63: + context: + "\uEEBF": true + query: '"\uEEbF"' + returns: true +test64: + context: + "U)\t": true + query: '"U)\t"' + returns: true +test65: + context: + fa0_9: true + query: fa0_9 + returns: true +test66: + context: + /: true + query: '"/"' + returns: true +test67: + context: + Gy: true + query: Gy + returns: true +test68: + context: + "\b": true + query: '"\b"' + returns: true +test69: + context: + <: true + query: '"<"' + returns: true +test70: + context: + "\t": true + query: '"\t"' + returns: true +test71: + context: + "\t&\\\r": true + query: '"\t&\\\r"' + returns: true +test72: + context: + '#': true + query: '"#"' + returns: true +test73: + context: + B__: true + query: B__ + returns: true +test74: + context: + ? "\nS \n" + : true + query: '"\nS \n"' + returns: true +test75: + context: + Bp: true + query: Bp + returns: true +test76: + context: + ",\t;": true + query: '",\t;"' + returns: true +test77: + context: + B_q: true + query: B_q + returns: true +test78: + context: + ? "/+\t\n\b!Z" + : true + query: '"\/+\t\n\b!Z"' + returns: true +test79: + context: + "\uDADD\uDFC7\\ueFAc": true + query: '"\uDADD\uDFC7\\ueFAc"' + returns: true +test80: + context: + ":\f": true + query: '":\f"' + returns: true +test81: + context: + /: true + query: '"\/"' + returns: true +test82: + context: + _BW_6Hg_Gl: true + query: _BW_6Hg_Gl + returns: true +test83: + context: + "\uDBCF\uDC02": true + query: '"\uDBCF\uDC02"' + returns: true +test84: + context: + zs1DC: true + query: zs1DC + returns: true +test85: + context: + __434: true + query: __434 + returns: true +test86: + context: + "\uDB94\uDD41": true + query: '"\uDB94\uDD41"' + returns: true +test87: + context: + Z_5: true + query: Z_5 + returns: true +test88: + context: + z_M_: true + query: z_M_ + returns: true +test89: + context: + YU_2: true + query: YU_2 + returns: true +test90: + context: + _0: true + query: _0 + returns: true +test91: + context: + "\b+": true + query: '"\b+"' + returns: true +test92: + context: + '"': true + query: '"\""' + returns: true +test93: + context: + D7: true + query: D7 + returns: true +test94: + context: + _62L: true + query: _62L + returns: true +test95: + context: + "\tK\t": true + query: '"\tK\t"' + returns: true +test96: + context: + ? "\n\\\f" + : true + query: '"\n\\\f"' + returns: true +test97: + context: + I_: true + query: I_ + returns: true +test98: + context: + W_a0_: true + query: W_a0_ + returns: true +test99: + context: + BQ: true + query: BQ + returns: true +test100: + context: + "\tX$\uABBB": true + query: '"\tX$\uABBb"' + returns: true +test101: + context: + Z9: true + query: Z9 + returns: true +test102: + context: + "\b%\"\uDA38\uDD0F": true + query: '"\b%\"\uDA38\uDD0F"' + returns: true +test103: + context: + _F: true + query: _F + returns: true +test104: + context: + '!,': true + query: '"!,"' + returns: true +test105: + context: + '"!': true + query: '"\"!"' + returns: true +test106: + context: + Hh: true + query: Hh + returns: true +test107: + context: + '&': true + query: '"&"' + returns: true +test108: + context: + "9\r\\R": true + query: '"9\r\\R"' + returns: true +test109: + context: + M_k: true + query: M_k + returns: true +test110: + context: + ? "!\b\n\uDB06\uDE52\"\"" + : true + query: '"!\b\n\uDB06\uDE52\"\""' + returns: true +test111: + context: + '6': true + query: '"6"' + returns: true +test112: + context: + _7: true + query: _7 + returns: true +test113: + context: + '0': true + query: '"0"' + returns: true +test114: + context: + \8\: true + query: '"\\8\\"' + returns: true +test115: + context: + b7eo: true + query: b7eo + returns: true +test116: + context: + xIUo9: true + query: xIUo9 + returns: true +test117: + context: + '5': true + query: '"5"' + returns: true +test118: + context: + '?': true + query: '"?"' + returns: true +test119: + context: + sU: true + query: sU + returns: true +test120: + context: + VH2&H\/: true + query: '"VH2&H\\\/"' + returns: true +test121: + context: + _C: true + query: _C + returns: true +test122: + context: + _: true + query: _ + returns: true +test123: + context: + "<\t": true + query: '"<\t"' + returns: true +test124: + context: + "\uD834\uDD1E": true + query: '"\uD834\uDD1E"' + returns: true diff --git a/grammar/indices.json b/grammar/indices.json deleted file mode 100644 index aa03b35..0000000 --- a/grammar/indices.json +++ /dev/null @@ -1,346 +0,0 @@ -[{ - "given": - {"foo": {"bar": ["zero", "one", "two"]}}, - "cases": [ - { - "expression": "foo.bar[0]", - "result": "zero" - }, - { - "expression": "foo.bar[1]", - "result": "one" - }, - { - "expression": "foo.bar[2]", - "result": "two" - }, - { - "expression": "foo.bar[3]", - "result": null - }, - { - "expression": "foo.bar[-1]", - "result": "two" - }, - { - "expression": "foo.bar[-2]", - "result": "one" - }, - { - "expression": "foo.bar[-3]", - "result": "zero" - }, - { - "expression": "foo.bar[-4]", - "result": null - } - ] -}, -{ - "given": - {"foo": [{"bar": "one"}, {"bar": "two"}, {"bar": "three"}, {"notbar": "four"}]}, - "cases": [ - { - "expression": "foo.bar", - "result": null - }, - { - "expression": "foo[0].bar", - "result": "one" - }, - { - "expression": "foo[1].bar", - "result": "two" - }, - { - "expression": "foo[2].bar", - "result": "three" - }, - { - "expression": "foo[3].notbar", - "result": "four" - }, - { - "expression": "foo[3].bar", - "result": null - }, - { - "expression": "foo[0]", - "result": {"bar": "one"} - }, - { - "expression": "foo[1]", - "result": {"bar": "two"} - }, - { - "expression": "foo[2]", - "result": {"bar": "three"} - }, - { - "expression": "foo[3]", - "result": {"notbar": "four"} - }, - { - "expression": "foo[4]", - "result": null - } - ] -}, -{ - "given": [ - "one", "two", "three" - ], - "cases": [ - { - "expression": "[0]", - "result": "one" - }, - { - "expression": "[1]", - "result": "two" - }, - { - "expression": "[2]", - "result": "three" - }, - { - "expression": "[-1]", - "result": "three" - }, - { - "expression": "[-2]", - "result": "two" - }, - { - "expression": "[-3]", - "result": "one" - } - ] -}, -{ - "given": {"reservations": [ - {"instances": [{"foo": 1}, {"foo": 2}]} - ]}, - "cases": [ - { - "expression": "reservations[].instances[].foo", - "result": [1, 2] - }, - { - "expression": "reservations[].instances[].bar", - "result": [] - }, - { - "expression": "reservations[].notinstances[].foo", - "result": [] - }, - { - "expression": "reservations[].notinstances[].foo", - "result": [] - } - ] -}, -{ - "given": {"reservations": [{ - "instances": [ - {"foo": [{"bar": 1}, {"bar": 2}, {"notbar": 3}, {"bar": 4}]}, - {"foo": [{"bar": 5}, {"bar": 6}, {"notbar": [7]}, {"bar": 8}]}, - {"foo": "bar"}, - {"notfoo": [{"bar": 20}, {"bar": 21}, {"notbar": [7]}, {"bar": 22}]}, - {"bar": [{"baz": [1]}, {"baz": [2]}, {"baz": [3]}, {"baz": [4]}]}, - {"baz": [{"baz": [1, 2]}, {"baz": []}, {"baz": []}, {"baz": [3, 4]}]}, - {"qux": [{"baz": []}, {"baz": [1, 2, 3]}, {"baz": [4]}, {"baz": []}]} - ], - "otherkey": {"foo": [{"bar": 1}, {"bar": 2}, {"notbar": 3}, {"bar": 4}]} - }, { - "instances": [ - {"a": [{"bar": 1}, {"bar": 2}, {"notbar": 3}, {"bar": 4}]}, - {"b": [{"bar": 5}, {"bar": 6}, {"notbar": [7]}, {"bar": 8}]}, - {"c": "bar"}, - {"notfoo": [{"bar": 23}, {"bar": 24}, {"notbar": [7]}, {"bar": 25}]}, - {"qux": [{"baz": []}, {"baz": [1, 2, 3]}, {"baz": [4]}, {"baz": []}]} - ], - "otherkey": {"foo": [{"bar": 1}, {"bar": 2}, {"notbar": 3}, {"bar": 4}]} - } - ]}, - "cases": [ - { - "expression": "reservations[].instances[].foo[].bar", - "result": [1, 2, 4, 5, 6, 8] - }, - { - "expression": "reservations[].instances[].foo[].baz", - "result": [] - }, - { - "expression": "reservations[].instances[].notfoo[].bar", - "result": [20, 21, 22, 23, 24, 25] - }, - { - "expression": "reservations[].instances[].notfoo[].notbar", - "result": [[7], [7]] - }, - { - "expression": "reservations[].notinstances[].foo", - "result": [] - }, - { - "expression": "reservations[].instances[].foo[].notbar", - "result": [3, [7]] - }, - { - "expression": "reservations[].instances[].bar[].baz", - "result": [[1], [2], [3], [4]] - }, - { - "expression": "reservations[].instances[].baz[].baz", - "result": [[1, 2], [], [], [3, 4]] - }, - { - "expression": "reservations[].instances[].qux[].baz", - "result": [[], [1, 2, 3], [4], [], [], [1, 2, 3], [4], []] - }, - { - "expression": "reservations[].instances[].qux[].baz[]", - "result": [1, 2, 3, 4, 1, 2, 3, 4] - } - ] -}, -{ - "given": { - "foo": [ - [["one", "two"], ["three", "four"]], - [["five", "six"], ["seven", "eight"]], - [["nine"], ["ten"]] - ] - }, - "cases": [ - { - "expression": "foo[]", - "result": [["one", "two"], ["three", "four"], ["five", "six"], - ["seven", "eight"], ["nine"], ["ten"]] - }, - { - "expression": "foo[][0]", - "result": ["one", "three", "five", "seven", "nine", "ten"] - }, - { - "expression": "foo[][1]", - "result": ["two", "four", "six", "eight"] - }, - { - "expression": "foo[][0][0]", - "result": [] - }, - { - "expression": "foo[][2][2]", - "result": [] - }, - { - "expression": "foo[][0][0][100]", - "result": [] - } - ] -}, -{ - "given": { - "foo": [{ - "bar": [ - { - "qux": 2, - "baz": 1 - }, - { - "qux": 4, - "baz": 3 - } - ] - }, - { - "bar": [ - { - "qux": 6, - "baz": 5 - }, - { - "qux": 8, - "baz": 7 - } - ] - } - ] - }, - "cases": [ - { - "expression": "foo", - "result": [{"bar": [{"qux": 2, "baz": 1}, {"qux": 4, "baz": 3}]}, - {"bar": [{"qux": 6, "baz": 5}, {"qux": 8, "baz": 7}]}] - }, - { - "expression": "foo[]", - "result": [{"bar": [{"qux": 2, "baz": 1}, {"qux": 4, "baz": 3}]}, - {"bar": [{"qux": 6, "baz": 5}, {"qux": 8, "baz": 7}]}] - }, - { - "expression": "foo[].bar", - "result": [[{"qux": 2, "baz": 1}, {"qux": 4, "baz": 3}], - [{"qux": 6, "baz": 5}, {"qux": 8, "baz": 7}]] - }, - { - "expression": "foo[].bar[]", - "result": [{"qux": 2, "baz": 1}, {"qux": 4, "baz": 3}, - {"qux": 6, "baz": 5}, {"qux": 8, "baz": 7}] - }, - { - "expression": "foo[].bar[].baz", - "result": [1, 3, 5, 7] - } - ] -}, -{ - "given": { - "string": "string", - "hash": {"foo": "bar", "bar": "baz"}, - "number": 23, - "nullvalue": null - }, - "cases": [ - { - "expression": "string[]", - "result": null - }, - { - "expression": "hash[]", - "result": null - }, - { - "expression": "number[]", - "result": null - }, - { - "expression": "nullvalue[]", - "result": null - }, - { - "expression": "string[].foo", - "result": null - }, - { - "expression": "hash[].foo", - "result": null - }, - { - "expression": "number[].foo", - "result": null - }, - { - "expression": "nullvalue[].foo", - "result": null - }, - { - "expression": "nullvalue[].foo[].bar", - "result": null - } - ] -} -] diff --git a/grammar/indices.yml b/grammar/indices.yml new file mode 100644 index 0000000..2c8096d --- /dev/null +++ b/grammar/indices.yml @@ -0,0 +1,260 @@ +test0: + context: &data + foo: {bar: [zero, one, two]} + query: foo.bar[0] + returns: zero +test1: + context: *data + query: foo.bar[1] + returns: one +test2: + context: *data + query: foo.bar[2] + returns: two +test3: + context: *data + query: foo.bar[3] + returns: +test4: + context: *data + query: foo.bar[-1] + returns: two +test5: + context: *data + query: foo.bar[-2] + returns: one +test6: + context: *data + query: foo.bar[-3] + returns: zero +test7: + context: *data + query: foo.bar[-4] + returns: +test8: + context: &data + foo: [bar: one, bar: two, bar: three, notbar: four] + query: foo.bar + returns: +test9: + context: *data + query: foo[0].bar + returns: one +test10: + context: *data + query: foo[1].bar + returns: two +test11: + context: *data + query: foo[2].bar + returns: three +test12: + context: *data + query: foo[3].notbar + returns: four +test13: + context: *data + query: foo[3].bar + returns: +test14: + context: *data + query: foo[0] + returns: {bar: one} +test15: + context: *data + query: foo[1] + returns: {bar: two} +test16: + context: *data + query: foo[2] + returns: {bar: three} +test17: + context: *data + query: foo[3] + returns: {notbar: four} +test18: + context: *data + query: foo[4] + returns: +test19: + context: &data + - one + - two + - three + query: '[0]' + returns: one +test20: + context: *data + query: '[1]' + returns: two +test21: + context: *data + query: '[2]' + returns: three +test22: + context: *data + query: '[-1]' + returns: three +test23: + context: *data + query: '[-2]' + returns: two +test24: + context: *data + query: '[-3]' + returns: one +test25: + context: &data + reservations: [instances: [foo: 1, foo: 2]] + query: reservations[].instances[].foo + returns: [1, 2] +test26: + context: *data + query: reservations[].instances[].bar + returns: [] +test27: + context: *data + query: reservations[].notinstances[].foo + returns: [] +test28: + context: *data + query: reservations[].notinstances[].foo + returns: [] +test29: + context: &data + reservations: [{instances: [foo: [bar: 1, bar: 2, notbar: 3, bar: 4], foo: [bar: 5, + bar: 6, notbar: [7], bar: 8], foo: bar, notfoo: [bar: 20, bar: 21, + notbar: [7], bar: 22], bar: [baz: [1], baz: [2], baz: [3], baz: [ + 4]], baz: [baz: [1, 2], baz: [], baz: [], baz: [3, 4]], qux: [baz: [], + baz: [1, 2, 3], baz: [4], baz: []]], otherkey: {foo: [bar: 1, bar: 2, + notbar: 3, bar: 4]}}, {instances: [a: [bar: 1, bar: 2, notbar: 3, bar: 4], + b: [bar: 5, bar: 6, notbar: [7], bar: 8], c: bar, notfoo: [bar: 23, bar: 24, + notbar: [7], bar: 25], qux: [baz: [], baz: [1, 2, 3], baz: [4], baz: []]], + otherkey: {foo: [bar: 1, bar: 2, notbar: 3, bar: 4]}}] + query: reservations[].instances[].foo[].bar + returns: [1, 2, 4, 5, 6, 8] +test30: + context: *data + query: reservations[].instances[].foo[].baz + returns: [] +test31: + context: *data + query: reservations[].instances[].notfoo[].bar + returns: [20, 21, 22, 23, 24, 25] +test32: + context: *data + query: reservations[].instances[].notfoo[].notbar + returns: [[7], [7]] +test33: + context: *data + query: reservations[].notinstances[].foo + returns: [] +test34: + context: *data + query: reservations[].instances[].foo[].notbar + returns: [3, [7]] +test35: + context: *data + query: reservations[].instances[].bar[].baz + returns: [[1], [2], [3], [4]] +test36: + context: *data + query: reservations[].instances[].baz[].baz + returns: [[1, 2], [], [], [3, 4]] +test37: + context: *data + query: reservations[].instances[].qux[].baz + returns: [[], [1, 2, 3], [4], [], [], [1, 2, 3], [4], []] +test38: + context: *data + query: reservations[].instances[].qux[].baz[] + returns: [1, 2, 3, 4, 1, 2, 3, 4] +test39: + context: &data + foo: [[[one, two], [three, four]], [[five, six], [seven, eight]], [[nine], [ten]]] + query: foo[] + returns: [[one, two], [three, four], [five, six], [seven, eight], [nine], [ten]] +test40: + context: *data + query: foo[][0] + returns: [one, three, five, seven, nine, ten] +test41: + context: *data + query: foo[][1] + returns: [two, four, six, eight] +test42: + context: *data + query: foo[][0][0] + returns: [] +test43: + context: *data + query: foo[][2][2] + returns: [] +test44: + context: *data + query: foo[][0][0][100] + returns: [] +test45: + context: &data + foo: [bar: [{qux: 2, baz: 1}, {qux: 4, baz: 3}], bar: [{qux: 6, baz: 5}, {qux: 8, + baz: 7}]] + query: foo + returns: [bar: [{qux: 2, baz: 1}, {qux: 4, baz: 3}], bar: [{qux: 6, baz: 5}, {qux: 8, + baz: 7}]] +test46: + context: *data + query: foo[] + returns: [bar: [{qux: 2, baz: 1}, {qux: 4, baz: 3}], bar: [{qux: 6, baz: 5}, {qux: 8, + baz: 7}]] +test47: + context: *data + query: foo[].bar + returns: [[{qux: 2, baz: 1}, {qux: 4, baz: 3}], [{qux: 6, baz: 5}, {qux: 8, baz: 7}]] +test48: + context: *data + query: foo[].bar[] + returns: [{qux: 2, baz: 1}, {qux: 4, baz: 3}, {qux: 6, baz: 5}, {qux: 8, baz: 7}] +test49: + context: *data + query: foo[].bar[].baz + returns: [1, 3, 5, 7] +test50: + context: &data + string: string + hash: {foo: bar, bar: baz} + number: 23 + nullvalue: + query: string[] + returns: +test51: + context: *data + query: hash[] + returns: +test52: + context: *data + query: number[] + returns: +test53: + context: *data + query: nullvalue[] + returns: +test54: + context: *data + query: string[].foo + returns: +test55: + context: *data + query: hash[].foo + returns: +test56: + context: *data + query: number[].foo + returns: +test57: + context: *data + query: nullvalue[].foo + returns: +test58: + context: *data + query: nullvalue[].foo[].bar + returns: diff --git a/grammar/literal.json b/grammar/literal.json deleted file mode 100644 index b5ddbed..0000000 --- a/grammar/literal.json +++ /dev/null @@ -1,200 +0,0 @@ -[ - { - "given": { - "foo": [{"name": "a"}, {"name": "b"}], - "bar": {"baz": "qux"} - }, - "cases": [ - { - "expression": "`\"foo\"`", - "result": "foo" - }, - { - "comment": "Interpret escaped unicode.", - "expression": "`\"\\u03a6\"`", - "result": "Φ" - }, - { - "expression": "`\"✓\"`", - "result": "✓" - }, - { - "expression": "`[1, 2, 3]`", - "result": [1, 2, 3] - }, - { - "expression": "`{\"a\": \"b\"}`", - "result": {"a": "b"} - }, - { - "expression": "`true`", - "result": true - }, - { - "expression": "`false`", - "result": false - }, - { - "expression": "`null`", - "result": null - }, - { - "expression": "`0`", - "result": 0 - }, - { - "expression": "`1`", - "result": 1 - }, - { - "expression": "`2`", - "result": 2 - }, - { - "expression": "`3`", - "result": 3 - }, - { - "expression": "`4`", - "result": 4 - }, - { - "expression": "`5`", - "result": 5 - }, - { - "expression": "`6`", - "result": 6 - }, - { - "expression": "`7`", - "result": 7 - }, - { - "expression": "`8`", - "result": 8 - }, - { - "expression": "`9`", - "result": 9 - }, - { - "comment": "Escaping a backtick in quotes", - "expression": "`\"foo\\`bar\"`", - "result": "foo`bar" - }, - { - "comment": "Double quote in literal", - "expression": "`\"foo\\\"bar\"`", - "result": "foo\"bar" - }, - { - "expression": "`\"1\\`\"`", - "result": "1`" - }, - { - "comment": "Multiple literal expressions with escapes", - "expression": "`\"\\\\\"`.{a:`\"b\"`}", - "result": {"a": "b"} - }, - { - "comment": "literal . identifier", - "expression": "`{\"a\": \"b\"}`.a", - "result": "b" - }, - { - "comment": "literal . identifier . identifier", - "expression": "`{\"a\": {\"b\": \"c\"}}`.a.b", - "result": "c" - }, - { - "comment": "literal . identifier bracket-expr", - "expression": "`[0, 1, 2]`[1]", - "result": 1 - } - ] - }, - { - "comment": "Literals", - "given": {"type": "object"}, - "cases": [ - { - "comment": "Literal with leading whitespace", - "expression": "` {\"foo\": true}`", - "result": {"foo": true} - }, - { - "comment": "Literal with trailing whitespace", - "expression": "`{\"foo\": true} `", - "result": {"foo": true} - }, - { - "comment": "Literal on RHS of subexpr not allowed", - "expression": "foo.`\"bar\"`", - "error": "syntax" - } - ] - }, - { - "comment": "Raw String Literals", - "given": {}, - "cases": [ - { - "expression": "'foo'", - "result": "foo" - }, - { - "expression": "' foo '", - "result": " foo " - }, - { - "expression": "'0'", - "result": "0" - }, - { - "expression": "'newline\n'", - "result": "newline\n" - }, - { - "expression": "'\n'", - "result": "\n" - }, - { - "expression": "'✓'", - "result": "✓" - }, - { - "expression": "'𝄞'", - "result": "𝄞" - }, - { - "expression": "' [foo] '", - "result": " [foo] " - }, - { - "expression": "'[foo]'", - "result": "[foo]" - }, - { - "comment": "Do not interpret escaped unicode.", - "expression": "'\\u03a6'", - "result": "\\u03a6" - }, - { - "comment": "Can escape the single quote", - "expression": "'foo\\'bar'", - "result": "foo'bar" - }, - { - "comment": "Backslash not followed by single quote is treated as any other character", - "expression": "'\\z'", - "result": "\\z" - }, - { - "comment": "Backslash not followed by single quote is treated as any other character", - "expression": "'\\\\'", - "result": "\\\\" - } - ] - } -] diff --git a/grammar/literal.yml b/grammar/literal.yml new file mode 100644 index 0000000..6e4eb80 --- /dev/null +++ b/grammar/literal.yml @@ -0,0 +1,181 @@ +test0: + context: &data + foo: [name: a, name: b] + bar: {baz: qux} + query: '`"foo"`' + returns: foo +test1: + context: *data + query: '`"\u03a6"`' + returns: "\u03A6" + comment: Interpret escaped unicode. +test2: + context: *data + query: "`\"\u2713\"`" + returns: "\u2713" +test3: + context: *data + query: '`[1, 2, 3]`' + returns: [1, 2, 3] +test4: + context: *data + query: '`{"a": "b"}`' + returns: {a: b} +test5: + context: *data + query: '`true`' + returns: true +test6: + context: *data + query: '`false`' + returns: false +test7: + context: *data + query: '`null`' + returns: +test8: + context: *data + query: '`0`' + returns: 0 +test9: + context: *data + query: '`1`' + returns: 1 +test10: + context: *data + query: '`2`' + returns: 2 +test11: + context: *data + query: '`3`' + returns: 3 +test12: + context: *data + query: '`4`' + returns: 4 +test13: + context: *data + query: '`5`' + returns: 5 +test14: + context: *data + query: '`6`' + returns: 6 +test15: + context: *data + query: '`7`' + returns: 7 +test16: + context: *data + query: '`8`' + returns: 8 +test17: + context: *data + query: '`9`' + returns: 9 +test18: + context: *data + query: '`"foo\`bar"`' + returns: foo`bar + comment: Escaping a backtick in quotes +test19: + context: *data + query: '`"foo\"bar"`' + returns: foo"bar + comment: Double quote in literal +test20: + context: *data + query: '`"1\`"`' + returns: 1` +test21: + context: *data + query: '`"\\"`.{a:`"b"`}' + returns: {a: b} + comment: Multiple literal expressions with escapes +test22: + context: *data + query: '`{"a": "b"}`.a' + returns: b + comment: literal . identifier +test23: + context: *data + query: '`{"a": {"b": "c"}}`.a.b' + returns: c + comment: literal . identifier . identifier +test24: + context: *data + query: '`[0, 1, 2]`[1]' + returns: 1 + comment: literal . identifier bracket-expr +test25: + context: &data + type: object + query: '` {"foo": true}`' + returns: {foo: true} + comment: Literal with leading whitespace +test26: + context: *data + query: '`{"foo": true} `' + returns: {foo: true} + comment: Literal with trailing whitespace +test27: + context: *data + query: foo.`"bar"` + error: syntax + comment: Literal on RHS of subexpr not allowed +test28: + context: &data {} + query: "'foo'" + returns: foo +test29: + context: *data + query: "' foo '" + returns: ' foo ' +test30: + context: *data + query: "'0'" + returns: '0' +test31: + context: *data + query: "'newline\n'" + returns: "newline\n" +test32: + context: *data + query: "'\n'" + returns: "\n" +test33: + context: *data + query: "'\u2713'" + returns: "\u2713" +test34: + context: *data + query: "'\U0001D11E'" + returns: "\U0001D11E" +test35: + context: *data + query: "' [foo] '" + returns: ' [foo] ' +test36: + context: *data + query: "'[foo]'" + returns: '[foo]' +test37: + context: *data + query: "'\\u03a6'" + returns: \u03a6 + comment: Do not interpret escaped unicode. +test38: + context: *data + query: "'foo\\'bar'" + returns: foo'bar + comment: Can escape the single quote +test39: + context: *data + query: "'\\z'" + returns: \z + comment: Backslash not followed by single quote is treated as any other character +test40: + context: *data + query: "'\\\\'" + returns: \\ + comment: Backslash not followed by single quote is treated as any other character diff --git a/grammar/multiselect.json b/grammar/multiselect.json deleted file mode 100644 index 4f46482..0000000 --- a/grammar/multiselect.json +++ /dev/null @@ -1,398 +0,0 @@ -[{ - "given": { - "foo": { - "bar": "bar", - "baz": "baz", - "qux": "qux", - "nested": { - "one": { - "a": "first", - "b": "second", - "c": "third" - }, - "two": { - "a": "first", - "b": "second", - "c": "third" - }, - "three": { - "a": "first", - "b": "second", - "c": {"inner": "third"} - } - } - }, - "bar": 1, - "baz": 2, - "qux\"": 3 - }, - "cases": [ - { - "expression": "foo.{bar: bar}", - "result": {"bar": "bar"} - }, - { - "expression": "foo.{\"bar\": bar}", - "result": {"bar": "bar"} - }, - { - "expression": "foo.{\"foo.bar\": bar}", - "result": {"foo.bar": "bar"} - }, - { - "expression": "foo.{bar: bar, baz: baz}", - "result": {"bar": "bar", "baz": "baz"} - }, - { - "expression": "foo.{\"bar\": bar, \"baz\": baz}", - "result": {"bar": "bar", "baz": "baz"} - }, - { - "expression": "{\"baz\": baz, \"qux\\\"\": \"qux\\\"\"}", - "result": {"baz": 2, "qux\"": 3} - }, - { - "expression": "foo.{bar:bar,baz:baz}", - "result": {"bar": "bar", "baz": "baz"} - }, - { - "expression": "foo.{bar: bar,qux: qux}", - "result": {"bar": "bar", "qux": "qux"} - }, - { - "expression": "foo.{bar: bar, noexist: noexist}", - "result": {"bar": "bar", "noexist": null} - }, - { - "expression": "foo.{noexist: noexist, alsonoexist: alsonoexist}", - "result": {"noexist": null, "alsonoexist": null} - }, - { - "expression": "foo.badkey.{nokey: nokey, alsonokey: alsonokey}", - "result": null - }, - { - "expression": "foo.nested.*.{a: a,b: b}", - "result": [{"a": "first", "b": "second"}, - {"a": "first", "b": "second"}, - {"a": "first", "b": "second"}] - }, - { - "expression": "foo.nested.three.{a: a, cinner: c.inner}", - "result": {"a": "first", "cinner": "third"} - }, - { - "expression": "foo.nested.three.{a: a, c: c.inner.bad.key}", - "result": {"a": "first", "c": null} - }, - { - "expression": "foo.{a: nested.one.a, b: nested.two.b}", - "result": {"a": "first", "b": "second"} - }, - { - "expression": "{bar: bar, baz: baz}", - "result": {"bar": 1, "baz": 2} - }, - { - "expression": "{bar: bar}", - "result": {"bar": 1} - }, - { - "expression": "{otherkey: bar}", - "result": {"otherkey": 1} - }, - { - "expression": "{no: no, exist: exist}", - "result": {"no": null, "exist": null} - }, - { - "expression": "foo.[bar]", - "result": ["bar"] - }, - { - "expression": "foo.[bar,baz]", - "result": ["bar", "baz"] - }, - { - "expression": "foo.[bar,qux]", - "result": ["bar", "qux"] - }, - { - "expression": "foo.[bar,noexist]", - "result": ["bar", null] - }, - { - "expression": "foo.[noexist,alsonoexist]", - "result": [null, null] - } - ] -}, { - "given": { - "foo": {"bar": 1, "baz": [2, 3, 4]} - }, - "cases": [ - { - "expression": "foo.{bar:bar,baz:baz}", - "result": {"bar": 1, "baz": [2, 3, 4]} - }, - { - "expression": "foo.[bar,baz[0]]", - "result": [1, 2] - }, - { - "expression": "foo.[bar,baz[1]]", - "result": [1, 3] - }, - { - "expression": "foo.[bar,baz[2]]", - "result": [1, 4] - }, - { - "expression": "foo.[bar,baz[3]]", - "result": [1, null] - }, - { - "expression": "foo.[bar[0],baz[3]]", - "result": [null, null] - } - ] -}, { - "given": { - "foo": {"bar": 1, "baz": 2} - }, - "cases": [ - { - "expression": "foo.{bar: bar, baz: baz}", - "result": {"bar": 1, "baz": 2} - }, - { - "expression": "foo.[bar,baz]", - "result": [1, 2] - } - ] -}, { - "given": { - "foo": { - "bar": {"baz": [{"common": "first", "one": 1}, - {"common": "second", "two": 2}]}, - "ignoreme": 1, - "includeme": true - } - }, - "cases": [ - { - "expression": "foo.{bar: bar.baz[1],includeme: includeme}", - "result": {"bar": {"common": "second", "two": 2}, "includeme": true} - }, - { - "expression": "foo.{\"bar.baz.two\": bar.baz[1].two, includeme: includeme}", - "result": {"bar.baz.two": 2, "includeme": true} - }, - { - "expression": "foo.[includeme, bar.baz[*].common]", - "result": [true, ["first", "second"]] - }, - { - "expression": "foo.[includeme, bar.baz[*].none]", - "result": [true, []] - }, - { - "expression": "foo.[includeme, bar.baz[].common]", - "result": [true, ["first", "second"]] - } - ] -}, { - "given": { - "reservations": [{ - "instances": [ - {"id": "id1", - "name": "first"}, - {"id": "id2", - "name": "second"} - ]}, { - "instances": [ - {"id": "id3", - "name": "third"}, - {"id": "id4", - "name": "fourth"} - ]} - ]}, - "cases": [ - { - "expression": "reservations[*].instances[*].{id: id, name: name}", - "result": [[{"id": "id1", "name": "first"}, {"id": "id2", "name": "second"}], - [{"id": "id3", "name": "third"}, {"id": "id4", "name": "fourth"}]] - }, - { - "expression": "reservations[].instances[].{id: id, name: name}", - "result": [{"id": "id1", "name": "first"}, - {"id": "id2", "name": "second"}, - {"id": "id3", "name": "third"}, - {"id": "id4", "name": "fourth"}] - }, - { - "expression": "reservations[].instances[].[id, name]", - "result": [["id1", "first"], - ["id2", "second"], - ["id3", "third"], - ["id4", "fourth"]] - } - ] -}, -{ - "given": { - "foo": [{ - "bar": [ - { - "qux": 2, - "baz": 1 - }, - { - "qux": 4, - "baz": 3 - } - ] - }, - { - "bar": [ - { - "qux": 6, - "baz": 5 - }, - { - "qux": 8, - "baz": 7 - } - ] - } - ] - }, - "cases": [ - { - "expression": "foo", - "result": [{"bar": [{"qux": 2, "baz": 1}, {"qux": 4, "baz": 3}]}, - {"bar": [{"qux": 6, "baz": 5}, {"qux": 8, "baz": 7}]}] - }, - { - "expression": "foo[]", - "result": [{"bar": [{"qux": 2, "baz": 1}, {"qux": 4, "baz": 3}]}, - {"bar": [{"qux": 6, "baz": 5}, {"qux": 8, "baz": 7}]}] - }, - { - "expression": "foo[].bar", - "result": [[{"qux": 2, "baz": 1}, {"qux": 4, "baz": 3}], - [{"qux": 6, "baz": 5}, {"qux": 8, "baz": 7}]] - }, - { - "expression": "foo[].bar[]", - "result": [{"qux": 2, "baz": 1}, {"qux": 4, "baz": 3}, - {"qux": 6, "baz": 5}, {"qux": 8, "baz": 7}] - }, - { - "expression": "foo[].bar[].[baz, qux]", - "result": [[1, 2], [3, 4], [5, 6], [7, 8]] - }, - { - "expression": "foo[].bar[].[baz]", - "result": [[1], [3], [5], [7]] - }, - { - "expression": "foo[].bar[].[baz, qux][]", - "result": [1, 2, 3, 4, 5, 6, 7, 8] - } - ] -}, -{ - "given": { - "foo": { - "baz": [ - { - "bar": "abc" - }, { - "bar": "def" - } - ], - "qux": ["zero"] - } - }, - "cases": [ - { - "expression": "foo.[baz[*].bar, qux[0]]", - "result": [["abc", "def"], "zero"] - } - ] -}, -{ - "given": { - "foo": { - "baz": [ - { - "bar": "a", - "bam": "b", - "boo": "c" - }, { - "bar": "d", - "bam": "e", - "boo": "f" - } - ], - "qux": ["zero"] - } - }, - "cases": [ - { - "expression": "foo.[baz[*].[bar, boo], qux[0]]", - "result": [[["a", "c" ], ["d", "f" ]], "zero"] - } - ] -}, -{ - "given": { - "foo": { - "baz": [ - { - "bar": "a", - "bam": "b", - "boo": "c" - }, { - "bar": "d", - "bam": "e", - "boo": "f" - } - ], - "qux": ["zero"] - } - }, - "cases": [ - { - "expression": "foo.[baz[*].not_there || baz[*].bar, qux[0]]", - "result": [["a", "d"], "zero"] - } - ] -}, -{ - "given": {"type": "object"}, - "cases": [ - { - "comment": "Nested multiselect", - "expression": "[[*],*]", - "result": [null, ["object"]] - } - ] -}, -{ - "given": [], - "cases": [ - { - "comment": "Nested multiselect", - "expression": "[[*]]", - "result": [[]] - }, - { - "comment": "Select on null", - "expression": "missing.{foo: bar}", - "result": null - } - ] -} -] diff --git a/grammar/multiselect.yml b/grammar/multiselect.yml new file mode 100644 index 0000000..9c361a0 --- /dev/null +++ b/grammar/multiselect.yml @@ -0,0 +1,236 @@ +test0: + context: &data + foo: {bar: bar, baz: baz, qux: qux, nested: {one: {a: first, b: second, c: third}, + two: {a: first, b: second, c: third}, three: {a: first, b: second, c: {inner: third}}}} + bar: 1 + baz: 2 + qux": 3 + query: 'foo.{bar: bar}' + returns: {bar: bar} +test1: + context: *data + query: 'foo.{"bar": bar}' + returns: {bar: bar} +test2: + context: *data + query: 'foo.{"foo.bar": bar}' + returns: {foo.bar: bar} +test3: + context: *data + query: 'foo.{bar: bar, baz: baz}' + returns: {bar: bar, baz: baz} +test4: + context: *data + query: 'foo.{"bar": bar, "baz": baz}' + returns: {bar: bar, baz: baz} +test5: + context: *data + query: '{"baz": baz, "qux\"": "qux\""}' + returns: {baz: 2, qux": 3} +test6: + context: *data + query: foo.{bar:bar,baz:baz} + returns: {bar: bar, baz: baz} +test7: + context: *data + query: 'foo.{bar: bar,qux: qux}' + returns: {bar: bar, qux: qux} +test8: + context: *data + query: 'foo.{bar: bar, noexist: noexist}' + returns: {bar: bar, noexist: !!null ''} +test9: + context: *data + query: 'foo.{noexist: noexist, alsonoexist: alsonoexist}' + returns: {noexist: !!null '', alsonoexist: !!null ''} +test10: + context: *data + query: 'foo.badkey.{nokey: nokey, alsonokey: alsonokey}' + returns: +test11: + context: *data + query: 'foo.nested.*.{a: a,b: b}' + returns: [{a: first, b: second}, {a: first, b: second}, {a: first, b: second}] +test12: + context: *data + query: 'foo.nested.three.{a: a, cinner: c.inner}' + returns: {a: first, cinner: third} +test13: + context: *data + query: 'foo.nested.three.{a: a, c: c.inner.bad.key}' + returns: {a: first, c: !!null ''} +test14: + context: *data + query: 'foo.{a: nested.one.a, b: nested.two.b}' + returns: {a: first, b: second} +test15: + context: *data + query: '{bar: bar, baz: baz}' + returns: {bar: 1, baz: 2} +test16: + context: *data + query: '{bar: bar}' + returns: {bar: 1} +test17: + context: *data + query: '{otherkey: bar}' + returns: {otherkey: 1} +test18: + context: *data + query: '{no: no, exist: exist}' + returns: {no: !!null '', exist: !!null ''} +test19: + context: *data + query: foo.[bar] + returns: [bar] +test20: + context: *data + query: foo.[bar,baz] + returns: [bar, baz] +test21: + context: *data + query: foo.[bar,qux] + returns: [bar, qux] +test22: + context: *data + query: foo.[bar,noexist] + returns: [bar, !!null ''] +test23: + context: *data + query: foo.[noexist,alsonoexist] + returns: [!!null '', !!null ''] +test24: + context: &data + foo: {bar: 1, baz: [2, 3, 4]} + query: foo.{bar:bar,baz:baz} + returns: {bar: 1, baz: [2, 3, 4]} +test25: + context: *data + query: foo.[bar,baz[0]] + returns: [1, 2] +test26: + context: *data + query: foo.[bar,baz[1]] + returns: [1, 3] +test27: + context: *data + query: foo.[bar,baz[2]] + returns: [1, 4] +test28: + context: *data + query: foo.[bar,baz[3]] + returns: [1, !!null ''] +test29: + context: *data + query: foo.[bar[0],baz[3]] + returns: [!!null '', !!null ''] +test30: + context: &data + foo: {bar: 1, baz: 2} + query: 'foo.{bar: bar, baz: baz}' + returns: {bar: 1, baz: 2} +test31: + context: *data + query: foo.[bar,baz] + returns: [1, 2] +test32: + context: &data + foo: {bar: {baz: [{common: first, one: 1}, {common: second, two: 2}]}, ignoreme: 1, + includeme: true} + query: 'foo.{bar: bar.baz[1],includeme: includeme}' + returns: {bar: {common: second, two: 2}, includeme: true} +test33: + context: *data + query: 'foo.{"bar.baz.two": bar.baz[1].two, includeme: includeme}' + returns: {bar.baz.two: 2, includeme: true} +test34: + context: *data + query: foo.[includeme, bar.baz[*].common] + returns: [true, [first, second]] +test35: + context: *data + query: foo.[includeme, bar.baz[*].none] + returns: [true, []] +test36: + context: *data + query: foo.[includeme, bar.baz[].common] + returns: [true, [first, second]] +test37: + context: &data + reservations: [instances: [{id: id1, name: first}, {id: id2, name: second}], + instances: [{id: id3, name: third}, {id: id4, name: fourth}]] + query: 'reservations[*].instances[*].{id: id, name: name}' + returns: [[{id: id1, name: first}, {id: id2, name: second}], [{id: id3, name: third}, + {id: id4, name: fourth}]] +test38: + context: *data + query: 'reservations[].instances[].{id: id, name: name}' + returns: [{id: id1, name: first}, {id: id2, name: second}, {id: id3, name: third}, + {id: id4, name: fourth}] +test39: + context: *data + query: reservations[].instances[].[id, name] + returns: [[id1, first], [id2, second], [id3, third], [id4, fourth]] +test40: + context: &data + foo: [bar: [{qux: 2, baz: 1}, {qux: 4, baz: 3}], bar: [{qux: 6, baz: 5}, {qux: 8, + baz: 7}]] + query: foo + returns: [bar: [{qux: 2, baz: 1}, {qux: 4, baz: 3}], bar: [{qux: 6, baz: 5}, {qux: 8, + baz: 7}]] +test41: + context: *data + query: foo[] + returns: [bar: [{qux: 2, baz: 1}, {qux: 4, baz: 3}], bar: [{qux: 6, baz: 5}, {qux: 8, + baz: 7}]] +test42: + context: *data + query: foo[].bar + returns: [[{qux: 2, baz: 1}, {qux: 4, baz: 3}], [{qux: 6, baz: 5}, {qux: 8, baz: 7}]] +test43: + context: *data + query: foo[].bar[] + returns: [{qux: 2, baz: 1}, {qux: 4, baz: 3}, {qux: 6, baz: 5}, {qux: 8, baz: 7}] +test44: + context: *data + query: foo[].bar[].[baz, qux] + returns: [[1, 2], [3, 4], [5, 6], [7, 8]] +test45: + context: *data + query: foo[].bar[].[baz] + returns: [[1], [3], [5], [7]] +test46: + context: *data + query: foo[].bar[].[baz, qux][] + returns: [1, 2, 3, 4, 5, 6, 7, 8] +test47: + context: + foo: {baz: [bar: abc, bar: def], qux: [zero]} + query: foo.[baz[*].bar, qux[0]] + returns: [[abc, def], zero] +test48: + context: + foo: {baz: [{bar: a, bam: b, boo: c}, {bar: d, bam: e, boo: f}], qux: [zero]} + query: foo.[baz[*].[bar, boo], qux[0]] + returns: [[[a, c], [d, f]], zero] +test49: + context: + foo: {baz: [{bar: a, bam: b, boo: c}, {bar: d, bam: e, boo: f}], qux: [zero]} + query: foo.[baz[*].not_there || baz[*].bar, qux[0]] + returns: [[a, d], zero] +test50: + context: + type: object + query: '[[*],*]' + returns: [!!null '', [object]] + comment: Nested multiselect +test51: + context: &data [] + query: '[[*]]' + returns: [[]] + comment: Nested multiselect +test52: + context: *data + query: 'missing.{foo: bar}' + returns: + comment: Select on null diff --git a/grammar/pipe.json b/grammar/pipe.json deleted file mode 100644 index b10c0a4..0000000 --- a/grammar/pipe.json +++ /dev/null @@ -1,131 +0,0 @@ -[{ - "given": { - "foo": { - "bar": { - "baz": "subkey" - }, - "other": { - "baz": "subkey" - }, - "other2": { - "baz": "subkey" - }, - "other3": { - "notbaz": ["a", "b", "c"] - }, - "other4": { - "notbaz": ["a", "b", "c"] - } - } - }, - "cases": [ - { - "expression": "foo.*.baz | [0]", - "result": "subkey" - }, - { - "expression": "foo.*.baz | [1]", - "result": "subkey" - }, - { - "expression": "foo.*.baz | [2]", - "result": "subkey" - }, - { - "expression": "foo.bar.* | [0]", - "result": "subkey" - }, - { - "expression": "foo.*.notbaz | [*]", - "result": [["a", "b", "c"], ["a", "b", "c"]] - }, - { - "expression": "{\"a\": foo.bar, \"b\": foo.other} | *.baz", - "result": ["subkey", "subkey"] - } - ] -}, { - "given": { - "foo": { - "bar": { - "baz": "one" - }, - "other": { - "baz": "two" - }, - "other2": { - "baz": "three" - }, - "other3": { - "notbaz": ["a", "b", "c"] - }, - "other4": { - "notbaz": ["d", "e", "f"] - } - } - }, - "cases": [ - { - "expression": "foo | bar", - "result": {"baz": "one"} - }, - { - "expression": "foo | bar | baz", - "result": "one" - }, - { - "expression": "foo|bar| baz", - "result": "one" - }, - { - "expression": "not_there | [0]", - "result": null - }, - { - "expression": "not_there | [0]", - "result": null - }, - { - "expression": "[foo.bar, foo.other] | [0]", - "result": {"baz": "one"} - }, - { - "expression": "{\"a\": foo.bar, \"b\": foo.other} | a", - "result": {"baz": "one"} - }, - { - "expression": "{\"a\": foo.bar, \"b\": foo.other} | b", - "result": {"baz": "two"} - }, - { - "expression": "foo.bam || foo.bar | baz", - "result": "one" - }, - { - "expression": "foo | not_there || bar", - "result": {"baz": "one"} - } - ] -}, { - "given": { - "foo": [{ - "bar": [{ - "baz": "one" - }, { - "baz": "two" - }] - }, { - "bar": [{ - "baz": "three" - }, { - "baz": "four" - }] - }] - }, - "cases": [ - { - "expression": "foo[*].bar[*] | [0][0]", - "result": {"baz": "one"} - } - ] -}] diff --git a/grammar/pipe.yml b/grammar/pipe.yml new file mode 100644 index 0000000..f32c4d2 --- /dev/null +++ b/grammar/pipe.yml @@ -0,0 +1,73 @@ +test0: + context: &data + foo: {bar: {baz: subkey}, other: {baz: subkey}, other2: {baz: subkey}, other3: { + notbaz: [a, b, c]}, other4: {notbaz: [a, b, c]}} + query: foo.*.baz | [0] + returns: subkey +test1: + context: *data + query: foo.*.baz | [1] + returns: subkey +test2: + context: *data + query: foo.*.baz | [2] + returns: subkey +test3: + context: *data + query: foo.bar.* | [0] + returns: subkey +test4: + context: *data + query: foo.*.notbaz | [*] + returns: [[a, b, c], [a, b, c]] +test5: + context: *data + query: '{"a": foo.bar, "b": foo.other} | *.baz' + returns: [subkey, subkey] +test6: + context: &data + foo: {bar: {baz: one}, other: {baz: two}, other2: {baz: three}, other3: {notbaz: [ + a, b, c]}, other4: {notbaz: [d, e, f]}} + query: foo | bar + returns: {baz: one} +test7: + context: *data + query: foo | bar | baz + returns: one +test8: + context: *data + query: foo|bar| baz + returns: one +test9: + context: *data + query: not_there | [0] + returns: +test10: + context: *data + query: not_there | [0] + returns: +test11: + context: *data + query: '[foo.bar, foo.other] | [0]' + returns: {baz: one} +test12: + context: *data + query: '{"a": foo.bar, "b": foo.other} | a' + returns: {baz: one} +test13: + context: *data + query: '{"a": foo.bar, "b": foo.other} | b' + returns: {baz: two} +test14: + context: *data + query: foo.bam || foo.bar | baz + returns: one +test15: + context: *data + query: foo | not_there || bar + returns: {baz: one} +test16: + context: + foo: [bar: [baz: one, baz: two], bar: [baz: three, baz: four]] + query: foo[*].bar[*] | [0][0] + returns: {baz: one} diff --git a/grammar/slice.json b/grammar/slice.json deleted file mode 100644 index 3594772..0000000 --- a/grammar/slice.json +++ /dev/null @@ -1,187 +0,0 @@ -[{ - "given": { - "foo": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], - "bar": { - "baz": 1 - } - }, - "cases": [ - { - "expression": "bar[0:10]", - "result": null - }, - { - "expression": "foo[0:10:1]", - "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - }, - { - "expression": "foo[0:10]", - "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - }, - { - "expression": "foo[0:10:]", - "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - }, - { - "expression": "foo[0::1]", - "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - }, - { - "expression": "foo[0::]", - "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - }, - { - "expression": "foo[0:]", - "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - }, - { - "expression": "foo[:10:1]", - "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - }, - { - "expression": "foo[::1]", - "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - }, - { - "expression": "foo[:10:]", - "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - }, - { - "expression": "foo[::]", - "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - }, - { - "expression": "foo[:]", - "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - }, - { - "expression": "foo[1:9]", - "result": [1, 2, 3, 4, 5, 6, 7, 8] - }, - { - "expression": "foo[0:10:2]", - "result": [0, 2, 4, 6, 8] - }, - { - "expression": "foo[5:]", - "result": [5, 6, 7, 8, 9] - }, - { - "expression": "foo[5::2]", - "result": [5, 7, 9] - }, - { - "expression": "foo[::2]", - "result": [0, 2, 4, 6, 8] - }, - { - "expression": "foo[::-1]", - "result": [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] - }, - { - "expression": "foo[1::2]", - "result": [1, 3, 5, 7, 9] - }, - { - "expression": "foo[10:0:-1]", - "result": [9, 8, 7, 6, 5, 4, 3, 2, 1] - }, - { - "expression": "foo[10:5:-1]", - "result": [9, 8, 7, 6] - }, - { - "expression": "foo[8:2:-2]", - "result": [8, 6, 4] - }, - { - "expression": "foo[0:20]", - "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - }, - { - "expression": "foo[10:-20:-1]", - "result": [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] - }, - { - "expression": "foo[10:-20]", - "result": [] - }, - { - "expression": "foo[-4:-1]", - "result": [6, 7, 8] - }, - { - "expression": "foo[:-5:-1]", - "result": [9, 8, 7, 6] - }, - { - "expression": "foo[8:2:0]", - "error": "invalid-value" - }, - { - "expression": "foo[8:2:0:1]", - "error": "syntax" - }, - { - "expression": "foo[8:2&]", - "error": "syntax" - }, - { - "expression": "foo[2:a:3]", - "error": "syntax" - } - ] -}, { - "given": { - "foo": [{"a": 1}, {"a": 2}, {"a": 3}], - "bar": [{"a": {"b": 1}}, {"a": {"b": 2}}, - {"a": {"b": 3}}], - "baz": 50 - }, - "cases": [ - { - "expression": "foo[:2].a", - "result": [1, 2] - }, - { - "expression": "foo[:2].b", - "result": [] - }, - { - "expression": "foo[:2].a.b", - "result": [] - }, - { - "expression": "bar[::-1].a.b", - "result": [3, 2, 1] - }, - { - "expression": "bar[:2].a.b", - "result": [1, 2] - }, - { - "expression": "baz[:2].a", - "result": null - } - ] -}, { - "given": [{"a": 1}, {"a": 2}, {"a": 3}], - "cases": [ - { - "expression": "[:]", - "result": [{"a": 1}, {"a": 2}, {"a": 3}] - }, - { - "expression": "[:2].a", - "result": [1, 2] - }, - { - "expression": "[::-1].a", - "result": [3, 2, 1] - }, - { - "expression": "[:2].b", - "result": [] - } - ] -}] diff --git a/grammar/slice.yml b/grammar/slice.yml new file mode 100644 index 0000000..88e6a4a --- /dev/null +++ b/grammar/slice.yml @@ -0,0 +1,172 @@ +test0: + context: &data + foo: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + bar: {baz: 1} + query: bar[0:10] + returns: +test1: + context: *data + query: foo[0:10:1] + returns: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +test2: + context: *data + query: foo[0:10] + returns: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +test3: + context: *data + query: foo[0:10:] + returns: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +test4: + context: *data + query: foo[0::1] + returns: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +test5: + context: *data + query: foo[0::] + returns: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +test6: + context: *data + query: foo[0:] + returns: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +test7: + context: *data + query: foo[:10:1] + returns: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +test8: + context: *data + query: foo[::1] + returns: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +test9: + context: *data + query: foo[:10:] + returns: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +test10: + context: *data + query: foo[::] + returns: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +test11: + context: *data + query: foo[:] + returns: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +test12: + context: *data + query: foo[1:9] + returns: [1, 2, 3, 4, 5, 6, 7, 8] +test13: + context: *data + query: foo[0:10:2] + returns: [0, 2, 4, 6, 8] +test14: + context: *data + query: foo[5:] + returns: [5, 6, 7, 8, 9] +test15: + context: *data + query: foo[5::2] + returns: [5, 7, 9] +test16: + context: *data + query: foo[::2] + returns: [0, 2, 4, 6, 8] +test17: + context: *data + query: foo[::-1] + returns: [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] +test18: + context: *data + query: foo[1::2] + returns: [1, 3, 5, 7, 9] +test19: + context: *data + query: foo[10:0:-1] + returns: [9, 8, 7, 6, 5, 4, 3, 2, 1] +test20: + context: *data + query: foo[10:5:-1] + returns: [9, 8, 7, 6] +test21: + context: *data + query: foo[8:2:-2] + returns: [8, 6, 4] +test22: + context: *data + query: foo[0:20] + returns: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +test23: + context: *data + query: foo[10:-20:-1] + returns: [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] +test24: + context: *data + query: foo[10:-20] + returns: [] +test25: + context: *data + query: foo[-4:-1] + returns: [6, 7, 8] +test26: + context: *data + query: foo[:-5:-1] + returns: [9, 8, 7, 6] +test27: + context: *data + query: foo[8:2:0] + error: invalid-value +test28: + context: *data + query: foo[8:2:0:1] + error: syntax +test29: + context: *data + query: foo[8:2&] + error: syntax +test30: + context: *data + query: foo[2:a:3] + error: syntax +test31: + context: &data + foo: [a: 1, a: 2, a: 3] + bar: [a: {b: 1}, a: {b: 2}, a: {b: 3}] + baz: 50 + query: foo[:2].a + returns: [1, 2] +test32: + context: *data + query: foo[:2].b + returns: [] +test33: + context: *data + query: foo[:2].a.b + returns: [] +test34: + context: *data + query: bar[::-1].a.b + returns: [3, 2, 1] +test35: + context: *data + query: bar[:2].a.b + returns: [1, 2] +test36: + context: *data + query: baz[:2].a + returns: +test37: + context: &data + - {a: 1} + - {a: 2} + - {a: 3} + query: '[:]' + returns: [a: 1, a: 2, a: 3] +test38: + context: *data + query: '[:2].a' + returns: [1, 2] +test39: + context: *data + query: '[::-1].a' + returns: [3, 2, 1] +test40: + context: *data + query: '[:2].b' + returns: [] diff --git a/grammar/syntax.json b/grammar/syntax.json deleted file mode 100644 index 9318901..0000000 --- a/grammar/syntax.json +++ /dev/null @@ -1,678 +0,0 @@ -[{ - "comment": "Dot syntax", - "given": {"type": "object"}, - "cases": [ - { - "expression": "foo.bar", - "result": null - }, - { - "expression": "foo.1", - "error": "syntax" - }, - { - "expression": "foo.-11", - "error": "syntax" - }, - { - "expression": "foo.", - "error": "syntax" - }, - { - "expression": ".foo", - "error": "syntax" - }, - { - "expression": "foo..bar", - "error": "syntax" - }, - { - "expression": "foo.bar.", - "error": "syntax" - }, - { - "expression": "foo[.]", - "error": "syntax" - } - ] -}, - { - "comment": "Simple token errors", - "given": {"type": "object"}, - "cases": [ - { - "expression": ".", - "error": "syntax" - }, - { - "expression": ":", - "error": "syntax" - }, - { - "expression": ",", - "error": "syntax" - }, - { - "expression": "]", - "error": "syntax" - }, - { - "expression": "[", - "error": "syntax" - }, - { - "expression": "}", - "error": "syntax" - }, - { - "expression": "{", - "error": "syntax" - }, - { - "expression": ")", - "error": "syntax" - }, - { - "expression": "(", - "error": "syntax" - }, - { - "expression": "((&", - "error": "syntax" - }, - { - "expression": "a[", - "error": "syntax" - }, - { - "expression": "a]", - "error": "syntax" - }, - { - "expression": "a][", - "error": "syntax" - }, - { - "expression": "!", - "error": "syntax" - }, - { - "expression": "@=", - "error": "syntax" - }, - { - "expression": "@``", - "error": "syntax" - } - ] - }, - { - "comment": "Boolean syntax errors", - "given": {"type": "object"}, - "cases": [ - { - "expression": "![!(!", - "error": "syntax" - } - ] - }, - { - "comment": "Paren syntax errors", - "given": {}, - "cases": [ - { - "comment": "missing closing paren", - "expression": "(@", - "error": "syntax" - } - ] - }, - { - "comment": "Function syntax errors", - "given": {}, - "cases": [ - { - "comment": "invalid start of function", - "expression": "@(foo)", - "error": "syntax" - }, - { - "comment": "function names cannot be quoted", - "expression": "\"foo\"(bar)", - "error": "syntax" - } - ] - }, - { - "comment": "Wildcard syntax", - "given": {"type": "object"}, - "cases": [ - { - "expression": "*", - "result": ["object"] - }, - { - "expression": "*.*", - "result": [] - }, - { - "expression": "*.foo", - "result": [] - }, - { - "expression": "*[0]", - "result": [] - }, - { - "expression": ".*", - "error": "syntax" - }, - { - "expression": "*foo", - "error": "syntax" - }, - { - "expression": "*0", - "error": "syntax" - }, - { - "expression": "foo[*]bar", - "error": "syntax" - }, - { - "expression": "foo[*]*", - "error": "syntax" - } - ] - }, - { - "comment": "Flatten syntax", - "given": {"type": "object"}, - "cases": [ - { - "expression": "[]", - "result": null - } - ] - }, - { - "comment": "Simple bracket syntax", - "given": {"type": "object"}, - "cases": [ - { - "expression": "[0]", - "result": null - }, - { - "expression": "[*]", - "result": null - }, - { - "expression": "*.[0]", - "error": "syntax" - }, - { - "expression": "*.[\"0\"]", - "result": [[null]] - }, - { - "expression": "[*].bar", - "result": null - }, - { - "expression": "[*][0]", - "result": null - }, - { - "expression": "foo[#]", - "error": "syntax" - }, - { - "comment": "missing rbracket for led wildcard index", - "expression": "led[*", - "error": "syntax" - } - ] - }, - { - "comment": "slice syntax", - "given": {}, - "cases": [ - { - "comment": "slice expected colon or rbracket", - "expression": "[:@]", - "error": "syntax" - }, - { - "comment": "slice has too many colons", - "expression": "[:::]", - "error": "syntax" - }, - { - "comment": "slice expected number", - "expression": "[:@:]", - "error": "syntax" - }, - { - "comment": "slice expected number of colon", - "expression": "[:1@]", - "error": "syntax" - } - ] - }, - { - "comment": "Multi-select list syntax", - "given": {"type": "object"}, - "cases": [ - { - "expression": "foo[0]", - "result": null - }, - { - "comment": "Valid multi-select of a list", - "expression": "foo[0, 1]", - "error": "syntax" - }, - { - "expression": "foo.[0]", - "error": "syntax" - }, - { - "expression": "foo.[*]", - "result": null - }, - { - "comment": "Multi-select of a list with trailing comma", - "expression": "foo[0, ]", - "error": "syntax" - }, - { - "comment": "Multi-select of a list with trailing comma and no close", - "expression": "foo[0,", - "error": "syntax" - }, - { - "comment": "Multi-select of a list with trailing comma and no close", - "expression": "foo.[a", - "error": "syntax" - }, - { - "comment": "Multi-select of a list with extra comma", - "expression": "foo[0,, 1]", - "error": "syntax" - }, - { - "comment": "Multi-select of a list using an identifier index", - "expression": "foo[abc]", - "error": "syntax" - }, - { - "comment": "Multi-select of a list using identifier indices", - "expression": "foo[abc, def]", - "error": "syntax" - }, - { - "comment": "Multi-select of a list using an identifier index", - "expression": "foo[abc, 1]", - "error": "syntax" - }, - { - "comment": "Multi-select of a list using an identifier index with trailing comma", - "expression": "foo[abc, ]", - "error": "syntax" - }, - { - "comment": "Valid multi-select of a hash using an identifier index", - "expression": "foo.[abc]", - "result": null - }, - { - "comment": "Valid multi-select of a hash", - "expression": "foo.[abc, def]", - "result": null - }, - { - "comment": "Multi-select of a hash using a numeric index", - "expression": "foo.[abc, 1]", - "error": "syntax" - }, - { - "comment": "Multi-select of a hash with a trailing comma", - "expression": "foo.[abc, ]", - "error": "syntax" - }, - { - "comment": "Multi-select of a hash with extra commas", - "expression": "foo.[abc,, def]", - "error": "syntax" - }, - { - "comment": "Multi-select of a hash using number indices", - "expression": "foo.[0, 1]", - "error": "syntax" - } - ] - }, - { - "comment": "Multi-select hash syntax", - "given": {"type": "object"}, - "cases": [ - { - "comment": "No key or value", - "expression": "a{}", - "error": "syntax" - }, - { - "comment": "No closing token", - "expression": "a{", - "error": "syntax" - }, - { - "comment": "Not a key value pair", - "expression": "a{foo}", - "error": "syntax" - }, - { - "comment": "Missing value and closing character", - "expression": "a{foo:", - "error": "syntax" - }, - { - "comment": "Missing closing character", - "expression": "a{foo: 0", - "error": "syntax" - }, - { - "comment": "Missing value", - "expression": "a{foo:}", - "error": "syntax" - }, - { - "comment": "Trailing comma and no closing character", - "expression": "a{foo: 0, ", - "error": "syntax" - }, - { - "comment": "Missing value with trailing comma", - "expression": "a{foo: ,}", - "error": "syntax" - }, - { - "comment": "Accessing Array using an identifier", - "expression": "a{foo: bar}", - "error": "syntax" - }, - { - "expression": "a{foo: 0}", - "error": "syntax" - }, - { - "comment": "Missing key-value pair", - "expression": "a.{}", - "error": "syntax" - }, - { - "comment": "Not a key-value pair", - "expression": "a.{foo}", - "error": "syntax" - }, - { - "comment": "Valid multi-select hash extraction", - "expression": "a.{foo: bar}", - "result": null - }, - { - "comment": "Valid multi-select hash extraction", - "expression": "a.{foo: bar, baz: bam}", - "result": null - }, - { - "comment": "Trailing comma", - "expression": "a.{foo: bar, }", - "error": "syntax" - }, - { - "comment": "Missing key in second key-value pair", - "expression": "a.{foo: bar, baz}", - "error": "syntax" - }, - { - "comment": "Missing value in second key-value pair", - "expression": "a.{foo: bar, baz:}", - "error": "syntax" - }, - { - "comment": "Trailing comma", - "expression": "a.{foo: bar, baz: bam, }", - "error": "syntax" - }, - { - "comment": "Nested multi select", - "expression": "{\"\\\\\":{\" \":*}}", - "result": {"\\": {" ": ["object"]}} - }, - { - "comment": "Missing closing } after a valid nud", - "expression": "{a: @", - "error": "syntax" - } - ] - }, - { - "comment": "Or expressions", - "given": {"type": "object"}, - "cases": [ - { - "expression": "foo || bar", - "result": null - }, - { - "expression": "foo ||", - "error": "syntax" - }, - { - "expression": "foo.|| bar", - "error": "syntax" - }, - { - "expression": " || foo", - "error": "syntax" - }, - { - "expression": "foo || || foo", - "error": "syntax" - }, - { - "expression": "foo.[a || b]", - "result": null - }, - { - "expression": "foo.[a ||]", - "error": "syntax" - }, - { - "expression": "\"foo", - "error": "syntax" - } - ] - }, - { - "comment": "Filter expressions", - "given": {"type": "object"}, - "cases": [ - { - "expression": "foo[?bar==`\"baz\"`]", - "result": null - }, - { - "expression": "foo[? bar == `\"baz\"` ]", - "result": null - }, - { - "expression": "foo[ ?bar==`\"baz\"`]", - "error": "syntax" - }, - { - "expression": "foo[?bar==]", - "error": "syntax" - }, - { - "expression": "foo[?==]", - "error": "syntax" - }, - { - "expression": "foo[?==bar]", - "error": "syntax" - }, - { - "expression": "foo[?bar==baz?]", - "error": "syntax" - }, - { - "expression": "foo[?a.b.c==d.e.f]", - "result": null - }, - { - "expression": "foo[?bar==`[0, 1, 2]`]", - "result": null - }, - { - "expression": "foo[?bar==`[\"a\", \"b\", \"c\"]`]", - "result": null - }, - { - "comment": "Literal char not escaped", - "expression": "foo[?bar==`[\"foo`bar\"]`]", - "error": "syntax" - }, - { - "comment": "Literal char escaped", - "expression": "foo[?bar==`[\"foo\\`bar\"]`]", - "result": null - }, - { - "comment": "Unknown comparator", - "expression": "foo[?bar<>baz]", - "error": "syntax" - }, - { - "comment": "Unknown comparator", - "expression": "foo[?bar^baz]", - "error": "syntax" - }, - { - "expression": "foo[bar==baz]", - "error": "syntax" - }, - { - "comment": "Quoted identifier in filter expression no spaces", - "expression": "[?\"\\\\\">`\"foo\"`]", - "result": null - }, - { - "comment": "Quoted identifier in filter expression with spaces", - "expression": "[?\"\\\\\" > `\"foo\"`]", - "result": null - } - ] - }, - { - "comment": "Filter expression errors", - "given": {"type": "object"}, - "cases": [ - { - "expression": "bar.`\"anything\"`", - "error": "syntax" - }, - { - "expression": "bar.baz.noexists.`\"literal\"`", - "error": "syntax" - }, - { - "comment": "Literal wildcard projection", - "expression": "foo[*].`\"literal\"`", - "error": "syntax" - }, - { - "expression": "foo[*].name.`\"literal\"`", - "error": "syntax" - }, - { - "expression": "foo[].name.`\"literal\"`", - "error": "syntax" - }, - { - "expression": "foo[].name.`\"literal\"`.`\"subliteral\"`", - "error": "syntax" - }, - { - "comment": "Projecting a literal onto an empty list", - "expression": "foo[*].name.noexist.`\"literal\"`", - "error": "syntax" - }, - { - "expression": "foo[].name.noexist.`\"literal\"`", - "error": "syntax" - }, - { - "expression": "twolen[*].`\"foo\"`", - "error": "syntax" - }, - { - "comment": "Two level projection of a literal", - "expression": "twolen[*].threelen[*].`\"bar\"`", - "error": "syntax" - }, - { - "comment": "Two level flattened projection of a literal", - "expression": "twolen[].threelen[].`\"bar\"`", - "error": "syntax" - }, - { - "comment": "expects closing ]", - "expression": "foo[? @ | @", - "error": "syntax" - } - ] - }, - { - "comment": "Identifiers", - "given": {"type": "object"}, - "cases": [ - { - "expression": "foo", - "result": null - }, - { - "expression": "\"foo\"", - "result": null - }, - { - "expression": "\"\\\\\"", - "result": null - }, - { - "expression": "\"\\u\"", - "error": "syntax" - } - ] - }, - { - "comment": "Combined syntax", - "given": [], - "cases": [ - { - "expression": "*||*|*|*", - "result": null - }, - { - "expression": "*[]||[*]", - "result": [] - }, - { - "expression": "[*.*]", - "result": [null] - } - ] - } -] diff --git a/grammar/syntax.yml b/grammar/syntax.yml new file mode 100644 index 0000000..c0f4e14 --- /dev/null +++ b/grammar/syntax.yml @@ -0,0 +1,593 @@ +test0: + context: &data + type: object + query: foo.bar + returns: +test1: + context: *data + query: foo.1 + error: syntax +test2: + context: *data + query: foo.-11 + error: syntax +test3: + context: *data + query: foo. + error: syntax +test4: + context: *data + query: .foo + error: syntax +test5: + context: *data + query: foo..bar + error: syntax +test6: + context: *data + query: foo.bar. + error: syntax +test7: + context: *data + query: foo[.] + error: syntax +test8: + context: &data + type: object + query: . + error: syntax +test9: + context: *data + query: ':' + error: syntax +test10: + context: *data + query: ',' + error: syntax +test11: + context: *data + query: ']' + error: syntax +test12: + context: *data + query: '[' + error: syntax +test13: + context: *data + query: '}' + error: syntax +test14: + context: *data + query: '{' + error: syntax +test15: + context: *data + query: ) + error: syntax +test16: + context: *data + query: ( + error: syntax +test17: + context: *data + query: ((& + error: syntax +test18: + context: *data + query: a[ + error: syntax +test19: + context: *data + query: a] + error: syntax +test20: + context: *data + query: a][ + error: syntax +test21: + context: *data + query: '!' + error: syntax +test22: + context: *data + query: '@=' + error: syntax +test23: + context: *data + query: '@``' + error: syntax +test24: + context: + type: object + query: '![!(!' + error: syntax +test25: + context: {} + query: (@ + error: syntax + comment: missing closing paren +test26: + context: &data {} + query: '@(foo)' + error: syntax + comment: invalid start of function +test27: + context: *data + query: '"foo"(bar)' + error: syntax + comment: function names cannot be quoted +test28: + context: &data + type: object + query: '*' + returns: [object] +test29: + context: *data + query: '*.*' + returns: [] +test30: + context: *data + query: '*.foo' + returns: [] +test31: + context: *data + query: '*[0]' + returns: [] +test32: + context: *data + query: .* + error: syntax +test33: + context: *data + query: '*foo' + error: syntax +test34: + context: *data + query: '*0' + error: syntax +test35: + context: *data + query: foo[*]bar + error: syntax +test36: + context: *data + query: foo[*]* + error: syntax +test37: + context: + type: object + query: '[]' + returns: +test38: + context: &data + type: object + query: '[0]' + returns: +test39: + context: *data + query: '[*]' + returns: +test40: + context: *data + query: '*.[0]' + error: syntax +test41: + context: *data + query: '*.["0"]' + returns: [[!!null '']] +test42: + context: *data + query: '[*].bar' + returns: +test43: + context: *data + query: '[*][0]' + returns: +test44: + context: *data + query: foo[#] + error: syntax +test45: + context: *data + query: led[* + error: syntax + comment: missing rbracket for led wildcard index +test46: + context: &data {} + query: '[:@]' + error: syntax + comment: slice expected colon or rbracket +test47: + context: *data + query: '[:::]' + error: syntax + comment: slice has too many colons +test48: + context: *data + query: '[:@:]' + error: syntax + comment: slice expected number +test49: + context: *data + query: '[:1@]' + error: syntax + comment: slice expected number of colon +test50: + context: &data + type: object + query: foo[0] + returns: +test51: + context: *data + query: foo[0, 1] + error: syntax + comment: Valid multi-select of a list +test52: + context: *data + query: foo.[0] + error: syntax +test53: + context: *data + query: foo.[*] + returns: +test54: + context: *data + query: foo[0, ] + error: syntax + comment: Multi-select of a list with trailing comma +test55: + context: *data + query: foo[0, + error: syntax + comment: Multi-select of a list with trailing comma and no close +test56: + context: *data + query: foo.[a + error: syntax + comment: Multi-select of a list with trailing comma and no close +test57: + context: *data + query: foo[0,, 1] + error: syntax + comment: Multi-select of a list with extra comma +test58: + context: *data + query: foo[abc] + error: syntax + comment: Multi-select of a list using an identifier index +test59: + context: *data + query: foo[abc, def] + error: syntax + comment: Multi-select of a list using identifier indices +test60: + context: *data + query: foo[abc, 1] + error: syntax + comment: Multi-select of a list using an identifier index +test61: + context: *data + query: foo[abc, ] + error: syntax + comment: Multi-select of a list using an identifier index with trailing comma +test62: + context: *data + query: foo.[abc] + returns: + comment: Valid multi-select of a hash using an identifier index +test63: + context: *data + query: foo.[abc, def] + returns: + comment: Valid multi-select of a hash +test64: + context: *data + query: foo.[abc, 1] + error: syntax + comment: Multi-select of a hash using a numeric index +test65: + context: *data + query: foo.[abc, ] + error: syntax + comment: Multi-select of a hash with a trailing comma +test66: + context: *data + query: foo.[abc,, def] + error: syntax + comment: Multi-select of a hash with extra commas +test67: + context: *data + query: foo.[0, 1] + error: syntax + comment: Multi-select of a hash using number indices +test68: + context: &data + type: object + query: a{} + error: syntax + comment: No key or value +test69: + context: *data + query: a{ + error: syntax + comment: No closing token +test70: + context: *data + query: a{foo} + error: syntax + comment: Not a key value pair +test71: + context: *data + query: 'a{foo:' + error: syntax + comment: Missing value and closing character +test72: + context: *data + query: 'a{foo: 0' + error: syntax + comment: Missing closing character +test73: + context: *data + query: a{foo:} + error: syntax + comment: Missing value +test74: + context: *data + query: 'a{foo: 0, ' + error: syntax + comment: Trailing comma and no closing character +test75: + context: *data + query: 'a{foo: ,}' + error: syntax + comment: Missing value with trailing comma +test76: + context: *data + query: 'a{foo: bar}' + error: syntax + comment: Accessing Array using an identifier +test77: + context: *data + query: 'a{foo: 0}' + error: syntax +test78: + context: *data + query: a.{} + error: syntax + comment: Missing key-value pair +test79: + context: *data + query: a.{foo} + error: syntax + comment: Not a key-value pair +test80: + context: *data + query: 'a.{foo: bar}' + returns: + comment: Valid multi-select hash extraction +test81: + context: *data + query: 'a.{foo: bar, baz: bam}' + returns: + comment: Valid multi-select hash extraction +test82: + context: *data + query: 'a.{foo: bar, }' + error: syntax + comment: Trailing comma +test83: + context: *data + query: 'a.{foo: bar, baz}' + error: syntax + comment: Missing key in second key-value pair +test84: + context: *data + query: 'a.{foo: bar, baz:}' + error: syntax + comment: Missing value in second key-value pair +test85: + context: *data + query: 'a.{foo: bar, baz: bam, }' + error: syntax + comment: Trailing comma +test86: + context: *data + query: '{"\\":{" ":*}}' + returns: {\: {' ': [object]}} + comment: Nested multi select +test87: + context: *data + query: '{a: @' + error: syntax + comment: Missing closing } after a valid nud +test88: + context: &data + type: object + query: foo || bar + returns: +test89: + context: *data + query: foo || + error: syntax +test90: + context: *data + query: foo.|| bar + error: syntax +test91: + context: *data + query: ' || foo' + error: syntax +test92: + context: *data + query: foo || || foo + error: syntax +test93: + context: *data + query: foo.[a || b] + returns: +test94: + context: *data + query: foo.[a ||] + error: syntax +test95: + context: *data + query: '"foo' + error: syntax +test96: + context: &data + type: object + query: foo[?bar==`"baz"`] + returns: +test97: + context: *data + query: foo[? bar == `"baz"` ] + returns: +test98: + context: *data + query: foo[ ?bar==`"baz"`] + error: syntax +test99: + context: *data + query: foo[?bar==] + error: syntax +test100: + context: *data + query: foo[?==] + error: syntax +test101: + context: *data + query: foo[?==bar] + error: syntax +test102: + context: *data + query: foo[?bar==baz?] + error: syntax +test103: + context: *data + query: foo[?a.b.c==d.e.f] + returns: +test104: + context: *data + query: foo[?bar==`[0, 1, 2]`] + returns: +test105: + context: *data + query: foo[?bar==`["a", "b", "c"]`] + returns: +test106: + context: *data + query: foo[?bar==`["foo`bar"]`] + error: syntax + comment: Literal char not escaped +test107: + context: *data + query: foo[?bar==`["foo\`bar"]`] + returns: + comment: Literal char escaped +test108: + context: *data + query: foo[?bar<>baz] + error: syntax + comment: Unknown comparator +test109: + context: *data + query: foo[?bar^baz] + error: syntax + comment: Unknown comparator +test110: + context: *data + query: foo[bar==baz] + error: syntax +test111: + context: *data + query: '[?"\\">`"foo"`]' + returns: + comment: Quoted identifier in filter expression no spaces +test112: + context: *data + query: '[?"\\" > `"foo"`]' + returns: + comment: Quoted identifier in filter expression with spaces +test113: + context: &data + type: object + query: bar.`"anything"` + error: syntax +test114: + context: *data + query: bar.baz.noexists.`"literal"` + error: syntax +test115: + context: *data + query: foo[*].`"literal"` + error: syntax + comment: Literal wildcard projection +test116: + context: *data + query: foo[*].name.`"literal"` + error: syntax +test117: + context: *data + query: foo[].name.`"literal"` + error: syntax +test118: + context: *data + query: foo[].name.`"literal"`.`"subliteral"` + error: syntax +test119: + context: *data + query: foo[*].name.noexist.`"literal"` + error: syntax + comment: Projecting a literal onto an empty list +test120: + context: *data + query: foo[].name.noexist.`"literal"` + error: syntax +test121: + context: *data + query: twolen[*].`"foo"` + error: syntax +test122: + context: *data + query: twolen[*].threelen[*].`"bar"` + error: syntax + comment: Two level projection of a literal +test123: + context: *data + query: twolen[].threelen[].`"bar"` + error: syntax + comment: Two level flattened projection of a literal +test124: + context: *data + query: foo[? @ | @ + error: syntax + comment: expects closing ] +test125: + context: &data + type: object + query: foo + returns: +test126: + context: *data + query: '"foo"' + returns: +test127: + context: *data + query: '"\\"' + returns: +test128: + context: *data + query: '"\u"' + error: syntax +test129: + context: &data [] + query: '*||*|*|*' + returns: +test130: + context: *data + query: '*[]||[*]' + returns: [] +test131: + context: *data + query: '[*.*]' + returns: [!!null ''] diff --git a/grammar/unicode.json b/grammar/unicode.json deleted file mode 100644 index 6b07b0b..0000000 --- a/grammar/unicode.json +++ /dev/null @@ -1,38 +0,0 @@ -[ - { - "given": {"foo": [{"✓": "✓"}, {"✓": "✗"}]}, - "cases": [ - { - "expression": "foo[].\"✓\"", - "result": ["✓", "✗"] - } - ] - }, - { - "given": {"☯": true}, - "cases": [ - { - "expression": "\"☯\"", - "result": true - } - ] - }, - { - "given": {"♪♫•*¨*•.¸¸❤¸¸.•*¨*•♫♪": true}, - "cases": [ - { - "expression": "\"♪♫•*¨*•.¸¸❤¸¸.•*¨*•♫♪\"", - "result": true - } - ] - }, - { - "given": {"☃": true}, - "cases": [ - { - "expression": "\"☃\"", - "result": true - } - ] - } -] diff --git a/grammar/unicode.yml b/grammar/unicode.yml new file mode 100644 index 0000000..96b07e4 --- /dev/null +++ b/grammar/unicode.yml @@ -0,0 +1,21 @@ +test0: + context: + foo: ["\u2713": "\u2713", "\u2713": "\u2717"] + query: "foo[].\"\u2713\"" + returns: ["\u2713", "\u2717"] +test1: + context: + "\u262F": true + query: "\"\u262F\"" + returns: true +test2: + context: + "\u266A\u266B\u2022*\xA8*\u2022.\xB8\xB8\u2764\xB8\xB8.\u2022*\xA8*\u2022\u266B\u266A": true + query: "\"\u266A\u266B\u2022*\xA8*\u2022.\xB8\xB8\u2764\xB8\xB8.\u2022*\xA8*\u2022\ + \u266B\u266A\"" + returns: true +test3: + context: + "\u2603": true + query: "\"\u2603\"" + returns: true diff --git a/grammar/wildcard.json b/grammar/wildcard.json deleted file mode 100644 index 3bcec30..0000000 --- a/grammar/wildcard.json +++ /dev/null @@ -1,460 +0,0 @@ -[{ - "given": { - "foo": { - "bar": { - "baz": "val" - }, - "other": { - "baz": "val" - }, - "other2": { - "baz": "val" - }, - "other3": { - "notbaz": ["a", "b", "c"] - }, - "other4": { - "notbaz": ["a", "b", "c"] - }, - "other5": { - "other": { - "a": 1, - "b": 1, - "c": 1 - } - } - } - }, - "cases": [ - { - "expression": "foo.*.baz", - "result": ["val", "val", "val"] - }, - { - "expression": "foo.bar.*", - "result": ["val"] - }, - { - "expression": "foo.*.notbaz", - "result": [["a", "b", "c"], ["a", "b", "c"]] - }, - { - "expression": "foo.*.notbaz[0]", - "result": ["a", "a"] - }, - { - "expression": "foo.*.notbaz[-1]", - "result": ["c", "c"] - } - ] -}, { - "given": { - "foo": { - "first-1": { - "second-1": "val" - }, - "first-2": { - "second-1": "val" - }, - "first-3": { - "second-1": "val" - } - } - }, - "cases": [ - { - "expression": "foo.*", - "result": [{"second-1": "val"}, {"second-1": "val"}, - {"second-1": "val"}] - }, - { - "expression": "foo.*.*", - "result": [["val"], ["val"], ["val"]] - }, - { - "expression": "foo.*.*.*", - "result": [[], [], []] - }, - { - "expression": "foo.*.*.*.*", - "result": [[], [], []] - } - ] -}, { - "given": { - "foo": { - "bar": "one" - }, - "other": { - "bar": "one" - }, - "nomatch": { - "notbar": "three" - } - }, - "cases": [ - { - "expression": "*.bar", - "result": ["one", "one"] - } - ] -}, { - "given": { - "top1": { - "sub1": {"foo": "one"} - }, - "top2": { - "sub1": {"foo": "one"} - } - }, - "cases": [ - { - "expression": "*", - "result": [{"sub1": {"foo": "one"}}, - {"sub1": {"foo": "one"}}] - }, - { - "expression": "*.sub1", - "result": [{"foo": "one"}, - {"foo": "one"}] - }, - { - "expression": "*.*", - "result": [[{"foo": "one"}], - [{"foo": "one"}]] - }, - { - "expression": "*.*.foo[]", - "result": ["one", "one"] - }, - { - "expression": "*.sub1.foo", - "result": ["one", "one"] - } - ] -}, -{ - "given": - {"foo": [{"bar": "one"}, {"bar": "two"}, {"bar": "three"}, {"notbar": "four"}]}, - "cases": [ - { - "expression": "foo[*].bar", - "result": ["one", "two", "three"] - }, - { - "expression": "foo[*].notbar", - "result": ["four"] - } - ] -}, -{ - "given": - [{"bar": "one"}, {"bar": "two"}, {"bar": "three"}, {"notbar": "four"}], - "cases": [ - { - "expression": "[*]", - "result": [{"bar": "one"}, {"bar": "two"}, {"bar": "three"}, {"notbar": "four"}] - }, - { - "expression": "[*].bar", - "result": ["one", "two", "three"] - }, - { - "expression": "[*].notbar", - "result": ["four"] - } - ] -}, -{ - "given": { - "foo": { - "bar": [ - {"baz": ["one", "two", "three"]}, - {"baz": ["four", "five", "six"]}, - {"baz": ["seven", "eight", "nine"]} - ] - } - }, - "cases": [ - { - "expression": "foo.bar[*].baz", - "result": [["one", "two", "three"], ["four", "five", "six"], ["seven", "eight", "nine"]] - }, - { - "expression": "foo.bar[*].baz[0]", - "result": ["one", "four", "seven"] - }, - { - "expression": "foo.bar[*].baz[1]", - "result": ["two", "five", "eight"] - }, - { - "expression": "foo.bar[*].baz[2]", - "result": ["three", "six", "nine"] - }, - { - "expression": "foo.bar[*].baz[3]", - "result": [] - } - ] -}, -{ - "given": { - "foo": { - "bar": [["one", "two"], ["three", "four"]] - } - }, - "cases": [ - { - "expression": "foo.bar[*]", - "result": [["one", "two"], ["three", "four"]] - }, - { - "expression": "foo.bar[0]", - "result": ["one", "two"] - }, - { - "expression": "foo.bar[0][0]", - "result": "one" - }, - { - "expression": "foo.bar[0][0][0]", - "result": null - }, - { - "expression": "foo.bar[0][0][0][0]", - "result": null - }, - { - "expression": "foo[0][0]", - "result": null - } - ] -}, -{ - "given": { - "foo": [ - {"bar": [{"kind": "basic"}, {"kind": "intermediate"}]}, - {"bar": [{"kind": "advanced"}, {"kind": "expert"}]}, - {"bar": "string"} - ] - - }, - "cases": [ - { - "expression": "foo[*].bar[*].kind", - "result": [["basic", "intermediate"], ["advanced", "expert"]] - }, - { - "expression": "foo[*].bar[0].kind", - "result": ["basic", "advanced"] - } - ] -}, -{ - "given": { - "foo": [ - {"bar": {"kind": "basic"}}, - {"bar": {"kind": "intermediate"}}, - {"bar": {"kind": "advanced"}}, - {"bar": {"kind": "expert"}}, - {"bar": "string"} - ] - }, - "cases": [ - { - "expression": "foo[*].bar.kind", - "result": ["basic", "intermediate", "advanced", "expert"] - } - ] -}, -{ - "given": { - "foo": [{"bar": ["one", "two"]}, {"bar": ["three", "four"]}, {"bar": ["five"]}] - }, - "cases": [ - { - "expression": "foo[*].bar[0]", - "result": ["one", "three", "five"] - }, - { - "expression": "foo[*].bar[1]", - "result": ["two", "four"] - }, - { - "expression": "foo[*].bar[2]", - "result": [] - } - ] -}, -{ - "given": { - "foo": [{"bar": []}, {"bar": []}, {"bar": []}] - }, - "cases": [ - { - "expression": "foo[*].bar[0]", - "result": [] - } - ] -}, -{ - "given": { - "foo": [["one", "two"], ["three", "four"], ["five"]] - }, - "cases": [ - { - "expression": "foo[*][0]", - "result": ["one", "three", "five"] - }, - { - "expression": "foo[*][1]", - "result": ["two", "four"] - } - ] -}, -{ - "given": { - "foo": [ - [ - ["one", "two"], ["three", "four"] - ], [ - ["five", "six"], ["seven", "eight"] - ], [ - ["nine"], ["ten"] - ] - ] - }, - "cases": [ - { - "expression": "foo[*][0]", - "result": [["one", "two"], ["five", "six"], ["nine"]] - }, - { - "expression": "foo[*][1]", - "result": [["three", "four"], ["seven", "eight"], ["ten"]] - }, - { - "expression": "foo[*][0][0]", - "result": ["one", "five", "nine"] - }, - { - "expression": "foo[*][1][0]", - "result": ["three", "seven", "ten"] - }, - { - "expression": "foo[*][0][1]", - "result": ["two", "six"] - }, - { - "expression": "foo[*][1][1]", - "result": ["four", "eight"] - }, - { - "expression": "foo[*][2]", - "result": [] - }, - { - "expression": "foo[*][2][2]", - "result": [] - }, - { - "expression": "bar[*]", - "result": null - }, - { - "expression": "bar[*].baz[*]", - "result": null - } - ] -}, -{ - "given": { - "string": "string", - "hash": {"foo": "bar", "bar": "baz"}, - "number": 23, - "nullvalue": null - }, - "cases": [ - { - "expression": "string[*]", - "result": null - }, - { - "expression": "hash[*]", - "result": null - }, - { - "expression": "number[*]", - "result": null - }, - { - "expression": "nullvalue[*]", - "result": null - }, - { - "expression": "string[*].foo", - "result": null - }, - { - "expression": "hash[*].foo", - "result": null - }, - { - "expression": "number[*].foo", - "result": null - }, - { - "expression": "nullvalue[*].foo", - "result": null - }, - { - "expression": "nullvalue[*].foo[*].bar", - "result": null - } - ] -}, -{ - "given": { - "string": "string", - "hash": {"foo": "val", "bar": "val"}, - "number": 23, - "array": [1, 2, 3], - "nullvalue": null - }, - "cases": [ - { - "expression": "string.*", - "result": null - }, - { - "expression": "hash.*", - "result": ["val", "val"] - }, - { - "expression": "number.*", - "result": null - }, - { - "expression": "array.*", - "result": null - }, - { - "expression": "nullvalue.*", - "result": null - } - ] -}, -{ - "given": { - "a": [0, 1, 2], - "b": [0, 1, 2] - }, - "cases": [ - { - "expression": "*[0]", - "result": [0, 0] - } - ] -} -] diff --git a/grammar/wildcard.yml b/grammar/wildcard.yml new file mode 100644 index 0000000..cb820cf --- /dev/null +++ b/grammar/wildcard.yml @@ -0,0 +1,296 @@ +test0: + context: &data + foo: {bar: {baz: val}, other: {baz: val}, other2: {baz: val}, other3: {notbaz: [ + a, b, c]}, other4: {notbaz: [a, b, c]}, other5: {other: {a: 1, b: 1, c: 1}}} + query: foo.*.baz + returns: [val, val, val] +test1: + context: *data + query: foo.bar.* + returns: [val] +test2: + context: *data + query: foo.*.notbaz + returns: [[a, b, c], [a, b, c]] +test3: + context: *data + query: foo.*.notbaz[0] + returns: [a, a] +test4: + context: *data + query: foo.*.notbaz[-1] + returns: [c, c] +test5: + context: &data + foo: {first-1: {second-1: val}, first-2: {second-1: val}, first-3: {second-1: val}} + query: foo.* + returns: [second-1: val, second-1: val, second-1: val] +test6: + context: *data + query: foo.*.* + returns: [[val], [val], [val]] +test7: + context: *data + query: foo.*.*.* + returns: [[], [], []] +test8: + context: *data + query: foo.*.*.*.* + returns: [[], [], []] +test9: + context: + foo: {bar: one} + other: {bar: one} + nomatch: {notbar: three} + query: '*.bar' + returns: [one, one] +test10: + context: &data + top1: {sub1: {foo: one}} + top2: {sub1: {foo: one}} + query: '*' + returns: [sub1: {foo: one}, sub1: {foo: one}] +test11: + context: *data + query: '*.sub1' + returns: [foo: one, foo: one] +test12: + context: *data + query: '*.*' + returns: [[foo: one], [foo: one]] +test13: + context: *data + query: '*.*.foo[]' + returns: [one, one] +test14: + context: *data + query: '*.sub1.foo' + returns: [one, one] +test15: + context: &data + foo: [bar: one, bar: two, bar: three, notbar: four] + query: foo[*].bar + returns: [one, two, three] +test16: + context: *data + query: foo[*].notbar + returns: [four] +test17: + context: &data + - {bar: one} + - {bar: two} + - {bar: three} + - {notbar: four} + query: '[*]' + returns: [bar: one, bar: two, bar: three, notbar: four] +test18: + context: *data + query: '[*].bar' + returns: [one, two, three] +test19: + context: *data + query: '[*].notbar' + returns: [four] +test20: + context: &data + foo: {bar: [baz: [one, two, three], baz: [four, five, six], baz: [seven, eight, + nine]]} + query: foo.bar[*].baz + returns: [[one, two, three], [four, five, six], [seven, eight, nine]] +test21: + context: *data + query: foo.bar[*].baz[0] + returns: [one, four, seven] +test22: + context: *data + query: foo.bar[*].baz[1] + returns: [two, five, eight] +test23: + context: *data + query: foo.bar[*].baz[2] + returns: [three, six, nine] +test24: + context: *data + query: foo.bar[*].baz[3] + returns: [] +test25: + context: &data + foo: {bar: [[one, two], [three, four]]} + query: foo.bar[*] + returns: [[one, two], [three, four]] +test26: + context: *data + query: foo.bar[0] + returns: [one, two] +test27: + context: *data + query: foo.bar[0][0] + returns: one +test28: + context: *data + query: foo.bar[0][0][0] + returns: +test29: + context: *data + query: foo.bar[0][0][0][0] + returns: +test30: + context: *data + query: foo[0][0] + returns: +test31: + context: &data + foo: [bar: [kind: basic, kind: intermediate], bar: [kind: advanced, kind: expert], + bar: string] + + query: foo[*].bar[*].kind + returns: [[basic, intermediate], [advanced, expert]] +test32: + context: *data + query: foo[*].bar[0].kind + returns: [basic, advanced] +test33: + context: + foo: [bar: {kind: basic}, bar: {kind: intermediate}, bar: {kind: advanced}, bar: { + kind: expert}, bar: string] + query: foo[*].bar.kind + returns: [basic, intermediate, advanced, expert] +test34: + context: &data + foo: [bar: [one, two], bar: [three, four], bar: [five]] + query: foo[*].bar[0] + returns: [one, three, five] +test35: + context: *data + query: foo[*].bar[1] + returns: [two, four] +test36: + context: *data + query: foo[*].bar[2] + returns: [] +test37: + context: + foo: [bar: [], bar: [], bar: []] + query: foo[*].bar[0] + returns: [] +test38: + context: &data + foo: [[one, two], [three, four], [five]] + query: foo[*][0] + returns: [one, three, five] +test39: + context: *data + query: foo[*][1] + returns: [two, four] +test40: + context: &data + foo: [[[one, two], [three, four]], [[five, six], [seven, eight]], [[nine], [ten]]] + query: foo[*][0] + returns: [[one, two], [five, six], [nine]] +test41: + context: *data + query: foo[*][1] + returns: [[three, four], [seven, eight], [ten]] +test42: + context: *data + query: foo[*][0][0] + returns: [one, five, nine] +test43: + context: *data + query: foo[*][1][0] + returns: [three, seven, ten] +test44: + context: *data + query: foo[*][0][1] + returns: [two, six] +test45: + context: *data + query: foo[*][1][1] + returns: [four, eight] +test46: + context: *data + query: foo[*][2] + returns: [] +test47: + context: *data + query: foo[*][2][2] + returns: [] +test48: + context: *data + query: bar[*] + returns: +test49: + context: *data + query: bar[*].baz[*] + returns: +test50: + context: &data + string: string + hash: {foo: bar, bar: baz} + number: 23 + nullvalue: + query: string[*] + returns: +test51: + context: *data + query: hash[*] + returns: +test52: + context: *data + query: number[*] + returns: +test53: + context: *data + query: nullvalue[*] + returns: +test54: + context: *data + query: string[*].foo + returns: +test55: + context: *data + query: hash[*].foo + returns: +test56: + context: *data + query: number[*].foo + returns: +test57: + context: *data + query: nullvalue[*].foo + returns: +test58: + context: *data + query: nullvalue[*].foo[*].bar + returns: +test59: + context: &data + string: string + hash: {foo: val, bar: val} + number: 23 + array: [1, 2, 3] + nullvalue: + query: string.* + returns: +test60: + context: *data + query: hash.* + returns: [val, val] +test61: + context: *data + query: number.* + returns: +test62: + context: *data + query: array.* + returns: +test63: + context: *data + query: nullvalue.* + returns: +test64: + context: + a: [0, 1, 2] + b: [0, 1, 2] + query: '*[0]' + returns: [0, 0] diff --git a/grammar_schema.yml b/grammar_schema.yml new file mode 100644 index 0000000..4eec168 --- /dev/null +++ b/grammar_schema.yml @@ -0,0 +1,32 @@ +type: object +additionalProperties: false +patternProperties: + "^\\w.*": + description: "" + oneOf: + - type: object + required: [ context, query, returns ] + additionalProperties: false + properties: + context: &context + type: &any [ "number","string","boolean","object","array","null" ] + description: "" + query: &query + type: string + description: "" + returns: + type: *any + description: "" + comment: &comment + type: string + description: "" + - type: object + required: [ context, query, error ] + additionalProperties: false + properties: + context: *context + query: *query + error: + type: string + description: "" + comment: *comment diff --git a/test_schema.yml b/test_schema.yml deleted file mode 100644 index af4c345..0000000 --- a/test_schema.yml +++ /dev/null @@ -1,29 +0,0 @@ -type: array -items: - type: object - additionalProperties: false - properties: - given: {} - comment: - type: string - cases: - type: array - items: - type: object - additionalProperties: false - properties: - expression: - type: string - result: {} - error: - enum: [invalid-arity, invalid-type, invalid-value, syntax, unknown-function] - bench: - enum: [parse, interpret, full] - comment: - type: string - required: [expression] - anyOf: - - required: [result] - - required: [error] - - required: [bench] - required: [given, cases]