예제 #1
0
 def test_boolean_hash_key(self) -> None:
     hello1 = monkey_object.Boolean(True)
     hello2 = monkey_object.Boolean(True)
     diff1 = monkey_object.Boolean(False)
     diff2 = monkey_object.Boolean(False)
     self.assertEqual(hello1.hash_key(), hello2.hash_key())
     self.assertEqual(diff1.hash_key(), diff2.hash_key())
예제 #2
0
def test_hash_literals():
    input = """
    let two = "two";
    {
        "one": 10 - 9,
        two: 1 + 1,
        "thr" + "ee": 6/2,
        4: 4,
        true: 5,
        false: 6
    }
    """

    expected = {
        mobject.String(value="one").hash_key: 1,
        mobject.String(value="two").hash_key: 2,
        mobject.String(value="three").hash_key: 3,
        mobject.Integer(value=4).hash_key: 4,
        mobject.Boolean(value=True).hash_key: 5,
        mobject.Boolean(value=False).hash_key: 6
    }

    evaluated = eval_test(input)
    assert type(evaluated
                ) == mobject.Hash, f"object is not Hash, got {type(evaluated)}"
    assert len(evaluated.pairs) == len(
        expected
    ), f"hash has wrong number of pairs. got {len(evaluated.pairs)}, want {len(expected)}"

    for k, v in expected.items():
        pair = evaluated.pairs.get(k)
        assert pair is not None, f"no pair for key {k} can be found in hash"
        integer_object_test(pair.value, v)
예제 #3
0
def test_bool_hash_key():
    t1 = mobject.Boolean(value=True)
    t2 = mobject.Boolean(value=True)
    f1 = mobject.Boolean(value=False)

    assert t1.hash_key == t2.hash_key, "bool with same content have different hash keys"
    assert t1.hash_key != f1.hash_key, "bool with different content have same hash keys"
