162 lines
4.9 KiB
Python
162 lines
4.9 KiB
Python
import sys
|
|
from collections import defaultdict
|
|
|
|
|
|
def generate_words(input_file):
|
|
"""Generator that yields words from a file-like object.
|
|
|
|
:param input_file: the file-like object
|
|
:yield: words from the file-like object until EOF
|
|
|
|
"""
|
|
for line in input_file:
|
|
for word in line.split():
|
|
yield word
|
|
|
|
|
|
def words_to_bits(words):
|
|
"""Converts a list of words to a string of bits. The '0's correspond
|
|
with an even length word, and the '1's correspond with odd-length
|
|
words.
|
|
|
|
>>> words_to_bits(["Hi,", "I'm", "Paul"])
|
|
"110"
|
|
|
|
:param words: List of words to convert into bits
|
|
:return: string of ASCII 1s and 0s representing the bits
|
|
|
|
"""
|
|
evenness = ['0' if len(word) % 2 == 0 else '1' for word in words]
|
|
return ''.join(evenness)
|
|
|
|
|
|
class BookwormInterpreter(object):
|
|
"""A bookworm interpreter executes a program written in the Bookworm
|
|
programming language for a particular file."""
|
|
|
|
code = {}
|
|
data = defaultdict(lambda: 0)
|
|
address = 0
|
|
instruction_pointer = 0
|
|
|
|
def __init__(self, input_file):
|
|
"""Initializer for bookworm interpreters.
|
|
|
|
:param input_file: the file to interpret
|
|
|
|
"""
|
|
self.word_generator = generate_words(input_file)
|
|
|
|
self.commands = {
|
|
"000": self.move_right,
|
|
"001": self.move_left,
|
|
"010": self.increment,
|
|
"011": self.decrement,
|
|
"100": self.output,
|
|
"101": self.read,
|
|
"110": self.open_bracket,
|
|
"111": self.close_bracket,
|
|
}
|
|
|
|
def move_right(self):
|
|
"""Moves the address on the data tape right."""
|
|
self.address += 1
|
|
|
|
def move_left(self):
|
|
"""Moves the address on the data tape left."""
|
|
self.address -= 1
|
|
|
|
def increment(self):
|
|
"""Increments the value of the data tape at the address."""
|
|
self.data[self.address] = self.data[self.address] + 1
|
|
|
|
def decrement(self):
|
|
"""Decrements the value of the data tape at the address."""
|
|
self.data[self.address] = self.data[self.address] - 1
|
|
|
|
def output(self):
|
|
"""Outputs the current value of the data tape to stdout."""
|
|
sys.stdout.write(chr(self.data[self.address]))
|
|
|
|
def read(self):
|
|
"""Writes from stdin one byte into the data tape."""
|
|
self.data[self.address] = sys.stdin.read(1)
|
|
|
|
def open_bracket(self):
|
|
"""If the current value of the data tape is 0, jumps to the next
|
|
matching close_bracket (denoted by opcode '111')."""
|
|
if self.data[self.address] == 0:
|
|
curr_byte = self.code[self.instruction_pointer]
|
|
num_unresolved_pairs = 1
|
|
while num_unresolved_pairs > 0:
|
|
self.instruction_pointer += 1
|
|
curr_byte = self.get_op_code_at(self.instruction_pointer)
|
|
if curr_byte == '110':
|
|
num_unresolved_pairs += 1
|
|
elif curr_byte == '111':
|
|
num_unresolved_pairs -= 1
|
|
|
|
def close_bracket(self):
|
|
"""If the current value of the data tape is 0, jumps back to
|
|
the next matching open_bracket (denoted by opcode '110')."""
|
|
if self.data[self.address] != 0:
|
|
curr_byte = None
|
|
num_unresolved_pairs = 1
|
|
while num_unresolved_pairs > 0:
|
|
self.instruction_pointer -= 1
|
|
curr_byte = self.get_op_code_at(self.instruction_pointer)
|
|
if curr_byte == '110':
|
|
num_unresolved_pairs -= 1
|
|
elif curr_byte == '111':
|
|
num_unresolved_pairs += 1
|
|
|
|
def get_op_code_at(self, index):
|
|
"""Returns the opcode stored in the code tape at the given
|
|
index. Reads it from the file if it's not available. Assumes
|
|
that if the given index is not already in the code tape, we have
|
|
to read the next opcode from the word generator.
|
|
|
|
:param index: The index on the code tape
|
|
:return: The value of the code tape at the given index
|
|
|
|
"""
|
|
if index not in self.code:
|
|
self.code[index] = self.get_op_code()
|
|
return self.code[index]
|
|
|
|
def get_op_code(self):
|
|
""""""
|
|
words = []
|
|
while len(words) < 3:
|
|
words.append(next(self.word_generator))
|
|
return words_to_bits(words)
|
|
|
|
def execute_code(self):
|
|
"""Executes code by getting the opcodes one at a time and
|
|
executing them sequentially."""
|
|
op_code = self.get_op_code_at(self.instruction_pointer)
|
|
command = self.commands[op_code]
|
|
command()
|
|
self.instruction_pointer += 1
|
|
|
|
|
|
def main():
|
|
"""Main function for this interpreter command line interface."""
|
|
|
|
# Check num args
|
|
if len(sys.argv) < 2:
|
|
sys.exit("Usage: {} <input file>")
|
|
|
|
input_file = open(sys.argv[1])
|
|
bookworm = BookwormInterpreter(input_file)
|
|
|
|
while True:
|
|
try:
|
|
bookworm.execute_code()
|
|
except StopIteration:
|
|
break
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|