Skip to content

Commit a44ec9e

Browse files
authored
Customize logical operators by subclassing Lexer. (#40)
1 parent 6691822 commit a44ec9e

File tree

4 files changed

+52
-16
lines changed

4 files changed

+52
-16
lines changed

docs/advanced.md

+20-1
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,26 @@ This table shows all available identifier token attributes.
193193

194194
### Logical Operator Tokens
195195

196-
TODO:
196+
By default, we accept both Python and C-style logical operators in filter expressions. That is, `not` and `!` are equivalent, `and` and `&&` are equivalent and `or` and `||` are equivalent. You can change this using class attributes on a [`Lexer`](custom_api.md#jsonpath.lex.Lexer) subclass and setting the `lexer_class` attribute on a `JSONPathEnvironment`.
197+
198+
This example changes all three logical operators to strictly match the JSONPath spec.
199+
200+
```python
201+
from jsonpath import JSONPathEnvironment
202+
from jsonpath import Lexer
203+
204+
class MyLexer(Lexer):
205+
logical_not_pattern = r"!"
206+
logical_and_pattern = r"&&"
207+
logical_or_pattern = r"\|\|"
208+
209+
class MyJSONPathEnvironment(JSONPathEnvironment):
210+
lexer_class = MyLexer
211+
212+
env = MyJSONPathEnvironment()
213+
env.compile("$.foo[[email protected] > 0 && @.b < 100]") # OK
214+
env.compile("$.foo[[email protected] > 0 and @.b < 100]") # JSONPathSyntaxError
215+
```
197216

198217
### Keys Selector
199218

docs/custom_api.md

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
# Low Level API Reference
22

33
::: jsonpath.token.Token
4+
45
handler: python
56

67
::: jsonpath.filter.FilterExpression
7-
handler: python
88

9-
## jsonpath.lex.Lexer
9+
handler: python
1010

11-
TODO:
11+
::: jsonpath.lex.Lexer
12+
handler: python
1213

1314
## jsonpath.parse.Parser
1415

jsonpath/lex.py

+27-12
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,34 @@
6666

6767

6868
class Lexer:
69-
"""Tokenize a JSONPath string."""
69+
"""Tokenize a JSONPath string.
70+
71+
Some customization can be achieved by subclassing _Lexer_ and setting
72+
class attributes. Then setting `lexer_class` on a `JSONPathEnvironment`.
73+
74+
Attributes:
75+
key_pattern: The regular expression pattern used to match mapping
76+
keys/properties.
77+
logical_not_pattern: The regular expression pattern used to match
78+
logical negation tokens. By default, `not` and `!` are
79+
equivalent.
80+
logical_and_pattern: The regular expression pattern used to match
81+
logical _and_ tokens. By default, `and` and `&&` are equivalent.
82+
logical_or_pattern: The regular expression pattern used to match
83+
logical _or_ tokens. By default, `or` and `||` are equivalent.
84+
"""
7085

7186
key_pattern = r"[\u0080-\uFFFFa-zA-Z_][\u0080-\uFFFFa-zA-Z0-9_-]*"
7287

88+
# `not` or !
89+
logical_not_pattern = r"(?:not|!)"
90+
91+
# && or `and`
92+
logical_and_pattern = r"(?:&&|and)"
93+
94+
# || or `or`
95+
logical_or_pattern = r"(?:\|\||or)"
96+
7397
def __init__(self, *, env: JSONPathEnvironment) -> None:
7498
self.env = env
7599

@@ -85,15 +109,6 @@ def __init__(self, *, env: JSONPathEnvironment) -> None:
85109
r"(?::\s*(?P<G_LSLICE_STEP>\-?\d*))?"
86110
)
87111

88-
# `not` or !
89-
self.logical_not_pattern = r"(?:not|!)"
90-
91-
# && or `and`
92-
self.bool_and_pattern = r"(?:&&|and)"
93-
94-
# || or `or`
95-
self.bool_or_pattern = r"(?:\|\||or)"
96-
97112
# /pattern/ or /pattern/flags
98113
self.re_pattern = r"/(?P<G_RE>.+?)/(?P<G_RE_FLAGS>[aims]*)"
99114

@@ -114,8 +129,8 @@ def compile_rules(self) -> Pattern[str]:
114129
(TOKEN_FLOAT, r"-?\d+\.\d*(?:e[+-]?\d+)?"),
115130
(TOKEN_INT, r"-?\d+(?P<G_EXP>e[+\-]?\d+)?\b"),
116131
(TOKEN_DDOT, r"\.\."),
117-
(TOKEN_AND, self.bool_and_pattern),
118-
(TOKEN_OR, self.bool_or_pattern),
132+
(TOKEN_AND, self.logical_and_pattern),
133+
(TOKEN_OR, self.logical_or_pattern),
119134
(TOKEN_ROOT, re.escape(self.env.root_token)),
120135
(TOKEN_SELF, re.escape(self.env.self_token)),
121136
(TOKEN_KEY, re.escape(self.env.key_token)),

jsonpath/parse.py

+1
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,7 @@ def parse_selector_list(self, stream: TokenStream) -> ListSelector: # noqa: PLR
446446
)
447447

448448
if stream.peek.kind != TOKEN_RBRACKET:
449+
# TODO: error message .. expected a comma or logical operator
449450
stream.expect_peek(TOKEN_COMMA)
450451
stream.next_token()
451452

0 commit comments

Comments
 (0)