示例#1
0
    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()
示例#2
0
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))
示例#3
0
    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)
示例#4
0
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)))
示例#5
0
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))
示例#6
0
    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()