def preprocessor_constants(lines): """ Replaces constants usage with the defined vaule. Example: $const = 5 MOV [2] $const Results in: MOV [2] 5 :type lines: list[Line] """ global automem_counter automem_counter = count() constants = {} for lineno, line in enumerate(lines): tokens = [] iterator = iter(line.contents.split()) is_assignment_line = False # Process all tokens in this line for token, next_token in neighborhood(iterator): if token[0] == '$': const_name = token[1:] if next_token == '=': # Found assignment, store the associated value is_assignment_line = True value = next(iterator) if const_name in constants: warn('Redefined ${}'.format(const_name), RedefinitionWarning, line) if value == '[_]': # Process auto increment memory value = automem(line) constants[const_name] = value else: # Found usage of constant, replace with stored value try: tokens.append(constants[const_name]) except KeyError: fatal_error('No such constant: ${}'.format(const_name), NoSuchConstantError, line) else: # Uninteresting token tokens.append(token) # Skip assignment lines if not is_assignment_line: debug('Constants:', constants) yield set_contents(line, ' '.join(tokens))
def assemble(code): """ :type code: list[Line] """ assert isinstance(code, list) hexcode = [] for line in code: iterator = iter(line.contents.split()) for mnem in iterator: debug('Processing token:', mnem) # Look up token in instructions list try: instruction = instructions[mnem.upper()] except KeyError: fatal_error('Unknown mnemonic: {}'.format(mnem), UnknownMnemonicError, line) debug('Instruction:', instruction) # Get arguments, look up arg count from first opcode opcode_def = list(instruction.values())[0] num_args = len(opcode_def) debug('Expected number of arguments:', num_args) arg_list = [next(iterator) for _ in range(num_args)] arg_types = [get_arg_type(arg) for arg in arg_list] debug('Arguments:', arg_list) debug('Argument types:', arg_types) # Find matching instruction opcode = None for op in instruction: # Check if the list of type arguments matches opcode_args = instruction[op] if opcode_args == tuple(arg_types): opcode = op if opcode is None: arg_str = ', '.join(t.name for t in arg_types) msg = 'Unknown argument types for mnemonic {} and ' \ 'given arguments: {}'.format(mnem, arg_str) fatal_error(msg, AssemblerSyntaxError, line) # Convert arguments to hex # 1. Strip '[ ]' arg_list = [arg.strip('[]') for arg in arg_list] # 2. Convert to hex arg_list = [to_hex(arg) for arg in arg_list] debug('Arguments (hex):', arg_list) # Finally, create the opcode/hex string hexcode.append('{} {}'.format(opcode, ' '.join(arg_list)).strip()) debug('') return ' '.join(hexcode)
def process_call(line, contents, subroutines): contents = contents.replace('@call', '') contents = contents.strip('()') parts = contents.split(',') name = parts[0] args = [s.strip(' )') for s in parts[1:]] if name not in subroutines: fatal_error('Unknown subroutine: {}'.format(name), AssemblerNameError, line) if len(args) != subroutines[name]: msg = 'Wrong number of arguments: Expected {}, got {}'.format( subroutines[name], len(args) ) fatal_error(msg, AssemblerException, line) debug('@call of {}'.format(name)) for i, arg in enumerate(args): yield build_line('MOV $arg{} {}'.format(i, arg)) counter = next(call_counter) yield build_line('MOV $jump_back :ret{}'.format(counter)) yield build_line('JMP :{}'.format(name)) yield build_line('ret{}:'.format(counter))
def process_call(line, contents, subroutines): contents = contents.replace('@call', '') contents = contents.strip('()') parts = contents.split(',') name = parts[0] args = [s.strip(' )') for s in parts[1:]] if name not in subroutines: fatal_error('Unknown subroutine: {}'.format(name), AssemblerNameError, line) if len(args) != subroutines[name]: msg = 'Wrong number of arguments: Expected {}, got {}'.format( subroutines[name], len(args)) fatal_error(msg, AssemblerException, line) debug('@call of {}'.format(name)) for i, arg in enumerate(args): yield build_line('MOV $arg{} {}'.format(i, arg)) counter = next(call_counter) yield build_line('MOV $jump_back :ret{}'.format(counter)) yield build_line('JMP :{}'.format(name)) yield build_line('ret{}:'.format(counter))
def automem(line): counter = next(automem_counter) if counter >= MEMORY_SIZE: fatal_error('[_]: No more memory slots left!', AssemblerException, line) return '[{}]'.format(counter)
def instr_jump(self, dest): """ Move the instruction pointer to dest. """ assert dest is not None, 'Tried to jump to None' if self.prev_instr_pointer == dest: fatal_error('Stuck in infinite loop!', VirtualRuntimeError) self.prev_instr_pointer = self.instr_pointer self.instr_pointer = dest self.jumping = True
def preprocessor_subroutine(lines): reset_counters() subroutines = collect_definitions(lines) if not subroutines: # Check, if there are calls w/o definition for line in lines: if '@call' in line.contents: fatal_error('@call without subroutine definition', AssemblerException, line) yield from lines return debug('{} subroutines found: {}'.format(len(subroutines), subroutines)) # Build preamble yield build_line('$return = [_]') yield build_line('$jump_back = [_]') for i in range(max(subroutines.values())): yield build_line('$arg{} = [_]'.format(i)) # Process start()/end()/call() in_subroutine = False call_count = 0 for line in lines: #: :type: str contents = line.contents.strip() if contents.startswith('@call'): yield from process_call(line, contents, subroutines) call_count += 1 elif contents.startswith('@start('): if in_subroutine: assert False in_subroutine = True yield from _subroutine_process_start(line, contents) elif contents.startswith('@end()'): if not in_subroutine: assert False debug('@end') in_subroutine = False yield Line(0, '<subroutine>', '', 'JMP $jump_back') else: yield line
def preprocessor_import(lines): """ Process import directives. Example: a.asm: APRINT '!' b.asm: #include a.asm HALT Results in: APRINT '!' HALT Note: A file will be imported only once. A file cannot import the importing file. :type lines: list[Line] """ lines = list(lines) included = set() # Files we already included while lines: # Take the first line from the stack line = lines.pop(0) contents = line.contents.strip() if contents.startswith('#import'): path = contents.split('#import')[1].strip() if path not in included: # Put the new contents to the top of the stack to_include = None try: to_include = read_file(path) except FileNotFoundError: fatal_error('File not found: {}'.format(path), FileNotFoundError, line) # Insert lines into the current position lines[0:0] = [ Line(i, path, l.strip(), l.strip()) for i, l in enumerate(to_include) ] included.add(path) else: # No includes to process, yield the line yield line
def preprocessor_import(lines): """ Process import directives. Example: a.asm: APRINT '!' b.asm: #include a.asm HALT Results in: APRINT '!' HALT Note: A file will be imported only once. A file cannot import the importing file. :type lines: list[Line] """ lines = list(lines) included = set() # Files we already included while lines: # Take the first line from the stack line = lines.pop(0) contents = line.contents.strip() if contents.startswith('#import'): path = contents.split('#import')[1].strip() if path not in included: # Put the new contents to the top of the stack to_include = None try: to_include = read_file(path) except FileNotFoundError: fatal_error('File not found: {}'.format(path), FileNotFoundError, line) # Insert lines into the current position lines[0:0] = [Line(i, path, l.strip(), l.strip()) for i, l in enumerate(to_include)] included.add(path) else: # No includes to process, yield the line yield line
def preprocessor_labels(lines): """ Replace labels with the referenced instruction number. Example: label: GOTO :label Results in: GOTO 0 :type lines: list[Line] """ labels = collect_labels(lines) # Update references for line in lines: tokens = [] for token in line.contents.split(): # Label usage if token[0] == ':': label_name = token[1:] try: instruction_no = labels[label_name] except KeyError: fatal_error('No such label: {}'.format(label_name), NoSuchLabelError, line) else: tokens.append(str(instruction_no)) # Label definitions elif token[-1] == ':': # Skip continue else: tokens.append(token) # If there any tokens left, yield them if tokens: debug('Labels:', labels) yield set_contents(line, ' '.join(tokens))
def collect_labels(lines): labels = {} token_count = 0 # Pass 1: Collect labels for line in lines: tokens = line.contents.split() for index, token in enumerate(tokens): if token[-1] == ':': # Label definition label = token[:-1] if label in labels: fatal_error('Redefinition of label: ' + label, RedefinitionError, line) labels[label] = token_count + index - len(labels) token_count += len(tokens) return labels
def run(self, asm, filename=None, preprocess=True): start = timer() # Tokenize code if preprocess: self.tokens = assembler.assembler_to_hex(asm, filename) self.tokens = self.tokens.split() # Main loop while self.running: self.jumping = False # Check bounds of instr_pointer if self.instr_pointer >= len(self.tokens): fatal_error('Reached end of code without seeing HALT', MissingHaltError, exit_func=self.halt) # Get current opcode opcode = self.tokens[self.instr_pointer] mnem = opcodes[opcode].upper() if self.debug: print('Instruction: {} ({})'.format(mnem, opcode)) # Look up instruction instruction_class = self.instructions[mnem] instruction = instruction_class(self) instruction_spec = instructions[mnem] annotations = get_annotations(instruction) # Look up number of arguments num_args = len(list(instruction_spec.values())[0]) if self.debug: print('Number of args:', num_args) print('Argument spec:', instruction_spec[opcode]) # Collect arguments if num_args: try: args = [self.process_arg(i, annotations, opcode) for i in range(num_args)] except IndexError: msg = 'Unexpectedly reached EOF. Maybe an argument is ' \ 'missing or a messed up jump occured' fatal_error(msg, VirtualRuntimeError, exit_func=self.halt) else: args = [] if self.debug: print('Arguments:', args) # Run instruction return_value = instruction(*args) # Process return value try: return_type = annotations['return'] except KeyError: return_type = None self.process_return_value(return_type, return_value) # Increase counters self.ticks += 1 if not self.jumping: self.prev_instr_pointer = self.instr_pointer self.instr_pointer += 1 # Skip current opcode self.instr_pointer += num_args # Skip arguments if self.debug: print('Memory:', self.memory) print() print() if self.debug: print() print('Exited after {} ticks in {:.5}s'.format(self.ticks, timer() - start)) return self.output.getvalue()