Skip to content

Commit 3e13aa8

Browse files
committed
Feature/ternary operator (#33)
* updated project system around poetry (#29) * updated project system around poetry * github workflows * updated package dependencies * added compliance CLI tool * [ternary] supports tokenizing ternary operator expressions * [ternary] supports parsing ternary operator expressions * [ternary] supports ternary operator expressions * Updated reference to compliance tests.
1 parent 8211b50 commit 3e13aa8

File tree

6 files changed

+62
-2
lines changed

6 files changed

+62
-2
lines changed

jmespath/ast.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,3 +112,7 @@ def value_projection(left, right):
112112

113113
def variable_ref(name):
114114
return {"type": "variable_ref", "children": [], "value": name}
115+
116+
117+
def ternary_operator(condition, left, right):
118+
return {"type": "ternary_operator", "children": [condition, left, right]}

jmespath/lexer.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ class Lexer(object):
2424
'}': 'rbrace',
2525
'+': 'plus',
2626
'%': 'modulo',
27+
'?': 'question',
2728
u'\u2212': 'minus',
2829
u'\u00d7': 'multiply',
2930
u'\u00f7': 'divide',

jmespath/parser.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,9 @@ class Parser(object):
5353
'expref': 0,
5454
'colon': 0,
5555
'pipe': 1,
56-
'or': 2,
57-
'and': 3,
56+
'question': 2,
57+
'or': 3,
58+
'and': 4,
5859
'eq': 5,
5960
'gt': 5,
6061
'lt': 5,
@@ -413,6 +414,12 @@ def _token_led_lbracket(self, left):
413414
self._match('rbracket')
414415
right = self._parse_projection_rhs(self.BINDING_POWER['star'])
415416
return ast.projection(left, right)
417+
418+
def _token_led_question(self, condition):
419+
left = self._expression()
420+
self._match('colon')
421+
right = self._expression()
422+
return ast.ternary_operator(condition, left, right)
416423

417424
def _project_if_slice(self, left, right):
418425
index_expr = ast.index_expression([left, right])

jmespath/visitor.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,17 @@ def visit_variable_ref(self, node, value):
361361
except KeyError:
362362
raise exceptions.UndefinedVariable(node['value'])
363363

364+
def visit_ternary_operator(self, node, value):
365+
condition = node['children'][0]
366+
evaluation = self.visit(condition, value)
367+
368+
if self._is_false(evaluation):
369+
falsyNode = node['children'][2]
370+
return self.visit(falsyNode, value)
371+
else:
372+
truthyNode = node['children'][1]
373+
return self.visit(truthyNode, value)
374+
364375
def visit_value_projection(self, node, value):
365376
base = self.visit(node['children'][0], value)
366377
try:

tests/test_lexer.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,20 @@ def test_variable(self):
221221
]
222222
)
223223

224+
def test_ternary(self):
225+
tokens = list(self.lexer.tokenize('true ? false : foo'))
226+
self.assertEqual(
227+
tokens,
228+
[
229+
{ 'type': 'unquoted_identifier', 'value': 'true', 'start': 0, 'end': 4 },
230+
{ 'type': 'question', 'value': '?', 'start': 5, 'end': 6 },
231+
{ 'type': 'unquoted_identifier', 'value': 'false', 'start': 7, 'end': 12 },
232+
{ 'type': 'colon', 'value': ':', 'start': 13, 'end': 14 },
233+
{ 'type': 'unquoted_identifier', 'value': 'foo', 'start': 15, 'end': 18 },
234+
{ 'type': 'eof', 'value': '', 'start': 18, 'end': 18 }
235+
]
236+
)
237+
224238
def test_unknown_character(self):
225239
with self.assertRaises(LexerError) as e:
226240
tokens = list(self.lexer.tokenize('foo[0^]'))

tests/test_parser.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,29 @@ def test_trailing_merged_operator(self):
352352
["five", "six"], ["seven", "eight"],
353353
["nine"], ["ten"]])
354354

355+
class TestTernaryOperatorExpressions(unittest.TestCase):
356+
def setUp(self):
357+
self.parser = parser.Parser()
358+
self.data = {
359+
'true': True,
360+
'false': False,
361+
'foo': 'foo',
362+
'bar': 'bar',
363+
'baz': 'baz',
364+
'qux': 'qux',
365+
'quux': 'quux'
366+
}
367+
368+
def test_ternaryOperatorExpression(self):
369+
parsed = self.parser.parse('true ? foo : bar')
370+
match = parsed.search(self.data)
371+
self.assertEqual(match, 'foo')
372+
373+
def test_nestedTernaryOperatorExpressions(self):
374+
parsed = self.parser.parse('foo ? bar ? baz : qux : quux')
375+
match = parsed.search(self.data)
376+
self.assertEqual(match, 'baz')
377+
355378

356379
class TestParserCaching(unittest.TestCase):
357380
def test_compile_lots_of_expressions(self):

0 commit comments

Comments
 (0)