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)
 |