summaryrefslogtreecommitdiff
path: root/AsmParser.py
blob: c3354dddc1c0b5608b194b998e3354d6506f61be (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
#!/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