#!/usr/bin/env python """Compiles C into assembly for the practicum processor (PP2) All rights reserved, you may not redistribute or use this program without prior permission from Peter Wu or Xander Houtman. Use of this program is entirely your own risk. In no circumstances can the authors of this program be held responsible for any damage including, but not limited to, financial damage or data loss. Modification of this program is not allowed without prior permission. The generated output (assembly and messages) are not subject to this license. """ __author__ = "Peter Wu" __copyright__ = "Copyright 2011, Peter Wu" __credits__ = ["Peter Wu"] __license__ = "Proprietary" __version__ = "1.0" __maintainer__ = "Peter Wu" __email__ = "uwretep@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.iteritems(): 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