Created
January 29, 2023 03:28
-
-
Save ColinPeppler/8e3dfbd0360b4afaf098de2e44207161 to your computer and use it in GitHub Desktop.
Very simple Python interpreter written in Python
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# ref: https://www.aosabook.org/en/500L/a-python-interpreter-written-in-python.html | |
from typing import NamedTuple, Optional, Union, List | |
from collections import deque | |
from dataclasses import dataclass | |
class Instruction(NamedTuple): | |
"""Represents a single instruction""" | |
method: str | |
argument: Optional[Union[str, int]] | |
@dataclass | |
class Program: | |
"""Represents a simple python bytecode program""" | |
instruction_set: List[Instruction] | |
data: List[int] | |
variables: List[str] | |
class Interpreter: | |
def __init__(self): | |
self.stack = deque() | |
self.symbols = {} | |
def STORE_NAME(self, name): | |
val = self.stack.pop() | |
self.symbols[name] = val | |
def LOAD_NAME(self, name): | |
val = self.symbols[name] | |
self.stack.append(val) | |
def LOAD_VALUE(self, number): | |
self.stack.append(number) | |
def ADD_TWO_VALUES(self): | |
first_num, second_num = self.stack.pop(), self.stack.pop() | |
total = first_num + second_num | |
self.stack.append(total) | |
def PRINT_ANSWER(self): | |
answer = self.stack.pop() | |
print(answer) | |
def _retrieve_argument(self, method, argument, program): | |
"""Gets the correct arguments based off the instruction type.""" | |
methods_using_data = "LOAD_VALUE" | |
methods_using_variables = ("LOAD_NAME", "STORE_NAME") | |
if method in methods_using_data: | |
argument = program.data[argument] | |
elif method in methods_using_variables: | |
argument = program.variables[argument] | |
return argument | |
def run_program(self, program): | |
instructions = program.instruction_set | |
for (method, argument) in instructions: | |
argument = self._retrieve_argument(method, argument, program) | |
bytecode_method = getattr(self, method) # dynamic lookup | |
if argument: | |
bytecode_method(argument) | |
else: | |
bytecode_method() | |
## test it ## | |
program = Program( | |
instruction_set=[ | |
("LOAD_VALUE", 0), | |
("STORE_NAME", 0), | |
("LOAD_VALUE", 1), | |
("STORE_NAME", 1), | |
# ("ADD_TWO_VALUES", None), | |
("LOAD_NAME", 0), | |
("LOAD_NAME", 1), | |
("ADD_TWO_VALUES", None), | |
("PRINT_ANSWER", None), | |
], | |
data=[1, 2], | |
variables=["a", "b"], | |
) | |
interpreter = Interpreter() | |
interpreter.run_program(program) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment