def __init__(self, parser, properties, templates): self.parser = parser self.templates = TEMPLATES.copy() self.templates.update(templates) self.show_warnings = self._get_int_property(properties, 'warnings', 1) self.fields = parser.fields # Build a label dictionary self.labels = {} for entry in self.parser.memory_map: for instruction in entry.instructions: label = instruction.asm_label if label: self.labels[instruction.address] = label # Determine the base and end addresses self.base_address = 16384 self.end_address = 65535 if self.labels: self.base_address = min(self.labels) elif self.parser.memory_map: self.base_address = self.parser.memory_map[0].instructions[ 0].address if self.parser.memory_map: self.end_address = self.parser.memory_map[-1].instructions[ -1].address self.lower = self.case == CASE_LOWER # Field widths (line = indent + instruction + ' ; ' + comment) if self._get_int_property(properties, 'tab', 0): self.indent_width = 8 self.indent = '\t' else: self.indent_width = self._get_int_property(properties, 'indent', 2) self.indent = ' ' * self.indent_width self.instr_width = self._get_int_property(properties, 'instruction-width', 23) self.min_comment_width = self._get_int_property( properties, 'comment-width-min', 10) self.line_width = self._get_int_property(properties, 'line-width', 79) self.desc_width = self._get_text_width('comment') # Line terminator if self._get_int_property(properties, 'crlf', 0): self.end = '\r\n' else: self.end = '\n' # Label suffix if self._get_int_property(properties, 'label-colons', 1): self.label_suffix = ':' else: self.label_suffix = '' min_col_width = self._get_int_property(properties, 'wrap-column-width-min', 10) self.table_writer = TableWriter(self, self.desc_width, min_col_width, properties) self.handle_unsupported_macros = self._get_int_property( properties, 'handle-unsupported-macros', 0) self.snapshot = self.parser.snapshot self._snapshots = [(self.snapshot, '')] self.list_parser = ListParser(properties.get('bullet', '*')) self.to_chr = lambda n: chr(n) self.get_reg = lambda r: r self.space = ' ' self.pc = 0 self.macros = skoolmacro.get_macros(self) self.init()
class AsmWriter: def __init__(self, parser, properties, templates): self.parser = parser self.templates = TEMPLATES.copy() self.templates.update(templates) self.show_warnings = self._get_int_property(properties, 'warnings', 1) self.fields = parser.fields # Build a label dictionary self.labels = {} for entry in self.parser.memory_map: for instruction in entry.instructions: label = instruction.asm_label if label: self.labels[instruction.address] = label # Determine the base and end addresses self.base_address = 16384 self.end_address = 65535 if self.labels: self.base_address = min(self.labels) elif self.parser.memory_map: self.base_address = self.parser.memory_map[0].instructions[ 0].address if self.parser.memory_map: self.end_address = self.parser.memory_map[-1].instructions[ -1].address self.lower = self.case == CASE_LOWER # Field widths (line = indent + instruction + ' ; ' + comment) if self._get_int_property(properties, 'tab', 0): self.indent_width = 8 self.indent = '\t' else: self.indent_width = self._get_int_property(properties, 'indent', 2) self.indent = ' ' * self.indent_width self.instr_width = self._get_int_property(properties, 'instruction-width', 23) self.min_comment_width = self._get_int_property( properties, 'comment-width-min', 10) self.line_width = self._get_int_property(properties, 'line-width', 79) self.desc_width = self._get_text_width('comment') # Line terminator if self._get_int_property(properties, 'crlf', 0): self.end = '\r\n' else: self.end = '\n' # Label suffix if self._get_int_property(properties, 'label-colons', 1): self.label_suffix = ':' else: self.label_suffix = '' min_col_width = self._get_int_property(properties, 'wrap-column-width-min', 10) self.table_writer = TableWriter(self, self.desc_width, min_col_width, properties) self.handle_unsupported_macros = self._get_int_property( properties, 'handle-unsupported-macros', 0) self.snapshot = self.parser.snapshot self._snapshots = [(self.snapshot, '')] self.list_parser = ListParser(properties.get('bullet', '*')) self.to_chr = lambda n: chr(n) self.get_reg = lambda r: r self.space = ' ' self.pc = 0 self.macros = skoolmacro.get_macros(self) self.init() # API def init(self): """Perform post-initialisation operations. This method is called after `__init__()` has completed. By default the method does nothing, but subclasses may override it. """ return # API @property def base(self): return self.parser.base # API @property def case(self): return self.parser.case def _get_int_property(self, properties, name, default): try: return int(properties[name]) except (KeyError, ValueError): return default def _get_text_width(self, template_name, **subs): template = self.templates[template_name] text_f = '{text' if text_f in template: text_f = skoolmacro.parse_strings(template, template.index(text_f), 1)[1] return self.line_width - len( template.replace(text_f, 'text').format(text='', **subs)) def warn(self, s): if self.show_warnings: warn(s) def format_template(self, name, fields): return format_template(self.templates.get(name, ''), name, **fields) def write(self): for index, entry in enumerate(self.parser.memory_map): self.pc = entry.address self.print_blocks(entry.headers) if index == 0: self.print_equs(self.parser.equs) if entry.instructions[0].org: subs = { 'indent': self.indent, 'org': 'ORG', 'address': self.parser.convert_address_operand( entry.instructions[0].org) } if self.lower: subs['org'] = 'org' self.write_line(self.format_template('org', subs)) self.write_line('') self.entry = entry self.print_entry() self.write_line('') self.print_blocks(entry.footers) def print_blocks(self, blocks): for block in blocks: if block: for line in block: self.write_line(line) self.write_line('') def print_equs(self, equs): if equs: if self.lower: equ_dir = 'equ' else: equ_dir = 'EQU' for label, value in equs: subs = { 'label': label, 'equ': equ_dir, 'value': self.parser.convert_equ_value(value) } self.write_line(self.format_template('equ', subs)) self.write_line('') def print_entry(self): self.print_comment_lines([self.entry.description], ignoreua=self.entry.ignoreua['t']) if self.entry.details: self.print_comment_lines(self.entry.details, ignoreua=self.entry.ignoreua['d'], started=True) if self.entry.registers: self.print_registers() self.print_instructions() if self.entry.end_comment: self.print_comment_lines(self.entry.end_comment, ignoreua=self.entry.ignoreua['e']) def write_line(self, s): write_text('{0}{1}'.format(s, self.end)) def pop_snapshot(self): """Replace the current memory snapshot with the one most recently saved by :meth:`~skoolkit.skoolasm.AsmWriter.push_snapshot`.""" if len(self._snapshots) < 2: raise SkoolKitError( "Cannot pop snapshot when snapshot stack is empty") self.snapshot[:] = self._snapshots.pop()[0] def push_snapshot(self, name=''): """Save a copy of the current memory snapshot for later retrieval (by :meth:`~skoolkit.skoolasm.AsmWriter.pop_snapshot`). :param name: An optional name for the snapshot. """ self._snapshots.append((self.snapshot[:], name)) def expand_font(self, text, index): if self.handle_unsupported_macros: return skoolmacro.parse_font(text, index)[0], '' raise skoolmacro.UnsupportedMacroError() def expand_html(self, text, index): end, message = skoolmacro.parse_html(text, index) return end, '' def expand_include(self, text, index): end, paragraphs, section = skoolmacro.parse_include(text, index) return end, '' def expand_link(self, text, index): end, page_id, anchor, link_text = skoolmacro.parse_link(text, index) if not link_text: raise skoolmacro.MacroParsingError( "Blank link text: #LINK{}".format(text[index:end])) return end, link_text def expand_list(self, text, index): return self._ignore_block(text, index, LIST_MARKER, LIST_END_MARKER) def expand_r(self, text, index): end, addr_str, address, code_id, anchor, link_text = skoolmacro.parse_r( text, index) if link_text: return end, link_text if code_id: return end, self.parser.get_instruction_addr_str( address, addr_str, code_id) label = self.labels.get(address) if label is None: if self.base_address <= address <= self.end_address: self.warn( 'Could not convert address {} to label'.format(addr_str)) label = self.parser.get_instruction_addr_str(address, addr_str) return end, label def expand_scr(self, text, index): if self.handle_unsupported_macros: return skoolmacro.parse_scr(text, index)[0], '' raise skoolmacro.UnsupportedMacroError() def expand_table(self, text, index): return self._ignore_block(text, index, TABLE_MARKER, TABLE_END_MARKER) def expand_udg(self, text, index): if self.handle_unsupported_macros: return skoolmacro.parse_udg(text, index)[0], '' raise skoolmacro.UnsupportedMacroError() def expand_udgarray(self, text, index): if self.handle_unsupported_macros: if index < len(text) and text[index] == '*': end = skoolmacro.parse_udgarray_with_frames(text, index)[0] else: end = skoolmacro.parse_udgarray(text, index)[0] return end, '' raise skoolmacro.UnsupportedMacroError() def expand_udgtable(self, text, index): return self._ignore_block(text, index, UDGTABLE_MARKER, TABLE_END_MARKER, '') # API def expand(self, text): """Return `text` with skool macros expanded.""" return skoolmacro.expand_macros(self, text).strip() def _ignore_block(self, text, index, marker, end_marker, rep=None): try: end = text.index(end_marker, index) + len(end_marker) except ValueError: raise SkoolParsingError("Missing end marker: {}...".format( text[index - len(marker):index + 15])) if rep is None: rep = BLOCK_SEP + text[index - len(marker):end] + BLOCK_SEP return -end, rep def format(self, text, width): lines = [] for index, block in enumerate(self.expand(text).split(BLOCK_SEP)): if index % 2 == 0: if block: lines.extend(wrap(block, width)) elif block.startswith(TABLE_MARKER): table_lines = self.table_writer.format_table( block[len(TABLE_MARKER):].lstrip()) if table_lines: table_width = max([len(line) for line in table_lines]) if table_width > width: self.warn( 'Table in entry at {0} is {1} characters wide'. format(self.entry.address, table_width)) lines.extend(table_lines) elif block.startswith(LIST_MARKER): list_obj = self.list_parser.parse_list( self, block[len(LIST_MARKER):].lstrip()) for item in list_obj.items: item_lines = [] if list_obj.bullet: prefix = list_obj.bullet + ' ' indent = ' ' * len(prefix) else: prefix = indent = '' for line in wrap(item, width - len(prefix)): item_lines.append(prefix + line) prefix = indent lines.extend(item_lines) return lines def print_paragraph_separator(self): self.write_line(self.format_template('comment', {'text': ''}).rstrip()) def print_comment_lines(self, paragraphs, instruction=None, ignoreua=False, started=False): for paragraph in paragraphs: lines = self.format(paragraph, self.desc_width) if started and lines: self.print_paragraph_separator() if lines: started = True for line in lines: if not ignoreua: uaddress = self.find_unconverted_address(line) if uaddress: if instruction: if not instruction.ignoremrcua: self.warn( 'Comment above {0} contains address ({1}) not converted to a label:\n; {2}' .format(instruction.address, uaddress, line)) else: self.warn( 'Comment contains address ({0}) not converted to a label:\n; {1}' .format(uaddress, line)) self.write_line( self.format_template('comment', { 'text': line }).rstrip()) def print_registers(self): self.print_paragraph_separator() max_reg_len = max([len(reg.name) for reg in self.entry.registers]) prefix_len = max([len(reg.prefix) for reg in self.entry.registers]) if prefix_len: prefix_len += 1 for reg in self.entry.registers: if reg.prefix: reg.prefix += ':' subs = { 'max_reg_len': max_reg_len, 'prefix': reg.prefix, 'prefix_len': prefix_len, 'reg': reg.name, 'reg_len': len(reg.name) } reg_lines = [] for line in self.format(reg.contents, self._get_text_width('register', ** subs)) or ['']: subs['text'] = line reg_lines.append( self.format_template('register', subs).rstrip()) subs['prefix'] = subs['reg'] = '' reg_desc = '\n'.join(reg_lines) if not self.entry.ignoreua['r']: uaddress = self.find_unconverted_address(reg_desc) if uaddress: self.warn( 'Register description contains address ({}) not converted to a label:\n{}' .format(uaddress, reg_desc)) self.write_line(reg_desc) def print_instruction_prefix(self, instruction, index): if instruction.mid_block_comment: if index == 0: self.print_paragraph_separator() self.print_comment_lines(instruction.mid_block_comment, instruction) if instruction.asm_label: subs = { 'label': instruction.asm_label, 'suffix': self.label_suffix } self.write_line(self.format_template('label', subs)) def find_unconverted_address(self, text): for match in re.finditer( '(\A|\s|\()((?:0x|\$)[0-9A-Fa-f]{4}|[1-9][0-9]{2,4})(?!([0-9A-Za-z]|[./*+][0-9]))', text): addr = match.group(2) if addr.startswith(('0x', '$')): if self.base_address <= int(addr[-4:], 16) <= self.end_address: return addr elif max(self.base_address, 257) <= int(addr) <= self.end_address: return addr def print_instructions(self): i = 0 rows = 0 lines = [] instructions = self.entry.instructions while i < len(instructions) or lines: if i < len(instructions): instruction = instructions[i] else: instruction = None # Deal with remaining comment lines or rowspan on the previous # instruction if lines or rows: if rows: self.print_instruction_prefix(instruction, i) operation = instruction.operation rows -= 1 i += 1 else: operation = '' subs = { 'indent': self.indent, 'operation': operation, 'width': instr_width, 'sep': ';', 'text': '' } if lines: subs['text'] = lines.pop(0) elif rowspan == 1: subs['sep'] = '' oline = self.format_template('instruction', subs).rstrip() if not ignoreua: uaddress = self.find_unconverted_address(subs['text']) if uaddress: self.warn( 'Comment at {} contains address ({}) not converted to a label:\n{}' .format(self.pc, uaddress, oline)) self.write_line(oline) if len(oline) > self.line_width: self.warn('Line is {0} characters long:\n{1}'.format( len(oline), oline)) continue ignoreua = instruction.ignoreua self.pc = instruction.address rowspan = rows = instruction.comment.rowspan instr_width = max( [len(i.operation) for i in instructions[i:i + rowspan]] + [self.instr_width]) comment_width = self.line_width - 3 - instr_width - self.indent_width lines = self.format(instruction.comment.text, max(comment_width, self.min_comment_width))
def __init__(self, parser, properties, lower): self.parser = parser self.base = parser.base self.show_warnings = self._get_int_property(properties, 'warnings', 1) # Build a label dictionary self.labels = {} for entry in self.parser.memory_map: for instruction in entry.instructions: label = instruction.asm_label if label: self.labels[instruction.address] = label # Determine the base and end addresses self.base_address = 16384 self.end_address = 65535 if self.labels: self.base_address = min([address for address in self.labels.keys()]) elif self.parser.memory_map: self.base_address = self.parser.memory_map[0].instructions[0].address if self.parser.memory_map: self.end_address = self.parser.memory_map[-1].instructions[-1].address self.bullet = properties.get('bullet', '*') self.lower = lower # Field widths (line = indent + instruction + ' ; ' + comment) if self._get_int_property(properties, 'tab', 0): self.indent_width = 8 self.indent = '\t' else: self.indent_width = self._get_int_property(properties, 'indent', 2) self.indent = ' ' * self.indent_width self.instr_width = self._get_int_property(properties, 'instruction-width', DEF_INSTRUCTION_WIDTH) self.min_comment_width = self._get_int_property(properties, 'comment-width-min', 10) self.line_width = self._get_int_property(properties, 'line-width', 79) self.desc_width = self.line_width - 2 # Line terminator if self._get_int_property(properties, 'crlf', 0): self.end = '\r\n' else: self.end = '\n' # Label suffix if self._get_int_property(properties, 'label-colons', 1): self.label_suffix = ':' else: self.label_suffix = '' min_col_width = self._get_int_property(properties, 'wrap-column-width-min', 10) self.table_writer = TableWriter(self, self.desc_width, min_col_width) self.handle_unsupported_macros = self._get_int_property(properties, 'handle-unsupported-macros', 0) self.snapshot = self.parser.snapshot self._snapshots = [(self.snapshot, '')] self.list_parser = ListParser() self.macros = skoolmacro.get_macros(self)
class AsmWriter: def __init__(self, parser, properties, lower): self.parser = parser self.base = parser.base self.show_warnings = self._get_int_property(properties, 'warnings', 1) # Build a label dictionary self.labels = {} for entry in self.parser.memory_map: for instruction in entry.instructions: label = instruction.asm_label if label: self.labels[instruction.address] = label # Determine the base and end addresses self.base_address = 16384 self.end_address = 65535 if self.labels: self.base_address = min([address for address in self.labels.keys()]) elif self.parser.memory_map: self.base_address = self.parser.memory_map[0].instructions[0].address if self.parser.memory_map: self.end_address = self.parser.memory_map[-1].instructions[-1].address self.bullet = properties.get('bullet', '*') self.lower = lower # Field widths (line = indent + instruction + ' ; ' + comment) if self._get_int_property(properties, 'tab', 0): self.indent_width = 8 self.indent = '\t' else: self.indent_width = self._get_int_property(properties, 'indent', 2) self.indent = ' ' * self.indent_width self.instr_width = self._get_int_property(properties, 'instruction-width', DEF_INSTRUCTION_WIDTH) self.min_comment_width = self._get_int_property(properties, 'comment-width-min', 10) self.line_width = self._get_int_property(properties, 'line-width', 79) self.desc_width = self.line_width - 2 # Line terminator if self._get_int_property(properties, 'crlf', 0): self.end = '\r\n' else: self.end = '\n' # Label suffix if self._get_int_property(properties, 'label-colons', 1): self.label_suffix = ':' else: self.label_suffix = '' min_col_width = self._get_int_property(properties, 'wrap-column-width-min', 10) self.table_writer = TableWriter(self, self.desc_width, min_col_width) self.handle_unsupported_macros = self._get_int_property(properties, 'handle-unsupported-macros', 0) self.snapshot = self.parser.snapshot self._snapshots = [(self.snapshot, '')] self.list_parser = ListParser() self.macros = skoolmacro.get_macros(self) def _get_int_property(self, properties, name, default): try: return int(properties[name]) except (KeyError, ValueError): return default def warn(self, s): if self.show_warnings: warn(s) def write(self): self.print_header(self.parser.header) for entry in self.parser.memory_map: first_instruction = entry.instructions[0] org = first_instruction.org if org: if self.lower: org_dir = 'org' else: org_dir = 'ORG' org_addr_str = self.parser.convert_address_operand(org) self.write_line('{0}{1} {2}'.format(self.indent, org_dir, org_addr_str)) self.write_line('') self.entry = entry self.print_entry() self.write_line('') def print_header(self, header): if header: for line in header: self.write_line(('; ' + line).rstrip()) self.write_line('') def print_entry(self): self.print_comment_lines([self.entry.description], ignoreua=self.entry.ignoreua['t']) if self.entry.details: self.print_comment_lines(self.entry.details, ignoreua=self.entry.ignoreua['d'], started=True) if self.entry.registers: self.print_registers() self.print_instructions() if self.entry.end_comment: self.print_comment_lines(self.entry.end_comment, ignoreua=self.entry.ignoreua['e']) def write_line(self, s): write_text('{0}{1}'.format(s, self.end)) def pop_snapshot(self): """Discard the current memory snapshot and replace it with the one that was most recently saved (by :meth:`~skoolkit.skoolasm.AsmWriter.push_snapshot`).""" if len(self._snapshots) < 2: raise SkoolKitError("Cannot pop snapshot when snapshot stack is empty") self.snapshot = self._snapshots.pop()[0] def push_snapshot(self, name=''): """Save the current memory snapshot for later retrieval (by :meth:`~skoolkit.skoolasm.AsmWriter.pop_snapshot`), and put a copy in its place. :param name: An optional name for the snapshot. """ self._snapshots.append((self.snapshot[:], name)) def needs_cwd(self): return False def expand_bug(self, text, index): end, item, link_text = skoolmacro.parse_bug(text, index) return end, link_text or 'bug' def expand_call(self, text, index): return skoolmacro.parse_call(text, index, self) def expand_chr(self, text, index): end, num = skoolmacro.parse_chr(text, index) return end, get_chr(num) def expand_d(self, text, index): return skoolmacro.parse_d(text, index, self.parser) def expand_erefs(self, text, index): return skoolmacro.parse_erefs(text, index, self.parser) def expand_eval(self, text, index): return skoolmacro.parse_eval(text, index) def expand_fact(self, text, index): end, item, link_text = skoolmacro.parse_fact(text, index) return end, link_text or 'fact' def expand_font(self, text, index): if self.handle_unsupported_macros: return skoolmacro.parse_font(text, index)[0], '' raise UnsupportedMacroError() def expand_for(self, text, index): return skoolmacro.parse_for(text, index) def expand_foreach(self, text, index): return skoolmacro.parse_foreach(text, index, self.parser) def expand_html(self, text, index): end, message = skoolmacro.parse_html(text, index) return end, '' def expand_if(self, text, index): return skoolmacro.parse_if(text, index) def expand_include(self, text, index): end, paragraphs, section = skoolmacro.parse_include(text, index) return end, '' def expand_link(self, text, index): end, page_id, anchor, link_text = skoolmacro.parse_link(text, index) if not link_text: raise MacroParsingError("Blank link text: #LINK{}".format(text[index:end])) return end, link_text def expand_map(self, text, index): return skoolmacro.parse_map(text, index) def expand_n(self, text, index): return skoolmacro.parse_n(text, index, self.base == BASE_16, self.lower) def expand_peek(self, text, index): return skoolmacro.parse_peek(text, index, self.snapshot) def expand_poke(self, text, index): end, item, link_text = skoolmacro.parse_poke(text, index) return end, link_text or 'poke' def expand_pokes(self, text, index): return skoolmacro.parse_pokes(text, index, self.snapshot) def expand_pops(self, text, index): return skoolmacro.parse_pops(text, index, self) def expand_pushs(self, text, index): return skoolmacro.parse_pushs(text, index, self) def expand_r(self, text, index): end, addr_str, address, code_id, anchor, link_text = skoolmacro.parse_r(text, index) if link_text: return end, link_text if code_id: return end, self.parser.get_instruction_addr_str(address, code_id) label = self.labels.get(address) if label is None: if self.base_address <= address <= self.end_address: self.warn('Could not convert address {} to label'.format(addr_str)) label = self.parser.get_instruction_addr_str(address) if label is None: if addr_str.startswith('$'): label = addr_str[1:] else: label = addr_str return end, label def expand_refs(self, text, index): return skoolmacro.parse_refs(text, index, self.parser) def expand_reg(self, text, index): return skoolmacro.parse_reg(text, index, self.lower) def expand_scr(self, text, index): if self.handle_unsupported_macros: return skoolmacro.parse_scr(text, index)[0], '' raise UnsupportedMacroError() def expand_space(self, text, index): return skoolmacro.parse_space(text, index, ' ') def expand_udg(self, text, index): if self.handle_unsupported_macros: return skoolmacro.parse_udg(text, index)[0], '' raise UnsupportedMacroError() def expand_udgarray(self, text, index): if self.handle_unsupported_macros: if index < len(text) and text[index] == '*': end = skoolmacro.parse_udgarray_with_frames(text, index)[0] else: end = skoolmacro.parse_udgarray(text, index)[0] return end, '' raise UnsupportedMacroError() def expand(self, text): """Return `text` with skool macros expanded.""" return skoolmacro.expand_macros(self, text).strip() def find_markers(self, block_indexes, text, marker, end_marker): index = 0 while text.find(marker, index) >= 0: block_index = text.index(marker, index) try: block_end_index = text.index(end_marker, block_index) + len(end_marker) except ValueError: raise SkoolParsingError("Missing end marker: {}...".format(text[block_index:block_index + len(marker) + 15])) block_indexes.append(block_index) block_indexes.append(block_end_index) index = block_end_index def extract_blocks(self, text): # Find table and list markers block_indexes = [] self.find_markers(block_indexes, text, TABLE_MARKER, TABLE_END_MARKER) self.find_markers(block_indexes, text, UDGTABLE_MARKER, TABLE_END_MARKER) self.find_markers(block_indexes, text, LIST_MARKER, LIST_END_MARKER) # Extract blocks blocks = [] all_indexes = [0] all_indexes += block_indexes all_indexes.sort() all_indexes.append(len(text)) for i in range(len(all_indexes) - 1): start = all_indexes[i] end = all_indexes[i + 1] block = text[start:end].strip() if block and not block.startswith(UDGTABLE_MARKER): blocks.append(block) return blocks def format(self, text, width): lines = [] for block in self.extract_blocks(text): if block.startswith(TABLE_MARKER): table_lines = self.table_writer.format_table(block[len(TABLE_MARKER):].lstrip()) if table_lines: table_width = max([len(line) for line in table_lines]) if table_width > width: self.warn('Table in entry at {0} is {1} characters wide'.format(self.entry.address, table_width)) lines.extend(table_lines) elif block.startswith(LIST_MARKER): list_obj = self.list_parser.parse_list(self, block[len(LIST_MARKER):].lstrip()) for item in list_obj.items: item_lines = [] bullet = self.bullet indent = ' ' * len(bullet) for line in wrap(item, width - len(bullet) - 1): item_lines.append('{0} {1}'.format(bullet, line)) bullet = indent lines.extend(item_lines) else: lines.extend(wrap(self.expand(block), width)) return lines def print_comment_lines(self, paragraphs, instruction=None, ignoreua=False, started=False): for paragraph in paragraphs: lines = self.format(paragraph, self.desc_width) if started and lines: self.write_line(';') if lines: started = True for line in lines: if not ignoreua: uaddress = self.find_unconverted_address(line) if uaddress: if instruction: if not instruction.ignoremrcua: self.warn('Comment above {0} contains address ({1}) not converted to a label:\n; {2}'.format(instruction.address, uaddress, line)) else: self.warn('Comment contains address ({0}) not converted to a label:\n; {1}'.format(uaddress, line)) self.write_line('; {0}'.format(line).rstrip()) def print_registers(self): self.write_line(';') prefix_len = max([len(reg.prefix) for reg in self.entry.registers]) if prefix_len: prefix_len += 1 indent = ''.ljust(prefix_len) for reg in self.entry.registers: if reg.prefix: prefix = '{}:'.format(reg.prefix).rjust(prefix_len) else: prefix = indent reg_label = prefix + reg.name reg_desc = self.expand(reg.contents) if not self.entry.ignoreua['r']: uaddress = self.find_unconverted_address(reg_desc) if uaddress: line = '; {} {}'.format(reg_label, reg_desc) self.warn('Register description contains address ({}) not converted to a label:\n{}'.format(uaddress, line)) if reg_desc: desc_indent = len(reg_label) + 1 desc_lines = wrap(reg_desc, self.desc_width - desc_indent) self.write_line('; {} {}'.format(reg_label, desc_lines[0])) desc_prefix = ''.ljust(desc_indent) for line in desc_lines[1:]: self.write_line('; {}{}'.format(desc_prefix, line)) else: self.write_line('; {}'.format(reg_label)) def print_instruction_prefix(self, instruction, index): if instruction.mid_block_comment: if index == 0: self.write_line(';') self.print_comment_lines(instruction.mid_block_comment, instruction) if instruction.asm_label: self.write_line("{0}{1}".format(instruction.asm_label, self.label_suffix)) def find_unconverted_address(self, text): for match in re.finditer('(\A|\s|\()((?:0x|\$)[0-9A-Fa-f]{4}|[1-9][0-9]{2,4})(?!([0-9A-Za-z]|[./*+][0-9]))', text): addr = match.group(2) if addr.startswith(('0x', '$')): if self.base_address <= int(addr[-4:], 16) <= self.end_address: return addr elif max(self.base_address, 257) <= int(addr) <= self.end_address: return addr def print_instructions(self): i = 0 rowspan = 0 lines = [] instructions = self.entry.instructions while i < len(instructions) or lines: if i < len(instructions): instruction = instructions[i] else: instruction = None # Deal with remaining comment lines or rowspan on the previous # instruction if lines or rowspan > 0: if rowspan > 0: self.print_instruction_prefix(instruction, i) operation = instruction.operation rowspan -= 1 i += 1 else: operation = '' if lines: line_comment = lines.pop(0) oline = '{0}{1} ; {2}'.format(self.indent, operation.ljust(instr_width), line_comment) else: line_comment = '' oline = '{0}{1}'.format(self.indent, operation) self.write_line(oline) if not ignoreua: uaddress = self.find_unconverted_address(line_comment) if uaddress: self.warn('Comment at {0} contains address ({1}) not converted to a label:\n{2}'.format(iaddress, uaddress, oline)) if len(oline) > self.line_width: self.warn('Line is {0} characters long:\n{1}'.format(len(oline), oline)) continue # pragma: no cover ignoreua = instruction.ignoreua iaddress = instruction.address rowspan = instruction.comment.rowspan instr_width = max(len(instruction.operation), self.instr_width) comment_width = self.line_width - 3 - instr_width - self.indent_width lines = wrap(self.expand(instruction.comment.text), max((comment_width, self.min_comment_width)))
class AsmWriter: def __init__(self, parser, properties): self.parser = parser self.show_warnings = self._get_int_property(properties, 'warnings', 1) self.fields = parser.fields # Build a label dictionary self.labels = {} for entry in self.parser.memory_map: for instruction in entry.instructions: label = instruction.asm_label if label: self.labels[instruction.address] = label # Determine the base and end addresses self.base_address = 16384 self.end_address = 65535 if self.labels: self.base_address = min([address for address in self.labels.keys()]) elif self.parser.memory_map: self.base_address = self.parser.memory_map[0].instructions[0].address if self.parser.memory_map: self.end_address = self.parser.memory_map[-1].instructions[-1].address self.lower = self.case == CASE_LOWER # Field widths (line = indent + instruction + ' ; ' + comment) if self._get_int_property(properties, 'tab', 0): self.indent_width = 8 self.indent = '\t' else: self.indent_width = self._get_int_property(properties, 'indent', 2) self.indent = ' ' * self.indent_width self.instr_width = self._get_int_property(properties, 'instruction-width', DEF_INSTRUCTION_WIDTH) self.min_comment_width = self._get_int_property(properties, 'comment-width-min', 10) self.line_width = self._get_int_property(properties, 'line-width', 79) self.desc_width = self.line_width - 2 # Line terminator if self._get_int_property(properties, 'crlf', 0): self.end = '\r\n' else: self.end = '\n' # Label suffix if self._get_int_property(properties, 'label-colons', 1): self.label_suffix = ':' else: self.label_suffix = '' min_col_width = self._get_int_property(properties, 'wrap-column-width-min', 10) self.table_writer = TableWriter(self, self.desc_width, min_col_width) self.handle_unsupported_macros = self._get_int_property(properties, 'handle-unsupported-macros', 0) self.snapshot = self.parser.snapshot self._snapshots = [(self.snapshot, '')] self.list_parser = ListParser(properties.get('bullet', '*')) self.to_chr = lambda n: chr(n) self.get_reg = lambda r: r self.space = ' ' self.macros = skoolmacro.get_macros(self) self.init() # API def init(self): """Perform post-initialisation operations. This method is called after `__init__()` has completed. By default the method does nothing, but subclasses may override it. """ return # API @property def base(self): return self.parser.base # API @property def case(self): return self.parser.case def _get_int_property(self, properties, name, default): try: return int(properties[name]) except (KeyError, ValueError): return default def warn(self, s): if self.show_warnings: warn(s) def write(self): for index, entry in enumerate(self.parser.memory_map): self.print_blocks(entry.headers) if index == 0: self.print_equs(self.parser.equs) first_instruction = entry.instructions[0] org = first_instruction.org if org: if self.lower: org_dir = 'org' else: org_dir = 'ORG' org_addr_str = self.parser.convert_address_operand(org) self.write_line('{0}{1} {2}'.format(self.indent, org_dir, org_addr_str)) self.write_line('') self.entry = entry self.print_entry() self.write_line('') self.print_blocks(entry.footers) def print_blocks(self, blocks): for block in blocks: if block: for line in block: self.write_line(line) self.write_line('') def print_equs(self, equs): if equs: if self.lower: equ_dir = 'equ' else: equ_dir = 'EQU' for label, value in equs: value = self.parser.convert_operand(value) self.write_line('{} {} {}'.format(label, equ_dir, value)) self.write_line('') def print_entry(self): self.print_comment_lines([self.entry.description], ignoreua=self.entry.ignoreua['t']) if self.entry.details: self.print_comment_lines(self.entry.details, ignoreua=self.entry.ignoreua['d'], started=True) if self.entry.registers: self.print_registers() self.print_instructions() if self.entry.end_comment: self.print_comment_lines(self.entry.end_comment, ignoreua=self.entry.ignoreua['e']) def write_line(self, s): write_text('{0}{1}'.format(s, self.end)) def pop_snapshot(self): """Discard the current memory snapshot and replace it with the one that was most recently saved (by :meth:`~skoolkit.skoolasm.AsmWriter.push_snapshot`).""" if len(self._snapshots) < 2: raise SkoolKitError("Cannot pop snapshot when snapshot stack is empty") self.snapshot = self._snapshots.pop()[0] def push_snapshot(self, name=''): """Save the current memory snapshot for later retrieval (by :meth:`~skoolkit.skoolasm.AsmWriter.pop_snapshot`), and put a copy in its place. :param name: An optional name for the snapshot. """ self._snapshots.append((self.snapshot[:], name)) def expand_font(self, text, index): if self.handle_unsupported_macros: return skoolmacro.parse_font(text, index)[0], '' raise skoolmacro.UnsupportedMacroError() def expand_html(self, text, index): end, message = skoolmacro.parse_html(text, index) return end, '' def expand_include(self, text, index): end, paragraphs, section = skoolmacro.parse_include(text, index) return end, '' def expand_link(self, text, index): end, page_id, anchor, link_text = skoolmacro.parse_link(text, index) if not link_text: raise skoolmacro.MacroParsingError("Blank link text: #LINK{}".format(text[index:end])) return end, link_text def expand_list(self, text, index): return self._ignore_block(text, index, LIST_MARKER, LIST_END_MARKER) def expand_r(self, text, index): end, addr_str, address, code_id, anchor, link_text = skoolmacro.parse_r(text, index) if link_text: return end, link_text if code_id: return end, self.parser.get_instruction_addr_str(address, addr_str, code_id) label = self.labels.get(address) if label is None: if self.base_address <= address <= self.end_address: self.warn('Could not convert address {} to label'.format(addr_str)) label = self.parser.get_instruction_addr_str(address, addr_str) return end, label def expand_scr(self, text, index): if self.handle_unsupported_macros: return skoolmacro.parse_scr(text, index)[0], '' raise skoolmacro.UnsupportedMacroError() def expand_table(self, text, index): return self._ignore_block(text, index, TABLE_MARKER, TABLE_END_MARKER) def expand_udg(self, text, index): if self.handle_unsupported_macros: return skoolmacro.parse_udg(text, index)[0], '' raise skoolmacro.UnsupportedMacroError() def expand_udgarray(self, text, index): if self.handle_unsupported_macros: if index < len(text) and text[index] == '*': end = skoolmacro.parse_udgarray_with_frames(text, index)[0] else: end = skoolmacro.parse_udgarray(text, index)[0] return end, '' raise skoolmacro.UnsupportedMacroError() def expand_udgtable(self, text, index): return self._ignore_block(text, index, UDGTABLE_MARKER, TABLE_END_MARKER, '') # API def expand(self, text): """Return `text` with skool macros expanded.""" return skoolmacro.expand_macros(self, text).strip() def _ignore_block(self, text, index, marker, end_marker, rep=None): try: end = text.index(end_marker, index) + len(end_marker) except ValueError: raise SkoolParsingError("Missing end marker: {}...".format(text[index - len(marker):index + 15])) if rep is None: rep = text[index - len(marker):end] return -end, rep def extract_blocks(self, text): blocks = [] index = 0 while 1: l_index = text.find(LIST_MARKER, index) t_index = text.find(TABLE_MARKER, index) if l_index < 0 and t_index < 0: blocks.append(text[index:].strip()) break if t_index < 0 or 0 <= l_index < t_index: index = text.index(LIST_END_MARKER, l_index) + len(LIST_END_MARKER) blocks.append(text[l_index:index]) else: index = text.index(TABLE_END_MARKER, t_index) + len(TABLE_END_MARKER) blocks.append(text[t_index:index]) return blocks def format(self, text, width): lines = [] for block in self.extract_blocks(self.expand(text)): if block.startswith(TABLE_MARKER): table_lines = self.table_writer.format_table(block[len(TABLE_MARKER):].lstrip()) if table_lines: table_width = max([len(line) for line in table_lines]) if table_width > width: self.warn('Table in entry at {0} is {1} characters wide'.format(self.entry.address, table_width)) lines.extend(table_lines) elif block.startswith(LIST_MARKER): list_obj = self.list_parser.parse_list(self, block[len(LIST_MARKER):].lstrip()) for item in list_obj.items: item_lines = [] if list_obj.bullet: prefix = list_obj.bullet + ' ' indent = ' ' * len(prefix) else: prefix = indent = '' for line in wrap(item, width - len(prefix)): item_lines.append(prefix + line) prefix = indent lines.extend(item_lines) elif block: lines.extend(wrap(block, width)) return lines def print_comment_lines(self, paragraphs, instruction=None, ignoreua=False, started=False): for paragraph in paragraphs: lines = self.format(paragraph, self.desc_width) if started and lines: self.write_line(';') if lines: started = True for line in lines: if not ignoreua: uaddress = self.find_unconverted_address(line) if uaddress: if instruction: if not instruction.ignoremrcua: self.warn('Comment above {0} contains address ({1}) not converted to a label:\n; {2}'.format(instruction.address, uaddress, line)) else: self.warn('Comment contains address ({0}) not converted to a label:\n; {1}'.format(uaddress, line)) self.write_line('; {0}'.format(line).rstrip()) def print_registers(self): self.write_line(';') prefix_len = max([len(reg.prefix) for reg in self.entry.registers]) if prefix_len: prefix_len += 1 indent = ''.ljust(prefix_len) for reg in self.entry.registers: if reg.prefix: prefix = '{}:'.format(reg.prefix).rjust(prefix_len) else: prefix = indent reg_label = prefix + reg.name reg_desc = self.expand(reg.contents) if not self.entry.ignoreua['r']: uaddress = self.find_unconverted_address(reg_desc) if uaddress: line = '; {} {}'.format(reg_label, reg_desc) self.warn('Register description contains address ({}) not converted to a label:\n{}'.format(uaddress, line)) if reg_desc: desc_indent = len(reg_label) + 1 desc_lines = wrap(reg_desc, self.desc_width - desc_indent) self.write_line('; {} {}'.format(reg_label, desc_lines[0])) desc_prefix = ''.ljust(desc_indent) for line in desc_lines[1:]: self.write_line('; {}{}'.format(desc_prefix, line)) else: self.write_line('; {}'.format(reg_label)) def print_instruction_prefix(self, instruction, index): if instruction.mid_block_comment: if index == 0: self.write_line(';') self.print_comment_lines(instruction.mid_block_comment, instruction) if instruction.asm_label: self.write_line("{0}{1}".format(instruction.asm_label, self.label_suffix)) def find_unconverted_address(self, text): for match in re.finditer('(\A|\s|\()((?:0x|\$)[0-9A-Fa-f]{4}|[1-9][0-9]{2,4})(?!([0-9A-Za-z]|[./*+][0-9]))', text): addr = match.group(2) if addr.startswith(('0x', '$')): if self.base_address <= int(addr[-4:], 16) <= self.end_address: return addr elif max(self.base_address, 257) <= int(addr) <= self.end_address: return addr def print_instructions(self): i = 0 rows = 0 lines = [] instructions = self.entry.instructions while i < len(instructions) or lines: if i < len(instructions): instruction = instructions[i] else: instruction = None # Deal with remaining comment lines or rowspan on the previous # instruction if lines or rows: if rows: self.print_instruction_prefix(instruction, i) operation = instruction.operation rows -= 1 i += 1 else: operation = '' if lines: line_comment = lines.pop(0) oline = '{0}{1} ; {2}'.format(self.indent, operation.ljust(instr_width), line_comment) if not ignoreua: uaddress = self.find_unconverted_address(line_comment) if uaddress: self.warn('Comment at {} contains address ({}) not converted to a label:\n{}'.format(iaddress, uaddress, oline)) elif rowspan > 1: oline = '{}{} ;'.format(self.indent, operation.ljust(instr_width)) else: oline = self.indent + operation self.write_line(oline) if len(oline) > self.line_width: self.warn('Line is {0} characters long:\n{1}'.format(len(oline), oline)) continue ignoreua = instruction.ignoreua iaddress = instruction.address rowspan = rows = instruction.comment.rowspan instr_width = max([len(i.operation) for i in instructions[i:i + rowspan]] + [self.instr_width]) comment_width = self.line_width - 3 - instr_width - self.indent_width lines = self.format(instruction.comment.text, max(comment_width, self.min_comment_width))
def __init__(self, parser, properties): self.parser = parser self.show_warnings = self._get_int_property(properties, 'warnings', 1) self.fields = { 'asm': 1, 'base': self.base, 'case': self.case, 'html': 0 } # Build a label dictionary self.labels = {} for entry in self.parser.memory_map: for instruction in entry.instructions: label = instruction.asm_label if label: self.labels[instruction.address] = label # Determine the base and end addresses self.base_address = 16384 self.end_address = 65535 if self.labels: self.base_address = min([address for address in self.labels.keys()]) elif self.parser.memory_map: self.base_address = self.parser.memory_map[0].instructions[0].address if self.parser.memory_map: self.end_address = self.parser.memory_map[-1].instructions[-1].address self.bullet = properties.get('bullet', '*') self.lower = self.case == CASE_LOWER # Field widths (line = indent + instruction + ' ; ' + comment) if self._get_int_property(properties, 'tab', 0): self.indent_width = 8 self.indent = '\t' else: self.indent_width = self._get_int_property(properties, 'indent', 2) self.indent = ' ' * self.indent_width self.instr_width = self._get_int_property(properties, 'instruction-width', DEF_INSTRUCTION_WIDTH) self.min_comment_width = self._get_int_property(properties, 'comment-width-min', 10) self.line_width = self._get_int_property(properties, 'line-width', 79) self.desc_width = self.line_width - 2 # Line terminator if self._get_int_property(properties, 'crlf', 0): self.end = '\r\n' else: self.end = '\n' # Label suffix if self._get_int_property(properties, 'label-colons', 1): self.label_suffix = ':' else: self.label_suffix = '' min_col_width = self._get_int_property(properties, 'wrap-column-width-min', 10) self.table_writer = TableWriter(self, self.desc_width, min_col_width) self.handle_unsupported_macros = self._get_int_property(properties, 'handle-unsupported-macros', 0) self.snapshot = self.parser.snapshot self._snapshots = [(self.snapshot, '')] self.list_parser = ListParser() self.macros = skoolmacro.get_macros(self) self.init()