Files
Alex Hochheiden c49fedbda1 Bug 1972681 - Use compatible release operator for vendored Python packages where possible and run ./mach vendor python --upgrade r=ahal,mach-reviewers
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
2025-06-19 22:31:02 +00:00

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)