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