Hone logo
Hone
Problems

Simple Bytecode Interpreter in Python

This challenge asks you to build a rudimentary bytecode interpreter in Python. Bytecode interpreters are fundamental to many programming languages (like Java and Python itself!), translating intermediate code into machine-executable instructions. This exercise will help you understand the core concepts of interpreters and how they execute code.

Problem Description

You are tasked with creating a Python interpreter that executes a simplified bytecode. The bytecode consists of a list of instructions, each represented as a tuple. Your interpreter should take this bytecode as input and execute it, maintaining a stack to store intermediate values.

Instructions:

  • PUSH <value>: Pushes the given value onto the stack. value can be an integer or a string.
  • ADD: Pops the top two values from the stack, adds them (if both are numbers), concatenates them (if both are strings), and pushes the result back onto the stack. If the types are incompatible, raise a TypeError.
  • SUB: Pops the top two values from the stack, subtracts them (if both are numbers), raises a TypeError otherwise.
  • MUL: Pops the top two values from the stack, multiplies them (if both are numbers), raises a TypeError otherwise.
  • DIV: Pops the top two values from the stack, divides them (if both are numbers), raises a TypeError and ZeroDivisionError if necessary.
  • PRINT: Pops the top value from the stack and prints it to the console.
  • HALT: Stops the execution of the interpreter.

Key Requirements:

  • Implement a run function that takes the bytecode as a list of tuples and executes it.
  • Use a stack (a Python list will suffice) to store values during execution.
  • Handle type errors appropriately when performing operations on incompatible types.
  • Handle ZeroDivisionError when dividing by zero.
  • Raise a ValueError if the bytecode contains an invalid instruction.
  • The interpreter should halt gracefully when encountering the HALT instruction.

Expected Behavior:

The interpreter should execute the bytecode instructions in order, modifying the stack as needed. The PRINT instruction should output the top value of the stack to the console. The HALT instruction should terminate the interpreter.

Edge Cases to Consider:

  • Empty bytecode list.
  • Bytecode with invalid instructions.
  • Stack underflow (attempting to pop from an empty stack).
  • Division by zero.
  • Incompatible types for arithmetic/concatenation operations.

Examples

Example 1:

Input: [('PUSH', 5), ('PUSH', 3), ('ADD'), ('PRINT'), ('HALT')]
Output:
5
Explanation: Pushes 5 and 3 onto the stack. Adds them (5 + 3 = 8). Prints 8. Halts.

Example 2:

Input: [('PUSH', 'hello'), ('PUSH', ' world'), ('ADD'), ('PRINT'), ('HALT')]
Output:
hello world
Explanation: Pushes "hello" and " world" onto the stack. Concatenates them ("hello" + " world" = "hello world"). Prints "hello world". Halts.

Example 3:

Input: [('PUSH', 10), ('PUSH', 2), ('DIV'), ('PRINT'), ('HALT')]
Output:
5.0
Explanation: Pushes 10 and 2 onto the stack. Divides them (10 / 2 = 5.0). Prints 5.0. Halts.

Example 4: (Edge Case - Division by Zero)

Input: [('PUSH', 10), ('PUSH', 0), ('DIV'), ('PRINT'), ('HALT')]
Output:
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero
Explanation: Pushes 10 and 0 onto the stack. Attempts to divide 10 by 0, raising a ZeroDivisionError.

Constraints

  • The bytecode list will contain tuples of length 2. The first element of each tuple is the instruction name (string), and the second element is the argument (string or number).
  • The interpreter should handle integer and string values.
  • The bytecode list will not exceed 100 instructions.
  • The interpreter should be reasonably efficient for the given constraints. Optimization is not the primary focus.

Notes

  • Start by implementing the PUSH instruction and verifying that values are correctly pushed onto the stack.
  • Then, implement the arithmetic and concatenation operations (ADD, SUB, MUL, DIV).
  • After that, implement the PRINT and HALT instructions.
  • Remember to handle errors gracefully and provide informative error messages.
  • Consider using a dictionary to map instruction names to their corresponding functions for better code organization.
  • Think about how to handle different data types and ensure type safety during operations.
Loading editor...
python