Updated aiohttp v3.12.12 -> v3.12.13 Updated attrs v23.1.0 -> v23.2.0 Updated blessed v1.19.1 -> v1.21.0 Updated cbor2 v4.0.1 -> v4.1.2 Updated compare-locales v9.0.1 -> v9.0.4 Updated distro v1.8.0 -> v1.9.0 Updated fluent-migrate v0.13.2 -> v0.13.3 Updated importlib-metadata v6.0.0 -> v6.11.0 Updated jsmin v3.0.0 -> v3.0.1 Updated json-e v4.5.3 -> v4.8.0 Updated looseversion v1.0.1 -> v1.3.0 Updated mozilla-taskgraph v3.3.1 -> v3.3.2 Updated multidict v6.4.4 -> v6.5.0 Updated pathspec v0.9.0 -> v0.12.1 Updated pyasn1 v0.4.8 -> v0.6.1 Updated pyasn1-modules v0.2.8 -> v0.4.2 Updated pylru v1.0.9 -> v1.2.1 Updated python-hglib v2.4 -> v2.6.2 Updated redo v2.0.3 -> v2.0.4 Updated requests-unixsocket v0.2.0 -> v0.4.1 Updated responses v0.10.6 -> v0.25.7 Updated rsa v4.9 -> v4.9.1 Updated sentry-sdk v1.14.0 -> v1.45.1 Updated six v1.16.0 -> v1.17.0 Updated tomlkit v0.12.3 -> v0.13.3 Updated tqdm v4.66.3 -> v4.67.1 Updated typing-extensions v4.12.2 -> v4.14.0 Updated voluptuous v0.12.1 -> v0.15.2 Updated yamllint v1.23.0 -> v1.37.1 Differential Revision: https://phabricator.services.mozilla.com/D254096
221 lines
7.6 KiB
Python
221 lines
7.6 KiB
Python
from .shared import InterpreterError, string
|
|
import operator
|
|
|
|
|
|
def infixExpectationError(operator, expected):
|
|
return InterpreterError(
|
|
"infix: {} expects {} {} {}".format(operator, expected, operator, expected)
|
|
)
|
|
|
|
|
|
class Interpreter:
|
|
def __init__(self, context):
|
|
self.context = context
|
|
|
|
def visit(self, node):
|
|
method_name = "visit_" + type(node).__name__
|
|
visitor = getattr(self, method_name)
|
|
return visitor(node)
|
|
|
|
def visit_ASTNode(self, node):
|
|
if node.token.kind == "number":
|
|
v = node.token.value
|
|
return float(v) if "." in v else int(v)
|
|
elif node.token.kind == "null":
|
|
return None
|
|
elif node.token.kind == "string":
|
|
return node.token.value[1:-1]
|
|
elif node.token.kind == "true":
|
|
return True
|
|
elif node.token.kind == "false":
|
|
return False
|
|
elif node.token.kind == "identifier":
|
|
return node.token.value
|
|
|
|
def visit_UnaryOp(self, node):
|
|
value = self.visit(node.expr)
|
|
if node.token.kind == "+":
|
|
if not is_number(value):
|
|
raise InterpreterError("{} expects {}".format("unary +", "number"))
|
|
return value
|
|
elif node.token.kind == "-":
|
|
if not is_number(value):
|
|
raise InterpreterError("{} expects {}".format("unary -", "number"))
|
|
return -value
|
|
elif node.token.kind == "!":
|
|
return not self.visit(node.expr)
|
|
|
|
def visit_BinOp(self, node):
|
|
left = self.visit(node.left)
|
|
if node.token.kind == "||":
|
|
return bool(left or self.visit(node.right))
|
|
elif node.token.kind == "&&":
|
|
return bool(left and self.visit(node.right))
|
|
else:
|
|
right = self.visit(node.right)
|
|
|
|
if node.token.kind == "+":
|
|
if not isinstance(left, (string, int, float)) or isinstance(left, bool):
|
|
raise infixExpectationError("+", "numbers/strings")
|
|
if not isinstance(right, (string, int, float)) or isinstance(right, bool):
|
|
raise infixExpectationError("+", "numbers/strings")
|
|
if type(right) != type(left) and (
|
|
isinstance(left, string) or isinstance(right, string)
|
|
):
|
|
raise infixExpectationError("+", "numbers/strings")
|
|
return left + right
|
|
elif node.token.kind == "-":
|
|
test_math_operands("-", left, right)
|
|
return left - right
|
|
elif node.token.kind == "/":
|
|
test_math_operands("/", left, right)
|
|
if right == 0:
|
|
raise InterpreterError("division by zero")
|
|
return operator.truediv(left, right)
|
|
elif node.token.kind == "*":
|
|
test_math_operands("*", left, right)
|
|
return left * right
|
|
elif node.token.kind == ">":
|
|
test_comparison_operands(">", left, right)
|
|
return left > right
|
|
elif node.token.kind == "<":
|
|
test_comparison_operands("<", left, right)
|
|
return left < right
|
|
elif node.token.kind == ">=":
|
|
test_comparison_operands(">=", left, right)
|
|
return left >= right
|
|
elif node.token.kind == "<=":
|
|
test_comparison_operands("<=", left, right)
|
|
return left <= right
|
|
elif node.token.kind == "!=":
|
|
return left != right
|
|
elif node.token.kind == "==":
|
|
return left == right
|
|
elif node.token.kind == "**":
|
|
test_math_operands("**", left, right)
|
|
return right**left
|
|
elif node.token.value == "in":
|
|
if isinstance(right, dict):
|
|
if not isinstance(left, string):
|
|
raise infixExpectationError("in-object", "string on left side")
|
|
elif isinstance(right, string):
|
|
if not isinstance(left, string):
|
|
raise infixExpectationError("in-string", "string on left side")
|
|
elif not isinstance(right, list):
|
|
raise infixExpectationError(
|
|
"in", "Array, string, or object on right side"
|
|
)
|
|
try:
|
|
return left in right
|
|
except TypeError:
|
|
raise infixExpectationError("in", "scalar value, collection")
|
|
|
|
elif node.token.kind == ".":
|
|
if not isinstance(left, dict):
|
|
raise InterpreterError("infix: {} expects {}".format(".", "objects"))
|
|
try:
|
|
return left[right]
|
|
except KeyError:
|
|
raise InterpreterError('object has no property "{}"'.format(right))
|
|
|
|
def visit_List(self, node):
|
|
list = []
|
|
|
|
if node.list[0] is not None:
|
|
for item in node.list:
|
|
list.append(self.visit(item))
|
|
|
|
return list
|
|
|
|
def visit_ValueAccess(self, node):
|
|
value = self.visit(node.arr)
|
|
left = 0
|
|
right = None
|
|
|
|
if node.left:
|
|
left = self.visit(node.left)
|
|
if node.right:
|
|
right = self.visit(node.right)
|
|
|
|
if isinstance(value, (list, string)):
|
|
if node.isInterval:
|
|
if right is None:
|
|
right = len(value)
|
|
try:
|
|
return value[left:right]
|
|
except TypeError:
|
|
raise InterpreterError(
|
|
"cannot perform interval access with non-integers"
|
|
)
|
|
else:
|
|
try:
|
|
return value[left]
|
|
except IndexError:
|
|
raise InterpreterError("index out of bounds")
|
|
except TypeError:
|
|
raise InterpreterError(
|
|
"should only use integers to access arrays or strings"
|
|
)
|
|
|
|
if not isinstance(value, dict):
|
|
raise InterpreterError(
|
|
"infix: {} expects {}".format('"[..]"', "object, array, or string")
|
|
)
|
|
if not isinstance(left, string):
|
|
raise InterpreterError("object keys must be strings")
|
|
|
|
try:
|
|
return value[left]
|
|
except KeyError:
|
|
return None
|
|
|
|
def visit_ContextValue(self, node):
|
|
try:
|
|
contextValue = self.context[node.token.value]
|
|
except KeyError:
|
|
raise InterpreterError("unknown context value {}".format(node.token.value))
|
|
return contextValue
|
|
|
|
def visit_FunctionCall(self, node):
|
|
args = []
|
|
func_name = self.visit(node.name)
|
|
if callable(func_name):
|
|
if node.args is not None:
|
|
for item in node.args:
|
|
args.append(self.visit(item))
|
|
if hasattr(func_name, "_jsone_builtin"):
|
|
return func_name(self.context, *args)
|
|
else:
|
|
return func_name(*args)
|
|
else:
|
|
raise InterpreterError("{} is not callable".format(func_name))
|
|
|
|
def visit_Object(self, node):
|
|
obj = {}
|
|
for key in node.obj:
|
|
obj[key] = self.visit(node.obj[key])
|
|
return obj
|
|
|
|
def interpret(self, tree):
|
|
return self.visit(tree)
|
|
|
|
|
|
def test_math_operands(op, left, right):
|
|
if not is_number(left):
|
|
raise infixExpectationError(op, "number")
|
|
if not is_number(right):
|
|
raise infixExpectationError(op, "number")
|
|
return
|
|
|
|
|
|
def test_comparison_operands(op, left, right):
|
|
if type(left) != type(right) or not (
|
|
isinstance(left, (int, float, string)) and not isinstance(left, bool)
|
|
):
|
|
raise infixExpectationError(op, "numbers/strings")
|
|
return
|
|
|
|
|
|
def is_number(v):
|
|
return isinstance(v, (int, float)) and not isinstance(v, bool)
|