diff --git a/src/Ophis/CmdLine.py b/src/Ophis/CmdLine.py index b3e3a54..201ff3e 100644 --- a/src/Ophis/CmdLine.py +++ b/src/Ophis/CmdLine.py @@ -1,17 +1,17 @@ -"""Command line options data. - - verbose: - 0: Only report errors - 1: Announce each file as it is read, and data count (default) - 2: As above, but also announce each pass. - 3: As above, but print the IR after each pass. - 4: As above, but print the labels after each pass. - - 6510 compatibility and deprecation are handled in Ophis.Main.""" - -# Copyright 2002 Michael C. Martin. -# You may use, modify, and distribute this file under the BSD -# license: See LICENSE.txt for details. - -verbose = 1; - +"""Command line options data. + + verbose: + 0: Only report errors + 1: Announce each file as it is read, and data count (default) + 2: As above, but also announce each pass. + 3: As above, but print the IR after each pass. + 4: As above, but print the labels after each pass. + + 6510 compatibility and deprecation are handled in Ophis.Main.""" + +# Copyright 2002-2012 Michael C. Martin and additional contributors. +# You may use, modify, and distribute this file under the MIT +# license: See README for details. + +verbose = 1; + diff --git a/src/Ophis/CorePragmas.py b/src/Ophis/CorePragmas.py index 3fcb334..d5faea7 100644 --- a/src/Ophis/CorePragmas.py +++ b/src/Ophis/CorePragmas.py @@ -1,13 +1,10 @@ """Core pragmas - Provides the core assembler directives. It does not guarantee - compatibility with older versions of P65-Perl.""" + Provides the core assembler directives.""" -# Copyright 2002 Michael C. Martin. -# You may use, modify, and distribute this file under the BSD -# license: See LICENSE.txt for details. - -from __future__ import nested_scopes +# Copyright 2002-2012 Michael C. Martin and additional contributors. +# You may use, modify, and distribute this file under the MIT +# license: See README for details. import Ophis.IR as IR import Ophis.Frontend as FE @@ -18,193 +15,193 @@ basecharmap = "".join([chr(x) for x in range(256)]) currentcharmap = basecharmap def reset(): - global loadedfiles, currentcharmap, basecharmap - loadedfiles={} - currentcharmap = basecharmap + global loadedfiles, currentcharmap, basecharmap + loadedfiles={} + currentcharmap = basecharmap def pragmaInclude(ppt, line, result): - "Includes a source file" - filename = line.expect("STRING").value - line.expect("EOL") - if type(filename)==str: result.append(FE.parse_file(ppt, filename)) + "Includes a source file" + filename = line.expect("STRING").value + line.expect("EOL") + if type(filename)==str: result.append(FE.parse_file(ppt, filename)) def pragmaRequire(ppt, line, result): - "Includes a source file at most one time" - filename = line.expect("STRING").value - line.expect("EOL") - if type(filename)==str: - global loadedfiles - if filename not in loadedfiles: - loadedfiles[filename]=1 - result.append(FE.parse_file(ppt, filename)) + "Includes a source file at most one time" + filename = line.expect("STRING").value + line.expect("EOL") + if type(filename)==str: + global loadedfiles + if filename not in loadedfiles: + loadedfiles[filename]=1 + result.append(FE.parse_file(ppt, filename)) def pragmaIncbin(ppt, line, result): - "Includes a binary file" - filename = line.expect("STRING").value - line.expect("EOL") - if type(filename)==str: - try: - f = file(filename, "rb") - bytes = f.read() - f.close() - except IOError: - Err.log ("Could not read "+filename) - return - bytes = [IR.ConstantExpr(ord(x)) for x in bytes] - result.append(IR.Node(ppt, "Byte", *bytes)) + "Includes a binary file" + filename = line.expect("STRING").value + line.expect("EOL") + if type(filename)==str: + try: + f = file(filename, "rb") + bytes = f.read() + f.close() + except IOError: + Err.log ("Could not read "+filename) + return + bytes = [IR.ConstantExpr(ord(x)) for x in bytes] + result.append(IR.Node(ppt, "Byte", *bytes)) def pragmaCharmap(ppt, line, result): - "Modify the character map." - global currentcharmap, basecharmap - bytes = readData(line) - if len(bytes) == 0: - currentcharmap = basecharmap - else: - try: - base = bytes[0].data - newsubstr = "".join([chr(x.data) for x in bytes[1:]]) - currentcharmap = currentcharmap[:base] + newsubstr + currentcharmap[base+len(newsubstr):] - if len(currentcharmap) != 256 or base < 0 or base > 255: - Err.log("Charmap replacement out of range") - currentcharmap = currentcharmap[:256] - except ValueError: - Err.log("Illegal character in .charmap directive") + "Modify the character map." + global currentcharmap, basecharmap + bytes = readData(line) + if len(bytes) == 0: + currentcharmap = basecharmap + else: + try: + base = bytes[0].data + newsubstr = "".join([chr(x.data) for x in bytes[1:]]) + currentcharmap = currentcharmap[:base] + newsubstr + currentcharmap[base+len(newsubstr):] + if len(currentcharmap) != 256 or base < 0 or base > 255: + Err.log("Charmap replacement out of range") + currentcharmap = currentcharmap[:256] + except ValueError: + Err.log("Illegal character in .charmap directive") def pragmaCharmapbin(ppt, line, result): - "Load a new character map from a file" - global currentcharmap - filename = line.expect("STRING").value - line.expect("EOL") - if type(filename)==str: - try: - f = file(filename, "rb") - bytes = f.read() - f.close() - except IOError: - Err.log ("Could not read "+filename) - return - if len(bytes)==256: - currentcharmap = bytes - else: - Err.log("Character map "+filename+" not 256 bytes long") + "Load a new character map from a file" + global currentcharmap + filename = line.expect("STRING").value + line.expect("EOL") + if type(filename)==str: + try: + f = file(filename, "rb") + bytes = f.read() + f.close() + except IOError: + Err.log ("Could not read "+filename) + return + if len(bytes)==256: + currentcharmap = bytes + else: + Err.log("Character map "+filename+" not 256 bytes long") def pragmaOrg(ppt, line, result): - "Relocates the PC with no output" - newPC = FE.parse_expr(line) - line.expect("EOL") - result.append(IR.Node(ppt, "SetPC", newPC)) + "Relocates the PC with no output" + newPC = FE.parse_expr(line) + line.expect("EOL") + result.append(IR.Node(ppt, "SetPC", newPC)) def pragmaAdvance(ppt, line, result): - "Outputs filler until reaching the target PC" - newPC = FE.parse_expr(line) - line.expect("EOL") - result.append(IR.Node(ppt, "Advance", newPC)) + "Outputs filler until reaching the target PC" + newPC = FE.parse_expr(line) + line.expect("EOL") + result.append(IR.Node(ppt, "Advance", newPC)) def pragmaCheckpc(ppt, line, result): - "Enforces that the PC has not exceeded a certain point" - target = FE.parse_expr(line) - line.expect("EOL") - result.append(IR.Node(ppt, "CheckPC", target)) + "Enforces that the PC has not exceeded a certain point" + target = FE.parse_expr(line) + line.expect("EOL") + result.append(IR.Node(ppt, "CheckPC", target)) def pragmaAlias(ppt, line, result): - "Assigns an arbitrary label" - lbl = line.expect("LABEL").value - target = FE.parse_expr(line) - result.append(IR.Node(ppt, "Label", lbl, target)) + "Assigns an arbitrary label" + lbl = line.expect("LABEL").value + target = FE.parse_expr(line) + result.append(IR.Node(ppt, "Label", lbl, target)) def pragmaSpace(ppt, line, result): - "Reserves space in a data segment for a variable" - lbl = line.expect("LABEL").value - size = line.expect("NUM").value - line.expect("EOL") - result.append(IR.Node(ppt, "Label", lbl, IR.PCExpr())) - result.append(IR.Node(ppt, "SetPC", IR.SequenceExpr([IR.PCExpr(), "+", IR.ConstantExpr(size)]))) + "Reserves space in a data segment for a variable" + lbl = line.expect("LABEL").value + size = line.expect("NUM").value + line.expect("EOL") + result.append(IR.Node(ppt, "Label", lbl, IR.PCExpr())) + result.append(IR.Node(ppt, "SetPC", IR.SequenceExpr([IR.PCExpr(), "+", IR.ConstantExpr(size)]))) def pragmaText(ppt, line, result): - "Switches to a text segment" - next = line.expect("LABEL", "EOL") - if next.type == "LABEL": - line.expect("EOL") - segment = next.value - else: - segment = "*text-default*" - result.append(IR.Node(ppt, "TextSegment", segment)) + "Switches to a text segment" + next = line.expect("LABEL", "EOL") + if next.type == "LABEL": + line.expect("EOL") + segment = next.value + else: + segment = "*text-default*" + result.append(IR.Node(ppt, "TextSegment", segment)) def pragmaData(ppt, line, result): - "Switches to a data segment (no output allowed)" - next = line.expect("LABEL", "EOL") - if next.type == "LABEL": - line.expect("EOL") - segment = next.value - else: - segment = "*data-default*" - result.append(IR.Node(ppt, "DataSegment", segment)) + "Switches to a data segment (no output allowed)" + next = line.expect("LABEL", "EOL") + if next.type == "LABEL": + line.expect("EOL") + segment = next.value + else: + segment = "*data-default*" + result.append(IR.Node(ppt, "DataSegment", segment)) def readData(line): - "Read raw data from a comma-separated list" - if line.lookahead(0).type == "STRING": - data = [IR.ConstantExpr(ord(x)) for x in line.expect("STRING").value.translate(currentcharmap)] - else: - data = [FE.parse_expr(line)] - next = line.expect(',', 'EOL').type - while next == ',': - if line.lookahead(0).type == "STRING": - data.extend([IR.ConstantExpr(ord(x)) for x in line.expect("STRING").value]) - else: - data.append(FE.parse_expr(line)) - next = line.expect(',', 'EOL').type - return data + "Read raw data from a comma-separated list" + if line.lookahead(0).type == "STRING": + data = [IR.ConstantExpr(ord(x)) for x in line.expect("STRING").value.translate(currentcharmap)] + else: + data = [FE.parse_expr(line)] + next = line.expect(',', 'EOL').type + while next == ',': + if line.lookahead(0).type == "STRING": + data.extend([IR.ConstantExpr(ord(x)) for x in line.expect("STRING").value]) + else: + data.append(FE.parse_expr(line)) + next = line.expect(',', 'EOL').type + return data def pragmaByte(ppt, line, result): - "Raw data, a byte at a time" - bytes = readData(line) - result.append(IR.Node(ppt, "Byte", *bytes)) + "Raw data, a byte at a time" + bytes = readData(line) + result.append(IR.Node(ppt, "Byte", *bytes)) def pragmaWord(ppt, line, result): - "Raw data, a word at a time, little-endian" - words = readData(line) - result.append(IR.Node(ppt, "Word", *words)) + "Raw data, a word at a time, little-endian" + words = readData(line) + result.append(IR.Node(ppt, "Word", *words)) def pragmaDword(ppt, line, result): - "Raw data, a double-word at a time, little-endian" - dwords = readData(line) - result.append(IR.Node(ppt, "Dword", *dwords)) + "Raw data, a double-word at a time, little-endian" + dwords = readData(line) + result.append(IR.Node(ppt, "Dword", *dwords)) def pragmaWordbe(ppt, line, result): - "Raw data, a word at a time, big-endian" - words = readData(line) - result.append(IR.Node(ppt, "WordBE", *words)) + "Raw data, a word at a time, big-endian" + words = readData(line) + result.append(IR.Node(ppt, "WordBE", *words)) def pragmaDwordbe(ppt, line, result): - "Raw data, a dword at a time, big-endian" - dwords = readData(line) - result.append(IR.Node(ppt, "DwordBE", *dwords)) + "Raw data, a dword at a time, big-endian" + dwords = readData(line) + result.append(IR.Node(ppt, "DwordBE", *dwords)) def pragmaScope(ppt, line, result): - "Create a new lexical scoping block" - line.expect("EOL") - result.append(IR.Node(ppt, "ScopeBegin")) + "Create a new lexical scoping block" + line.expect("EOL") + result.append(IR.Node(ppt, "ScopeBegin")) def pragmaScend(ppt, line, result): - "End the innermost lexical scoping block" - line.expect("EOL") - result.append(IR.Node(ppt, "ScopeEnd")) + "End the innermost lexical scoping block" + line.expect("EOL") + result.append(IR.Node(ppt, "ScopeEnd")) def pragmaMacro(ppt, line, result): - "Begin a macro definition" - lbl = line.expect("LABEL").value - line.expect("EOL") - result.append(IR.Node(ppt, "MacroBegin", lbl)) + "Begin a macro definition" + lbl = line.expect("LABEL").value + line.expect("EOL") + result.append(IR.Node(ppt, "MacroBegin", lbl)) def pragmaMacend(ppt, line, result): - "End a macro definition" - line.expect("EOL") - result.append(IR.Node(ppt, "MacroEnd")) + "End a macro definition" + line.expect("EOL") + result.append(IR.Node(ppt, "MacroEnd")) def pragmaInvoke(ppt, line, result): - macro = line.expect("LABEL").value - if line.lookahead(0).type == "EOL": - args = [] - else: - args = readData(line) - result.append(IR.Node(ppt, "MacroInvoke", macro, *args)) + macro = line.expect("LABEL").value + if line.lookahead(0).type == "EOL": + args = [] + else: + args = readData(line) + result.append(IR.Node(ppt, "MacroInvoke", macro, *args)) diff --git a/src/Ophis/Environment.py b/src/Ophis/Environment.py index 1308b3e..533be44 100644 --- a/src/Ophis/Environment.py +++ b/src/Ophis/Environment.py @@ -1,75 +1,74 @@ -"""Symbol tables and environments for P65. - - Implements the symbol lookup, through nested environments - - any non-temporary variable is stored at the top level.""" - -# Copyright 2002 Michael C. Martin. -# You may use, modify, and distribute this file under the BSD -# license: See LICENSE.txt for details. - -from __future__ import nested_scopes -import Ophis.Errors as Err - -class Environment: - """Environment class. - Controls the various scopes and global abstract execution variables.""" - def __init__(self): - self.dicts = [{}] - self.stack = [0] - self.pc = 0 - self.segmentdict = {} - self.segment = "*text-default*" - self.scopecount = 0 - def __contains__(self, item): - if item[0] == '_': - for dict in [self.dicts[i] for i in self.stack]: - if item in dict: return 1 - return 0 - return item in self.dicts[0] - def __getitem__(self, item): - if item[0] == '_': - for dict in [self.dicts[i] for i in self.stack]: - if item in dict: return dict[item] - else: - if item in self.dicts[0]: return self.dicts[0][item] - Err.log("Unknown label '%s'" % item) - return 0 - def __setitem__(self, item, value): - if item[0] == '_': - self.dicts[self.stack[0]][item] = value - else: - self.dicts[0][item] = value - def __str__(self): - return str(self.dicts) - def getPC(self): - return self.pc - def setPC(self, value): - self.pc = value - def incPC(self, amount): - self.pc += amount - def getsegment(self): - return self.segment - def setsegment(self, segment): - self.segmentdict[self.segment] = self.pc - self.segment = segment - self.pc = self.segmentdict.get(segment, 0) - def reset(self): - "Clears out program counter, segment, and scoping information" - self.pc = 0 - self.segmentdict = {} - self.segment = "*text-default*" - self.scopecount = 0 - if len(self.stack) > 1: - Err.log("Unmatched .scope") - self.stack = [0] - def newscope(self): - "Enters a new scope for temporary labels." - self.scopecount += 1 - self.stack.insert(0, self.scopecount) - if len(self.dicts) <= self.scopecount: self.dicts.append({}) - def endscope(self): - "Leaves a scope." - if len(self.stack) == 1: - Err.log("Unmatched .scend") - self.stack.pop(0) - +"""Symbol tables and environments for Ophis. + + Implements the symbol lookup, through nested environments - + any non-temporary variable is stored at the top level.""" + +# Copyright 2002-2012 Michael C. Martin and additional contributors. +# You may use, modify, and distribute this file under the MIT +# license: See README for details. + +import Ophis.Errors as Err + +class Environment: + """Environment class. + Controls the various scopes and global abstract execution variables.""" + def __init__(self): + self.dicts = [{}] + self.stack = [0] + self.pc = 0 + self.segmentdict = {} + self.segment = "*text-default*" + self.scopecount = 0 + def __contains__(self, item): + if item[0] == '_': + for dict in [self.dicts[i] for i in self.stack]: + if item in dict: return 1 + return 0 + return item in self.dicts[0] + def __getitem__(self, item): + if item[0] == '_': + for dict in [self.dicts[i] for i in self.stack]: + if item in dict: return dict[item] + else: + if item in self.dicts[0]: return self.dicts[0][item] + Err.log("Unknown label '%s'" % item) + return 0 + def __setitem__(self, item, value): + if item[0] == '_': + self.dicts[self.stack[0]][item] = value + else: + self.dicts[0][item] = value + def __str__(self): + return str(self.dicts) + def getPC(self): + return self.pc + def setPC(self, value): + self.pc = value + def incPC(self, amount): + self.pc += amount + def getsegment(self): + return self.segment + def setsegment(self, segment): + self.segmentdict[self.segment] = self.pc + self.segment = segment + self.pc = self.segmentdict.get(segment, 0) + def reset(self): + "Clears out program counter, segment, and scoping information" + self.pc = 0 + self.segmentdict = {} + self.segment = "*text-default*" + self.scopecount = 0 + if len(self.stack) > 1: + Err.log("Unmatched .scope") + self.stack = [0] + def newscope(self): + "Enters a new scope for temporary labels." + self.scopecount += 1 + self.stack.insert(0, self.scopecount) + if len(self.dicts) <= self.scopecount: self.dicts.append({}) + def endscope(self): + "Leaves a scope." + if len(self.stack) == 1: + Err.log("Unmatched .scend") + self.stack.pop(0) + diff --git a/src/Ophis/Errors.py b/src/Ophis/Errors.py index 024d0f9..0711ca0 100644 --- a/src/Ophis/Errors.py +++ b/src/Ophis/Errors.py @@ -1,24 +1,24 @@ -"""Error logging - - Keeps track of the number of errors inflicted so far, and - where in the assembly the errors are occurring.""" - -# Copyright 2002 Michael C. Martin. -# You may use, modify, and distribute this file under the BSD -# license: See LICENSE.txt for details. - -count = 0 -currentpoint = "" - -def log(err): - """Reports an error at the current program point, and increases -the global error count.""" - global count - count = count+1 - print currentpoint+": "+err - -def report(): - "Print out the number of errors." - if count == 0: print "No errors" - elif count == 1: print "1 error" - else: print str(count)+" errors" \ No newline at end of file +"""Error logging + + Keeps track of the number of errors inflicted so far, and + where in the assembly the errors are occurring.""" + +# Copyright 2002-2012 Michael C. Martin and additional contributors. +# You may use, modify, and distribute this file under the MIT +# license: See README for details. + +count = 0 +currentpoint = "" + +def log(err): + """Reports an error at the current program point, and increases +the global error count.""" + global count + count = count+1 + print currentpoint+": "+err + +def report(): + "Print out the number of errors." + if count == 0: print "No errors" + elif count == 1: print "1 error" + else: print str(count)+" errors" diff --git a/src/Ophis/Frontend.py b/src/Ophis/Frontend.py index 48e319b..0bd3c19 100644 --- a/src/Ophis/Frontend.py +++ b/src/Ophis/Frontend.py @@ -1,333 +1,331 @@ -"""Lexer and Parser - - Constructs a list of IR nodes from a list of input strings.""" - -from __future__ import nested_scopes -import Ophis.Errors as Err -import Ophis.Opcodes as Ops -import Ophis.IR as IR -import Ophis.CmdLine as Cmd -import os - -# Copyright 2002 Michael C. Martin. -# You may use, modify, and distribute this file under the BSD -# license: See LICENSE.txt for details. - - -class Lexeme: - "Class for lexer tokens. Used by lexer and parser." - def __init__(self, type="UNKNOWN", value=None): - self.type = type.upper() - self.value = value - def __str__(self): - if self.value == None: - return self.type - else: - return self.type+":"+str(self.value) - def __repr__(self): - return "Lexeme("+`self.type`+", "+`self.value`+")" - def matches(self, other): - "1 if Lexemes a and b have the same type." - return self.type == other.type - -bases = {"$":("hexadecimal", 16), - "%":("binary", 2), - "0":("octal", 8)} - -punctuation = "#,`<>():.+-*/&|^[]" - -def lex(point, line): - """Turns a line of source into a sequence of lexemes.""" - Err.currentpoint = point - result = [] - def is_opcode(op): - "Tests whether a string is an opcode or an identifier" - return op in Ops.opcodes - def add_token(token): - "Converts a substring into a single lexeme" - if token == "": - return - if token == "0": - result.append(Lexeme("NUM", 0)) - return - firstchar = token[0] - rest = token[1:] - if firstchar == '"': - result.append(Lexeme("STRING", rest)) - return - elif firstchar in bases: - try: - result.append(Lexeme("NUM", long(rest, bases[firstchar][1]))) - return - except ValueError: - Err.log('Invalid '+bases[firstchar][0]+' constant: '+rest) - result.append(Lexeme("NUM", 0)) - return - elif firstchar.isdigit(): - try: - result.append(Lexeme("NUM", long(token))) - except ValueError: - Err.log('Identifiers may not begin with a number') - result.append(Lexeme("LABEL", "ERROR")) - return - elif firstchar == "'": - if len(rest) == 1: - result.append(Lexeme("NUM", ord(rest))) - else: - Err.log("Invalid character constant '"+rest+"'") - result.append(Lexeme("NUM", 0)) - return - elif firstchar in punctuation: - if rest != "": - Err.log("Internal lexer error! '"+token+"' can't happen!") - result.append(Lexeme(firstchar)) - return - else: # Label, opcode, or index register - id = token.lower() - if is_opcode(id): - result.append(Lexeme("OPCODE", id)) - elif id == "x": - result.append(Lexeme("X")) - elif id == "y": - result.append(Lexeme("Y")) - else: - result.append(Lexeme("LABEL", id)) - return - # should never reach here - Err.log("Internal lexer error: add_token fall-through") - def add_EOL(): - "Adds an end-of-line lexeme" - result.append(Lexeme("EOL")) - # Actual routine begins here - value = "" - quotemode = 0 - backslashmode = 0 - for c in line.strip(): - if backslashmode: - backslashmode = 0 - value = value + c - elif c == "\\": - backslashmode = 1 - elif quotemode: - if c == '"': - quotemode = 0 - else: - value = value + c - elif c == ';': - add_token(value) - value = "" - break - elif c.isspace(): - add_token(value) - value = "" - elif c in punctuation: - add_token(value) - add_token(c) - value = "" - elif c == '"': - add_token(value) - value = '"' - quotemode = 1 - else: - value = value + c - if backslashmode: - Err.log("Backslashed newline") - if quotemode: - Err.log("Unterminated string constant") - add_token(value) - add_EOL() - return result - -class ParseLine: - "Maintains the parse state of a line of code. Enables arbitrary lookahead." - def __init__(self, lexemes): - self.lexemes = lexemes - self.location = 0 - def lookahead(self, i): - """Returns the token i units ahead in the parse. - lookahead(0) returns the next token; trying to read off the end of - the sequence returns the last token in the sequence (usually EOL).""" - target = self.location+i - if target >= len(self.lexemes): target = -1 - return self.lexemes[target] - def pop(self): - "Returns and removes the next element in the line." - old = self.location - if self.location < len(self.lexemes)-1: self.location += 1 - return self.lexemes[old] - def expect(self, *tokens): - """Reads a token from the ParseLine line and returns it if it's of a type - in the sequence tokens. Otherwise, it logs an error.""" - token = self.pop() - if token.type not in tokens: - Err.log('Expected: "'+'", "'.join(tokens)+'"') - return token - -pragma_modules = [] - -def parse_expr(line): - "Parses an Ophis arithmetic expression." - def atom(): - "Parses lowest-priority expression components." - next = line.lookahead(0).type - if next == "NUM": - return IR.ConstantExpr(line.expect("NUM").value) - elif next == "LABEL": - return IR.LabelExpr(line.expect("LABEL").value) - elif next == "^": - line.expect("^") - return IR.PCExpr() - elif next == "[": - line.expect("[") - result = parse_expr(line) - line.expect("]") - return result - elif next == "+": - offset = 0 - while next == "+": - offset += 1 - line.expect("+") - next = line.lookahead(0).type - return IR.LabelExpr("*"+str(templabelcount+offset)) - elif next == "-": - offset = 1 - while next == "-": - offset -= 1 - line.expect("-") - next = line.lookahead(0).type - return IR.LabelExpr("*"+str(templabelcount+offset)) - elif next == ">": - line.expect(">") - return IR.HighByteExpr(atom()) - elif next == "<": - line.expect("<") - return IR.LowByteExpr(atom()) - else: - Err.log('Expected: expression') - def precedence_read(constructor, reader, separators): - """Handles precedence. The reader argument is a function that returns - expressions that bind more tightly than these; separators is a list - of strings naming the operators at this precedence level. The - constructor argument is a class, indicating what node type holds - objects of this precedence level. - - Returns a list of Expr objects with separator strings between them.""" - result = [reader()] # first object - nextop = line.lookahead(0).type - while (nextop in separators): - line.expect(nextop) - result.append(nextop) - result.append(reader()) - nextop = line.lookahead(0).type - if len(result) == 1: return result[0] - return constructor(result) - def term(): - "Parses * and /" - return precedence_read(IR.SequenceExpr, atom, ["*", "/"]) - def arith(): - "Parses + and -" - return precedence_read(IR.SequenceExpr, term, ["+", "-"]) - def bits(): - "Parses &, |, and ^" - return precedence_read(IR.SequenceExpr, arith, ["&", "|", "^"]) - return bits() - -def parse_line(ppt, lexemelist): - "Turn a line of source into an IR Node." - Err.currentpoint = ppt - result = [] - line = ParseLine(lexemelist) - def aux(): - "Accumulates all IR nodes defined by this line." - if line.lookahead(0).type == "EOL": - pass - elif line.lookahead(1).type == ":": - newlabel=line.expect("LABEL").value - line.expect(":") - result.append(IR.Node(ppt, "Label", newlabel, IR.PCExpr())) - aux() - elif line.lookahead(0).type == "*": - global templabelcount - templabelcount = templabelcount + 1 - result.append(IR.Node(ppt, "Label", "*"+str(templabelcount), IR.PCExpr())) - line.expect("*") - aux() - elif line.lookahead(0).type == "." or line.lookahead(0).type == "`": - which = line.expect(".", "`").type - if (which == "."): pragma = line.expect("LABEL").value - else: pragma = "invoke" - pragmaFunction = "pragma"+pragma.title() - for mod in pragma_modules: - if hasattr(mod, pragmaFunction): - getattr(mod, pragmaFunction)(ppt, line, result) - break - else: - Err.log("Unknown pragma "+pragma) - - else: # Instruction - opcode = line.expect("OPCODE").value - if line.lookahead(0).type == "#": - mode = "Immediate" - line.expect("#") - arg = parse_expr(line) - line.expect("EOL") - elif line.lookahead(0).type == "(": - line.expect("(") - arg = parse_expr(line) - if line.lookahead(0).type == ",": - mode = "PointerX" - line.expect(",") - line.expect("X") - line.expect(")") - line.expect("EOL") - else: - line.expect(")") - tok = line.expect(",", "EOL").type - if tok == "EOL": - mode = "Pointer" - else: - mode = "PointerY" - line.expect("Y") - line.expect("EOL") - elif line.lookahead(0).type == "EOL": - mode = "Implied" - arg = None - else: - arg = parse_expr(line) - tok = line.expect("EOL", ",").type - if tok == ",": - tok = line.expect("X", "Y").type - if tok == "X": mode = "MemoryX" - else: mode = "MemoryY" - line.expect("EOL") - else: mode = "Memory" - result.append(IR.Node(ppt, mode, opcode, arg)) - aux() - result = [node for node in result if node is not IR.NullNode] - if len(result) == 0: return IR.NullNode - if len(result) == 1: return result[0] - return IR.SequenceNode(ppt, result) - -def parse_file(ppt, filename): - "Loads a .P65 source file, and returns an IR list." - Err.currentpoint = ppt - if Cmd.verbose > 0: print "Loading "+filename - try: - f = file(filename) - linelist = f.readlines() - f.close() - pptlist = ["%s:%d" % (filename, i+1) for i in range(len(linelist))] - lexlist = map(lex, pptlist, linelist) - IRlist = map(parse_line, pptlist, lexlist) - IRlist = [node for node in IRlist if node is not IR.NullNode] - return IR.SequenceNode(ppt, IRlist) - except IOError: - Err.log ("Could not read "+filename) - return IR.NullNode - -def parse(filename): - "Top level parsing routine, taking a source file name and returning an IR list." - global templabelcount - templabelcount = 0 - return parse_file("", filename) - +"""Lexer and Parser + + Constructs a list of IR nodes from a list of input strings.""" + +import Ophis.Errors as Err +import Ophis.Opcodes as Ops +import Ophis.IR as IR +import Ophis.CmdLine as Cmd +import os + +# Copyright 2002-2012 Michael C. Martin and additional contributors. +# You may use, modify, and distribute this file under the MIT +# license: See README for details. + +class Lexeme: + "Class for lexer tokens. Used by lexer and parser." + def __init__(self, type="UNKNOWN", value=None): + self.type = type.upper() + self.value = value + def __str__(self): + if self.value == None: + return self.type + else: + return self.type+":"+str(self.value) + def __repr__(self): + return "Lexeme("+`self.type`+", "+`self.value`+")" + def matches(self, other): + "1 if Lexemes a and b have the same type." + return self.type == other.type + +bases = {"$":("hexadecimal", 16), + "%":("binary", 2), + "0":("octal", 8)} + +punctuation = "#,`<>():.+-*/&|^[]" + +def lex(point, line): + """Turns a line of source into a sequence of lexemes.""" + Err.currentpoint = point + result = [] + def is_opcode(op): + "Tests whether a string is an opcode or an identifier" + return op in Ops.opcodes + def add_token(token): + "Converts a substring into a single lexeme" + if token == "": + return + if token == "0": + result.append(Lexeme("NUM", 0)) + return + firstchar = token[0] + rest = token[1:] + if firstchar == '"': + result.append(Lexeme("STRING", rest)) + return + elif firstchar in bases: + try: + result.append(Lexeme("NUM", long(rest, bases[firstchar][1]))) + return + except ValueError: + Err.log('Invalid '+bases[firstchar][0]+' constant: '+rest) + result.append(Lexeme("NUM", 0)) + return + elif firstchar.isdigit(): + try: + result.append(Lexeme("NUM", long(token))) + except ValueError: + Err.log('Identifiers may not begin with a number') + result.append(Lexeme("LABEL", "ERROR")) + return + elif firstchar == "'": + if len(rest) == 1: + result.append(Lexeme("NUM", ord(rest))) + else: + Err.log("Invalid character constant '"+rest+"'") + result.append(Lexeme("NUM", 0)) + return + elif firstchar in punctuation: + if rest != "": + Err.log("Internal lexer error! '"+token+"' can't happen!") + result.append(Lexeme(firstchar)) + return + else: # Label, opcode, or index register + id = token.lower() + if is_opcode(id): + result.append(Lexeme("OPCODE", id)) + elif id == "x": + result.append(Lexeme("X")) + elif id == "y": + result.append(Lexeme("Y")) + else: + result.append(Lexeme("LABEL", id)) + return + # should never reach here + Err.log("Internal lexer error: add_token fall-through") + def add_EOL(): + "Adds an end-of-line lexeme" + result.append(Lexeme("EOL")) + # Actual routine begins here + value = "" + quotemode = 0 + backslashmode = 0 + for c in line.strip(): + if backslashmode: + backslashmode = 0 + value = value + c + elif c == "\\": + backslashmode = 1 + elif quotemode: + if c == '"': + quotemode = 0 + else: + value = value + c + elif c == ';': + add_token(value) + value = "" + break + elif c.isspace(): + add_token(value) + value = "" + elif c in punctuation: + add_token(value) + add_token(c) + value = "" + elif c == '"': + add_token(value) + value = '"' + quotemode = 1 + else: + value = value + c + if backslashmode: + Err.log("Backslashed newline") + if quotemode: + Err.log("Unterminated string constant") + add_token(value) + add_EOL() + return result + +class ParseLine: + "Maintains the parse state of a line of code. Enables arbitrary lookahead." + def __init__(self, lexemes): + self.lexemes = lexemes + self.location = 0 + def lookahead(self, i): + """Returns the token i units ahead in the parse. + lookahead(0) returns the next token; trying to read off the end of + the sequence returns the last token in the sequence (usually EOL).""" + target = self.location+i + if target >= len(self.lexemes): target = -1 + return self.lexemes[target] + def pop(self): + "Returns and removes the next element in the line." + old = self.location + if self.location < len(self.lexemes)-1: self.location += 1 + return self.lexemes[old] + def expect(self, *tokens): + """Reads a token from the ParseLine line and returns it if it's of a type + in the sequence tokens. Otherwise, it logs an error.""" + token = self.pop() + if token.type not in tokens: + Err.log('Expected: "'+'", "'.join(tokens)+'"') + return token + +pragma_modules = [] + +def parse_expr(line): + "Parses an Ophis arithmetic expression." + def atom(): + "Parses lowest-priority expression components." + next = line.lookahead(0).type + if next == "NUM": + return IR.ConstantExpr(line.expect("NUM").value) + elif next == "LABEL": + return IR.LabelExpr(line.expect("LABEL").value) + elif next == "^": + line.expect("^") + return IR.PCExpr() + elif next == "[": + line.expect("[") + result = parse_expr(line) + line.expect("]") + return result + elif next == "+": + offset = 0 + while next == "+": + offset += 1 + line.expect("+") + next = line.lookahead(0).type + return IR.LabelExpr("*"+str(templabelcount+offset)) + elif next == "-": + offset = 1 + while next == "-": + offset -= 1 + line.expect("-") + next = line.lookahead(0).type + return IR.LabelExpr("*"+str(templabelcount+offset)) + elif next == ">": + line.expect(">") + return IR.HighByteExpr(atom()) + elif next == "<": + line.expect("<") + return IR.LowByteExpr(atom()) + else: + Err.log('Expected: expression') + def precedence_read(constructor, reader, separators): + """Handles precedence. The reader argument is a function that returns + expressions that bind more tightly than these; separators is a list + of strings naming the operators at this precedence level. The + constructor argument is a class, indicating what node type holds + objects of this precedence level. + + Returns a list of Expr objects with separator strings between them.""" + result = [reader()] # first object + nextop = line.lookahead(0).type + while (nextop in separators): + line.expect(nextop) + result.append(nextop) + result.append(reader()) + nextop = line.lookahead(0).type + if len(result) == 1: return result[0] + return constructor(result) + def term(): + "Parses * and /" + return precedence_read(IR.SequenceExpr, atom, ["*", "/"]) + def arith(): + "Parses + and -" + return precedence_read(IR.SequenceExpr, term, ["+", "-"]) + def bits(): + "Parses &, |, and ^" + return precedence_read(IR.SequenceExpr, arith, ["&", "|", "^"]) + return bits() + +def parse_line(ppt, lexemelist): + "Turn a line of source into an IR Node." + Err.currentpoint = ppt + result = [] + line = ParseLine(lexemelist) + def aux(): + "Accumulates all IR nodes defined by this line." + if line.lookahead(0).type == "EOL": + pass + elif line.lookahead(1).type == ":": + newlabel=line.expect("LABEL").value + line.expect(":") + result.append(IR.Node(ppt, "Label", newlabel, IR.PCExpr())) + aux() + elif line.lookahead(0).type == "*": + global templabelcount + templabelcount = templabelcount + 1 + result.append(IR.Node(ppt, "Label", "*"+str(templabelcount), IR.PCExpr())) + line.expect("*") + aux() + elif line.lookahead(0).type == "." or line.lookahead(0).type == "`": + which = line.expect(".", "`").type + if (which == "."): pragma = line.expect("LABEL").value + else: pragma = "invoke" + pragmaFunction = "pragma"+pragma.title() + for mod in pragma_modules: + if hasattr(mod, pragmaFunction): + getattr(mod, pragmaFunction)(ppt, line, result) + break + else: + Err.log("Unknown pragma "+pragma) + + else: # Instruction + opcode = line.expect("OPCODE").value + if line.lookahead(0).type == "#": + mode = "Immediate" + line.expect("#") + arg = parse_expr(line) + line.expect("EOL") + elif line.lookahead(0).type == "(": + line.expect("(") + arg = parse_expr(line) + if line.lookahead(0).type == ",": + mode = "PointerX" + line.expect(",") + line.expect("X") + line.expect(")") + line.expect("EOL") + else: + line.expect(")") + tok = line.expect(",", "EOL").type + if tok == "EOL": + mode = "Pointer" + else: + mode = "PointerY" + line.expect("Y") + line.expect("EOL") + elif line.lookahead(0).type == "EOL": + mode = "Implied" + arg = None + else: + arg = parse_expr(line) + tok = line.expect("EOL", ",").type + if tok == ",": + tok = line.expect("X", "Y").type + if tok == "X": mode = "MemoryX" + else: mode = "MemoryY" + line.expect("EOL") + else: mode = "Memory" + result.append(IR.Node(ppt, mode, opcode, arg)) + aux() + result = [node for node in result if node is not IR.NullNode] + if len(result) == 0: return IR.NullNode + if len(result) == 1: return result[0] + return IR.SequenceNode(ppt, result) + +def parse_file(ppt, filename): + "Loads a .P65 source file, and returns an IR list." + Err.currentpoint = ppt + if Cmd.verbose > 0: print "Loading "+filename + try: + f = file(filename) + linelist = f.readlines() + f.close() + pptlist = ["%s:%d" % (filename, i+1) for i in range(len(linelist))] + lexlist = map(lex, pptlist, linelist) + IRlist = map(parse_line, pptlist, lexlist) + IRlist = [node for node in IRlist if node is not IR.NullNode] + return IR.SequenceNode(ppt, IRlist) + except IOError: + Err.log ("Could not read "+filename) + return IR.NullNode + +def parse(filename): + "Top level parsing routine, taking a source file name and returning an IR list." + global templabelcount + templabelcount = 0 + return parse_file("", filename) + diff --git a/src/Ophis/IR.py b/src/Ophis/IR.py index a0164d1..a0255e2 100644 --- a/src/Ophis/IR.py +++ b/src/Ophis/IR.py @@ -1,161 +1,160 @@ -"""P65 Intermediate Representation - - Classes for representing the Intermediate nodes upon which the - assembler passes operate.""" - -# Copyright 2002 Michael C. Martin. -# You may use, modify, and distribute this file under the BSD -# license: See LICENSE.txt for details. - -from __future__ import nested_scopes -import Ophis.Errors as Err - -class Node: - """The default IR Node - Instances of Node always have the three fields ppt(Program Point), - nodetype(a string), and data (a list).""" - def __init__(self, ppt, nodetype, *data): - self.ppt = ppt - self.nodetype = nodetype - self.data = list(data) - def accept(self, asmpass, env=None): - """Implements the Visitor pattern for an assembler pass. - Calls the routine 'asmpass.visitTYPE(self, env)' where - TYPE is the value of self.nodetype.""" - Err.currentpoint = self.ppt - routine = getattr(asmpass, "visit"+self.nodetype, asmpass.visitUnknown) - routine(self, env) - def __str__(self): - if self.nodetype != "SEQUENCE": - return str(self.ppt)+": "+self.nodetype+" - "+" ".join(map(str, self.data)) - else: - return "\n".join(map(str, self.data)) - def __repr__(self): - args = [self.ppt, self.nodetype] + self.data - return "Node(" + ", ".join(map(repr, args)) + ")" - -NullNode = Node("", "None") - -def SequenceNode(ppt, nodelist): - return Node(ppt, "SEQUENCE", *nodelist) - -class Expr: - """Base class for P65 expressions - All expressions have a field called "data" and a boolean field - called "hardcoded". An expression is hardcoded if it has no - symbolic values in it.""" - def __init__(self, data): - self.data = data - self.hardcoded = 0 - def __str__(self): - return "" - def valid(self, env=None, PCvalid=0): - """Returns true if the the expression can be successfully - evaluated in the specified environment.""" - return 0 - def value(self, env=None): - "Evaluates this expression in the given environment." - return None - -class ConstantExpr(Expr): - "Represents a numeric constant" - def __init__(self, data): - self.data = data - self.hardcoded = 1 - def __str__(self): - return str(self.data) - def valid(self, env=None, PCvalid=0): - return 1 - def value(self, env=None): - return self.data - -class LabelExpr(Expr): - "Represents a symbolic constant" - def __init__(self, data): - self.data = data - self.hardcoded = 0 - def __str__(self): - return self.data - def valid(self, env=None, PCvalid=0): - return (env is not None) and self.data in env - def value(self, env=None): - return env[self.data] - -class PCExpr(Expr): - "Represents the current program counter: ^" - def __init__(self): - self.hardcoded = 0 - def __str__(self): - return "^" - def valid(self, env=None, PCvalid=0): - return env is not None and PCvalid - def value(self, env=None): - return env.getPC() - -class HighByteExpr(Expr): - "Represents the expression >{data}" - def __init__(self, data): - self.data = data - self.hardcoded = data.hardcoded - def __str__(self): - return ">"+str(self.data) - def valid(self, env=None, PCvalid=0): - return self.data.valid(env, PCvalid) - def value(self, env=None): - val = self.data.value(env) - return (val >> 8) & 0xff - -class LowByteExpr(Expr): - "Represents the expression <{data}" - def __init__(self, data): - self.data = data - self.hardcoded = data.hardcoded - def __str__(self): - return "<"+str(self.data) - def valid(self, env=None, PCvalid=0): - return self.data.valid(env, PCvalid) - def value(self, env=None): - val = self.data.value(env) - return val & 0xff - -class SequenceExpr(Expr): - """Represents an interleaving of operands (of type Expr) and - operators (of type String). Subclasses must provide a routine - operate(self, firstarg, op, secondarg) that evaluates the - operator.""" - def __init__(self, data): - """Constructor for Sequence Expressions. Results will be - screwy if the data inpot isn't a list with types - [Expr, str, Expr, str, Expr, str, ... Expr, str, Expr].""" - self.data = data - self.operands = [x for x in data if isinstance(x, Expr)] - self.operators = [x for x in data if type(x)==str] - for i in self.operands: - if not i.hardcoded: - self.hardcoded = 0 - break - else: - self.hardcoded = 1 - def __str__(self): - return "["+" ".join(map(str, self.data))+"]" - def valid(self, env=None, PCvalid=0): - for i in self.operands: - if not i.valid(env, PCvalid): - return 0 - return 1 - def value(self, env=None): - subs = map((lambda x: x.value(env)), self.operands) - result = subs[0] - index = 1 - for op in self.operators: - result = self.operate(result, op, subs[index]) - index += 1 - return result - def operate(self, start, op, other): - if op=="*": return start * other - if op=="/": return start // other - if op=="+": return start + other - if op=="-": return start - other - if op=="&": return start & other - if op=="|": return start | other - if op=="^": return start ^ other +"""Ophis Intermediate Representation + + Classes for representing the Intermediate nodes upon which the + assembler passes operate.""" + +# Copyright 2002-2012 Michael C. Martin and additional contributors. +# You may use, modify, and distribute this file under the MIT +# license: See README for details. + +import Ophis.Errors as Err + +class Node: + """The default IR Node + Instances of Node always have the three fields ppt(Program Point), + nodetype(a string), and data (a list).""" + def __init__(self, ppt, nodetype, *data): + self.ppt = ppt + self.nodetype = nodetype + self.data = list(data) + def accept(self, asmpass, env=None): + """Implements the Visitor pattern for an assembler pass. + Calls the routine 'asmpass.visitTYPE(self, env)' where + TYPE is the value of self.nodetype.""" + Err.currentpoint = self.ppt + routine = getattr(asmpass, "visit"+self.nodetype, asmpass.visitUnknown) + routine(self, env) + def __str__(self): + if self.nodetype != "SEQUENCE": + return str(self.ppt)+": "+self.nodetype+" - "+" ".join(map(str, self.data)) + else: + return "\n".join(map(str, self.data)) + def __repr__(self): + args = [self.ppt, self.nodetype] + self.data + return "Node(" + ", ".join(map(repr, args)) + ")" + +NullNode = Node("", "None") + +def SequenceNode(ppt, nodelist): + return Node(ppt, "SEQUENCE", *nodelist) + +class Expr: + """Base class for P65 expressions + All expressions have a field called "data" and a boolean field + called "hardcoded". An expression is hardcoded if it has no + symbolic values in it.""" + def __init__(self, data): + self.data = data + self.hardcoded = 0 + def __str__(self): + return "" + def valid(self, env=None, PCvalid=0): + """Returns true if the the expression can be successfully + evaluated in the specified environment.""" + return 0 + def value(self, env=None): + "Evaluates this expression in the given environment." + return None + +class ConstantExpr(Expr): + "Represents a numeric constant" + def __init__(self, data): + self.data = data + self.hardcoded = 1 + def __str__(self): + return str(self.data) + def valid(self, env=None, PCvalid=0): + return 1 + def value(self, env=None): + return self.data + +class LabelExpr(Expr): + "Represents a symbolic constant" + def __init__(self, data): + self.data = data + self.hardcoded = 0 + def __str__(self): + return self.data + def valid(self, env=None, PCvalid=0): + return (env is not None) and self.data in env + def value(self, env=None): + return env[self.data] + +class PCExpr(Expr): + "Represents the current program counter: ^" + def __init__(self): + self.hardcoded = 0 + def __str__(self): + return "^" + def valid(self, env=None, PCvalid=0): + return env is not None and PCvalid + def value(self, env=None): + return env.getPC() + +class HighByteExpr(Expr): + "Represents the expression >{data}" + def __init__(self, data): + self.data = data + self.hardcoded = data.hardcoded + def __str__(self): + return ">"+str(self.data) + def valid(self, env=None, PCvalid=0): + return self.data.valid(env, PCvalid) + def value(self, env=None): + val = self.data.value(env) + return (val >> 8) & 0xff + +class LowByteExpr(Expr): + "Represents the expression <{data}" + def __init__(self, data): + self.data = data + self.hardcoded = data.hardcoded + def __str__(self): + return "<"+str(self.data) + def valid(self, env=None, PCvalid=0): + return self.data.valid(env, PCvalid) + def value(self, env=None): + val = self.data.value(env) + return val & 0xff + +class SequenceExpr(Expr): + """Represents an interleaving of operands (of type Expr) and + operators (of type String). Subclasses must provide a routine + operate(self, firstarg, op, secondarg) that evaluates the + operator.""" + def __init__(self, data): + """Constructor for Sequence Expressions. Results will be + screwy if the data inpot isn't a list with types + [Expr, str, Expr, str, Expr, str, ... Expr, str, Expr].""" + self.data = data + self.operands = [x for x in data if isinstance(x, Expr)] + self.operators = [x for x in data if type(x)==str] + for i in self.operands: + if not i.hardcoded: + self.hardcoded = 0 + break + else: + self.hardcoded = 1 + def __str__(self): + return "["+" ".join(map(str, self.data))+"]" + def valid(self, env=None, PCvalid=0): + for i in self.operands: + if not i.valid(env, PCvalid): + return 0 + return 1 + def value(self, env=None): + subs = map((lambda x: x.value(env)), self.operands) + result = subs[0] + index = 1 + for op in self.operators: + result = self.operate(result, op, subs[index]) + index += 1 + return result + def operate(self, start, op, other): + if op=="*": return start * other + if op=="/": return start // other + if op=="+": return start + other + if op=="-": return start - other + if op=="&": return start & other + if op=="|": return start | other + if op=="^": return start ^ other diff --git a/src/Ophis/Macro.py b/src/Ophis/Macro.py index 384f47c..f4cdce9 100644 --- a/src/Ophis/Macro.py +++ b/src/Ophis/Macro.py @@ -1,62 +1,66 @@ -"""Macro support for P65. - - P65 Macros are cached SequenceNodes with arguments - set via .alias commands and prevented from escaping - with .scope and .scend commands.""" - -import sys - -import Ophis.IR as IR -import Ophis.CmdLine as Cmd -import Ophis.Errors as Err - -macros = {} -currentname = None -currentbody = None - -def newMacro(name): - "Start creating a new macro with the specified name." - global currentname - global currentbody - global macros - if currentname is not None: - Err.log("Internal error! Nested macro attempt!") - else: - if name in macros: - Err.log("Duplicate macro definition '%s'" % name) - currentname = name - currentbody = [] - -def registerNode(node): - global currentbody - currentbody.append(IR.Node(node.ppt, node.nodetype, *node.data)) - -def endMacro(): - global currentname - global currentbody - global macros - if currentname is None: - Err.log("Internal error! Ended a non-existent macro!") - else: - macros[currentname] = currentbody - currentname = None - currentbody = None - -def expandMacro(ppt, name, arglist): - global macros - if name not in macros: - Err.log("Undefined macro '%s'" % name) - return IR.NullNode - argexprs = [IR.Node(ppt, "Label", "_*%d" % i, arg) for (i, arg) in zip(xrange(1, sys.maxint), arglist)] - bindexprs = [IR.Node(ppt, "Label", "_%d" % i, IR.LabelExpr("_*%d" % i)) for i in range(1, len(arglist)+1)] - body = [IR.Node("%s->%s" % (ppt, node.ppt), node.nodetype, *node.data) for node in macros[name]] - invocation = [IR.Node(ppt, "ScopeBegin")] + argexprs + [IR.Node(ppt, "ScopeBegin")] + bindexprs + body + [IR.Node(ppt, "ScopeEnd"), IR.Node(ppt, "ScopeEnd")] - return IR.SequenceNode(ppt, invocation) - -def dump(): - global macros - for mac in macros: - body = macros[mac] - print "Macro: "+mac - for node in body: print node - print "" +"""Macro support for Ophis. + + Ophis Macros are cached SequenceNodes with arguments + set via .alias commands and prevented from escaping + with .scope and .scend commands.""" + +# Copyright 2002-2012 Michael C. Martin and additional contributors. +# You may use, modify, and distribute this file under the MIT +# license: See README for details. + +import sys + +import Ophis.IR as IR +import Ophis.CmdLine as Cmd +import Ophis.Errors as Err + +macros = {} +currentname = None +currentbody = None + +def newMacro(name): + "Start creating a new macro with the specified name." + global currentname + global currentbody + global macros + if currentname is not None: + Err.log("Internal error! Nested macro attempt!") + else: + if name in macros: + Err.log("Duplicate macro definition '%s'" % name) + currentname = name + currentbody = [] + +def registerNode(node): + global currentbody + currentbody.append(IR.Node(node.ppt, node.nodetype, *node.data)) + +def endMacro(): + global currentname + global currentbody + global macros + if currentname is None: + Err.log("Internal error! Ended a non-existent macro!") + else: + macros[currentname] = currentbody + currentname = None + currentbody = None + +def expandMacro(ppt, name, arglist): + global macros + if name not in macros: + Err.log("Undefined macro '%s'" % name) + return IR.NullNode + argexprs = [IR.Node(ppt, "Label", "_*%d" % i, arg) for (i, arg) in zip(xrange(1, sys.maxint), arglist)] + bindexprs = [IR.Node(ppt, "Label", "_%d" % i, IR.LabelExpr("_*%d" % i)) for i in range(1, len(arglist)+1)] + body = [IR.Node("%s->%s" % (ppt, node.ppt), node.nodetype, *node.data) for node in macros[name]] + invocation = [IR.Node(ppt, "ScopeBegin")] + argexprs + [IR.Node(ppt, "ScopeBegin")] + bindexprs + body + [IR.Node(ppt, "ScopeEnd"), IR.Node(ppt, "ScopeEnd")] + return IR.SequenceNode(ppt, invocation) + +def dump(): + global macros + for mac in macros: + body = macros[mac] + print "Macro: "+mac + for node in body: print node + print "" diff --git a/src/Ophis/Main.py b/src/Ophis/Main.py index 281c4a8..46ed086 100644 --- a/src/Ophis/Main.py +++ b/src/Ophis/Main.py @@ -1,124 +1,123 @@ -"""Main controller routines for the P65 assembler. - - When invoked as main, interprets its command line and goes from there. - Otherwise, use run_all to interpret a file set.""" - -# Copyright 2002 Michael C. Martin. -# You may use, modify, and distribute this file under the BSD -# license: See LICENSE.txt for details. - -from __future__ import nested_scopes -import sys -import Ophis.Frontend -import Ophis.IR -import Ophis.CorePragmas -import Ophis.OldPragmas -import Ophis.Passes -import Ophis.Errors as Err -import Ophis.Environment -import Ophis.CmdLine -import Ophis.Opcodes - - -def usage(): - "Prints a usage message and quits." - print "Usage:" - print "\tOphis [options] infile outfile" - print "" - print "Options:" - print "\t-6510 Allow 6510 undocumented opcodes" - print "\t-65c02 Enable 65c02 extensions" - print "\t-d Allow deprecated pragmas" - print "\t-v n Set verbosity to n (0-4, 1=default)" - sys.exit(1) - -def run_all(infile, outfile): - "Transforms the source infile to a binary outfile." - Err.count = 0 - z = Ophis.Frontend.parse(infile) - env = Ophis.Environment.Environment() - - m = Ophis.Passes.ExpandMacros() - i = Ophis.Passes.InitLabels() - l_basic = Ophis.Passes.UpdateLabels() - l = Ophis.Passes.FixPoint("label update", [l_basic], lambda: l_basic.changed == 0) - c = Ophis.Passes.Collapse() - a = Ophis.Passes.Assembler() - - passes = [] - passes.append(Ophis.Passes.DefineMacros()) - passes.append(Ophis.Passes.FixPoint("macro expansion", [m], lambda: m.changed == 0)) - passes.append(Ophis.Passes.FixPoint("label initialization", [i], lambda: i.changed == 0)) - passes.extend([Ophis.Passes.CircularityCheck(), Ophis.Passes.CheckExprs(), Ophis.Passes.EasyModes()]) - passes.append(Ophis.Passes.FixPoint("instruction selection", [l, c], lambda: c.collapsed == 0)) - passes.extend([Ophis.Passes.NormalizeModes(), Ophis.Passes.UpdateLabels(), a]) - - for p in passes: p.go(z, env) - - if Err.count == 0: - try: - output = file(outfile, 'wb') - output.write("".join(map(chr, a.output))) - except IOError: - print "Could not write to "+outfile - else: - Err.report() - -def run_ophis(): - infile = None - outfile = None - - p65_compatibility_mode = 0 - chip_extension = None - - reading_arg = 0 - - for x in sys.argv[1:]: - if reading_arg: - try: - Ophis.CmdLine.verbose = int(x) - reading_arg = 0 - except ValueError: - print "FATAL: Non-integer passed as argument to -v" - usage() - elif x[0] == '-': - if x == '-v': - reading_arg = 1 - elif x == '-6510': - chip_extension = Ophis.Opcodes.undocops - elif x == '-65c02': - chip_extension = Ophis.Opcodes.c02extensions - elif x == '-d': - p65_compatibility_mode = 1 - else: - print "FATAL: Unknown option "+x - usage() - elif infile == None: - infile = x - elif outfile == None: - outfile = x - else: - print "FATAL: Too many files specified" - usage() - - if infile is None: - print "FATAL: No files specified" - usage() - - if outfile is None: - print "FATAL: No output file specified" - usage() - - Ophis.Frontend.pragma_modules.append(Ophis.CorePragmas) - - if p65_compatibility_mode: - Ophis.Frontend.pragma_modules.append(Ophis.OldPragmas) - - if chip_extension is not None: - Ophis.Opcodes.opcodes.update(chip_extension) - - Ophis.CorePragmas.reset() - run_all(infile, outfile) - -if __name__ == '__main__': - run_ophis() +"""Main controller routines for the Ophis assembler. + + When invoked as main, interprets its command line and goes from there. + Otherwise, use run_all to interpret a file set.""" + +# Copyright 2002-2012 Michael C. Martin and additional contributors. +# You may use, modify, and distribute this file under the MIT +# license: See README for details. + +import sys +import Ophis.Frontend +import Ophis.IR +import Ophis.CorePragmas +import Ophis.OldPragmas +import Ophis.Passes +import Ophis.Errors as Err +import Ophis.Environment +import Ophis.CmdLine +import Ophis.Opcodes + + +def usage(): + "Prints a usage message and quits." + print "Usage:" + print "\tOphis [options] infile outfile" + print "" + print "Options:" + print "\t-6510 Allow 6510 undocumented opcodes" + print "\t-65c02 Enable 65c02 extensions" + print "\t-d Allow deprecated pragmas" + print "\t-v n Set verbosity to n (0-4, 1=default)" + sys.exit(1) + +def run_all(infile, outfile): + "Transforms the source infile to a binary outfile." + Err.count = 0 + z = Ophis.Frontend.parse(infile) + env = Ophis.Environment.Environment() + + m = Ophis.Passes.ExpandMacros() + i = Ophis.Passes.InitLabels() + l_basic = Ophis.Passes.UpdateLabels() + l = Ophis.Passes.FixPoint("label update", [l_basic], lambda: l_basic.changed == 0) + c = Ophis.Passes.Collapse() + a = Ophis.Passes.Assembler() + + passes = [] + passes.append(Ophis.Passes.DefineMacros()) + passes.append(Ophis.Passes.FixPoint("macro expansion", [m], lambda: m.changed == 0)) + passes.append(Ophis.Passes.FixPoint("label initialization", [i], lambda: i.changed == 0)) + passes.extend([Ophis.Passes.CircularityCheck(), Ophis.Passes.CheckExprs(), Ophis.Passes.EasyModes()]) + passes.append(Ophis.Passes.FixPoint("instruction selection", [l, c], lambda: c.collapsed == 0)) + passes.extend([Ophis.Passes.NormalizeModes(), Ophis.Passes.UpdateLabels(), a]) + + for p in passes: p.go(z, env) + + if Err.count == 0: + try: + output = file(outfile, 'wb') + output.write("".join(map(chr, a.output))) + except IOError: + print "Could not write to "+outfile + else: + Err.report() + +def run_ophis(): + infile = None + outfile = None + + p65_compatibility_mode = 0 + chip_extension = None + + reading_arg = 0 + + for x in sys.argv[1:]: + if reading_arg: + try: + Ophis.CmdLine.verbose = int(x) + reading_arg = 0 + except ValueError: + print "FATAL: Non-integer passed as argument to -v" + usage() + elif x[0] == '-': + if x == '-v': + reading_arg = 1 + elif x == '-6510': + chip_extension = Ophis.Opcodes.undocops + elif x == '-65c02': + chip_extension = Ophis.Opcodes.c02extensions + elif x == '-d': + p65_compatibility_mode = 1 + else: + print "FATAL: Unknown option "+x + usage() + elif infile == None: + infile = x + elif outfile == None: + outfile = x + else: + print "FATAL: Too many files specified" + usage() + + if infile is None: + print "FATAL: No files specified" + usage() + + if outfile is None: + print "FATAL: No output file specified" + usage() + + Ophis.Frontend.pragma_modules.append(Ophis.CorePragmas) + + if p65_compatibility_mode: + Ophis.Frontend.pragma_modules.append(Ophis.OldPragmas) + + if chip_extension is not None: + Ophis.Opcodes.opcodes.update(chip_extension) + + Ophis.CorePragmas.reset() + run_all(infile, outfile) + +if __name__ == '__main__': + run_ophis() diff --git a/src/Ophis/OldPragmas.py b/src/Ophis/OldPragmas.py index 9f110db..ca0ae41 100644 --- a/src/Ophis/OldPragmas.py +++ b/src/Ophis/OldPragmas.py @@ -1,28 +1,28 @@ -"""P65-Perl compatibility pragmas - - Additional assembler directives to permit assembly of - old P65-Perl sources. This is not, in itself, sufficient, - as the precedence of < and > vs. + and - has changed - between P65-Perl and P65-Ophis. - - Supported pragmas are: .ascii (byte), .address (word), - .segment (text), .code (text), and .link.""" - -# Copyright 2002 Michael C. Martin. -# You may use, modify, and distribute this file under the BSD -# license: See LICENSE.txt for details. - -import Ophis.CorePragmas as core - -pragmaAscii = core.pragmaByte -pragmaAddress = core.pragmaWord -pragmaSegment = core.pragmaText -pragmaCode = core.pragmaText - -def pragmaLink(ppt, line, result): - "Load a file in a precise memory location." - filename = line.expect("STRING").value - newPC = FE.parse_expr(line) - line.expect("EOL") - result.append(IR.Node(ppt, "SetPC", newPC)) - if type(filename)==str: result.append(FE.parse_file(ppt, filename)) +"""P65-Perl compatibility pragmas + + Additional assembler directives to permit assembly of + old P65-Perl sources. This is not, in itself, sufficient, + as the precedence of < and > vs. + and - has changed + between P65-Perl and Ophis. + + Supported pragmas are: .ascii (byte), .address (word), + .segment (text), .code (text), and .link.""" + +# Copyright 2002-2012 Michael C. Martin and additional contributors. +# You may use, modify, and distribute this file under the MIT +# license: See README for details. + +import Ophis.CorePragmas as core + +pragmaAscii = core.pragmaByte +pragmaAddress = core.pragmaWord +pragmaSegment = core.pragmaText +pragmaCode = core.pragmaText + +def pragmaLink(ppt, line, result): + "Load a file in a precise memory location." + filename = line.expect("STRING").value + newPC = FE.parse_expr(line) + line.expect("EOL") + result.append(IR.Node(ppt, "SetPC", newPC)) + if type(filename)==str: result.append(FE.parse_file(ppt, filename)) diff --git a/src/Ophis/Opcodes.py b/src/Ophis/Opcodes.py index fd3bbfc..5a8270f 100644 --- a/src/Ophis/Opcodes.py +++ b/src/Ophis/Opcodes.py @@ -1,28 +1,28 @@ """Opcodes file. - Tables for the assembly of 6502-family instructions, mapping - opcodes and addressing modes to binary instructions.""" + Tables for the assembly of 6502-family instructions, mapping + opcodes and addressing modes to binary instructions.""" -# Copyright 2002 Michael C. Martin. -# You may use, modify, and distribute this file under the BSD -# license: See LICENSE.txt for details. +# Copyright 2002-2012 Michael C. Martin and additional contributors. +# You may use, modify, and distribute this file under the MIT +# license: See README for details. # Names of addressing modes modes = ["Implied", # 0 - "Immediate", # 1 - "Zero Page", # 2 - "Zero Page, X", # 3 - "Zero Page, Y", # 4 - "Absolute", # 5 - "Absolute, X", # 6 - "Absolute, Y", # 7 - "(Absolute)", # 8 + "Immediate", # 1 + "Zero Page", # 2 + "Zero Page, X", # 3 + "Zero Page, Y", # 4 + "Absolute", # 5 + "Absolute, X", # 6 + "Absolute, Y", # 7 + "(Absolute)", # 8 "(Absolute, X)", # 9 "(Absolute), Y", # 10 "(Zero Page)", # 11 - "(Zero Page, X)", # 12 - "(Zero Page), Y", # 13 - "Relative"] # 14 + "(Zero Page, X)", # 12 + "(Zero Page), Y", # 13 + "Relative"] # 14 # Lengths of the argument lengths = [0, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1] diff --git a/src/Ophis/Passes.py b/src/Ophis/Passes.py index 5901843..7a2ff70 100644 --- a/src/Ophis/Passes.py +++ b/src/Ophis/Passes.py @@ -1,518 +1,516 @@ -"""The P65 Assembler passes - - P65's design philosophy is to build the IR once, then run a great - many assembler passes over the result. Thus, each pass does a - single, specialized job. When strung together, the full - translation occurs. This structure also makes the assembler - very extensible; additional analyses or optimizations may be - added as new subclasses of Pass.""" - - -# Copyright 2002 Michael C. Martin. -# You may use, modify, and distribute this file under the BSD -# license: See LICENSE.txt for details. - -from __future__ import nested_scopes -import Ophis.Errors as Err -import Ophis.IR as IR -import Ophis.Opcodes as Ops -import Ophis.CmdLine as Cmd -import Ophis.Macro as Macro - -# The passes themselves - -class Pass: - """Superclass for all assembler passes. Automatically handles IR - types that modify the environent's structure, and by default - raises an error on anything else. Override visitUnknown in your - extension pass to produce a pass that accepts everything.""" - name = "Default Pass" - def __init__(self): - self.writeOK = 1 - def visitNone(self, node, env): - pass - def visitSEQUENCE(self, node, env): - Err.currentpoint = node.ppt - for n in node.data: - n.accept(self, env) - def visitDataSegment(self, node, env): - self.writeOK = 0 - env.setsegment(node.data[0]) - def visitTextSegment(self, node, env): - self.writeOK = 1 - env.setsegment(node.data[0]) - def visitScopeBegin(self, node, env): - env.newscope() - def visitScopeEnd(self, node, env): - env.endscope() - def visitUnknown(self, node, env): - Err.log("Internal error! "+self.name+" cannot understand node type "+node.nodetype) - def prePass(self): - pass - def postPass(self): - pass - def go(self, node, env): - """Prepares the environment and runs this pass, possibly - printing debugging information.""" - if Err.count == 0: - if Cmd.verbose > 1: print "Running: "+self.name - env.reset() - self.prePass() - node.accept(self, env) - self.postPass() - env.reset() - if Cmd.verbose > 3: - print "Current labels:" - print env - if Cmd.verbose > 2: - print "Current IR:" - print node - -class FixPoint: - """A specialized class that is not a pass but can be run like one. - This class takes a list of passes and a "fixpoint" function.""" - def __init__(self, name, passes, fixpoint): - self.name = name - self.passes = passes - self.fixpoint = fixpoint - def go(self, node, env): - """Runs this FixPoint's passes, in order, until the fixpoint - is true. Always runs the passes at least once.""" - for i in xrange(100): - if Err.count != 0: break - for p in self.passes: - p.go(node, env) - if Err.count != 0: break - if self.fixpoint(): break - if Cmd.verbose > 1: print "Fixpoint failed, looping back" - else: - Err.log("Can't make %s converge! Maybe there's a recursive dependency somewhere?" % self.name) - -class DefineMacros(Pass): - "Extract macro definitions and remove them from the IR" - name = "Macro definition pass" - def prePass(self): - self.inDef = 0 - self.nestedError = 0 - def postPass(self): - if self.inDef: - Err.log("Unmatched .macro") - elif Cmd.verbose > 2: - print "Macro definitions:" - Macro.dump() - def visitMacroBegin(self, node, env): - if self.inDef: - Err.log("Nested macro definition") - self.nestedError = 1 - else: - Macro.newMacro(node.data[0]) - node.nodetype = "None" - node.data = [] - self.inDef = 1 - def visitMacroEnd(self, node, env): - if self.inDef: - Macro.endMacro() - node.nodetype = "None" - node.data = [] - self.inDef = 0 - elif not self.nestedError: - Err.log("Unmatched .macend") - def visitUnknown(self, node, env): - if self.inDef: - Macro.registerNode(node) - node.nodetype = "None" - node.data = [] - - -class ExpandMacros(Pass): - "Replace macro invocations with the appropriate text" - name = "Macro expansion pass" - def prePass(self): - self.changed = 0 - def visitMacroInvoke(self, node, env): - replacement = Macro.expandMacro(node.ppt, node.data[0], node.data[1:]) - node.nodetype = replacement.nodetype - node.data = replacement.data - self.changed = 1 - def visitUnknown(self, node, env): - pass - -class InitLabels(Pass): - "Finds all reachable labels" - name = "Label initialization pass" - def __init__(self): - Pass.__init__(self) - self.labelmap = {} - def prePass(self): - self.changed = 0 - self.PCvalid = 1 - def visitAdvance(self, node, env): - self.PCvalid=node.data[0].valid(env, self.PCvalid) - def visitSetPC(self, node, env): - self.PCvalid=node.data[0].valid(env, self.PCvalid) - def visitLabel(self, node, env): - (label, val) = node.data - fulllabel = "%d:%s" % (env.stack[0], label) - if fulllabel in self.labelmap and self.labelmap[fulllabel] is not node: - Err.log("Duplicate label definition '%s'" % label) - if fulllabel not in self.labelmap: - self.labelmap[fulllabel] = node - if val.valid(env, self.PCvalid) and label not in env: - env[label]=0 - self.changed=1 - def visitUnknown(self, node, env): - pass - -class CircularityCheck(Pass): - "Checks for circular label dependencies" - name = "Circularity check pass" - def prePass(self): - self.changed=0 - self.PCvalid=1 - def visitAdvance(self, node, env): - PCvalid = self.PCvalid - self.PCvalid=node.data[0].valid(env, self.PCvalid) - if not node.data[0].valid(env, PCvalid): - Err.log("Undefined or circular reference on .advance") - def visitSetPC(self, node, env): - PCvalid = self.PCvalid - self.PCvalid=node.data[0].valid(env, self.PCvalid) - if not node.data[0].valid(env, PCvalid): - Err.log("Undefined or circular reference on program counter set") - def visitCheckPC(self, node, env): - if not node.data[0].valid(env, self.PCvalid): - Err.log("Undefined or circular reference on program counter check") - def visitLabel(self, node, env): - (label, val) = node.data - if not val.valid(env, self.PCvalid): - Err.log("Undefined or circular dependency for label '%s'" % label) - def visitUnknown(self, node, env): - pass - -class CheckExprs(Pass): - "Ensures all expressions can resolve" - name = "Expression checking pass" - def visitUnknown(self, node, env): - for i in [x for x in node.data if isinstance(x, IR.Expr)]: - i.value(env) # Throw away result, just confirm validity of all expressions - -class EasyModes(Pass): - "Assigns address modes to hardcoded and branch instructions" - name = "Easy addressing modes pass" - def visitMemory(self, node, env): - if Ops.opcodes[node.data[0]][14] is not None: - node.nodetype = "Relative" - return - if node.data[1].hardcoded: - if not collapse_no_index(node, env): - node.nodetype = "Absolute" - def visitMemoryX(self, node, env): - if node.data[1].hardcoded: - if not collapse_x(node, env): - node.nodetype = "AbsoluteX" - def visitMemoryY(self, node, env): - if node.data[1].hardcoded: - if not collapse_y(node, env): - node.nodetype = "AbsoluteY" - def visitPointer(self, node, env): - if node.data[1].hardcoded: - if not collapse_no_index_ind(node, env): - node.nodetype = "Indirect" - def visitPointerX(self, node, env): - if node.data[1].hardcoded: - if not collapse_x_ind(node, env): - node.nodetype = "AbsIndX" - def visitPointerY(self, node, env): - if node.data[1].hardcoded: - if not collapse_y_ind(node, env): - node.nodetype = "AbsIndY" - def visitUnknown(self, node, env): - pass - -class UpdateLabels(Pass): - "Computes the new values for all entries in the symbol table" - name = "Label Update Pass" - def prePass(self): - self.changed = 0 - def visitSetPC(self, node, env): env.setPC(node.data[0].value(env)) - def visitAdvance(self, node, env): env.setPC(node.data[0].value(env)) - def visitImplied(self, node, env): env.incPC(1) - def visitImmediate(self, node, env): env.incPC(2) - def visitIndirectX(self, node, env): env.incPC(2) - def visitIndirectY(self, node, env): env.incPC(2) - def visitZPIndirect(self, node, env): env.incPC(2) - def visitZeroPage(self, node, env): env.incPC(2) - def visitZeroPageX(self, node, env): env.incPC(2) - def visitZeroPageY(self, node, env): env.incPC(2) - def visitRelative(self, node, env): env.incPC(2) - def visitIndirect(self, node, env): env.incPC(3) - def visitAbsolute(self, node, env): env.incPC(3) - def visitAbsoluteX(self, node, env): env.incPC(3) - def visitAbsoluteY(self, node, env): env.incPC(3) - def visitAbsIndX(self, node, env): env.incPC(3) - def visitAbsIndY(self, node, env): env.incPC(3) - def visitMemory(self, node, env): env.incPC(3) - def visitMemoryX(self, node, env): env.incPC(3) - def visitMemoryY(self, node, env): env.incPC(3) - def visitPointer(self, node, env): env.incPC(3) - def visitPointerX(self, node, env): env.incPC(3) - def visitPointerY(self, node, env): env.incPC(3) - def visitCheckPC(self, node, env): pass - def visitLabel(self, node, env): - (label, val) = node.data - old = env[label] - env[label] = val.value(env) - if old != env[label]: - self.changed = 1 - def visitByte(self, node, env): env.incPC(len(node.data)) - def visitWord(self, node, env): env.incPC(len(node.data)*2) - def visitDword(self, node, env): env.incPC(len(node.data)*4) - def visitWordBE(self, node, env): env.incPC(len(node.data)*2) - def visitDwordBE(self, node, env): env.incPC(len(node.data)*4) - -class Collapse(Pass): - """Selects as many zero-page instructions to convert as - possible, and tracks how many instructions have been - converted this pass.""" - name = "Instruction Collapse Pass" - def prePass(self): - self.collapsed = 0 - def visitMemory(self, node, env): - if collapse_no_index(node, env): self.collapsed += 1 - def visitMemoryX(self, node, env): - if collapse_x(node, env): self.collapsed += 1 - def visitMemoryY(self, node, env): - if collapse_y(node, env): self.collapsed += 1 - def visitPointer(self, node, env): - if collapse_no_index_ind(node, env): self.collapsed += 1 - def visitPointerX(self, node, env): - if collapse_x_ind(node, env): self.collapsed += 1 - def visitPointerY(self, node, env): - if collapse_y_ind(node, env): self.collapsed += 1 - def visitUnknown(self, node, env): - pass - -def collapse_no_index(node, env): - """Transforms a Memory node into a ZeroPage one if possible. - Returns 1 if it made the collapse, false otherwise.""" - if node.data[1].value(env) < 0x100 and Ops.opcodes[node.data[0]][2] is not None: - node.nodetype = "ZeroPage" - return 1 - else: - return 0 - -def collapse_x(node, env): - """Transforms a MemoryX node into a ZeroPageX one if possible. - Returns 1 if it made the collapse, false otherwise.""" - if node.data[1].value(env) < 0x100 and Ops.opcodes[node.data[0]][3] is not None: - node.nodetype = "ZeroPageX" - return 1 - else: - return 0 - -def collapse_y(node, env): - """Transforms a MemoryY node into a ZeroPageY one if possible. - Returns 1 if it made the collapse, false otherwise.""" - if node.data[1].value(env) < 0x100 and Ops.opcodes[node.data[0]][4] is not None: - node.nodetype = "ZeroPageY" - return 1 - else: - return 0 - -def collapse_no_index_ind(node, env): - """Transforms a Pointer node into a ZPIndirect one if possible. - Returns 1 if it made the collapse, false otherwise.""" - if node.data[1].value(env) < 0x100 and Ops.opcodes[node.data[0]][11] is not None: - node.nodetype = "ZPIndirect" - return 1 - else: - return 0 - -def collapse_x_ind(node, env): - """Transforms a PointerX node into an IndirectX one if possible. - Returns 1 if it made the collapse, false otherwise.""" - if node.data[1].value(env) < 0x100 and Ops.opcodes[node.data[0]][12] is not None: - node.nodetype = "IndirectX" - return 1 - else: - return 0 - -def collapse_y_ind(node, env): - """Transforms a PointerY node into an IndirectY one if possible. - Returns 1 if it made the collapse, false otherwise.""" - if node.data[1].value(env) < 0x100 and Ops.opcodes[node.data[0]][13] is not None: - node.nodetype = "IndirectY" - return 1 - else: - return 0 - - -class NormalizeModes(Pass): - """Eliminates the intermediate "Memory" and "Pointer" nodes, - converting them to "Absolute".""" - name = "Mode Normalization pass" - def visitMemory(self, node, env): node.nodetype = "Absolute" - def visitMemoryX(self, node, env): node.nodetype = "AbsoluteX" - def visitMemoryY(self, node, env): node.nodetype = "AbsoluteY" - def visitPointer(self, node, env): node.nodetype = "Indirect" - def visitPointerX(self, node, env): node.nodetype = "AbsIndX" - # If we ever hit a PointerY by this point, we have a bug. - def visitPointerY(self, node, env): node.nodetype = "AbsIndY" - def visitUnknown(self, node, env): pass - -class Assembler(Pass): - """Converts the IR into a list of bytes, suitable for writing to - a file.""" - name = "Assembler" - - def prePass(self): - self.output = [] - self.code = 0 - self.data = 0 - self.filler = 0 - - def postPass(self): - if Cmd.verbose > 0 and Err.count == 0: - print "Assembly complete: %s bytes output (%s code, %s data, %s filler)" \ - % (len(self.output), self.code, self.data, self.filler) - - def outputbyte(self, expr, env): - 'Outputs a byte, with range checking' - if self.writeOK: - val = expr.value(env) - if val < 0x00 or val > 0xff: - Err.log("Byte constant "+str(expr)+" out of range") - val = 0 - self.output.append(int(val)) - else: - Err.log("Attempt to write to data segment") - def outputword(self, expr, env): - 'Outputs a little-endian word, with range checking' - if self.writeOK: - val = expr.value(env) - if val < 0x0000 or val > 0xFFFF: - Err.log("Word constant "+str(expr)+" out of range") - val = 0 - self.output.append(int(val & 0xFF)) - self.output.append(int((val >> 8) & 0xFF)) - else: - Err.log("Attempt to write to data segment") - def outputdword(self, expr, env): - 'Outputs a little-endian dword, with range checking' - if self.writeOK: - val = expr.value(env) - if val < 0x00000000 or val > 0xFFFFFFFFL: - Err.log("DWord constant "+str(expr)+" out of range") - val = 0 - self.output.append(int(val & 0xFF)) - self.output.append(int((val >> 8) & 0xFF)) - self.output.append(int((val >> 16) & 0xFF)) - self.output.append(int((val >> 24) & 0xFF)) - else: - Err.log("Attempt to write to data segment") - - def outputword_be(self, expr, env): - 'Outputs a big-endian word, with range checking' - if self.writeOK: - val = expr.value(env) - if val < 0x0000 or val > 0xFFFF: - Err.log("Word constant "+str(expr)+" out of range") - val = 0 - self.output.append(int((val >> 8) & 0xFF)) - self.output.append(int(val & 0xFF)) - else: - Err.log("Attempt to write to data segment") - def outputdword_be(self, expr, env): - 'Outputs a big-endian dword, with range checking' - if self.writeOK: - val = expr.value(env) - if val < 0x00000000 or val > 0xFFFFFFFFL: - Err.log("DWord constant "+str(expr)+" out of range") - val = 0 - self.output.append(int((val >> 24) & 0xFF)) - self.output.append(int((val >> 16) & 0xFF)) - self.output.append(int((val >> 8) & 0xFF)) - self.output.append(int(val & 0xFF)) - else: - Err.log("Attempt to write to data segment") - - def assemble(self, node, mode, env): - "A generic instruction called by the visitor methods themselves" - (opcode, expr) = node.data - bin_op = Ops.opcodes[opcode][mode] - if bin_op is None: - Err.log('%s does not have mode "%s"' % (opcode.upper(), Ops.modes[mode])) - return - self.outputbyte(IR.ConstantExpr(bin_op), env) - arglen = Ops.lengths[mode] - if mode == 14: # Special handling for relative mode - arg = expr.value(env) - arg = arg-(env.getPC()+2) - if arg < -128 or arg > 127: - Err.log("Branch target out of bounds") - arg = 0 - if arg < 0: arg += 256 - expr = IR.ConstantExpr(arg) - if arglen == 1: self.outputbyte(expr, env) - if arglen == 2: self.outputword(expr, env) - env.incPC(1+arglen) - self.code += 1+arglen - - def visitImplied(self, node, env): self.assemble(node, 0, env) - def visitImmediate(self, node, env): self.assemble(node, 1, env) - def visitZeroPage(self, node, env): self.assemble(node, 2, env) - def visitZeroPageX(self, node, env): self.assemble(node, 3, env) - def visitZeroPageY(self, node, env): self.assemble(node, 4, env) - def visitAbsolute(self, node, env): self.assemble(node, 5, env) - def visitAbsoluteX(self, node, env): self.assemble(node, 6, env) - def visitAbsoluteY(self, node, env): self.assemble(node, 7, env) - def visitIndirect(self, node, env): self.assemble(node, 8, env) - def visitAbsIndX(self, node, env): self.assemble(node, 9, env) - def visitAbsIndY(self, node, env): self.assemble(node, 10, env) - def visitZPIndirect(self, node, env): self.assemble(node, 11, env) - def visitIndirectX(self, node, env): self.assemble(node, 12, env) - def visitIndirectY(self, node, env): self.assemble(node, 13, env) - def visitRelative(self, node, env): self.assemble(node, 14, env) - def visitLabel(self, node, env): pass - def visitByte(self, node, env): - for expr in node.data: - self.outputbyte(expr, env) - env.incPC(len(node.data)) - self.data += len(node.data) - def visitWord(self, node, env): - for expr in node.data: - self.outputword(expr, env) - env.incPC(len(node.data)*2) - self.data += len(node.data)*2 - def visitDword(self, node, env): - for expr in node.data: - self.outputdword(expr, env) - env.incPC(len(node.data)*4) - self.data += len(node.data)*4 - def visitWordBE(self, node, env): - for expr in node.data: - self.outputword_be(expr, env) - env.incPC(len(node.data)*2) - self.data += len(node.data)*2 - def visitDwordBE(self, node, env): - for expr in node.data: - self.outputdword_be(expr, env) - env.incPC(len(node.data)*4) - self.data += len(node.data)*4 - def visitSetPC(self, node, env): - env.setPC(node.data[0].value(env)) - def visitCheckPC(self, node, env): - pc = env.getPC() - target = node.data[0].value(env) - if (pc > target): - Err.log(".checkpc assertion failed: $%x > $%x" % (pc, target)) - def visitAdvance(self, node, env): - pc = env.getPC() - target = node.data[0].value(env) - if (pc > target): - Err.log("Attempted to .advance backwards: $%x to $%x" % (pc, target)) - else: - zero = IR.ConstantExpr(0) - for i in xrange(target-pc): self.outputbyte(zero, env) - self.filler += target-pc - env.setPC(target) +"""The Ophis Assembler passes + + Ophis's design philosophy is to build the IR once, then run a great + many assembler passes over the result. Thus, each pass does a + single, specialized job. When strung together, the full + translation occurs. This structure also makes the assembler + very extensible; additional analyses or optimizations may be + added as new subclasses of Pass.""" + +# Copyright 2002-2012 Michael C. Martin and additional contributors. +# You may use, modify, and distribute this file under the MIT +# license: See README for details. + +import Ophis.Errors as Err +import Ophis.IR as IR +import Ophis.Opcodes as Ops +import Ophis.CmdLine as Cmd +import Ophis.Macro as Macro + +# The passes themselves + +class Pass: + """Superclass for all assembler passes. Automatically handles IR + types that modify the environent's structure, and by default + raises an error on anything else. Override visitUnknown in your + extension pass to produce a pass that accepts everything.""" + name = "Default Pass" + def __init__(self): + self.writeOK = 1 + def visitNone(self, node, env): + pass + def visitSEQUENCE(self, node, env): + Err.currentpoint = node.ppt + for n in node.data: + n.accept(self, env) + def visitDataSegment(self, node, env): + self.writeOK = 0 + env.setsegment(node.data[0]) + def visitTextSegment(self, node, env): + self.writeOK = 1 + env.setsegment(node.data[0]) + def visitScopeBegin(self, node, env): + env.newscope() + def visitScopeEnd(self, node, env): + env.endscope() + def visitUnknown(self, node, env): + Err.log("Internal error! "+self.name+" cannot understand node type "+node.nodetype) + def prePass(self): + pass + def postPass(self): + pass + def go(self, node, env): + """Prepares the environment and runs this pass, possibly + printing debugging information.""" + if Err.count == 0: + if Cmd.verbose > 1: print "Running: "+self.name + env.reset() + self.prePass() + node.accept(self, env) + self.postPass() + env.reset() + if Cmd.verbose > 3: + print "Current labels:" + print env + if Cmd.verbose > 2: + print "Current IR:" + print node + +class FixPoint: + """A specialized class that is not a pass but can be run like one. + This class takes a list of passes and a "fixpoint" function.""" + def __init__(self, name, passes, fixpoint): + self.name = name + self.passes = passes + self.fixpoint = fixpoint + def go(self, node, env): + """Runs this FixPoint's passes, in order, until the fixpoint + is true. Always runs the passes at least once.""" + for i in xrange(100): + if Err.count != 0: break + for p in self.passes: + p.go(node, env) + if Err.count != 0: break + if self.fixpoint(): break + if Cmd.verbose > 1: print "Fixpoint failed, looping back" + else: + Err.log("Can't make %s converge! Maybe there's a recursive dependency somewhere?" % self.name) + +class DefineMacros(Pass): + "Extract macro definitions and remove them from the IR" + name = "Macro definition pass" + def prePass(self): + self.inDef = 0 + self.nestedError = 0 + def postPass(self): + if self.inDef: + Err.log("Unmatched .macro") + elif Cmd.verbose > 2: + print "Macro definitions:" + Macro.dump() + def visitMacroBegin(self, node, env): + if self.inDef: + Err.log("Nested macro definition") + self.nestedError = 1 + else: + Macro.newMacro(node.data[0]) + node.nodetype = "None" + node.data = [] + self.inDef = 1 + def visitMacroEnd(self, node, env): + if self.inDef: + Macro.endMacro() + node.nodetype = "None" + node.data = [] + self.inDef = 0 + elif not self.nestedError: + Err.log("Unmatched .macend") + def visitUnknown(self, node, env): + if self.inDef: + Macro.registerNode(node) + node.nodetype = "None" + node.data = [] + + +class ExpandMacros(Pass): + "Replace macro invocations with the appropriate text" + name = "Macro expansion pass" + def prePass(self): + self.changed = 0 + def visitMacroInvoke(self, node, env): + replacement = Macro.expandMacro(node.ppt, node.data[0], node.data[1:]) + node.nodetype = replacement.nodetype + node.data = replacement.data + self.changed = 1 + def visitUnknown(self, node, env): + pass + +class InitLabels(Pass): + "Finds all reachable labels" + name = "Label initialization pass" + def __init__(self): + Pass.__init__(self) + self.labelmap = {} + def prePass(self): + self.changed = 0 + self.PCvalid = 1 + def visitAdvance(self, node, env): + self.PCvalid=node.data[0].valid(env, self.PCvalid) + def visitSetPC(self, node, env): + self.PCvalid=node.data[0].valid(env, self.PCvalid) + def visitLabel(self, node, env): + (label, val) = node.data + fulllabel = "%d:%s" % (env.stack[0], label) + if fulllabel in self.labelmap and self.labelmap[fulllabel] is not node: + Err.log("Duplicate label definition '%s'" % label) + if fulllabel not in self.labelmap: + self.labelmap[fulllabel] = node + if val.valid(env, self.PCvalid) and label not in env: + env[label]=0 + self.changed=1 + def visitUnknown(self, node, env): + pass + +class CircularityCheck(Pass): + "Checks for circular label dependencies" + name = "Circularity check pass" + def prePass(self): + self.changed=0 + self.PCvalid=1 + def visitAdvance(self, node, env): + PCvalid = self.PCvalid + self.PCvalid=node.data[0].valid(env, self.PCvalid) + if not node.data[0].valid(env, PCvalid): + Err.log("Undefined or circular reference on .advance") + def visitSetPC(self, node, env): + PCvalid = self.PCvalid + self.PCvalid=node.data[0].valid(env, self.PCvalid) + if not node.data[0].valid(env, PCvalid): + Err.log("Undefined or circular reference on program counter set") + def visitCheckPC(self, node, env): + if not node.data[0].valid(env, self.PCvalid): + Err.log("Undefined or circular reference on program counter check") + def visitLabel(self, node, env): + (label, val) = node.data + if not val.valid(env, self.PCvalid): + Err.log("Undefined or circular dependency for label '%s'" % label) + def visitUnknown(self, node, env): + pass + +class CheckExprs(Pass): + "Ensures all expressions can resolve" + name = "Expression checking pass" + def visitUnknown(self, node, env): + for i in [x for x in node.data if isinstance(x, IR.Expr)]: + i.value(env) # Throw away result, just confirm validity of all expressions + +class EasyModes(Pass): + "Assigns address modes to hardcoded and branch instructions" + name = "Easy addressing modes pass" + def visitMemory(self, node, env): + if Ops.opcodes[node.data[0]][14] is not None: + node.nodetype = "Relative" + return + if node.data[1].hardcoded: + if not collapse_no_index(node, env): + node.nodetype = "Absolute" + def visitMemoryX(self, node, env): + if node.data[1].hardcoded: + if not collapse_x(node, env): + node.nodetype = "AbsoluteX" + def visitMemoryY(self, node, env): + if node.data[1].hardcoded: + if not collapse_y(node, env): + node.nodetype = "AbsoluteY" + def visitPointer(self, node, env): + if node.data[1].hardcoded: + if not collapse_no_index_ind(node, env): + node.nodetype = "Indirect" + def visitPointerX(self, node, env): + if node.data[1].hardcoded: + if not collapse_x_ind(node, env): + node.nodetype = "AbsIndX" + def visitPointerY(self, node, env): + if node.data[1].hardcoded: + if not collapse_y_ind(node, env): + node.nodetype = "AbsIndY" + def visitUnknown(self, node, env): + pass + +class UpdateLabels(Pass): + "Computes the new values for all entries in the symbol table" + name = "Label Update Pass" + def prePass(self): + self.changed = 0 + def visitSetPC(self, node, env): env.setPC(node.data[0].value(env)) + def visitAdvance(self, node, env): env.setPC(node.data[0].value(env)) + def visitImplied(self, node, env): env.incPC(1) + def visitImmediate(self, node, env): env.incPC(2) + def visitIndirectX(self, node, env): env.incPC(2) + def visitIndirectY(self, node, env): env.incPC(2) + def visitZPIndirect(self, node, env): env.incPC(2) + def visitZeroPage(self, node, env): env.incPC(2) + def visitZeroPageX(self, node, env): env.incPC(2) + def visitZeroPageY(self, node, env): env.incPC(2) + def visitRelative(self, node, env): env.incPC(2) + def visitIndirect(self, node, env): env.incPC(3) + def visitAbsolute(self, node, env): env.incPC(3) + def visitAbsoluteX(self, node, env): env.incPC(3) + def visitAbsoluteY(self, node, env): env.incPC(3) + def visitAbsIndX(self, node, env): env.incPC(3) + def visitAbsIndY(self, node, env): env.incPC(3) + def visitMemory(self, node, env): env.incPC(3) + def visitMemoryX(self, node, env): env.incPC(3) + def visitMemoryY(self, node, env): env.incPC(3) + def visitPointer(self, node, env): env.incPC(3) + def visitPointerX(self, node, env): env.incPC(3) + def visitPointerY(self, node, env): env.incPC(3) + def visitCheckPC(self, node, env): pass + def visitLabel(self, node, env): + (label, val) = node.data + old = env[label] + env[label] = val.value(env) + if old != env[label]: + self.changed = 1 + def visitByte(self, node, env): env.incPC(len(node.data)) + def visitWord(self, node, env): env.incPC(len(node.data)*2) + def visitDword(self, node, env): env.incPC(len(node.data)*4) + def visitWordBE(self, node, env): env.incPC(len(node.data)*2) + def visitDwordBE(self, node, env): env.incPC(len(node.data)*4) + +class Collapse(Pass): + """Selects as many zero-page instructions to convert as + possible, and tracks how many instructions have been + converted this pass.""" + name = "Instruction Collapse Pass" + def prePass(self): + self.collapsed = 0 + def visitMemory(self, node, env): + if collapse_no_index(node, env): self.collapsed += 1 + def visitMemoryX(self, node, env): + if collapse_x(node, env): self.collapsed += 1 + def visitMemoryY(self, node, env): + if collapse_y(node, env): self.collapsed += 1 + def visitPointer(self, node, env): + if collapse_no_index_ind(node, env): self.collapsed += 1 + def visitPointerX(self, node, env): + if collapse_x_ind(node, env): self.collapsed += 1 + def visitPointerY(self, node, env): + if collapse_y_ind(node, env): self.collapsed += 1 + def visitUnknown(self, node, env): + pass + +def collapse_no_index(node, env): + """Transforms a Memory node into a ZeroPage one if possible. + Returns 1 if it made the collapse, false otherwise.""" + if node.data[1].value(env) < 0x100 and Ops.opcodes[node.data[0]][2] is not None: + node.nodetype = "ZeroPage" + return 1 + else: + return 0 + +def collapse_x(node, env): + """Transforms a MemoryX node into a ZeroPageX one if possible. + Returns 1 if it made the collapse, false otherwise.""" + if node.data[1].value(env) < 0x100 and Ops.opcodes[node.data[0]][3] is not None: + node.nodetype = "ZeroPageX" + return 1 + else: + return 0 + +def collapse_y(node, env): + """Transforms a MemoryY node into a ZeroPageY one if possible. + Returns 1 if it made the collapse, false otherwise.""" + if node.data[1].value(env) < 0x100 and Ops.opcodes[node.data[0]][4] is not None: + node.nodetype = "ZeroPageY" + return 1 + else: + return 0 + +def collapse_no_index_ind(node, env): + """Transforms a Pointer node into a ZPIndirect one if possible. + Returns 1 if it made the collapse, false otherwise.""" + if node.data[1].value(env) < 0x100 and Ops.opcodes[node.data[0]][11] is not None: + node.nodetype = "ZPIndirect" + return 1 + else: + return 0 + +def collapse_x_ind(node, env): + """Transforms a PointerX node into an IndirectX one if possible. + Returns 1 if it made the collapse, false otherwise.""" + if node.data[1].value(env) < 0x100 and Ops.opcodes[node.data[0]][12] is not None: + node.nodetype = "IndirectX" + return 1 + else: + return 0 + +def collapse_y_ind(node, env): + """Transforms a PointerY node into an IndirectY one if possible. + Returns 1 if it made the collapse, false otherwise.""" + if node.data[1].value(env) < 0x100 and Ops.opcodes[node.data[0]][13] is not None: + node.nodetype = "IndirectY" + return 1 + else: + return 0 + + +class NormalizeModes(Pass): + """Eliminates the intermediate "Memory" and "Pointer" nodes, + converting them to "Absolute".""" + name = "Mode Normalization pass" + def visitMemory(self, node, env): node.nodetype = "Absolute" + def visitMemoryX(self, node, env): node.nodetype = "AbsoluteX" + def visitMemoryY(self, node, env): node.nodetype = "AbsoluteY" + def visitPointer(self, node, env): node.nodetype = "Indirect" + def visitPointerX(self, node, env): node.nodetype = "AbsIndX" + # If we ever hit a PointerY by this point, we have a bug. + def visitPointerY(self, node, env): node.nodetype = "AbsIndY" + def visitUnknown(self, node, env): pass + +class Assembler(Pass): + """Converts the IR into a list of bytes, suitable for writing to + a file.""" + name = "Assembler" + + def prePass(self): + self.output = [] + self.code = 0 + self.data = 0 + self.filler = 0 + + def postPass(self): + if Cmd.verbose > 0 and Err.count == 0: + print "Assembly complete: %s bytes output (%s code, %s data, %s filler)" \ + % (len(self.output), self.code, self.data, self.filler) + + def outputbyte(self, expr, env): + 'Outputs a byte, with range checking' + if self.writeOK: + val = expr.value(env) + if val < 0x00 or val > 0xff: + Err.log("Byte constant "+str(expr)+" out of range") + val = 0 + self.output.append(int(val)) + else: + Err.log("Attempt to write to data segment") + def outputword(self, expr, env): + 'Outputs a little-endian word, with range checking' + if self.writeOK: + val = expr.value(env) + if val < 0x0000 or val > 0xFFFF: + Err.log("Word constant "+str(expr)+" out of range") + val = 0 + self.output.append(int(val & 0xFF)) + self.output.append(int((val >> 8) & 0xFF)) + else: + Err.log("Attempt to write to data segment") + def outputdword(self, expr, env): + 'Outputs a little-endian dword, with range checking' + if self.writeOK: + val = expr.value(env) + if val < 0x00000000 or val > 0xFFFFFFFFL: + Err.log("DWord constant "+str(expr)+" out of range") + val = 0 + self.output.append(int(val & 0xFF)) + self.output.append(int((val >> 8) & 0xFF)) + self.output.append(int((val >> 16) & 0xFF)) + self.output.append(int((val >> 24) & 0xFF)) + else: + Err.log("Attempt to write to data segment") + + def outputword_be(self, expr, env): + 'Outputs a big-endian word, with range checking' + if self.writeOK: + val = expr.value(env) + if val < 0x0000 or val > 0xFFFF: + Err.log("Word constant "+str(expr)+" out of range") + val = 0 + self.output.append(int((val >> 8) & 0xFF)) + self.output.append(int(val & 0xFF)) + else: + Err.log("Attempt to write to data segment") + def outputdword_be(self, expr, env): + 'Outputs a big-endian dword, with range checking' + if self.writeOK: + val = expr.value(env) + if val < 0x00000000 or val > 0xFFFFFFFFL: + Err.log("DWord constant "+str(expr)+" out of range") + val = 0 + self.output.append(int((val >> 24) & 0xFF)) + self.output.append(int((val >> 16) & 0xFF)) + self.output.append(int((val >> 8) & 0xFF)) + self.output.append(int(val & 0xFF)) + else: + Err.log("Attempt to write to data segment") + + def assemble(self, node, mode, env): + "A generic instruction called by the visitor methods themselves" + (opcode, expr) = node.data + bin_op = Ops.opcodes[opcode][mode] + if bin_op is None: + Err.log('%s does not have mode "%s"' % (opcode.upper(), Ops.modes[mode])) + return + self.outputbyte(IR.ConstantExpr(bin_op), env) + arglen = Ops.lengths[mode] + if mode == 14: # Special handling for relative mode + arg = expr.value(env) + arg = arg-(env.getPC()+2) + if arg < -128 or arg > 127: + Err.log("Branch target out of bounds") + arg = 0 + if arg < 0: arg += 256 + expr = IR.ConstantExpr(arg) + if arglen == 1: self.outputbyte(expr, env) + if arglen == 2: self.outputword(expr, env) + env.incPC(1+arglen) + self.code += 1+arglen + + def visitImplied(self, node, env): self.assemble(node, 0, env) + def visitImmediate(self, node, env): self.assemble(node, 1, env) + def visitZeroPage(self, node, env): self.assemble(node, 2, env) + def visitZeroPageX(self, node, env): self.assemble(node, 3, env) + def visitZeroPageY(self, node, env): self.assemble(node, 4, env) + def visitAbsolute(self, node, env): self.assemble(node, 5, env) + def visitAbsoluteX(self, node, env): self.assemble(node, 6, env) + def visitAbsoluteY(self, node, env): self.assemble(node, 7, env) + def visitIndirect(self, node, env): self.assemble(node, 8, env) + def visitAbsIndX(self, node, env): self.assemble(node, 9, env) + def visitAbsIndY(self, node, env): self.assemble(node, 10, env) + def visitZPIndirect(self, node, env): self.assemble(node, 11, env) + def visitIndirectX(self, node, env): self.assemble(node, 12, env) + def visitIndirectY(self, node, env): self.assemble(node, 13, env) + def visitRelative(self, node, env): self.assemble(node, 14, env) + def visitLabel(self, node, env): pass + def visitByte(self, node, env): + for expr in node.data: + self.outputbyte(expr, env) + env.incPC(len(node.data)) + self.data += len(node.data) + def visitWord(self, node, env): + for expr in node.data: + self.outputword(expr, env) + env.incPC(len(node.data)*2) + self.data += len(node.data)*2 + def visitDword(self, node, env): + for expr in node.data: + self.outputdword(expr, env) + env.incPC(len(node.data)*4) + self.data += len(node.data)*4 + def visitWordBE(self, node, env): + for expr in node.data: + self.outputword_be(expr, env) + env.incPC(len(node.data)*2) + self.data += len(node.data)*2 + def visitDwordBE(self, node, env): + for expr in node.data: + self.outputdword_be(expr, env) + env.incPC(len(node.data)*4) + self.data += len(node.data)*4 + def visitSetPC(self, node, env): + env.setPC(node.data[0].value(env)) + def visitCheckPC(self, node, env): + pc = env.getPC() + target = node.data[0].value(env) + if (pc > target): + Err.log(".checkpc assertion failed: $%x > $%x" % (pc, target)) + def visitAdvance(self, node, env): + pc = env.getPC() + target = node.data[0].value(env) + if (pc > target): + Err.log("Attempted to .advance backwards: $%x to $%x" % (pc, target)) + else: + zero = IR.ConstantExpr(0) + for i in xrange(target-pc): self.outputbyte(zero, env) + self.filler += target-pc + env.setPC(target) diff --git a/src/Ophis/__init__.py b/src/Ophis/__init__.py index 0f35954..fb67870 100644 --- a/src/Ophis/__init__.py +++ b/src/Ophis/__init__.py @@ -1,5 +1,5 @@ -"P65 - a cross-assembler for the 6502 series of chips" - -# Copyright 2002 Michael C. Martin. -# You may use, modify, and distribute this file under the BSD -# license: See LICENSE.txt for details. +"Ophis - a cross-assembler for the 6502 series of chips" + +# Copyright 2002-2012 Michael C. Martin and additional contributors. +# You may use, modify, and distribute this file under the MIT +# license: See README for details.