bookworm/bookworm.py
Ian Adam Naval 0e819c08f9 Fix typo
2014-09-30 22:38:02 -04:00

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()