Skip to content

Commit 52ebfbb

Browse files
committed
Initial commit of improved filters (JEP 9)
1 parent f90040c commit 52ebfbb

File tree

7 files changed

+465
-71
lines changed

7 files changed

+465
-71
lines changed

jmespath/ast.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,14 @@ def or_expression(left, right):
6262
return {"type": "or_expression", "children": [left, right]}
6363

6464

65+
def and_expression(left, right):
66+
return {"type": "and_expression", "children": [left, right]}
67+
68+
69+
def not_expression(expr):
70+
return {"type": "not_expression", "children": [expr]}
71+
72+
6573
def pipe(left, right):
6674
return {'type': 'pipe', 'children': [left, right]}
6775

jmespath/parser.py

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -48,21 +48,27 @@ class Parser(object):
4848
'expref': 0,
4949
'colon': 0,
5050
'pipe': 1,
51-
'eq': 2,
52-
'gt': 2,
53-
'lt': 2,
54-
'gte': 2,
55-
'lte': 2,
56-
'ne': 2,
57-
'or': 5,
58-
'flatten': 6,
51+
'or': 2,
52+
'and': 3,
53+
'eq': 5,
54+
'gt': 5,
55+
'lt': 5,
56+
'gte': 5,
57+
'lte': 5,
58+
'ne': 5,
59+
'flatten': 9,
60+
# Everything above stops a projection.
5961
'star': 20,
6062
'filter': 21,
6163
'dot': 40,
64+
'not': 45,
6265
'lbrace': 50,
6366
'lbracket': 55,
6467
'lparen': 60,
6568
}
69+
# The maximum binding power for a token that can stop
70+
# a projection.
71+
_PROJECTION_STOP = 10
6672
# The _MAX_SIZE most recent expressions are cached in
6773
# _CACHE dict.
6874
_CACHE = {}
@@ -161,12 +167,21 @@ def _token_nud_filter(self, token):
161167
def _token_nud_lbrace(self, token):
162168
return self._parse_multi_select_hash()
163169

170+
def _token_nud_lparen(self, token):
171+
expression = self._expression()
172+
self._match('rparen')
173+
return expression
174+
164175
def _token_nud_flatten(self, token):
165176
left = ast.flatten(ast.identity())
166177
right = self._parse_projection_rhs(
167178
self.BINDING_POWER['flatten'])
168179
return ast.projection(left, right)
169180

181+
def _token_nud_not(self, token):
182+
expr = self._expression(self.BINDING_POWER['not'])
183+
return ast.not_expression(expr)
184+
170185
def _token_nud_lbracket(self, token):
171186
if self._current_token() in ['number', 'colon']:
172187
right = self._parse_index_expression()
@@ -254,6 +269,10 @@ def _token_led_or(self, left):
254269
right = self._expression(self.BINDING_POWER['or'])
255270
return ast.or_expression(left, right)
256271

272+
def _token_led_and(self, left):
273+
right = self._expression(self.BINDING_POWER['and'])
274+
return ast.and_expression(left, right)
275+
257276
def _token_led_lparen(self, left):
258277
name = left['value']
259278
args = []
@@ -370,7 +389,7 @@ def _parse_multi_select_hash(self):
370389

371390
def _parse_projection_rhs(self, binding_power):
372391
# Parse the right hand side of the projection.
373-
if self.BINDING_POWER[self._current_token()] < 10:
392+
if self.BINDING_POWER[self._current_token()] < self._PROJECTION_STOP:
374393
# BP of 10 are all the tokens that stop a projection.
375394
right = ast.identity()
376395
elif self._current_token() == 'lbracket':

jmespath/visitor.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ def __init__(self):
7575
# properly freed.
7676
self._functions.interpreter = self
7777

78+
def default_visit(self, node, *args, **kwargs):
79+
raise NotImplementedError(node['type'])
80+
7881
def visit_subexpression(self, node, value):
7982
result = value
8083
for node in node['children']:
@@ -114,7 +117,7 @@ def visit_filter_projection(self, node, value):
114117
comparator_node = node['children'][2]
115118
collected = []
116119
for element in base:
117-
if self.visit(comparator_node, element):
120+
if self._is_true(self.visit(comparator_node, element)):
118121
current = self.visit(node['children'][1], element)
119122
if current is not None:
120123
collected.append(current)
@@ -186,6 +189,20 @@ def visit_or_expression(self, node, value):
186189
matched = self.visit(node['children'][1], value)
187190
return matched
188191

192+
def visit_and_expression(self, node, value):
193+
matched = self.visit(node['children'][0], value)
194+
if self._is_false(matched):
195+
return matched
196+
return self.visit(node['children'][1], value)
197+
198+
def visit_not_expression(self, node, value):
199+
original_result = self.visit(node['children'][0], value)
200+
if original_result is 0:
201+
# Special case for 0, !0 should be false, not true.
202+
# 0 is not a special cased integer in jmespath.
203+
return False
204+
return not original_result
205+
189206
def visit_pipe(self, node, value):
190207
result = value
191208
for node in node['children']:
@@ -223,6 +240,9 @@ def _is_false(self, value):
223240
return (value == '' or value == [] or value == {} or value is None or
224241
value is False)
225242

243+
def _is_true(self, value):
244+
return not self._is_false(value)
245+
226246

227247
class GraphvizVisitor(Visitor):
228248
def __init__(self):

0 commit comments

Comments
 (0)