def parse_asm_block_directive(directive, stack): prefix = directive[:4] infix = directive[len(prefix):len(prefix) + 1] suffix = directive[len(prefix) + len(infix):].rstrip() if prefix in ('ofix', 'bfix', 'rfix', 'isub', 'ssub', 'rsub') and infix in '+-' and suffix in ('begin', 'else', 'end'): if stack: cur_op = stack[-1] else: cur_op = (None, None) if suffix == 'begin': if prefix == cur_op[0]: raise SkoolParsingError('{0} inside {1}{2} block'.format(directive, cur_op[0], cur_op[1])) stack.append((prefix, infix)) elif suffix == 'else': if cur_op[0] is None: raise SkoolParsingError('{0} not inside block'.format(directive)) if prefix != cur_op[0] or infix == cur_op[1]: raise SkoolParsingError('{0} inside {1}{2} block'.format(directive, cur_op[0], cur_op[1])) stack[-1] = (prefix, infix) elif suffix == 'end': if cur_op[0] is None: raise SkoolParsingError('{0} has no matching start directive'.format(directive)) if (prefix, infix) != cur_op: raise SkoolParsingError('{0} cannot end {1}{2} block'.format(directive, cur_op[0], cur_op[1])) stack.pop() return True return False
def expand_macros(writer, text, *cwd): global _writer, _cwd _writer = writer _cwd = cwd if text.find('#') < 0: return text while 1: search = RE_MACRO.search(text) if not search: break marker = search.group() if marker not in writer.macros: raise SkoolParsingError('Found unknown macro: {}'.format(marker)) start, index = search.span() while RE_EXPAND.match(text, index): end, expr = parse_strings(text, index + 1, 1) text = text[:index] + expand_macros(writer, expr, *cwd) + text[end:] repf = writer.macros[marker] try: end, rep = repf(text, index, *cwd) except UnsupportedMacroError: raise SkoolParsingError('Found unsupported macro: {}'.format(marker)) except MacroParsingError as e: raise SkoolParsingError('Error while parsing {} macro: {}'.format(marker, e.args[0])) text = text[:start] + rep + text[end:] return text
def parse_list(self, writer, list_def, *cwd): text = list_def for ws_char in '\n\r\t': text = text.replace(ws_char, ' ') try: index, params = parse_brackets(text, default='') except ClosingBracketError: raise SkoolParsingError("Cannot find closing ')' in parameter list:\n{}".format(list_def)) css_class = writer.expand(params, *cwd).strip() list_obj = List(css_class) text = writer.expand(text[index:], *cwd) index = 0 while text.find('{', index) >= 0: item_start = text.find('{ ', index) if item_start < 0: raise SkoolParsingError("Cannot find opening '{{ ' in list item:\n{0}".format(list_def)) item_end = text.find(' }', item_start) if item_end < 0: raise SkoolParsingError("Cannot find closing ' }}' in list item:\n{0}".format(list_def)) list_obj.add_item(text[item_start + 1:item_end].strip()) index = item_end + 2 return list_obj
def _parse_instruction(self, address, line, removed): try: skool_address = get_int_param(line[1:6]) except ValueError: raise SkoolParsingError("Invalid address ({}):\n{}".format( line[1:6], line.rstrip())) if address is None: address = skool_address original_op = partition_unquoted(line[6:], ';')[0].strip() subbed = max(self.subs) if subbed: operations = self.subs[subbed] else: operations = [original_op] self.subs = defaultdict(list, {0: []}) parsed = [parse_asm_sub_fix_directive(v)[::2] for v in operations] before = [i[1] for i in parsed if i[0].prepend and i[1]] for operation in before: address = self._assemble(operation, address) after = [(i[0].overwrite, i[1], i[0].append) for i in parsed if not i[0].prepend] if not after or after[0][2]: after.insert(0, (False, original_op, False)) overwrite, operation = after.pop(0)[:2] operation = operation or original_op if operation and skool_address not in removed: address = self._assemble(operation, address, overwrite, removed) for overwrite, operation, append in after: if operation: address = self._assemble(operation, address, overwrite, removed) return address
def parse_text(self, writer, text, index, *cwd): try: end = text.index(TABLE_END_MARKER, index) + len(TABLE_END_MARKER) except ValueError: marker = text[text.rindex('#', 0, index):index] raise SkoolParsingError("Missing table end marker: {}{}...".format(marker, text[index:index + 15])) return end, self.parse_table(writer, text[index:end], *cwd)
def apply_asm_attributes(self, instruction): instruction.keep = self.keep if self.asm_labels and self.label: if self.label in self.labels: raise SkoolParsingError('Duplicate label {0} at {1}'.format(self.label, instruction.address)) self.labels.append(self.label) instruction.asm_label = self.label if not self.html: sub = self.isub if self.rfix is not None and self.do_rfixes: sub = self.rfix elif self.bfix is not None and self.do_bfixes: sub = self.bfix elif self.ofix is not None and self.do_ofixes: sub = self.ofix elif self.rsub is not None and self.do_rsubs: sub = self.rsub elif self.ssub is not None and self.do_ssubs: sub = self.ssub if sub is not None: _, sub = self.apply_case('', sub) _, sub = self.apply_base('', sub) instruction.apply_sub(sub) instruction.warn = not self.nowarn instruction.nolabel = self.nolabel instruction.ignoreua = self.ignoreua instruction.ignoremrcua = self.ignoremrcua instruction.org = self.org self.reset()
def _replace(self, text): for pattern, rep in self._replacements: try: text = re.sub(pattern, rep, text) except Exception as e: raise SkoolParsingError("Failed to replace '{}' with '{}': {}".format(pattern, rep, e.args[0])) return text
def _get_size(self, operation, address, marker, overwrite=False, removed=None, offset=0, sub=True, skool_address=None): if operation.upper().startswith(('DJNZ ', 'JR ')): size = 2 else: size = self.assembler.get_size(operation, address) if size: if overwrite: removed.update(range(address + offset, address + offset + size)) marker = '|' if self.start <= address < self.end: self.instructions.append( Instruction(skool_address, address, operation, sub, self.keep, self.nowarn, self.data, marker)) return size raise SkoolParsingError("Failed to assemble:\n {} {}".format( address, operation))
def _parse_instruction(self, line, removed): try: address = get_int_param(line[1:6]) except ValueError: raise SkoolParsingError("Invalid address ({}):\n{}".format( line[1:6], line.rstrip())) original_op = partition_unquoted(line[6:], ';')[0].strip() subbed = max(self.subs) if subbed: operations = [ partition_unquoted(s, ';') for s in self.subs[subbed] ] else: operations = [(original_op, '', '')] self.subs = defaultdict(list, {0: []}) for op, sep, comment in operations: operation = op.strip() or original_op if operation: data = assemble(operation, address) if data: end_address = address + len(data) if address not in removed: self.snapshot[address:end_address] = data self.base_address = min(self.base_address, address) self.end_address = max(self.end_address, end_address) if subbed: removed.update(range(address, end_address)) address = end_address else: warn("Failed to assemble:\n {} {}".format( address, operation)) break original_op = None
def _get_defs_length(self, operation): if self.preserve_base: fmt = FORMAT_PRESERVE_BASE else: fmt = FORMAT_NO_BASE items = self.op_evaluator.split_operands(operation[5:])[:2] try: size = self.op_evaluator.eval_int(items[0]) except ValueError: raise SkoolParsingError("Invalid integer '{}': {}".format( items[0], operation)) size_base = _get_base(items[0], self.preserve_base) try: get_int_param(items[0]) size_fmt = fmt[size_base].format(items[0]) except ValueError: size_fmt = fmt[size_base].format(size) if len(items) == 1: return size, size_fmt value_base = _get_base(items[1], self.preserve_base) if value_base in 'dh' and not self.preserve_base: value_base = 'n' return size, '{}:{}'.format(size_fmt, value_base)
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 _parse_instruction(self, line): ctl, addr_str, operation, comment = parse_instruction(line) try: address = get_int_param(addr_str) except ValueError: raise SkoolParsingError("Invalid address ({}):\n{}".format(addr_str, line.rstrip())) instruction = Instruction(ctl, address, operation, self.preserve_base) self.mode.apply_asm_directives(instruction) return instruction, comment
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 _assemble(self, operation, address, overwrite=False, removed=None): data = self.assembler.assemble(operation, address) if data: end_address = address + len(data) if removed is None or address not in removed: self.snapshot[address:end_address] = data self.base_address = min(self.base_address, address) self.end_address = max(self.end_address, end_address) if overwrite: removed.update(range(address, end_address)) return end_address raise SkoolParsingError("Failed to assemble:\n {} {}".format( address, operation))
def _parse_instruction(self, line): ctl = line[0] try: address = get_int_param(line[1:6]) except ValueError: raise SkoolParsingError("Invalid address ({}):\n{}".format( line[1:6], line.rstrip())) addr_str = self.address_fmt.format(address) comment_index = find_unquoted(line, ';', 6, neg=True) if comment_index > 0: end = comment_index else: end = len(line) operation = line[7:end].strip() comment = line[end + 1:].strip() return ControlLine(ctl, address, addr_str, operation, comment_index, comment, self.preserve_base)
def _parse_asm_directive(self, address, directive, removed): if directive.startswith( ('isub=', 'ssub=', 'rsub=', 'ofix=', 'bfix=', 'rfix=')): weight = self.weights[directive[:4]] if weight > (0, 0): value = directive[5:].rstrip() if value.startswith('!'): removed.update(parse_address_range(value[1:])) else: self.subs[weight].append(value) elif directive.startswith('if('): try: address = self._parse_asm_directive( address, parse_if(self.fields, directive, 2)[1], removed) except MacroParsingError: pass elif directive.startswith('org'): org = directive.rstrip().partition('=')[2] if org: try: address = get_int_param(org) except ValueError: raise SkoolParsingError( "Invalid org address: {}".format(org)) else: address = None elif directive.startswith('keep'): self.keep = parse_asm_keep_directive(directive) elif directive.startswith('nowarn'): self.nowarn = parse_asm_nowarn_directive(directive) elif self.data is not None and directive.startswith( ('defb=', 'defs=', 'defw=')): self.data.append(directive) elif directive.startswith('remote='): addrs = [ parse_int(a) for a in directive[7:].partition(':')[-1].split(',') ] if addrs[0] is not None: self.remote_entries.append( Entry(None, [Instruction(a) for a in addrs if a is not None])) return address
def _parse_instruction(self, address, line, removed): if self.entry_ctl is None: self.entry_ctl = line[0] try: skool_address = get_int_param(line[1:6]) except ValueError: if address is None or line[1:6].strip(): raise SkoolParsingError("Invalid address ({}):\n{}".format( line[1:6], line.rstrip())) skool_address = None if address is None: address = skool_address if skool_address not in removed: original_op = partition_unquoted(line[6:], ';')[0].strip() address = self._add_instructions(address, skool_address, self.subs[max(self.subs)], original_op, removed) self._reset(self.data is not None) return address
def get_defs_length(operation, preserve_base): if preserve_base: fmt = FORMAT_PRESERVE_BASE else: fmt = FORMAT_NO_BASE values = [] for item in split_operation(operation)[1:3]: base = _get_base(item, preserve_base) try: value = get_int_param(item) except ValueError: try: item = value = parse_word(item) except ValueError: raise SkoolParsingError("Invalid integer '{}': {}".format(item, operation)) if base == 'c': base = 'd' values.append((value, fmt[base].format(item))) return values[0][0], ':'.join([v[1] for v in values])
def _parse_instruction(self, line): try: address = get_int_param(line[1:6]) except ValueError: raise SkoolParsingError("Invalid address ({}):\n{}".format(line[1:6], line.rstrip())) for sub in self.subs: if sub is not None: operation = sub self.subs = [None] * 4 break else: comment_index = find_unquoted(line, ';', 6) operation = line[7:comment_index].strip() data = assemble(operation, address) if data: end_address = address + len(data) self.snapshot[address:end_address] = data self.base_address = min(self.base_address, address) self.end_address = max(self.end_address, end_address) else: warn("Failed to assemble:\n {} {}".format(address, operation))
def parse_text(self, writer, text, index, *cwd): try: end = text.index(LIST_END_MARKER, index) + len(LIST_END_MARKER) except ValueError: raise SkoolParsingError("No end marker: #LIST{}...".format(text[index:index + 15])) return end, self.parse_list(writer, text[index:end], *cwd)
def _parse_skool(self, skoolfile, min_address, max_address): map_entry = None instruction = None address_comments = [] for line in skoolfile: if line.startswith(';'): if self.mode.started and self.mode.include: self.comments.append(line[2:].rstrip()) self.mode.ignoreua = False instruction = None address_comments.append((None, None)) continue if line.startswith('@'): self._parse_asm_directive(line[1:].rstrip()) continue if not self.mode.include: continue s_line = line.strip() if not s_line: instruction = None address_comments.append((None, None)) if self.comments: if map_entry: self._add_end_comment(map_entry) else: self.header += self.comments self.comments[:] = [] self.ignores[:] = [] map_entry = None continue if s_line[0] == ';' and map_entry and instruction: # This is an instruction comment continuation line address_comments[-1][1] = '{0} {1}'.format(address_comments[-1][1], s_line[1:].lstrip()) continue # This line contains an instruction instruction, address_comment = self._parse_instruction(line) address = instruction.address addr_str = instruction.addr_str ctl = instruction.ctl if ctl in DIRECTIVES: if address is None: raise SkoolParsingError("Invalid address: '{}'".format(addr_str)) start_comment, desc, details, registers = parse_comment_block(self.comments, self.ignores, self.mode) map_entry = SkoolEntry(address, addr_str, ctl, desc, details, registers) instruction.mid_block_comment = start_comment map_entry.ignoreua.update(self.mode.entry_ignoreua) self.mode.reset_entry_ignoreua() self._entries[address] = map_entry self.memory_map.append(map_entry) self.comments[:] = [] self.base_address = min((address, self.base_address)) elif ctl == 'd': # This is a data definition entry map_entry = None elif ctl == 'r': # This is a remote entry map_entry = RemoteEntry(instruction.operation, address) if map_entry: address_comments.append([instruction, address_comment]) if address is not None: self._instructions.setdefault(address, []).append(instruction) map_entry.add_instruction(instruction) if self.comments: instruction.mid_block_comment = join_comments(self.comments, split=True, html=self.mode.html) self.comments[:] = [] self.mode.ignoremrcua = 0 in self.ignores self.mode.apply_asm_attributes(instruction) self.ignores[:] = [] # Set bytes in the snapshot if the instruction is DEF{B,M,S,W} if address is not None: operation = instruction.operation if self.mode.assemble or operation.upper().startswith(('DEFB ', 'DEFM ', 'DEFS ', 'DEFW ')): set_bytes(self.snapshot, address, operation) if self.comments and map_entry: self._add_end_comment(map_entry) if min_address > 0 or max_address < 65536: self.memory_map = [e for e in self.memory_map if min_address <= e.address < max_address] self._entries = {k: v for k, v in self._entries.items() if min_address <= k < max_address} if self._entries: self.base_address = min(self._entries) last_entry = self._entries[max(self._entries)] last_entry.instructions = [i for i in last_entry.instructions if i.address is None or i.address < max_address] else: self.base_address = max_address self._instructions = {k: v for k, v in self._instructions.items() if self.base_address <= k < max_address} address_comments = [c for c in address_comments if c[0] is None or c[0].address is None or self.base_address <= c[0].address < max_address] if self.memory_map: end_address = max([i.address for e in self.memory_map for i in e.instructions if i.address is not None]) last_instruction = self.get_instruction(end_address) self.end_address = end_address + (get_size(last_instruction.operation, end_address) or 1) # Do some post-processing parse_address_comments(address_comments, self.mode.html) self.make_replacements(self) self._calculate_references() if self.mode.asm_labels: self._generate_labels() if self.mode.html: self._calculate_entry_sizes() self._escape_instructions() else: self._substitute_labels()
def parse_table(self, writer, table_def, *cwd): text = table_def for ws_char in '\n\r\t': text = text.replace(ws_char, ' ') try: index, params = parse_brackets(text, default='') except ClosingBracketError: raise SkoolParsingError("Cannot find closing ')' in table CSS class list:\n{}".format(table_def)) classes = [c.strip() for c in writer.expand(params, *cwd).split(',')] table_class = classes[0] column_classes = classes[1:] wrap_columns = [] for i, column_class in enumerate(column_classes): if column_class.endswith(COLUMN_WRAP_MARKER): column_classes[i] = column_class[:-len(COLUMN_WRAP_MARKER)] wrap_columns.append(i) table = Table(table_class, wrap_columns) text = writer.expand(text[index:], *cwd) index = 0 prev_spans = {} while text.find('{', index) >= 0: row = [] row_start = text.find('{ ', index) if row_start < 0: raise SkoolParsingError("Cannot find opening '{{ ' in table row:\n{0}".format(table_def)) row_end = text.find(' }', row_start) if row_end < 0: raise SkoolParsingError("Cannot find closing ' }}' in table row:\n{0}".format(table_def)) col_index = 0 for cell in text[row_start + 1:row_end + 1].split(' | '): prev_rowspan, prev_colspan = prev_spans.get(col_index, (1, 1)) while prev_rowspan > 1: prev_spans[col_index] = (prev_rowspan - 1, prev_colspan) col_index += prev_colspan prev_rowspan, prev_colspan = prev_spans.get(col_index, (1, 1)) header, transparent = False, False rowspan, colspan = 1, 1 if len(column_classes) > col_index: cell_class = column_classes[col_index] else: cell_class = '' cell = cell.strip() if cell.startswith('='): end = cell.find(' ') if end < 0: end = len(cell) for span in cell[1:end].split(','): if span[0] == 'c': try: colspan = int(span[1:]) except ValueError: raise SkoolParsingError("Invalid colspan indicator: '{}'".format(span)) elif span[0] == 'r': try: rowspan = int(span[1:]) except ValueError: raise SkoolParsingError("Invalid rowspan indicator: '{}'".format(span)) elif span[0] == 'h': header = True elif span[0] == 't': transparent = True cell = cell[end:].lstrip() row.append(Cell(cell, transparent, colspan, rowspan, header, cell_class)) prev_spans[col_index] = (rowspan, colspan) col_index += colspan # Deal with the case where the previous row contains one or more # cells at the end with rowspan > 1 while col_index in prev_spans: prev_rowspan, prev_colspan = prev_spans[col_index] if prev_rowspan > 1: prev_spans[col_index] = (prev_rowspan - 1, prev_colspan) col_index += prev_colspan table.add_row(row) index = row_end + 1 return table