예제 #4
0
class Evaluator:
    # As there's only ever a need for a single instance of each of these values,
    # we optimize by pre-creating instances to return during evaluation.
    null = monkey_object.Null()
    true = monkey_object.Boolean(True)
    false = monkey_object.Boolean(False)

    def eval(self, node: ast.Node,
             env: environment.Environment) -> monkey_object.MonkeyObject:
        # statements
        if isinstance(node, ast.Program):
            return self._eval_program(
                cast(List[ast.BlockStatement], node.statements), env)
        if isinstance(node, ast.ExpressionStatement):
            return self.eval(node.expression, env)
        if isinstance(node, ast.BlockStatement):
            return self._eval_block_statement(
                cast(List[ast.Statement], node.statements), env)
        if isinstance(node, ast.ReturnStatement):
            value = self.eval(node.return_value, env)

            # Check for errors whenever Eval is called inside Eval in order to
            # stop errors from being passed around and bubbling up far from
            # their origin.
            if self._is_error(value):
                return value
            return monkey_object.ReturnValue(value)
        if isinstance(node, ast.LetStatement):
            value = self.eval(node.value, env)
            if self._is_error(value):
                return value
            return env.set(node.name.value, value)

        # expressions
        if isinstance(node, ast.IntegerLiteral):
            return monkey_object.Integer(node.value)
        if isinstance(node, ast.StringLiteral):
            return monkey_object.String(node.value)
        if isinstance(node, ast.Boolean):
            return self._native_bool_to_boolean_object(node.value)
        if isinstance(node, ast.PrefixExpression):
            right = self.eval(node.right, env)
            if self._is_error(right):
                return right
            return self._eval_prefix_expression(node.operator, right)
        if isinstance(node, ast.InfixExpression):
            left = self.eval(node.left, env)
            if self._is_error(left):
                return left
            right = self.eval(node.right, env)
            if self._is_error(right):
                return right
            return self._eval_infix_expression(node.operator, left, right)
        if isinstance(node, ast.IfExpression):
            return self._eval_if_expression(node, env)
        if isinstance(node, ast.Identifier):
            return self._eval_identifier(node, env)
        if isinstance(node, ast.FunctionLiteral):
            params = node.parameters
            body = node.body
            return monkey_object.Function(params, body, env)
        if isinstance(node, ast.CallExpression):
            function = self.eval(node.function, env)
            if self._is_error(function):
                return function
            args = self._eval_expressions(node.arguments, env)
            if len(args) == 1 and self._is_error(args[0]):
                return args[0]
            return self._apply_function(function, args)
        if isinstance(node, ast.ArrayLiteral):
            elements = self._eval_expressions(node.elements, env)
            if len(elements) == 1 and self._is_error(elements[0]):
                return elements[0]
            return monkey_object.Array(elements)
        if isinstance(node, ast.IndexExpression):
            left = self.eval(node.left, env)
            if self._is_error(left):
                return left
            index = self.eval(node.index, env)
            if self._is_error(index):
                return index
            return self._eval_index_expression(left, index)
        if isinstance(node, ast.HashLiteral):
            return self._eval_hash_literal(node, env)

        raise NotImplementedError

    def _apply_function(
            self, function: monkey_object.MonkeyObject,
            args: List[monkey_object.MonkeyObject]
    ) -> monkey_object.MonkeyObject:
        if isinstance(function, monkey_object.Function):
            extended_env = self._extend_function_environment(function, args)
            evaluated = self.eval(function.body, extended_env)
            return self._unwrap_return_value(evaluated)
        if isinstance(function, monkey_object.Builtin):
            return function.function(args)
        return monkey_object.Error(f"not a function: {function.type_().value}")

    def _extend_function_environment(self, function: monkey_object.Function,
                                     args: List[monkey_object.MonkeyObject]) -> \
                                     environment.Environment:
        env = environment.Environment.new_enclosed_environment(function.env)
        for param_idx, param in enumerate(function.parameters):
            env.set(param.value, args[param_idx])
        return env

    def _unwrap_return_value(
            self,
            obj: monkey_object.MonkeyObject) -> monkey_object.MonkeyObject:
        # Unwrapping prevents a return statement from bubbling up through
        # several functions and stopping evaluation in all of them. We only want
        # to stop the evaluation of the last called function's body. Otherwise,
        # _eval_block_statement would stop evaluating statements in outer
        # functions.
        return obj.value if isinstance(obj, monkey_object.ReturnValue) else obj

    def _eval_program(
            self, stmts: List[ast.BlockStatement],
            env: environment.Environment) -> monkey_object.MonkeyObject:
        result: monkey_object.MonkeyObject = Evaluator.null
        for stmt in stmts:
            result = self.eval(stmt, env)

            # Prevents further evaluation if the result of the evaluation is a
            # return statement. Note how we don't return ReturnValue directly,
            # but unwrap its value. ReturnValue is an internal detail to allow
            # Eval() to signal to its caller that it encountered and evaluated a
            # return statement.
            if isinstance(result, monkey_object.ReturnValue):
                return result.value
            if isinstance(result, monkey_object.Error):
                return result
        return result

    def _eval_block_statement(
            self, stmts: List[ast.Statement],
            env: environment.Environment) -> monkey_object.MonkeyObject:
        result: monkey_object.MonkeyObject = Evaluator.null
        for stmt in stmts:
            result = self.eval(stmt, env)
            if result is not None:
                if isinstance(
                        result,
                    (monkey_object.ReturnValue, monkey_object.Error)):
                    # Compared to _eval_program(), we don't unwrap the return
                    # value. Instead when an ReturnValue is encountered as the
                    # result of evaluating a statement, we return it to
                    # _eval_program() for unwrapping. This halts outer block
                    # evaluation and bubbles up the result.
                    return result
        return result

    def _native_bool_to_boolean_object(self,
                                       value: bool) -> monkey_object.Boolean:
        return Evaluator.true if value else Evaluator.false

    def _eval_prefix_expression(self, operator: str,
                                right: monkey_object.MonkeyObject) -> \
                                monkey_object.MonkeyObject:
        if operator == "!":
            return self._eval_bang_operator_expression(right)
        if operator == "-":
            return self._eval_minus_prefix_operator_expression(right)
        return monkey_object.Error(
            f"unknown operator: {operator}{right.type_().value}")

    def _eval_bang_operator_expression(self, right: monkey_object.MonkeyObject) -> \
                                       monkey_object.MonkeyObject:
        if right == Evaluator.true:
            return Evaluator.false
        if right == Evaluator.false:
            return Evaluator.true
        if right == Evaluator.null:
            return Evaluator.true
        return Evaluator.false

    def _eval_minus_prefix_operator_expression(self, right: monkey_object.MonkeyObject) -> \
                                               monkey_object.MonkeyObject:
        if right.type_() != monkey_object.ObjectType.INTEGER:
            return monkey_object.Error(
                f"unknown operator: -{right.type_().value}")
        value = cast(monkey_object.Integer, right).value
        return monkey_object.Integer(-value)

    def _eval_infix_expression(
            self, operator: str, left: monkey_object.MonkeyObject,
            right: monkey_object.MonkeyObject) -> monkey_object.MonkeyObject:
        if left.type_() == monkey_object.ObjectType.INTEGER and \
           right.type_() == monkey_object.ObjectType.INTEGER:
            return self._eval_integer_infix_expression(operator, left, right)
        if left.type_() == monkey_object.ObjectType.STRING and \
           right.type_() == monkey_object.ObjectType.STRING:
            return self._eval_string_infix_expression(operator, left, right)
        # For booleans we can use reference comparison to check for equality. It
        # works because of our singleton True and False instances but wouldn't
        # work for integers since they aren't singletons. 5 == 5 would be false
        # when comparing references. To compare integer we must unwrap the
        # integer stored inside each Integer object and compare their values.
        if operator == "==":
            return self._native_bool_to_boolean_object(left == right)
        if operator == "!=":
            return self._native_bool_to_boolean_object(left != right)
        if left.type_() != right.type_():
            return monkey_object.Error(
                f"type mismatch: {left.type_().value} {operator} {right.type_().value}"
            )
        return monkey_object.Error(
            f"unknown operator: {left.type_().value} {operator} {right.type_().value}"
        )

    def _eval_integer_infix_expression(self, operator: str, left: monkey_object.MonkeyObject,
                                       right: monkey_object.MonkeyObject) -> \
                                       monkey_object.MonkeyObject:
        # Called from _eval_infix_expression which type type assertion. mypy
        # cannot infer, so we have to explicitly guard with asserts.
        assert isinstance(left, monkey_object.Integer)
        assert isinstance(right, monkey_object.Integer)
        left_val = left.value
        right_val = right.value
        if operator == "+":
            return monkey_object.Integer(left_val + right_val)
        if operator == "-":
            return monkey_object.Integer(left_val - right_val)
        if operator == "*":
            return monkey_object.Integer(left_val * right_val)
        if operator == "/":
            return monkey_object.Integer(left_val // right_val)
        if operator == "<":
            return self._native_bool_to_boolean_object(left_val < right_val)
        if operator == ">":
            return self._native_bool_to_boolean_object(left_val > right_val)
        if operator == "==":
            return self._native_bool_to_boolean_object(left_val == right_val)
        if operator == "!=":
            return self._native_bool_to_boolean_object(left_val != right_val)
        return monkey_object.Error(
            f"unknown operator: {left.type_().value} {operator} {right.type_().value}"
        )

    def _eval_string_infix_expression(self, operator: str,
                                      left: monkey_object.MonkeyObject,
                                      right: monkey_object.MonkeyObject) -> \
                                      monkey_object.MonkeyObject:
        assert isinstance(left, monkey_object.String)
        assert isinstance(right, monkey_object.String)
        if operator != "+":
            return monkey_object.Error(
                f"unknown operator: {left.type_().value} {operator} {right.type_().value}"
            )
        left_val = left.value
        right_val = right.value
        return monkey_object.String(left_val + right_val)

    def _eval_if_expression(
            self, expr: ast.IfExpression,
            env: environment.Environment) -> monkey_object.MonkeyObject:
        condition = self.eval(expr.condition, env)
        if self._is_error(condition):
            return condition
        if self._is_truthy(condition):
            return self.eval(expr.consequence, env)
        if expr.alternative is not None:
            return self.eval(expr.alternative, env)
        return Evaluator.null

    def _eval_identifier(self, node: ast.Identifier, env: environment.Environment) -> \
                         monkey_object.MonkeyObject:
        value = env.get(node.value)
        if value is not None:
            return value
        if node.value in builtin.builtins:
            return builtin.builtins[node.value]
        return monkey_object.Error(f"identifier not found: {node.value}")

    def _eval_expressions(
            self, exprs: List[ast.Expression],
            env: environment.Environment) -> List[monkey_object.MonkeyObject]:
        result = []

        # By definition arguments are evaluated left to right. Since the side
        # effect of evaluating one argument might be relied on during evaluation
        # of the next, defining an explicit evaluation order is important.
        for expr in exprs:
            evaluated = self.eval(expr, env)
            if self._is_error(evaluated):
                return [evaluated]
            result.append(evaluated)
        return result

    def _eval_index_expression(self, left: monkey_object.MonkeyObject,
                               index: monkey_object.MonkeyObject) -> \
                               monkey_object.MonkeyObject:
        if left.type_() == monkey_object.ObjectType.ARRAY and \
           index.type_() == monkey_object.ObjectType.INTEGER:
            return self._eval_array_index_expression(left, index)
        if left.type_() == monkey_object.ObjectType.HASH:
            return self._eval_hash_index_expression(left, index)
        return monkey_object.Error(
            f"index operator not supported: {left.type_().value}")

    def _eval_array_index_expression(self, array: monkey_object.MonkeyObject,
                                     index: monkey_object.MonkeyObject) -> \
                                     monkey_object.MonkeyObject:
        assert isinstance(array, monkey_object.Array)
        assert isinstance(index, monkey_object.Integer)
        idx = index.value
        max_index = len(array.elements) - 1
        if idx < 0 or idx > max_index:
            # Some languages throw an exception when the index is out of bounds.
            # In Monkey by definition we return null as the result.
            return Evaluator.null
        return array.elements[idx]

    def _eval_hash_index_expression(self, expr: monkey_object.MonkeyObject,
                                    index: monkey_object.MonkeyObject) -> \
                                    monkey_object.MonkeyObject:
        if not isinstance(index, monkey_object.Hashable):
            return monkey_object.Error(
                f"unusable as hash key: {index.type_().value}")
        if not index.hash_key() in expr.pairs:
            return Evaluator.null
        return expr.pairs[index.hash_key()].value

    def _eval_hash_literal(
            self, node: ast.HashLiteral,
            env: environment.Environment) -> monkey_object.MonkeyObject:
        pairs: Dict[monkey_object.HashKey, monkey_object.HashPair] = {}
        for key_node, value_node in node.pairs.items():
            key = self.eval(key_node, env)
            if self._is_error(key):
                return key
            if not isinstance(key, monkey_object.Hashable):
                return monkey_object.Error(
                    f"unusable as hash key: {key.type_().value}")
            value = self.eval(value_node, env)
            if self._is_error(value):
                return value
            hashed = key.hash_key()
            pairs[hashed] = monkey_object.HashPair(key, value)
        return monkey_object.Hash(pairs)

    def _is_truthy(self, obj: monkey_object.MonkeyObject) -> bool:
        if obj == Evaluator.null:
            return False
        if obj == Evaluator.true:
            return True
        if obj == Evaluator.false:
            return False
        return True

    def _is_error(self, obj: monkey_object.MonkeyObject) -> bool:
        if obj is not None:
            return isinstance(obj, monkey_object.Error)
        return False
예제 #5
0
from dataclasses import dataclass
from monkey_compiler import Bytecode
from typing import List
import monkey_object
import monkey_code as code

STACK_SIZE = 2048
GLOBALS_SIZE = 65536
TRUE = monkey_object.Boolean(True)
FALSE = monkey_object.Boolean(False)
NULL = monkey_object.Null()


def native_bool_to_boolean_object(b):
    if b:
        return TRUE
    return FALSE


def is_truthy(obj: monkey_object.Object):
    if isinstance(obj, monkey_object.Boolean):
        return obj.value
    elif obj == NULL:
        return False
    else:
        return True


@dataclass
class VM:
    _constants: List[monkey_object.Object]