forked from mirrors/gecko-dev
211 lines
7.4 KiB
Python
211 lines
7.4 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)
|
|
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)
|