Massive code modernization spree.

Full PEP8 compliance. Also, booleans have been inserted where
they make sense (introduced in 2.3!) and I haven't knowingly
added anything that will break 2.3 compatibility.

At this point the code really doesn't look like it was written
ten years ago. Hooray!
This commit is contained in:
Michael C. Martin 2012-06-01 10:25:48 -07:00
parent f83379287f
commit 14a37ca879
38 changed files with 1459 additions and 925 deletions

2
README
View File

@ -28,4 +28,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
THE SOFTWARE.

View File

@ -9,4 +9,4 @@ _next: .word 0 ; End of program
.advance 2064
.require "kernal.oph"
.require "kernal.oph"

View File

@ -37,4 +37,4 @@ _next: .word 0 ; End of program
_main:
; Program follows...
.scend
.scend

View File

@ -249,7 +249,7 @@
.checkpc $100
</programlisting>
</section>
</section>
</section>
<section>
<title>Macros</title>
<para>
@ -400,7 +400,7 @@
code in and of itself, nor does it overwrite anything that
previously existed.</emphasis> If you wish to jump ahead in memory,
use <literal>.advance</literal>.</para></listitem>
<listitem><para><literal>.require</literal> <emphasis>filename</emphasis>: Includes the entirety
<listitem><para><literal>.require</literal> <emphasis>filename</emphasis>: Includes the entirety
of the file specified at that point in the program. Unlike <literal>.include</literal>,
however, code included with <literal>.require</literal> will only be inserted once.
The <literal>.require</literal> directive is useful for ensuring that certain code libraries
@ -447,5 +447,5 @@
macro definition intends to read. A shorthand for <literal>.invoke</literal>
is the name of the macro to invoke, backquoted.</para></listitem>
</itemizedlist>
</section>
</section>
</appendix>

View File

@ -333,7 +333,7 @@ delay: tax
iny
bne -
dex
bne -
bne -
rts
</programlisting>
</section>

View File

@ -153,19 +153,19 @@ next: .word 0 ; End of program
Labels are defined by putting their name, then a colon, as
seen in the definition of <literal>next</literal>.
</para></listitem>
<listitem><para>
<listitem><para>
Instead of putting in the hex codes for the string part of
the BASIC code, we included the string directly. Each
character in the string becomes one byte.
</para></listitem>
<listitem><para>
<listitem><para>
Instead of adding the buffer ourselves, we
used <literal>.advance</literal>, which outputs zeros until
the specified address is reached. Attempting
to <literal>.advance</literal> backwards produces an
assemble-time error.
</para></listitem>
<listitem><para>
<listitem><para>
It has comments that explain what the data are for. The
semicolon is the comment marker; everything from a semicolon
to the end of the line is ignored.
@ -263,9 +263,9 @@ hello: .byte "HELLO, WORLD!", 0
<row>
<entry align="center">Option</entry>
<entry align="center">Effect</entry>
</row>
</row>
</thead>
<tbody>
<tbody>
<row><entry><option>-6510</option></entry><entry>Allows the 6510 undocumented opcodes as listed in the VICE documentation.</entry></row>
<row><entry><option>-65c02</option></entry><entry>Allows opcodes and addressing modes added by the 65C02.</entry></row>
<row><entry><option>-v 0</option></entry><entry>Quiet operation. Only reports errors.</entry></row>

View File

@ -64,4 +64,4 @@
; ...and character set
.alias upper'case 142
.alias lower'case 14
.alias lower'case 14

View File

@ -15,4 +15,4 @@ loop: lda hello, x
bne loop
done: rts
hello: .byte "HELLO, WORLD!", 0
hello: .byte "HELLO, WORLD!", 0

View File

@ -19,4 +19,4 @@ _next: .word 0 ; End of program
bne -
* rts
hello: .byte "HELLO, WORLD!", 0
hello: .byte "HELLO, WORLD!", 0

View File

@ -42,4 +42,4 @@ target6: .byte "NATION", 0
target7: .byte "WORLD", 0
target8: .byte "SOLAR SYSTEM", 0
target9: .byte "GALAXY", 0
target10: .byte "UNIVERSE", 0
target10: .byte "UNIVERSE", 0

