diff --git a/CHANGELOG.md b/CHANGELOG.md index 22cc84a..8b764ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,10 +5,14 @@ **Fixes** - Fixed handling of JSONPath literals in filter expressions. We now raise a `JSONPathSyntaxError` if a filter expression literal is not part of a comparison, membership or function expression. See [jsonpath-compliance-test-suite#81](https://github.com/jsonpath-standard/jsonpath-compliance-test-suite/pull/81). +- Fixed parsing of number literals including an exponent. Upper case 'e's are now allowed. +- Fixed handling of trailing commas in bracketed selection lists. We now raise a `JSONPathSyntaxError` in such cases. **Compliance** - Skipped tests for invalid escape sequences. The JSONPath spec is more strict than Python's JSON decoder when it comes to parsing `\u` escape sequences in string literals. We are adopting a policy of least surprise. The assertion is that most people will expect the JSONPath parser to behave the same as Python's JSON parser. See [jsonpath-compliance-test-suite #87](https://github.com/jsonpath-standard/jsonpath-compliance-test-suite/pull/87). +- Skipped tests for invalid integer and float literals. Same as above. We are deliberately choosing to match Python's int and float parsing behavior. See [jsonpath-compliance-test-suite #89](https://github.com/jsonpath-standard/jsonpath-compliance-test-suite/pull/89). +- Skipped tests for incorrect casing `true`, `false` and `null` literals. **Features** diff --git a/docs/syntax.md b/docs/syntax.md index f247f4c..5ed7236 100644 --- a/docs/syntax.md +++ b/docs/syntax.md @@ -208,7 +208,7 @@ And this is a list of areas where we deviate from [RFC 9535](https://datatracker - We don't require property names to be quoted inside a bracketed selection, unless the name contains reserved characters. - We don't require the recursive descent segment to have a selector. `$..` is equivalent to `$..*`. - We support explicit comparisons to `undefined` as well as implicit existence tests. -- Float literals without a fractional digit are OK. `1.` is equivalent to `1.0`. +- Float literals without a fractional digit are OK or leading digit. `1.` is equivalent to `1.0`. - We treat literals (such as `true` and `false`) as valid "basic" expressions. For example, `$[?true || false]`, without an existence test or comparison either side of logical _or_, does not raise a syntax error. - By default, `and` is equivalent to `&&` and `or` is equivalent to `||`. - `none` and `nil` are aliases for `null`. diff --git a/jsonpath/lex.py b/jsonpath/lex.py index 9866880..0399631 100644 --- a/jsonpath/lex.py +++ b/jsonpath/lex.py @@ -1,4 +1,5 @@ """JSONPath tokenization.""" + from __future__ import annotations import re @@ -138,8 +139,8 @@ def compile_rules(self) -> Pattern[str]: (TOKEN_LIST_SLICE, self.slice_list_pattern), (TOKEN_FUNCTION, self.function_pattern), (TOKEN_DOT_PROPERTY, self.dot_property_pattern), - (TOKEN_FLOAT, r"-?\d+\.\d*(?:e[+-]?\d+)?"), - (TOKEN_INT, r"-?\d+(?Pe[+\-]?\d+)?\b"), + (TOKEN_FLOAT, r"-?\d+\.\d*(?:[eE][+-]?\d+)?"), + (TOKEN_INT, r"-?\d+(?P[eE][+\-]?\d+)?\b"), (TOKEN_DDOT, r"\.\."), (TOKEN_AND, self.logical_and_pattern), (TOKEN_OR, self.logical_or_pattern), diff --git a/jsonpath/parse.py b/jsonpath/parse.py index c055d56..eaef7fc 100644 --- a/jsonpath/parse.py +++ b/jsonpath/parse.py @@ -474,6 +474,12 @@ def parse_selector_list(self, stream: TokenStream) -> ListSelector: # noqa: PLR stream.expect_peek(TOKEN_COMMA) stream.next_token() + if stream.peek.kind == TOKEN_RBRACKET: + raise JSONPathSyntaxError( + "unexpected trailing comma", + token=stream.peek, + ) + stream.next_token() if not list_items: diff --git a/tests/cts b/tests/cts index b11c029..733fe52 160000 --- a/tests/cts +++ b/tests/cts @@ -1 +1 @@ -Subproject commit b11c0290808bd7021bd0b018cbb42fa1cc60bc7c +Subproject commit 733fe526ceccb183ed0e3397340f2ee9feec0d67 diff --git a/tests/test_compliance.py b/tests/test_compliance.py index dcdca5c..613f4a2 100644 --- a/tests/test_compliance.py +++ b/tests/test_compliance.py @@ -8,6 +8,7 @@ import json import operator from dataclasses import dataclass +from dataclasses import field from typing import Any from typing import List from typing import Mapping @@ -28,6 +29,7 @@ class Case: result: Any = None results: Optional[List[Any]] = None invalid_selector: Optional[bool] = None + tags: List[str] = field(default_factory=list) SKIP = { @@ -43,12 +45,35 @@ class Case: "functions, match, filter, match function, unicode char class negated, uppercase": "\\P not supported", # noqa: E501 "functions, search, filter, search function, unicode char class, uppercase": "\\p not supported", # noqa: E501 "functions, search, filter, search function, unicode char class negated, uppercase": "\\P not supported", # noqa: E501 - "filter, equals number, decimal fraction, no fractional digit": "TODO", + "filter, equals number, decimal fraction, no fractional digit": "expected behavior policy", # noqa: E501 + "filter, equals number, decimal fraction, no int digit": "expected behavior policy", + "filter, equals number, invalid no int digit": "expected behavior policy", + "filter, equals number, invalid 00": "expected behavior policy", + "filter, equals number, invalid leading 0": "expected behavior policy", + "filter, equals number, invalid no fractional digit": "expected behavior policy", + "filter, equals number, invalid no fractional digit e": "expected behavior policy", + "slice selector, start, leading 0": "expected behavior policy", + "slice selector, start, -0": "expected behavior policy", + "slice selector, start, leading -0": "expected behavior policy", + "slice selector, end, leading 0": "expected behavior policy", + "slice selector, end, minus space": "expected behavior policy", + "slice selector, end, -0": "expected behavior policy", + "slice selector, end, leading -0": "expected behavior policy", + "slice selector, step, leading 0": "expected behavior policy", + "slice selector, step, minus space": "expected behavior policy", + "slice selector, step, -0": "expected behavior policy", + "slice selector, step, leading -0": "expected behavior policy", + "filter, true, incorrectly capitalized": "flexible literal policy", + "filter, false, incorrectly capitalized": "flexible literal policy", + "filter, null, incorrectly capitalized": "flexible literal policy", "name selector, double quotes, single high surrogate": "expected behavior policy", "name selector, double quotes, single low surrogate": "expected behavior policy", "name selector, double quotes, high high surrogate": "expected behavior policy", "name selector, double quotes, low low surrogate": "expected behavior policy", "name selector, double quotes, surrogate non-surrogate": "expected behavior policy", + "name selector, double quotes, non-surrogate surrogate": "expected behavior policy", + "name selector, double quotes, surrogate supplementary": "expected behavior policy", + "name selector, double quotes, supplementary surrogate": "expected behavior policy", "whitespace, selectors, space between dot and name": "flexible whitespace policy", # noqa: E501 "whitespace, selectors, newline between dot and name": "flexible whitespace policy", # noqa: E501 "whitespace, selectors, tab between dot and name": "flexible whitespace policy", # noqa: E501