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)