View File

@ -63,7 +63,7 @@ delay: tax
bne -
dex
bne -
rts

View File

@ -65,7 +65,7 @@ delay: tax
bne -
dex
bne -
rts

View File

@ -68,6 +68,6 @@ delay: tax
bne -
dex
bne -
rts
rts

View File

@ -72,4 +72,4 @@ delay: sta _tmp ; save argument (rdtim destroys it)
.checkpc $A000
.data
.checkpc $D000
.checkpc $D000

View File

@ -99,4 +99,4 @@ _done: rts
.checkpc $A000
.data
.checkpc $D000
.checkpc $D000

View File

@ -93,4 +93,4 @@ _done: rts
.checkpc $D000
.data zp
.checkpc $80
.checkpc $80

View File

@ -6,44 +6,59 @@ import optparse
# You may use, modify, and distribute this file under the MIT
# license: See README for details.
enable_collapse = True
enable_branch_extend = True
enable_undoc_ops = False
enable_65c02_exts = False
enable_collapse = True
enable_branch_extend = True
enable_undoc_ops = False
enable_65c02_exts = False
warn_on_branch_extend = True
print_summary = True
print_loaded_files = False
print_pass = False
print_ir = False
print_labels = False
print_summary = True
print_loaded_files = False
print_pass = False
print_ir = False
print_labels = False
infile = None
outfile = None
infile = None
outfile = None
def parse_args(raw_args):
"Populate the module's globals based on the command-line options given."
global enable_collapse, enable_branch_extend, enable_undoc_ops, enable_65c02_exts
global enable_collapse, enable_branch_extend
global enable_undoc_ops, enable_65c02_exts
global warn_on_branch_extend
global print_summary, print_loaded_files, print_pass, print_ir, print_labels
global print_summary, print_loaded_files
global print_pass, print_ir, print_labels
global infile, outfile
parser = optparse.OptionParser(usage= "Usage: %prog [options] srcfile outfile", version="Ophis 6502 cross-assembler, version 2.0")
parser = optparse.OptionParser(
usage="Usage: %prog [options] srcfile outfile",
version="Ophis 6502 cross-assembler, version 2.0")
ingrp = optparse.OptionGroup(parser, "Input options")
ingrp.add_option("-u", "--undoc", action="store_true", default=False, help="Enable 6502 undocumented opcodes")
ingrp.add_option("-c", "--65c02", action="store_true", default=False, dest="c02", help="Enable 65c02 extended instruction set")
ingrp.add_option("-u", "--undoc", action="store_true", default=False,
help="Enable 6502 undocumented opcodes")
ingrp.add_option("-c", "--65c02", action="store_true", default=False,
dest="c02", help="Enable 65c02 extended instruction set")
outgrp = optparse.OptionGroup(parser, "Console output options")
outgrp.add_option("-v", "--verbose", action="store_const", const=2, help="Verbose mode", default=1)
outgrp.add_option("-q", "--quiet", action="store_const", help="Quiet mode", dest="verbose", const=0)
outgrp.add_option("-d", "--debug", action="count", dest="verbose", help=optparse.SUPPRESS_HELP)
outgrp.add_option("--no-warn", action="store_false", dest="warn", default=True, help="Do not print warnings")
outgrp.add_option("-v", "--verbose", action="store_const", const=2,
help="Verbose mode", default=1)
outgrp.add_option("-q", "--quiet", action="store_const", help="Quiet mode",
dest="verbose", const=0)
outgrp.add_option("-d", "--debug", action="count", dest="verbose",
help=optparse.SUPPRESS_HELP)
outgrp.add_option("--no-warn", action="store_false", dest="warn",
default=True, help="Do not print warnings")
bingrp = optparse.OptionGroup(parser, "Binary output options")
bingrp.add_option("--no-collapse", action="store_false", dest="enable_collapse", default="True", help="Disable zero-page collapse pass")
bingrp.add_option("--no-branch-extend", action="store_false", dest="enable_branch_extend", default="True", help="Disable branch-extension pass")
bingrp = optparse.OptionGroup(parser, "Compilation options")
bingrp.add_option("--no-collapse", action="store_false",
dest="enable_collapse", default="True",
help="Disable zero-page collapse pass")
bingrp.add_option("--no-branch-extend", action="store_false",
dest="enable_branch_extend", default="True",
help="Disable branch-extension pass")
parser.add_option_group(ingrp)
parser.add_option_group(outgrp)
@ -60,15 +75,15 @@ def parse_args(raw_args):
if options.c02 and options.undoc:
parser.error("--undoc and --65c02 are mutually exclusive")
infile = args[0]
outfile = args[1]
enable_collapse = options.enable_collapse
enable_branch_extend = options.enable_branch_extend
enable_undoc_ops = options.undoc
enable_65c02_exts = options.c02
infile = args[0]
outfile = args[1]
enable_collapse = options.enable_collapse
enable_branch_extend = options.enable_branch_extend
enable_undoc_ops = options.undoc
enable_65c02_exts = options.c02
warn_on_branch_extend = options.warn
print_summary = options.verbose > 0 # no options set
print_loaded_files = options.verbose > 1 # v
print_pass = options.verbose > 2 # dd
print_ir = options.verbose > 3 # ddd
print_labels = options.verbose > 4 # dddd
print_summary = options.verbose > 0 # no options set
print_loaded_files = options.verbose > 1 # v
print_pass = options.verbose > 2 # dd
print_ir = options.verbose > 3 # ddd
print_labels = options.verbose > 4 # dddd

