#!/usr/bin/env python """Compiles C into assembly for the practicum processor (PP2) Copyright (C) 2011-2014 Peter Wu Licensed under the MIT license . """ __author__ = "Peter Wu" __copyright__ = "Copyright (C) 2011-2014 Peter Wu" __credits__ = ["Peter Wu"] __license__ = "MIT" __version__ = "1.0" __maintainer__ = "Peter Wu" __email__ = "lekensteyn@gmail.com" from AsmLine import AsmLine from NamedId import NamedId import re class AsmParser(object): def __init__(self, filename, parent=None): self.filename = filename if parent: self.parent = parent self.defined_names = parent.defined_names else: self.parent = None self.data = {} self.code = [] # defined labels for the assembly files self.labels = [] # dictionary for holding NamedId objects for defined labels and # identifiers (variables) self.defined_names = {} # valid values: None, DATA and CODE self.in_section = None # key: name, value: list of line numbers self.constants = {} self.re_whitespace = re.compile("\s+") file = open(filename, "rU") line_no = 0 buffered_line = "" while True: line_no += 1 line = file.readline() # line is empty if EoF, otherwise a string including newline if line: # strip comments and leading/trailing whitespace line = line.split(";")[0].strip() # assume that lines ending with ":" are incomplete and that # the instruction contains on a next line buffered_line += line if buffered_line and not buffered_line.endswith(":"): self.parseLine(buffered_line, line_no) buffered_line = "" else: break file.close() # substitute constants and remove the name for name, value in self.constants.items(): if name in self.defined_names: self.defined_names[name].rename(value) del self.defined_names[name] def parseLine(self, line, line_no): """Processes the a line from assembly""" if line.startswith("@"): cmd, opts = re.split("\s+", line + " ", 1) cmd = cmd[1:] opts = opts.strip() if cmd == "CODE": self.in_section = "CODE" elif cmd == "DATA": self.in_section = "DATA" elif cmd == "END": self.in_section = None elif cmd == "INCLUDE": if opts.count('"', 1, -1) == 0 and opts.count('"') == 2: raise RuntimeError('Expected the format @INCLUDE "file", ' "found '{}' instead".format(line)) filename = opts[1:-1] if not filename.endswith(".asm"): filename += ".asm" AsmParser(filename, self) elif cmd in ("STACKSIZE", "STACK"): # ignore pass else: raise RuntimeError("Unrecognized command '{}'".format(cmd)) elif self.in_section in ("DATA", "CODE"): match = re.split(self.re_whitespace, line, 2) if len(match) == 3: name, what, data = match if what == "EQU": self.setConstant(name, data) line = "" elif what in ("DS", "DW"): if self.in_section == "CODE": raise RuntimeError("DS or DW found in @CODE section") # we shouldn't need to change the name for DS/DW stuff #self.addName(name) if what == "DS": # DS initializes names with zero, let's convert it to # DW to make it compatible with the Parser self.addData(name, ["0"] * int(data)) else: self.addData(name, data.split(",")) line = "" if line: if self.in_section == "DATA": raise RuntimeError("Found non-definition data in @DATA, " "namely: " + line) try: lineobj = AsmLine(line, id_dict=self.defined_names) except RuntimeError as errmsg: raise RuntimeError("{} on line {} in {}" .format(errmsg, line_no, self.filename)) if lineobj.label: self.labels.append(lineobj.label) self.addCode(lineobj) else: # ignore other lines pass def setConstant(self, name, value): """Defines a constant for the current assembly file """ if name in self.constants: raise RuntimeError("Redefinition of constant '{}'".format(name)) self.constants[name] = self.evaluateConstant(value) def addCode(self, line): """Add a line to the @CODE section""" if self.parent: self.parent.addCode(line) else: self.code.append(line) def addData(self, name, words): """Define a name in the @DATA section initialized with words Keyword arguments: name -- The name as it appears in the @DATA section words -- A list of words to be initialized """ if self.parent: self.parent.addData(name, words) elif name in self.data: raise RuntimeError("Redefinition of '{}'".format(name)) else: self.data[name] = words def evaluateConstant(self, expression): """Evaluates a constant expression in an EQU""" if not expression.isdigit(): raise RuntimeError("I am lazy and do not support values other than" " digits in an EQU") return expression def addName(self, name): """Adds a NamedId object for the name of a label or variable to the list of defined names """ if self.parent: self.parent.addName(name) elif name in self.defined_names: raise RuntimeError("Name '{}' is already defined".format(name)) else: self.defined_names[name] = NamedId(name) def renameId(self, name, new_name): """Renames an identifier for a label or variable""" if not name in self.defined_names: raise RuntimeError("Attempt to rename an undefined '{}' to '{}'" .format(name, new_name)) self.defined_names[name].rename(new_name) def getCodeLines(self): """Returns the known assembly lines for the @CODE section as a list""" if self.parent: raise RuntimeError("You can only get the lines for @CODE from the" " root node") return [str(elm) for elm in self.code] def getDataDefinitions(self): """Returns the data section as a dictionary""" return self.data