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)