Source code for py_register_machine2.tools.assembler.assembler

#!/usr/bin/python3

"""
**py_register_machine2.tools.assembler.assember**: The Basic Assembler

Just replaces mnemonics with opcodes and handles references.
"""

from ...engine_tools.conversions import *
import logging

# logging.basicConfig(level = logging.DEBUG)


[docs]def isreference(wordlist): """ if the line is a reference (jump mark), return true """ return wordlist[0].endswith(":")
[docs]class Assembler(object): """ .. _Assembler: Reads assembly code from ``open_stream`` and converts it to a ``list`` of integers that can be programmed to the ROM or the Flash. **Stages**: Split Run Reads the complete file and converts it to a list of tuples: ``[(lineno, "command", <command>, arguments), ...]``` or ``[(lineno, "data", <data description>, data), ...]``` Argument Run Checks and converts the arguments. Unconvertable ``str`` objects are interpreted as addresses. Dereference Run Handles references. Program Run Generates one iterable of integers """ def __init__(self, processor, open_stream, directives = [], commentstarts = [";"]): self.processor = processor self.open_stream = open_stream self.directives = {d.name: d for d in directives} self.line_count = 0 self.word_count = 0 self.refs = {} # jump marks self.static_refs = {} # will stay in flash self.commentstarts = commentstarts self.commands = {} for opcode, command in self.processor.commands_by_opcode.items(): self.commands[command.mnemonic()] = command self.register = self.processor.register_interface.registers_by_name self.register_indices = {} for index, register in enumerate(self.processor.register_interface.registers_by_index): self.register_indices[register.name] = index
[docs] def split_run(self): """ .. _split_run: Splits the assembly code into * commands * directives * jump marks """ sp_run = [] for line in self.open_stream.read().split("\n"): self.line_count += 1 if(self.iscomment(line)): continue if(line.isspace()): continue line = self.stripcomments(line) words = line.split() if(len(words) == 0): continue if(isreference(words)): self.add_ref(words) continue if(self.isdirective(words)): sp_run.append(self.handle_directive(words)) continue if(not words[0] in self.commands): raise AssembleError("[Line {}]: Unknown Mnemonic '{}'".format(self.line_count, words[0])) mnemo = words[0] args = words[1:] if(len(args) != self.commands[mnemo].numargs()): # check for default arguments args_ = list(args) for argtype in self.commands[mnemo].argtypes()[len(args):]: if(not argtype.can_default): raise AssembleError("[Line {}]: Mnemonic '{}' expects {} arguments, but got {}".format(self.line_count, mnemo, self.commands[mnemo].numargs(), len(args))) else: args_.append(argtype.default) args = args_ self.word_count += 1 + len(args) logging.debug("split run: " + str((self.line_count, "command", self.commands[mnemo], (args)))) sp_run.append((self.line_count, "command", self.commands[mnemo], (args))) return sp_run
[docs] def add_ref(self, wordlist): """ Adds a reference. """ refname = wordlist[0][:-1] if(refname in self.refs): raise ReferenceError("[line {}]:{} already defined here (word) {} (line) {}".format(self.line_count, refname, self.refs[refname][0], self.refs[refname][1])) self.refs[refname] = (self.word_count, self.line_count)
[docs] def handle_directive(self, words): """ handles directives: adds the reference and allocates space for the content """ refname = words[1] logging.debug("Handling directive " + str(self.getdirective(words[0]))) logging.debug("First argument is " + str(words[1])) if(self.getdirective(words[0]).isstatic()): if(refname in self.static_refs): raise ReferenceError("[line {}]:{} already defined here (word) {} (line) {}".format(self.line_count, refname, self.static_refs[refname][0], self.static_refs[refname][1])) self.static_refs[refname] = (self.word_count, self.line_count) else: if(refname in self.refs): raise ReferenceError("[line {}]:{} already defined here (word) {} (line) {}".format(self.line_count, refname, self.refs[refname][0], self.refs[refname][1])) self.refs[refname] = (self.word_count, self.line_count) directive = self.getdirective(words[0]) self.word_count += directive.get_word_count(words[2:]) logging.debug("Directive allocates {} words.".format(directive.get_word_count(words[2:]))) return (self.line_count, "data", directive, words[2:])
[docs] def isdirective(self, words): """ Check if the line ``words`` is a directive. """ return words[0] in self.directives
[docs] def getdirective(self, name): """ Returns the directive with the name ``name``. """ return self.directives[name]
[docs] def argument_run(self, sp_r): """ .. _argument_run: Converts Arguments according to ``to_int`` """ arg_run = [] for line in sp_r: logging.debug("argument run: handling: " + str(line)) if(line[1] == "data"): arg_run.append( (line[0], line[1], line[2], line[2].get_words(line[3]))) continue if(line[1] == "command"): self.checkargs(line[0], line[2], line[3]) arg_run.append( (line[0], line[1], line[2], [a for a in self.convert_args(line[2], line[3])])) return arg_run
[docs] def checkargs(self, lineno, command, args): """ Check if the arguments fit the requirements of the command. Raises ArgumentError_, if an argument does not fit. """ for wanted, arg in zip(command.argtypes(), args): wanted = wanted.type_ if(wanted == "register" and (not arg in self.register)): raise ArgumentError("[line {}]: Command '{}' wants argument of type register, but {} is not a register".format(lineno, command.mnemonic(), arg)) if(wanted == "const" and (arg in self.register)): raise ArgumentError("[line {}]: Command '{}' wants argument of type const, but {} is a register.".format(lineno, command.mnemonic(), arg))
[docs] def convert_args(self, command, args): """ Converts ``str -> int`` or ``register -> int``. """ for wanted, arg in zip(command.argtypes(), args): wanted = wanted.type_ if(wanted == "const"): try: yield to_int(arg) except: if(arg in self.processor.constants): yield self.processor.constants[arg] else: yield arg if(wanted == "register"): yield self.register_indices[arg]
[docs] def dereference_run(self, arg_r): """ .. _dereference_run: Converts the commands to opcodes and inserts the (relative or static) references. """ wc = 0 der_run = [] for line in arg_r: args = [] for argument in line[3]: logging.debug("dereference run: handling argument " + str(argument)) if(isinstance(argument, int)): logging.debug("Argument interpreted as integer") args.append(argument) continue if((not argument in self.refs) and (not argument in self.static_refs)): raise ArgumentError("[line {}]: Argument '{}' is neither an int nor a reference.".format(line[0], argument)) if(argument in self.static_refs): logging.debug("Argument interpreted as static reference") args.append(self.static_refs[argument][0]) continue my_word = wc ref_word = self.refs[argument][0] args.append(ref_word - my_word) logging.debug("Argument interpreted as reference") data = [] if(line[1] == "command"): data = [line[2].opcode()] data.extend(args) wc += len(data) der_run.append((line[0], line[1], data)) return der_run
[docs] def program_run(self, der_r): """ .. _program_run: Generates an iterable that can be programmed onto the register machine. """ program = [] for line in der_r: program.extend(line[2]) return program
[docs] def assemble(self): """ .. _assembler: Chains split_run_, argument_run_, dereference_run_ and program_run_. """ sp_r = self.split_run() ar_r = self.argument_run(sp_r) de_r = self.dereference_run(ar_r) program = self.program_run(de_r) return program
def iscomment(self, line): for commentstart in self.commentstarts: if(line.startswith(commentstart)): return True return False def stripcomments(self, line): for commentstart in self.commentstarts: if(commentstart in line): line = line[:line.index(commentstart)] return line
[docs]class ArgumentError(Exception): """ .. _ArgumentError: Raised if an argument does not fit the requirements. """ def __init__(self, *args): Exception.__init__(self, *args)
[docs]class AssembleError(BaseException): """ .. _AssembleError: Rasied if the assemler terminates without success. """ def __init__(self, *args): BaseException.__init__(self, *args)