def eval_index_expression(left: MonkeyObject, index: MonkeyObject) -> MonkeyObject: if left.typ == mobject.ARRAY_OBJ and index.typ == mobject.INTEGER_OBJ: return eval_array_index_expression(left, index) elif left.typ == mobject.HASH_OBJ: if not isinstance(index, mobject.Hashable): return new_error("unusable as hash key: {}", index.typ) return eval_hash_index_expression(left, index) else: return new_error("index operator not supported: {}", left.typ)
def apply_function(fn: MonkeyObject, args: List[MonkeyObject]) -> MonkeyObject: if isinstance(fn, mobject.Function): if len(fn.parameters) > len(args): return new_error( "function call missing required arguments: {}", ', '.join([param.value for param in fn.parameters[len(args):]])) extended_env = extend_function_env(fn, args) evaluated = eval(fn.body, extended_env) return unwrap_return_value(evaluated) elif isinstance(fn, mobject.Builtin): return fn.fn(*args) else: return new_error("not a function: {}", fn.typ)
def eval_infix_expression(operator: str, left: MonkeyObject, right: MonkeyObject) -> MonkeyObject: if left.typ == mobject.INTEGER_OBJ and right.typ == mobject.INTEGER_OBJ: return eval_integer_infix_expression(operator, left, right) elif left.typ == mobject.BOOLEAN_OBJ and right.typ == mobject.BOOLEAN_OBJ: if operator == "==": return native_bool_to_boolean_object(left == right) elif operator == "!=": return native_bool_to_boolean_object(left != right) else: return new_error("unknown operator: {} {} {}", left.typ, operator, right.typ) elif left.typ == mobject.STRING_OBJ and right.typ == mobject.STRING_OBJ: return eval_string_infix_expression(operator, left, right) else: return new_error("type mismatch: {} {} {}", left.typ, operator, right.typ)
def inner(*args): err = check_args_len(args, len(typs)) if err is not None: return err for arg, typ in zip(args, typs): if typ is not None and arg.typ != typ: return new_error("wrong argument type") return f(*args)
def inner(*args): err = check_args_len(args, 1) if err is not None: return err arg = args[0] if arg.typ != typ: return new_error("argument must be {}, got {}", typ, arg.typ) return f(arg)
def puts(*args): for arg in args: if arg.typ not in (mobject.STRING_OBJ, mobject.INTEGER_OBJ, mobject.BOOLEAN_OBJ): return new_error("wrong argument type, 'puts' does not accept {}", arg.typ) for arg in args: print(arg.value)
def eval_program(program: ast.Program, env: mobject.Environment) -> MonkeyObject: result: MonkeyObject for statement in program.statements: result = eval(statement, env) if result is None: continue elif result.typ == mobject.RETURN_VALUE_OBJ: return result.value elif result.typ == mobject.ERROR_OBJ: return result elif is_break(result): return new_error("break cannot be used outside of a loop") elif is_continue(result): return new_error("continue cannot be used outside of a loop") return result
def eval_prefix_expression(operator: str, right: MonkeyObject) -> MonkeyObject: if operator == "!": return eval_bang_operator_expression(right) elif operator == "-": return eval_minus_prefix_operator_expression(right) else: return new_error("unknown operator: {operator}{typ}", operator=operator, typ=right.typ)
def eval_identifier(node: ast.Identifier, env: mobject.Environment) -> MonkeyObject: val = env.get(node.value) if val is not None: return val builtin = builtins.get(node.value) if builtin is not None: return builtin return new_error("identifier not found: {}", node.value)
def last(*args) -> MonkeyObject: err = check_args_len(args, 1) if err is not None: return err arg = args[0] if arg.typ != mobject.ARRAY_OBJ: return new_error("argument to 'last' must be ARRAY, got {}", arg.typ) if len(arg.elements) > 0: return arg.elements[-1] return NULL
def rest(*args) -> mobject.Array: err = check_args_len(args, 1) if err is not None: return err arg = args[0] if arg.typ != mobject.ARRAY_OBJ: return new_error("argument to 'last' must be ARRAY, got {}", arg.typ) if len(arg.elements) > 0: return mobject.Array(elements=arg.elements[1:]) return NULL
def len_builtin(*args) -> mobject.Integer: err = check_args_len(args, 1) if err is not None: return err if isinstance(args[0], mobject.String): return mobject.Integer(value=len(args[0].value)) elif isinstance(args[0], mobject.Array): return mobject.Integer(value=len(args[0].elements)) else: return new_error("argument to 'len' not supported, got {}", args[0].typ)
def eval_for_expression(exp: ast.ForExpression, env: mobject.Environment) -> MonkeyObject: iterator: mobject.Array = eval(exp.iterator, env) if not iterator.typ == mobject.ARRAY_OBJ: return new_error("iterator must be ARRAY. found {}", iterator.typ) evaluated = NULL for value in iterator.elements: extended_env = extend_for_body_env(env, exp.element, value) evaluated = eval_loop_block_statement(exp.body, extended_env) if is_error(evaluated) or is_return(evaluated): return evaluated if is_break(evaluated): return NULL if is_continue(evaluated): evaluated = NULL return evaluated
def eval_hash_literal(node: ast.HashLiteral, env: mobject.Environment) -> MonkeyObject: pairs: Dict[mobject.HashKey, mobject.HashPair] = {} for key_node, value_node in node.pairs: key = eval(key_node, env) if is_error(key): return key if not isinstance(key, mobject.Hashable): return new_error("unusable as hash key: {}", key.typ) value = eval(value_node, env) if is_error(value): return value hashed = key.hash_key pairs[hashed] = mobject.HashPair(key=key, value=value) return mobject.Hash(pairs=pairs)
def eval_integer_infix_expression(operator: str, left: MonkeyObject, right: MonkeyObject) -> MonkeyObject: if operator == "+": return mobject.Integer(value=left.value + right.value) elif operator == "-": return mobject.Integer(value=left.value - right.value) elif operator == "*": return mobject.Integer(value=left.value * right.value) elif operator == "/": return mobject.Integer(value=left.value // right.value) elif operator == "<": return native_bool_to_boolean_object(left.value < right.value) elif operator == ">": return native_bool_to_boolean_object(left.value > right.value) elif operator == "!=": return native_bool_to_boolean_object(left.value != right.value) elif operator == "==": return native_bool_to_boolean_object(left.value == right.value) else: return new_error("unknown operator: {} {} {}", left.typ, operator, right.typ)
def eval(node: ast.Node, env: mobject.Environment) -> MonkeyObject: typ = type(node) if typ == ast.Program: return eval_program(node, env) elif typ == ast.ExpressionStatement: return eval(node.expression, env) elif typ == ast.IntegerLiteral: return mobject.Integer(value=node.value) elif typ == ast.Boolean: return native_bool_to_boolean_object(node.value) elif typ == ast.StringLiteral: return mobject.String(value=node.value) elif typ == ast.Identifier: return eval_identifier(node, env) elif typ == ast.PrefixExpression: right = eval(node.right, env) if is_error(right): return right return eval_prefix_expression(node.operator, right) elif typ == ast.InfixExpression: left = eval(node.left, env) if is_error(left): return left right = eval(node.right, env) if is_error(right): return right return eval_infix_expression(node.operator, left, right) elif typ == ast.AssignExpression: value = eval(node.value, env) if is_error(value): return value val = env.reset(node.name.value, value) if val is None: return new_error("variable '{}' does not exist. Can't reassign", node.name.value) return val elif typ == ast.BlockStatement: return eval_block_statement(node, env) elif typ == ast.IfExpression: return eval_if_expression(node, env) elif typ == ast.ForExpression: return eval_for_expression(node, env) elif typ == ast.WhileExpression: return eval_while_expression(node, env) elif typ == ast.ReturnStatement: val = eval(node.return_value, env) if is_error(val): return val return mobject.ReturnValue(val) elif typ == ast.BreakStatement: return mobject.Break() elif typ == ast.ContinueStatement: return mobject.Continue() elif typ == ast.LetStatement: val = eval(node.value, env) if is_error(val): return val env.set(node.name.value, val) elif typ == ast.FunctionLiteral: params = node.parameters body = node.body return mobject.Function(parameters=params, body=body, env=env) elif typ == ast.CallExpression: function = eval(node.function, env) if is_error(function): return function args = eval_expressions(node.arguments, env) if len(args) == 1 and is_error(args[0]): return args[0] return apply_function(function, args) elif typ == ast.ArrayLiteral: elements = eval_expressions(node.elements, env) if len(elements) == 1 and is_error(elements[0]): return elements[0] return mobject.Array(elements=elements) elif typ == ast.IndexExpression: left = eval(node.left, env) if is_error(left): return left index = eval(node.index, env) if is_error(index): return index return eval_index_expression(left, index) elif typ == ast.HashLiteral: return eval_hash_literal(node, env) else: return NULL
def eval_string_infix_expression(operator: str, left: MonkeyObject, right: MonkeyObject) -> MonkeyObject: if operator != "+": return new_error("unknown operator: {} {} {}", left.typ, operator, right.typ) return mobject.String(value=left.value + right.value)
def eval_minus_prefix_operator_expression(right: MonkeyObject) -> MonkeyObject: if right.typ != mobject.INTEGER_OBJ: return new_error("unknown operator: -{}", right.typ) return mobject.Integer(value=-right.value)
def to_str(arg: MonkeyObject) -> mobject.String: if arg.typ not in (mobject.STRING_OBJ, mobject.INTEGER_OBJ, mobject.BOOLEAN_OBJ): return new_error("wrong argument type, 'to_str' does not accept {}", arg.typ) return mobject.String(value=str(arg.value))
def check_args_len(args, n) -> mobject.Error: if len(args) != n: return new_error("wrong number of arguments. got {}, want {}", len(args), n) return None