View File

@ -10,46 +10,52 @@ import Ophis.IR as IR
import Ophis.Frontend as FE
import Ophis.Errors as Err
loadedfiles={}
loadedfiles = {}
basecharmap = "".join([chr(x) for x in range(256)])
currentcharmap = basecharmap
def reset():
global loadedfiles, currentcharmap, basecharmap
loadedfiles={}
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))
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:
if type(filename) == str:
global loadedfiles
if filename not in loadedfiles:
loadedfiles[filename]=1
loadedfiles[filename] = True
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:
if type(filename) == str:
try:
f = file(filename, "rb")
bytes = f.read()
f.close()
except IOError:
Err.log ("Could not read "+filename)
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
@ -60,30 +66,33 @@ def pragmaCharmap(ppt, line, result):
try:
base = bytes[0].data
newsubstr = "".join([chr(x.data) for x in bytes[1:]])
currentcharmap = currentcharmap[:base] + newsubstr + currentcharmap[base+len(newsubstr):]
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:
if type(filename) == str:
try:
f = file(filename, "rb")
bytes = f.read()
f.close()
except IOError:
Err.log ("Could not read "+filename)
Err.log("Could not read " + filename)
return
if len(bytes)==256:
if len(bytes) == 256:
currentcharmap = bytes
else:
Err.log("Character map "+filename+" not 256 bytes long")
Err.log("Character map " + filename + " not 256 bytes long")
def pragmaOrg(ppt, line, result):
"Relocates the PC with no output"
@ -91,6 +100,7 @@ def pragmaOrg(ppt, line, result):
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)
@ -102,25 +112,31 @@ def pragmaAdvance(ppt, line, result):
line.expect("EOL")
result.append(IR.Node(ppt, "Advance", newPC, fillexpr))
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))
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))
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)])))
result.append(IR.Node(ppt, "SetPC",
IR.SequenceExpr([IR.PCExpr(), "+",
IR.ConstantExpr(size)])))
def pragmaText(ppt, line, result):
"Switches to a text segment"
@ -132,6 +148,7 @@ def pragmaText(ppt, line, result):
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")
@ -142,67 +159,80 @@ def pragmaData(ppt, line, result):
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)]
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])
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))
def pragmaWord(ppt, line, result):
"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))
def pragmaWordbe(ppt, line, result):
"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))
def pragmaScope(ppt, line, result):
"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"))
def pragmaMacro(ppt, line, result):
"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"))
def pragmaInvoke(ppt, line, result):
macro = line.expect("LABEL").value
if line.lookahead(0).type == "EOL":

