def _codegen_org(context: Context, op: Op) -> Tuple[Context, Op]: """ORG pseudoinstruction: set current output stream position.""" # Try to parse our one argument. If successful, update our stream position. # Otherwise, leaving the op's `todo` unchanged means we'll try again later. op = op._replace(args=parse_args_if_able( _PARSE_OPTIONS, context, op, Type.ADDRESS)) if all_args_parsed(op.args): op = op._replace(hex='', todo=None) context = context._replace(pos=op.args[0].integer) return context, op
def _switch_arch( lineno: int, line: Text, context: Context, arch: Text, ) -> Context: """Switch the architecture we're generating code for.""" try: module = importlib.import_module('.' + arch, 'tsasm.codegen') context = context._replace(arch=arch, codegen=getattr(module, 'get_codegen')(), encode_str=getattr(module, 'encode_str')) except (ModuleNotFoundError, AttributeError): raise Error( lineno, line, 'Failed to load a code-generation library for architecture ' '{!r}'.format(arch)) return context
def assemble( context: Context, input_file: TextIO, output_file: BinaryIO, listing_file: Optional[TextIO], ): """Assemble source code from a file. Args: context: an assembler context. input_file: handle for file containing input source code. output_file: handle for file receiving binary output. listing_file: optional handle for file receiving a text listing. Raises: Error: if any error is encountered. """ # Load source code from the input. ops, lines = read_source(input_file) if not ops: raise Error(-1, '<EOF>', 'No code to compile in the input?') # Track where each op will commit its hex data to RAM. Entries are None when # we don't know that yet. addrs: List[Optional[int]] = [None] * len(ops) # Keep making passes through all of the ops until the number of pending # invocations of `asmpass_codegen` stops changing. num_ops_with_codegen_todos = None for pass_count in itertools.count(start=1): # At the beginning of the pass, reset the current output position to 0. context = context._replace(pos=0) # Perform a pass through the code. When catching errors, ValueErrors are # "normal" errors owing to bugs in user code; other types are "internal" # errors that are likely our fault. for i in range(len(ops)): # If the position of this op has already been calculated, that value is # authoritative. Otherwise, if we have new knowledge of this position, # and we're at or after code generation, save it. if addrs[i] is not None: context = context._replace(pos=addrs[i]) elif context.pos is not None and ops[i].todo is not asmpass_lexer: addrs[i] = context.pos # If this op has a `todo`, execute it and apply some checks. if ops[i].todo is not None: try: context, ops[i] = ops[i].todo(context, ops[i]) if ops[i].hex and len(ops[i].hex) % 2: raise Error(ops[i].lineno, ops[i].line, 'Extra nybble in generated hex.') except ValueError as error: raise Error(ops[i].lineno, ops[i].line, str(error)) except Exception as error: raise Error(ops[i].lineno, ops[i].line, 'Internal error, sorry!\n {}'.format(error)) # With the pass complete, see if it's time to stop. ops_with_codegen_todos = tuple(op for op in ops if op.todo == asmpass_codegen) if num_ops_with_codegen_todos == len(ops_with_codegen_todos): break num_ops_with_codegen_todos = len(ops_with_codegen_todos) # See if compilation was successful. if ops_with_codegen_todos: raise Error( ops[-1].lineno + 1, '<EOF>', 'After {} passes, {} statements still have unresolved labels or other ' 'issues preventing full assembly. These statements are:\n' ' {}\n'.format( pass_count, len(ops_with_codegen_todos), '\n '.join('{:>5}: {}'.format(op.lineno, op.line) for op in ops_with_codegen_todos))) # Construct a mapping from memory addresses to ops whose binary data will # start at those addresses. Complain if multiple ops that actually generate # binary data attempt to start in the same location. addr_to_op: Dict[int, Op] = {} for addr, op in zip(addrs, ops): maybe_old_op = addr_to_op.setdefault(addr, op) if maybe_old_op is not op and maybe_old_op.hex and op.hex: logging.warning( 'At memory location $%X: replacing previously-generated code.\n' ' old - %5d: %s\n new - %5d: %s', addr, maybe_old_op.lineno, maybe_old_op.line, op.lineno, op.line) # Write binary output. _emit_binary(output_file, addr_to_op) # Write listing. if listing_file: _emit_listing(listing_file, lines, addr_to_op)