--- a/third_party/python/json-e/jsone/interpreter.py
+++ b/third_party/python/json-e/jsone/interpreter.py
@@ -1,12 +1,12 @@
from __future__ import absolute_import, print_function, unicode_literals
from .prattparser import PrattParser, infix, prefix
-from .shared import TemplateError, string
+from .shared import TemplateError, InterpreterError, string
import operator
import json
OPERATORS = {
'-': operator.sub,
'*': operator.mul,
'/': operator.truediv,
'**': operator.pow,
@@ -16,18 +16,19 @@ OPERATORS = {
'<': operator.lt,
'>': operator.gt,
'>=': operator.ge,
'&&': lambda a, b: bool(a and b),
'||': lambda a, b: bool(a or b),
}
-def expectationError(operator, expected):
- return TemplateError('{} expected {}'.format(operator, expected))
+def infixExpectationError(operator, expected):
+ return InterpreterError('infix: {} expects {} {} {}'.
+ format(operator, expected, operator, expected))
class ExpressionEvaluator(PrattParser):
ignore = '\\s+'
patterns = {
'number': '[0-9]+(?:\\.[0-9]+)?',
'identifier': '[a-zA-Z_][a-zA-Z_0-9]*',
@@ -39,19 +40,19 @@ class ExpressionEvaluator(PrattParser):
'null': 'null(?![a-zA-Z_0-9])',
}
tokens = [
'**', '+', '-', '*', '/', '[', ']', '.', '(', ')', '{', '}', ':', ',',
'>=', '<=', '<', '>', '==', '!=', '!', '&&', '||', 'true', 'false', 'in',
'null', 'number', 'identifier', 'string',
]
precedence = [
- ['in'],
['||'],
['&&'],
+ ['in'],
['==', '!='],
['>=', '<=', '<', '>'],
['+', '-'],
['*', '/'],
['**-right-associative'],
['**'],
['[', '.'],
['('],
@@ -75,33 +76,33 @@ class ExpressionEvaluator(PrattParser):
@prefix("!")
def bang(self, token, pc):
return not pc.parse('unary')
@prefix("-")
def uminus(self, token, pc):
v = pc.parse('unary')
if not isNumber(v):
- raise expectationError('unary -', 'number')
+ raise InterpreterError('{} expects {}'.format('unary -', 'number'))
return -v
@prefix("+")
def uplus(self, token, pc):
v = pc.parse('unary')
if not isNumber(v):
- raise expectationError('unary +', 'number')
+ raise InterpreterError('{} expects {}'.format('unary +', 'number'))
return v
@prefix("identifier")
def identifier(self, token, pc):
try:
return self.context[token.value]
except KeyError:
- raise TemplateError(
- 'no context value named "{}"'.format(token.value))
+ raise InterpreterError(
+ 'unknown context value {}'.format(token.value))
@prefix("null")
def null(self, token, pc):
return None
@prefix("[")
def array_bracket(self, token, pc):
return parseList(pc, ',', ']')
@@ -126,33 +127,33 @@ class ExpressionEvaluator(PrattParser):
@prefix("false")
def false(self, token, ps):
return False
@infix("+")
def plus(self, left, token, pc):
if not isinstance(left, (string, int, float)) or isinstance(left, bool):
- raise expectationError('+', 'number or string')
+ raise infixExpectationError('+', 'number/string')
right = pc.parse(token.kind)
if not isinstance(right, (string, int, float)) or isinstance(right, bool):
- raise expectationError('+', 'number or string')
+ raise infixExpectationError('+', 'number/string')
if type(right) != type(left) and \
(isinstance(left, string) or isinstance(right, string)):
- raise expectationError('+', 'matching types')
+ raise infixExpectationError('+', 'numbers/strings')
return left + right
@infix('-', '*', '/', '**')
def arith(self, left, token, pc):
op = token.kind
if not isNumber(left):
- raise expectationError(op, 'number')
+ raise infixExpectationError(op, 'number')
right = pc.parse({'**': '**-right-associative'}.get(op))
if not isNumber(right):
- raise expectationError(op, 'number')
+ raise infixExpectationError(op, 'number')
return OPERATORS[op](left, right)
@infix("[")
def index_slice(self, left, token, pc):
a = None
b = None
is_interval = False
if pc.attempt(':'):
@@ -170,17 +171,17 @@ class ExpressionEvaluator(PrattParser):
if not is_interval:
pc.require(']')
return accessProperty(left, a, b, is_interval)
@infix(".")
def property_dot(self, left, token, pc):
if not isinstance(left, dict):
- raise expectationError('.', 'object')
+ raise infixExpectationError('.', 'object')
k = pc.require('identifier').value
try:
return left[k]
except KeyError:
raise TemplateError(
'{} not found in {}'.format(k, json.dumps(left)))
@infix("(")
@@ -197,35 +198,35 @@ class ExpressionEvaluator(PrattParser):
return OPERATORS[op](left, right)
@infix('<=', '<', '>', '>=')
def inequality(self, left, token, pc):
op = token.kind
right = pc.parse(op)
if type(left) != type(right) or \
not (isinstance(left, (int, float, string)) and not isinstance(left, bool)):
- raise expectationError(op, 'matching types')
+ raise infixExpectationError(op, 'numbers/strings')
return OPERATORS[op](left, right)
@infix("in")
def contains(self, left, token, pc):
right = pc.parse(token.kind)
if isinstance(right, dict):
if not isinstance(left, string):
- raise expectationError('in-object', 'string on left side')
+ raise infixExpectationError('in-object', 'string on left side')
elif isinstance(right, string):
if not isinstance(left, string):
- raise expectationError('in-string', 'string on left side')
+ raise infixExpectationError('in-string', 'string on left side')
elif not isinstance(right, list):
- raise expectationError(
+ raise infixExpectationError(
'in', 'Array, string, or object on right side')
try:
return left in right
except TypeError:
- raise expectationError('in', 'scalar value, collection')
+ raise infixExpectationError('in', 'scalar value, collection')
def isNumber(v):
return isinstance(v, (int, float)) and not isinstance(v, bool)
def parseString(v):
return v[1:-1]
@@ -263,26 +264,26 @@ def parseObject(pc):
def accessProperty(value, a, b, is_interval):
if isinstance(value, (list, string)):
if is_interval:
if b is None:
b = len(value)
try:
return value[a:b]
except TypeError:
- raise expectationError('[..]', 'integer')
+ raise infixExpectationError('[..]', 'integer')
else:
try:
return value[a]
except IndexError:
raise TemplateError('index out of bounds')
except TypeError:
- raise expectationError('[..]', 'integer')
+ raise infixExpectationError('[..]', 'integer')
if not isinstance(value, dict):
- raise expectationError('[..]', 'object, array, or string')
+ raise infixExpectationError('[..]', 'object, array, or string')
if not isinstance(a, string):
- raise expectationError('[..]', 'string index')
+ raise infixExpectationError('[..]', 'string index')
try:
return value[a]
except KeyError:
return None
--- a/third_party/python/json-e/jsone/prattparser.py
+++ b/third_party/python/json-e/jsone/prattparser.py
@@ -6,17 +6,17 @@ from .shared import TemplateError
from .six import with_metaclass, viewitems
class SyntaxError(TemplateError):
@classmethod
def unexpected(cls, got, exp):
exp = ', '.join(sorted(exp))
- return cls('Found {}, expected {}'.format(got, exp))
+ return cls('Found {}, expected {}'.format(got.value, exp))
Token = namedtuple('Token', ['kind', 'value', 'start', 'end'])
def prefix(*kinds):
"""Decorate a method as handling prefix tokens of the given kinds"""
def wrap(fn):
@@ -87,25 +87,25 @@ class PrattParser(with_metaclass(PrattPa
precedence = []
def parse(self, source):
pc = ParseContext(self, source, self._generate_tokens(source))
result = pc.parse()
# if there are any tokens remaining, that's an error..
token = pc.attempt()
if token:
- raise SyntaxError.unexpected(token.kind, self.infix_rules)
+ raise SyntaxError.unexpected(token, self.infix_rules)
return result
def parseUntilTerminator(self, source, terminator):
pc = ParseContext(self, source, self._generate_tokens(source))
result = pc.parse()
token = pc.attempt()
if token.kind != terminator:
- raise SyntaxError.unexpected(token.kind, [terminator])
+ raise SyntaxError.unexpected(token, [terminator])
return (result, token.start)
def _generate_tokens(self, source):
offset = 0
while True:
start = offset
remainder = source[offset:]
mo = self.token_re.match(remainder)
@@ -164,26 +164,26 @@ class ParseContext(object):
def require(self, *kinds):
"""Get the next token, raising an exception if it doesn't match one of
the given kinds, or the input ends. If no kinds are given, returns the
next token of any kind."""
token = self.attempt()
if not token:
raise SyntaxError('Unexpected end of input')
if kinds and token.kind not in kinds:
- raise SyntaxError.unexpected(token.kind, kinds)
+ raise SyntaxError.unexpected(token, kinds)
return token
def parse(self, precedence=None):
parser = self.parser
precedence = parser.precedence_map[precedence] if precedence else 0
token = self.require()
prefix_rule = parser.prefix_rules.get(token.kind)
if not prefix_rule:
- raise SyntaxError.unexpected(token.kind, parser.prefix_rules)
+ raise SyntaxError.unexpected(token, parser.prefix_rules)
left = prefix_rule(parser, token, self)
while self.next_token:
kind = self.next_token.kind
if kind not in parser.infix_rules:
break
if precedence >= parser.precedence_map[kind]:
break
token = self.require()
--- a/third_party/python/json-e/jsone/render.py
+++ b/third_party/python/json-e/jsone/render.py
@@ -67,17 +67,20 @@ def checkUndefinedProperties(template, a
if not re.match(combined, key)]
if unknownKeys:
raise TemplateError(allowed[0].replace('\\', '') +
" has undefined properties: " + " ".join(unknownKeys))
@operator('$eval')
def eval(template, context):
- return evaluateExpression(renderValue(template['$eval'], context), context)
+ checkUndefinedProperties(template, ['\$eval'])
+ if not isinstance(template['$eval'], string):
+ raise TemplateError("$eval must be given a string expression")
+ return evaluateExpression(template['$eval'], context)
@operator('$flatten')
def flatten(template, context):
checkUndefinedProperties(template, ['\$flatten'])
value = renderValue(template['$flatten'], context)
if not isinstance(value, list):
raise TemplateError('$flatten value must evaluate to an array')
@@ -135,31 +138,31 @@ def ifConstruct(template, context):
return DeleteMarker
return renderValue(rv, context)
@operator('$json')
def jsonConstruct(template, context):
checkUndefinedProperties(template, ['\$json'])
value = renderValue(template['$json'], context)
- return json.dumps(value, separators=(',', ':'))
+ return json.dumps(value, separators=(',', ':'), sort_keys=True)
@operator('$let')
def let(template, context):
checkUndefinedProperties(template, ['\$let', 'in'])
- variables = renderValue(template['$let'], context)
- if not isinstance(variables, dict):
- raise TemplateError("$let value must evaluate to an object")
- else:
- if not all(IDENTIFIER_RE.match(variableNames) for variableNames in variables.keys()):
- raise TemplateError('top level keys of $let must follow /[a-zA-Z_][a-zA-Z0-9_]*/')
+ if not isinstance(template['$let'], dict):
+ raise TemplateError("$let value must be an object")
subcontext = context.copy()
- subcontext.update(variables)
+ for k, v in template['$let'].items():
+ if not IDENTIFIER_RE.match(k):
+ raise TemplateError('top level keys of $let must follow /[a-zA-Z_][a-zA-Z0-9_]*/')
+ subcontext[k] = renderValue(v, context)
+
try:
in_expression = template['in']
except KeyError:
raise TemplateError("$let operator requires an `in` clause")
return renderValue(in_expression, subcontext)
@operator('$map')
@@ -238,27 +241,27 @@ def merge(template, context):
return functools.reduce(merge, value[1:], value[0])
@operator('$reverse')
def reverse(template, context):
checkUndefinedProperties(template, ['\$reverse'])
value = renderValue(template['$reverse'], context)
if not isinstance(value, list):
- raise TemplateError("$reverse value must evaluate to an array")
+ raise TemplateError("$reverse value must evaluate to an array of objects")
return list(reversed(value))
@operator('$sort')
def sort(template, context):
BY_RE = 'by\([a-zA-Z_][a-zA-Z0-9_]*\)'
checkUndefinedProperties(template, ['\$sort', BY_RE])
value = renderValue(template['$sort'], context)
if not isinstance(value, list):
- raise TemplateError("$sort value must evaluate to an array")
+ raise TemplateError('$sorted values to be sorted must have the same type')
# handle by(..) if given, applying the schwartzian transform
by_keys = [k for k in template if k.startswith('by(')]
if len(by_keys) == 1:
by_key = by_keys[0]
by_var = by_key[3:-1]
by_expr = template[by_key]
@@ -274,19 +277,19 @@ def sort(template, context):
raise TemplateError('only one by(..) is allowed')
# check types
try:
eltype = type(to_sort[0][0])
except IndexError:
return []
if eltype in (list, dict, bool, type(None)):
- raise TemplateError('$sort values must be sortable')
+ raise TemplateError('$sorted values to be sorted must have the same type')
if not all(isinstance(e[0], eltype) for e in to_sort):
- raise TemplateError('$sorted values must all have the same type')
+ raise TemplateError('$sorted values to be sorted must have the same type')
# unzip the schwartzian transform
return list(e[1] for e in sorted(to_sort))
def renderValue(template, context):
if isinstance(template, string):
return interpolate(template, context)