View File

@ -9,6 +9,7 @@
import Ophis.Errors as Err
class Environment(object):
"""Environment class.
Controls the various scopes and global abstract execution variables."""
@ -19,39 +20,52 @@ class Environment(object):
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
if item in dict:
return True
return False
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]
if item in dict:
return dict[item]
else:
if item in self.dicts[0]: return self.dicts[0][item]
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
@ -61,14 +75,16 @@ class Environment(object):
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({})
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)

View File

@ -12,15 +12,20 @@ import sys
count = 0
currentpoint = "<Top Level>"
def log(err):
"""Reports an error at the current program point, and increases
the global error count."""
global count
count = count+1
print>>sys.stderr, currentpoint+": "+err
count = count + 1
print>>sys.stderr, currentpoint + ": " + err
def report():
"Print out the number of errors."
if count == 0: print>>sys.stderr, "No errors"
elif count == 1: print>>sys.stderr, "1 error"
else: print>>sys.stderr, str(count)+" errors"
if count == 0:
print>>sys.stderr, "No errors"
elif count == 1:
print>>sys.stderr, "1 error"
else:
print>>sys.stderr, str(count) + " errors"

View File

@ -13,35 +13,44 @@ import os
# You may use, modify, and distribute this file under the MIT
# license: See README for details.
class Lexeme(object):
"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:
if self.value is None:
return self.type
else:
return self.type+":"+str(self.value)
return self.type + ":" + str(self.value)
def __repr__(self):
return "Lexeme("+`self.type`+", "+`self.value`+")"
return "Lexeme(" + repr(self.type) + ", " + repr(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)}
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 == "":
@ -59,7 +68,8 @@ def lex(point, line):
result.append(Lexeme("NUM", long(rest, bases[firstchar][1])))
return
except ValueError:
Err.log('Invalid '+bases[firstchar][0]+' constant: '+rest)
Err.log('Invalid ' + bases[firstchar][0] + ' constant: ' +
rest)
result.append(Lexeme("NUM", 0))
return
elif firstchar.isdigit():
@ -73,12 +83,12 @@ def lex(point, line):
if len(rest) == 1:
result.append(Lexeme("NUM", ord(rest)))
else:
Err.log("Invalid character constant '"+rest+"'")
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!")
Err.log("Internal lexer error! '" + token + "' can't happen!")
result.append(Lexeme(firstchar))
return
else: # Label, opcode, or index register
@ -94,22 +104,24 @@ def lex(point, line):
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
quotemode = False
backslashmode = False
for c in line.strip():
if backslashmode:
backslashmode = 0
backslashmode = False
value = value + c
elif c == "\\":
backslashmode = 1
backslashmode = True
elif quotemode:
if c == '"':
quotemode = 0
quotemode = False
else:
value = value + c
elif c == ';':
@ -126,7 +138,7 @@ def lex(point, line):
elif c == '"':
add_token(value)
value = '"'
quotemode = 1
quotemode = True
else:
value = value + c
if backslashmode:
@ -137,35 +149,45 @@ def lex(point, line):
add_EOL()
return result
class ParseLine(object):
"Maintains the parse state of a line of code. Enables arbitrary lookahead."
"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
"""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
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
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."""
"""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)+'"')
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
@ -187,14 +209,14 @@ def parse_expr(line):
offset += 1
line.expect("+")
next = line.lookahead(0).type
return IR.LabelExpr("*"+str(templabelcount+offset))
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))
return IR.LabelExpr("*" + str(templabelcount + offset))
elif next == ">":
line.expect(">")
return IR.HighByteExpr(atom())
@ -203,6 +225,7 @@ def parse_expr(line):
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
@ -218,51 +241,60 @@ def parse_expr(line):
result.append(nextop)
result.append(reader())
nextop = line.lookahead(0).type
if len(result) == 1: return result[0]
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
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()))
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()
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)
Err.log("Unknown pragma " + pragma)
else: # Instruction
opcode = line.expect("OPCODE").value
if line.lookahead(0).type == "#":
@ -296,23 +328,30 @@ def parse_line(ppt, lexemelist):
tok = line.expect("EOL", ",").type
if tok == ",":
tok = line.expect("X", "Y").type
if tok == "X": mode = "MemoryX"
else: mode = "MemoryY"
if tok == "X":
mode = "MemoryX"
else:
mode = "MemoryY"
line.expect("EOL")
else: mode = "Memory"
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]
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 an Ophis source file, and returns an IR list."
Err.currentpoint = ppt
if Cmd.print_loaded_files:
if filename != '-':
print>>sys.stderr, "Loading "+filename
print>>sys.stderr, "Loading " + filename
else:
print>>sys.stderr, "Loading from standard input"
try:
@ -322,18 +361,19 @@ def parse_file(ppt, filename):
f.close()
else:
linelist = sys.stdin.readlines()
pptlist = ["%s:%d" % (filename, i+1) for i in range(len(linelist))]
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)
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."
"""Top level parsing routine, taking a source file name
and returning an IR list."""
global templabelcount
templabelcount = 0
return parse_file("<Top Level>", filename)

View File

@ -9,6 +9,7 @@
import Ophis.Errors as Err
class Node(object):
"""The default IR Node
Instances of Node always have the three fields ppt(Program Point),
@ -17,27 +18,35 @@ class Node(object):
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
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 = 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))
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>", "None")
def SequenceNode(ppt, nodelist):
return Node(ppt, "SEQUENCE", *nodelist)
class Expr(object):
"""Base class for Ophis expressions
All expressions have a field called "data" and a boolean field
@ -45,78 +54,102 @@ class Expr(object):
symbolic values in it."""
def __init__(self, data):
self.data = data
self.hardcoded = 0
self.hardcoded = False
def __str__(self):
return "<UNKNOWN: "+`self.data`+">"
def valid(self, env=None, PCvalid=0):
return "<UNKNOWN: " + repr(self.data) + ">"
def valid(self, env=None, PCvalid=False):
"""Returns true if the the expression can be successfully
evaluated in the specified environment."""
return 0
return False
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
self.hardcoded = True
def __str__(self):
return str(self.data)
def valid(self, env=None, PCvalid=0):
return 1
def valid(self, env=None, PCvalid=False):
return True
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
self.hardcoded = False
def __str__(self):
return self.data
def valid(self, env=None, PCvalid=0):
def valid(self, env=None, PCvalid=False):
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
self.hardcoded = False
def __str__(self):
return "^"
def valid(self, env=None, PCvalid=0):
def valid(self, env=None, PCvalid=False):
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 ">" + str(self.data)
def valid(self, env=None, PCvalid=False):
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 "<" + str(self.data)
def valid(self, env=None, PCvalid=False):
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
@ -128,20 +161,23 @@ class SequenceExpr(Expr):
[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]
self.operators = [x for x in data if type(x) == str]
for i in self.operands:
if not i.hardcoded:
self.hardcoded = 0
self.hardcoded = False
break
else:
self.hardcoded = 1
self.hardcoded = True
def __str__(self):
return "["+" ".join(map(str, self.data))+"]"
def valid(self, env=None, PCvalid=0):
return "[" + " ".join(map(str, self.data)) + "]"
def valid(self, env=None, PCvalid=False):
for i in self.operands:
if not i.valid(env, PCvalid):
return 0
return 1
return False
return True
def value(self, env=None):
subs = map((lambda x: x.value(env)), self.operands)
result = subs[0]
@ -150,11 +186,19 @@ class SequenceExpr(Expr):
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
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

View File

@ -18,6 +18,7 @@ macros = {}
currentname = None
currentbody = None
def newMacro(name):
"Start creating a new macro with the specified name."
global currentname
@ -31,10 +32,12 @@ def newMacro(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
@ -46,21 +49,29 @@ def endMacro():
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")]
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>>sys.stderr, "Macro: "+mac
for node in body: print>>sys.stderr, node
print>>sys.stderr, "Macro: " + mac