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 = open(sys.argv[1]) bookworm = BookwormInterpreter(input_file) while True: try: bookworm.execute_code() except StopIteration: break if __name__ == '__main__': main()