Compare commits

...

5 Commits
v2.1 ... master

Author SHA1 Message Date
Michael Martin
99f074da27 Fix the listing/map output to be Python 3 compatible 2019-04-12 20:47:01 -07:00
Michael Martin
41bf01d035 Convert Ophis to Python 3.
Most of the work is handled by 2to3, but there's a few extra tricks
needed to finish the job, mostly about picking the right bits to be
Unicode and the right bits to be bytes.
2019-01-09 20:45:01 -08:00
Michael Martin
971fafd918 Fix the 4502 opcode table generator 2019-01-09 20:23:58 -08:00
Michael Martin
f6990095c1 A more Atari font for the Color Test 2018-09-20 21:53:51 -07:00
Michael Martin
778fbf7e2c Improved Atari 2600 example programs
Add the color test program as a sample program. Also update
the hi_stella example so that it runs properly when run in a Harmony
cartridge.
2015-01-07 00:35:21 -08:00
18 changed files with 640 additions and 230 deletions

View File

@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
from os.path import realpath, dirname, join
from sys import argv, exit, path

View File

@ -1,7 +0,0 @@
"Hi Stella" is a simple "Hello World" program for the "Stella" chip,
more famously known as the Atari 2600. Simply running
ophis hi_stella.oph
should produce hi_stella.bin, a 256-byte file that prints "HI" on
the screen with some rolling color bars.

View File

@ -0,0 +1,13 @@
"Hi Stella" is a simple "Hello World" program for the "Stella" chip,
more famously known as the Atari 2600. Simply running
ophis hi_stella.oph
should produce hi_stella.bin, a 256-byte file that prints "HI" on
the screen with some rolling color bars.
A more sophisticated program is colortest, which lets the user
explore the 128 colors provided by the system. Use up and down
to move the color value by 2, and left and right to move it
by 16. (The lowest bit in the color value byte is ignored, for
a total of 128 colors available.)

View File

@ -0,0 +1,391 @@
;;; ---------- COLOR TEST ----------
;;; Michael Martin, 2014
;;;
;;; This is a sample program for the Atari 2600 VCS that lets you
;;; explore the 128-color palette the system provides. This is
;;; presented mainly as a more sophisticated example program to
;;; supplement "hi_stella".
;;;
;;; It makes use of every graphical element but the Ball, and also
;;; makes use of multicolor asymmetric playfields.
.require "../../platform/stella.oph"
.outfile "colortest.bin"
.data
.org $0080
.space startcol 1 ; Starting color for the striped playfield
.space subrow 1 ; Counting lines per "tall pixel"
.space curcol 1 ; The color number we are focusing on at the moment
.space high_nybble 2 ; Pointer to graphic data for 16s hexit
.space low_nybble 2 ; Pointer to graphic data for ones hexit
.space input_allowed 1 ; Flag for whether or not to ignore input.
.text
;; Start at $f800 - 2KB ROMs are the smallest size available.
.org $f800
reset: `clean'start
;; We offer 1c as the initial color. It's a nice yellow shade.
lda #$1c
sta curcol
frame: `vertical'sync ; Beginning of the frame. Set up the timer
lda #43 ; to count out the length of VBLANK while
sta TIM64T ; we do the processing for the display.
;; Place the player and missile graphics appropriately. We
;; count cycles and write the missile and player reset registers
;; at the closest times we can manage. Due to the way the TIA
;; timing works, the formula for the pixel they will show up at
;; is N*3-55+P, where N is the number of cycles from the end of
;; the latest STA WSYNC and the end of the STA RES* instruction,
;; and P is 1 for player sprites and 0 for missiles and the ball.
;;
;; The line after that we can strobe HMOVE to adjust them the
;; rest of the way into place.
;;
;; We will be using the missiles to draw the left and right
;; sides of a largish square and the player sprites to display a
;; byte value (the current color) as two hex digits (one per
;; player). All the rest of our graphics will be done via the
;; playfield registers.
;;
;; The missile graphics are being targeted to pixels 40 and 116
;; and will be 4 pixels wide each. The player graphics will be
;; 8 pixels wide and are targeting pixels 72 and 80.
sta WSYNC
sta WSYNC ; = 0
ldy #$06 ; +2 = 2
* dey ; +2 = 4- 9-14-19-24-29
bne - ; +3 = 7-12-17-22-27-31
sta RESM0 ; +3 = 34 (31*3-55 = 38. Needs to move 2 pixels right.)
lda #$E0 ; +2 = 36
sta HMCLR ; +3 = 39 - reset the fine-move registers
sta HMM0 ; +3 = 42 - set M0 to move 2 right
sta RESP0 ; +3 = 45 (42*3-54 = 72. Placed perfectly.)
sta RESP1 ; +3 = 48 (45*3-54 = 81. Needs to move 1 pixel left.)
lda #$10 ; +2 = 50
sta HMP1 ; +3 = 53 - and set P1 to move 1 left.
nop ; +2 = 55
nop ; +2 = 57
sta RESM1 ; +3 = 60 (57*3-55 = 116. Placed perfectly.)
sta WSYNC
sta HMOVE ; Next scanline, execute the fine moves.
lda #$20
sta NUSIZ0 ; Quad-size missiles, single copy of single player
sta NUSIZ1 ; M1 and P1 are the same
;; Read the input
lda #$00
sta SWACNT
lda SWCHA
bit input_allowed
bmi true_input_read
;; Wait for neutral stick so we can re-enable input.
and #$f0
cmp #$f0
bne input_done
;; Bits are set if the direction isn't active, so we only get
;; here if the stick was neutral. this also means the accumulator
;; has #$f0 in it now, which means we can store it directly and
;; the BIT/BMI above will start succeeding next frame.
sta input_allowed
beq input_done
true_input_read:
;; Now we rotate it through the carry bit to see what
;; direction was pushed. We advance the color 2 or 16 at a time,
;; depending. (The least significant bit in the color register is
;; the one ignored, so we are not missing anything here.)
ror ; Skip P2 input
ror
ror
ror
ror ; Carry clear if up
bcs +
inc curcol ; If up, increase color by 2
inc curcol
jmp input_found
* ror ; Carry clear if down
bcs +
dec curcol ; If down, decrease color by 2
dec curcol
jmp input_found
* ror ; Carry clear if left
bcs +
lda curcol
sec
sbc #$10 ; Left decreases color by 16
sta curcol
jmp input_found
* ror ; Carry clear if right
bcs input_done
lda curcol
adc #$10 ; Right increases color by 16
sta curcol
input_found:
lda #$00
sta input_allowed
input_done:
;; Clear the playfield while we wait, and make it asymmetric
lda #$00
sta PF0
sta PF1
sta PF2
sta CTRLPF
;; alter playfield color so we get a rotating effect
dec startcol
;; prepare numeric sprite values
lda curcol
lsr
lsr
lsr
lsr
tay
lda digits_low, y
sta high_nybble
lda curcol
and #$0f
tay
lda digits_low, y
sta low_nybble
lda #$ff
sta low_nybble+1
sta high_nybble+1
;; Wait for VBLANK to finish, then turn off the VBLANK signal.
* lda INTIM
bne -
sta WSYNC
sta VBLANK
;; Display kernel.
;; Top blank: 4 lines
ldx #4
stx subrow
* sta WSYNC
dex
bne -
;; Header graphics: 20 lines
ldy #5
ldx startcol
header_loop:
sta WSYNC
stx COLUPF ; +3 = 3
lda pf0_left-1,y ; +4 = 7
sta PF0 ; +3 = 10
lda pf1_left-1,y ; +4 = 14
sta PF1 ; +3 = 17
lda pf2_left-1,y ; +4 = 21
sta PF2 ; +3 = 24
cmp $80 ; +3 = 27 (3-cycle no-op)
lda pf0_right-1,y ; +4 = 31
sta PF0 ; +3 = 34
lda pf1_right-1,y ; +4 = 38
sta PF1 ; +3 = 41
lda pf2_right-1,y ; +4 = 45
sta PF2 ; +3 = 48 ** MUST STORE PF2 2ND TIME ON EXACTLY CYCLE 48 **
inx ; +2 = 50
inx ; +2 = 52
dec subrow ; +5 = 57
bne header_loop ; +2 = 59
dey ; +2 = 61
beq header_done ; +2 = 63
lda #4 ; +2 = 65
sta subrow ; +3 = 68
bne header_loop ; +3 = 71
;; We've cut it very fine here! We only have 76 cycles per
;; scanline and we use nearly all of them.
header_done:
;; Ruled split between title and data (8 lines)
ldy #$00 ; Clear playfield now that we're done (+2 = 72)
ldx #$0c ; Default status color is light grey (+2 = 74)
sta WSYNC ; Rest of previous line
sty PF0
sty PF1
sty PF2
stx COLUPF
stx COLUP0
stx COLUP1
dey
ldx #$f0
sta WSYNC
sta WSYNC
sta WSYNC
stx PF0 ; Fill playfield completely
sty PF1
sty PF2
ldy #$01
sty CTRLPF ; Symmetric PF
sta WSYNC
sta WSYNC
sta WSYNC
sta WSYNC
dey
sty PF0 ; Clear playfield again
sty PF1
sty PF2
ldy #$08 ; 32 lines (for letters; 8, 16, 8)
* sta WSYNC
dey
bne -
ldy #$05
* lda (high_nybble), y
sta GRP0
lda (low_nybble), y
sta GRP1
sta WSYNC
sta WSYNC
dey
bpl -
iny
sty GRP0
sty GRP1
ldy #$0A
* sta WSYNC
dey
bne -
;; Top border (12 lines)
lda #$03
sta PF1
lda #$ff
sta PF2
sta WSYNC
sta WSYNC
sta WSYNC
sta WSYNC
sta WSYNC
sta WSYNC
ldx #$00
stx PF1
stx PF2
ldx #$02 ; Turn on walls (the missiles)
stx ENAM0
stx ENAM1
sta WSYNC
sta WSYNC
sta WSYNC
sta WSYNC
sta WSYNC
sta WSYNC
sta PF2
lda curcol
sta COLUPF
;; Color blob (96 lines)
ldx #96
* sta WSYNC
dex
bne -
;; Bottom border (12 lines)
stx PF2
lda #$0c
sta COLUPF
sta WSYNC
sta WSYNC
sta WSYNC
sta WSYNC
sta WSYNC
sta WSYNC
stx ENAM0 ; Turn off walls (the missles)
stx ENAM1
lda #$03
sta PF1
lda #$ff
sta PF2
sta WSYNC
sta WSYNC
sta WSYNC
sta WSYNC
sta WSYNC
sta WSYNC
stx PF1
stx PF2
ldx #$0a
* sta WSYNC
dex
bne -
; Turn on VBLANK, do 30 lines of Overscan
lda #$02
sta VBLANK
ldy #30
* sta WSYNC
dey
bne -
jmp frame ; And the frame is done, back to VSYNC.
;;; Graphical data. Notice that we have to start not on a page
;;; boundary, but with all graphics in each group on one page.
.advance $FF01
pf0_left:
.byte $e0,$20,$20,$20,$e0
pf1_left:
.byte $77,$54,$54,$54,$74
pf2_left:
.byte $ae,$6a,$ea,$aa,$ee
pf0_right:
.byte $00,$00,$00,$00,$00
pf1_right:
.byte $4e,$48,$4c,$48,$ee
pf2_right:
.byte $27,$24,$27,$21,$77
;; We don't need a digits_high. It's always $FF!
digits_low:
.byte <digit_0, <digit_1, <digit_2, <digit_3
.byte <digit_4, <digit_5, <digit_6, <digit_7
.byte <digit_8, <digit_9, <digit_a, <digit_b
.byte <digit_c, <digit_d, <digit_e, <digit_f
digit_0:
.byte $3c,$66,$76,$6e,$66,$3c
digit_1:
.byte $7e,$18,$18,$18,$38,$18
digit_2:
.byte $7e,$30,$18,$0c,$66,$3c
digit_3:
.byte $3c,$66,$0c,$18,$0c,$7e
digit_4:
.byte $0c,$7e,$6c,$3c,$1c,$0c
digit_5:
.byte $3c,$66,$06,$7c,$60,$7e
digit_6:
.byte $3c,$66,$66,$7c,$60,$3c
digit_7:
.byte $30,$30,$18,$0c,$06,$7e
digit_8:
.byte $3c,$66,$66,$3c,$66,$3c
digit_9:
.byte $38,$0c,$06,$3e,$66,$3c
digit_a:
.byte $66,$7e,$66,$66,$3c,$18
digit_b:
.byte $7c,$66,$66,$7c,$66,$7c
digit_c:
.byte $3c,$66,$60,$60,$66,$3c
digit_d:
.byte $78,$6c,$66,$66,$6c,$78
digit_e:
.byte $7e,$60,$60,$7c,$60,$7e
digit_f:
.byte $60,$60,$60,$7c,$60,$7e
;;; Interrupt vectors.
.advance $FFFA
.word reset, reset, reset

View File

@ -9,37 +9,42 @@
.space counter 1
.text
.org $FF00
.org $F800
reset: `clean'start
; Initialize the player sprites.
; We're going to use quad-sized players for our
; letters. With 160 color clocks, and letters
; twenty clocks wide, and a 68 color clock HBLANK,
; we want to place the two letters at color clock
; 68+80-20-2 = 126 and 68+80+2 = 150. Those
; translate to cycle counts 42 and 50.
; letters, and each one is 5 notional pixels wide.
; When we write RESP*, the start cycle after WSYNC
; determines the pixel it appears at with the
; formula 3N - 54 (minimum 1). Cycles 36 and 44
; get us close. We end up 4 pixels too far left.
; While we wait, we set up our sprites to be
; quad-sized and initialize the independent
; color-counters for each sprite.
sta WSYNC ; 3
lda #$07 ; 5
sta NUSIZ0 ; 7
sta NUSIZ1 ; 10
lda #$20 ; 13
ldy #5 ; 15
sta WSYNC
lda #$07 ; +2= 2
sta NUSIZ0 ; +3= 5
sta NUSIZ1 ; +3= 8
lda #$20 ; +2=10
ldy #5 ; +2=12
* dey
bne - ; 20-25-30-35-39
sta RESP0 ; 42 - color clock 126
sta col'0 ; 45
eor #$80 ; 47
sta RESP1 ; 50 - color clock 150
bne - ; 17-22-27-32-36
sta RESP0 ; +3=39 (P0 at pixel 54)
sta col'0 ; +3=42
eor #$80 ; +2=44
sta RESP1 ; (P1 at pixel 78)
sta col'1
sta temp
lda #$C0 ; HMOVE us 4 right
sta HMP0
sta HMP1
sta WSYNC
sta HMOVE
frame: `vertical'sync
lda #43
@ -117,7 +122,7 @@ frame: `vertical'sync
dey ; loop.
bne -
; Clear out the player graphics,,,
; Clear out the player graphics...
lda #$00
sta GRP0
sta GRP1

View File

@ -10,7 +10,8 @@ import Ophis.CmdLine
import Ophis.IR as IR
import Ophis.Frontend as FE
import Ophis.Errors as Err
import math, os.path
import math
import os.path
basecharmap = "".join([chr(x) for x in range(256)])
currentcharmap = basecharmap
@ -68,7 +69,7 @@ def pragmaIncbin(ppt, line, result):
line.expect("EOL")
if type(filename) == str:
try:
f = file(os.path.join(FE.context_directory, filename), "rb")
f = open(os.path.join(FE.context_directory, filename), "rb")
if offset.hardcoded and (size is None or size.hardcoded):
# We know how big it will be, we can just use the values.
# First check to make sure they're sane
@ -94,7 +95,7 @@ def pragmaIncbin(ppt, line, result):
size = IR.ConstantExpr(-1)
f.seek(offset.value())
bytes = f.read(size.value())
bytes = [IR.ConstantExpr(ord(x)) for x in bytes]
bytes = [IR.ConstantExpr(x) for x in bytes]
result.append(IR.Node(ppt, "Byte", *bytes))
else:
# offset or length could change based on label placement.
@ -103,7 +104,7 @@ def pragmaIncbin(ppt, line, result):
# alias. Don't use symbolic aliases when extracting tiny
# pieces out of humongous files, I guess.
bytes = f.read()
bytes = [IR.ConstantExpr(ord(x)) for x in bytes]
bytes = [IR.ConstantExpr(x) for x in bytes]
if size is None:
size = IR.SequenceExpr([IR.ConstantExpr(len(bytes)),
"-",
@ -141,7 +142,7 @@ def pragmaCharmapbin(ppt, line, result):
line.expect("EOL")
if type(filename) == str:
try:
f = file(os.path.join(FE.context_directory, filename), "rb")
f = open(os.path.join(FE.context_directory, filename), "rb")
bytes = f.read()
f.close()
except IOError:

View File

@ -18,14 +18,14 @@ def log(err):
the global error count."""
global count
count = count + 1
print>>sys.stderr, currentpoint + ": " + err
print(currentpoint + ": " + err, file=sys.stderr)
def report():
"Print out the number of errors."
if count == 0:
print>>sys.stderr, "No errors"
print("No errors", file=sys.stderr)
elif count == 1:
print>>sys.stderr, "1 error"
print("1 error", file=sys.stderr)
else:
print>>sys.stderr, str(count) + " errors"
print(str(count) + " errors", file=sys.stderr)

View File

@ -70,7 +70,7 @@ def lex(point, line):
return
elif firstchar in bases:
try:
result.append(Lexeme("NUM", long(rest, bases[firstchar][1])))
result.append(Lexeme("NUM", int(rest, bases[firstchar][1])))
return
except ValueError:
Err.log('Invalid ' + bases[firstchar][0] + ' constant: ' +
@ -79,7 +79,7 @@ def lex(point, line):
return
elif firstchar.isdigit():
try:
result.append(Lexeme("NUM", long(token)))
result.append(Lexeme("NUM", int(token)))
except ValueError:
Err.log('Identifiers may not begin with a number')
result.append(Lexeme("LABEL", "ERROR"))
@ -403,19 +403,19 @@ def parse_file(ppt, filename, load_once=False):
filename))
if load_once and filename in loadedfiles:
if Cmd.print_loaded_files:
print>>sys.stderr, "Skipping " + filename
print("Skipping " + filename, file=sys.stderr)
return IR.NullNode
loadedfiles[filename] = True
if Cmd.print_loaded_files:
if filename != '-':
print>>sys.stderr, "Loading " + filename
print("Loading " + filename, file=sys.stderr)
else:
print>>sys.stderr, "Loading from standard input"
print("Loading from standard input", file=sys.stderr)
try:
if filename != '-':
if context_directory is not None:
filename = os.path.join(context_directory, filename)
f = file(filename)
f = open(filename, "rt")
linelist = f.readlines()
f.close()
context_directory = os.path.abspath(os.path.dirname(filename))
@ -423,8 +423,8 @@ def parse_file(ppt, filename, load_once=False):
context_directory = os.getcwd()
linelist = sys.stdin.readlines()
pptlist = ["%s:%d" % (filename, i + 1) for i in range(len(linelist))]
lexlist = map(lex, pptlist, linelist)
IRlist = map(parse_line, pptlist, lexlist)
lexlist = list(map(lex, pptlist, linelist))
IRlist = list(map(parse_line, pptlist, lexlist))
IRlist = [node for node in IRlist if node is not IR.NullNode]
context_directory = old_context
return IR.SequenceNode(ppt, IRlist)

View File

@ -179,7 +179,7 @@ class SequenceExpr(Expr):
return True
def value(self, env=None):
subs = map((lambda x: x.value(env)), self.operands)
subs = list(map((lambda x: x.value(env)), self.operands))
result = subs[0]
index = 1
for op in self.operators:

View File

@ -40,10 +40,10 @@ class Listing(object):
if self.filename == "-":
out = sys.stdout
else:
out = file(self.filename, "w")
out = open(self.filename, "wt")
for x in self.listing:
if type(x) is str:
print>>out, x
print(x, file=out)
elif type(x) is tuple:
i = 0
pc = x[0]
@ -59,7 +59,7 @@ class Listing(object):
charline += "."
else:
charline += chr(c)
print>>out, "%-54s |%-16s|" % (dataline, charline)
print("%-54s |%-16s|" % (dataline, charline), file=out)
i += 16
if self.filename != "-":
out.close()
@ -118,7 +118,7 @@ class LabelMapper(object):
if self.filename == "-":
out = sys.stdout
else:
out = file(self.filename, "w")
out = open(self.filename, "wt")
maxlabellen = 0
self.labeldata.sort()
for (loc, label, srcloc) in self.labeldata:

View File

@ -55,7 +55,7 @@ def expandMacro(ppt, name, arglist):
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)]
for (i, arg) in zip(range(1, sys.maxsize), 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)
@ -70,7 +70,7 @@ def dump():
global macros
for mac in macros:
body = macros[mac]
print>>sys.stderr, "Macro: " + mac
print("Macro: " + mac, file=sys.stderr)
for node in body:
print>>sys.stderr, node
print>>sys.stderr, ""
print(node, file=sys.stderr)
print("", file=sys.stderr)

View File

@ -72,7 +72,7 @@ def run_all():
try:
outfile = Ophis.CmdLine.outfile
if outfile == '-':
output = sys.stdout
output = sys.stdout.buffer
if sys.platform == "win32":
# We can't dump our binary in text mode; that would be
# disastrous. So, we'll do some platform-specific
@ -80,16 +80,16 @@ def run_all():
import msvcrt
msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
elif outfile is None:
output = file('ophis.bin', 'wb')
output = open('ophis.bin', 'wb')
else:
output = file(outfile, 'wb')
output.write("".join(map(chr, a.output)))
output = open(outfile, 'wb')
output.write(bytes(a.output))
output.flush()
if outfile != '-':
output.close()
return 0
except IOError:
print>>sys.stderr, "Could not write to " + outfile
print("Could not write to " + outfile, file=sys.stderr)
return 1
else:
Err.report()

View File

@ -67,18 +67,18 @@ class Pass(object):
printing debugging information."""
if Err.count == 0:
if Cmd.print_pass:
print>>sys.stderr, "Running: " + self.name
print("Running: " + self.name, file=sys.stderr)
env.reset()
self.prePass()
node.accept(self, env)
self.postPass()
env.reset()
if Cmd.print_labels:
print>>sys.stderr, "Current labels:"
print>>sys.stderr, env
print("Current labels:", file=sys.stderr)
print(env, file=sys.stderr)
if Cmd.print_ir:
print>>sys.stderr, "Current IR:"
print>>sys.stderr, node
print("Current IR:", file=sys.stderr)
print(node, file=sys.stderr)
class FixPoint(object):
@ -92,7 +92,7 @@ class FixPoint(object):
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):
for i in range(100):
if Err.count != 0:
break
for p in self.passes:
@ -102,7 +102,7 @@ class FixPoint(object):
if self.fixpoint():
break
if Cmd.print_pass:
print>>sys.stderr, "Fixpoint failed, looping back"
print("Fixpoint failed, looping back", file=sys.stderr)
else:
Err.log("Can't make %s converge! Maybe there's a recursive "
"dependency somewhere?" % self.name)
@ -120,7 +120,7 @@ class DefineMacros(Pass):
if self.inDef:
Err.log("Unmatched .macro")
elif Cmd.print_ir:
print>>sys.stderr, "Macro definitions:"
print("Macro definitions:", file=sys.stderr)
Macro.dump()
def visitMacroBegin(self, node, env):
@ -197,11 +197,11 @@ class InitLabels(Pass):
env[label] = 0
self.changed = True
if label in ['a', 'x', 'y'] and self.runcount == 1:
print>>sys.stderr, str(node.ppt) + ": WARNING: " \
"using register name as label"
print(str(node.ppt) + ": WARNING: " \
"using register name as label", file=sys.stderr)
if label in Ops.opcodes and self.runcount == 1:
print>>sys.stderr, str(node.ppt) + ": WARNING: " \
"using opcode name as label"
print(str(node.ppt) + ": WARNING: " \
"using opcode name as label", file=sys.stderr)
def visitUnknown(self, node, env):
pass
@ -636,16 +636,16 @@ class ExtendBranches(PCTracker):
if Cmd.enable_4502_exts:
node.nodetype = "RelativeLong"
if Cmd.warn_on_branch_extend:
print>>sys.stderr, str(node.ppt) + ": WARNING: " \
"branch out of range, replacing with 16-bit relative branch"
print(str(node.ppt) + ": WARNING: " \
"branch out of range, replacing with 16-bit relative branch", file=sys.stderr)
else:
if opcode == 'bra':
# If BRA - BRanch Always - is out of range, it's a JMP.
node.data = ('jmp', expr, None)
node.nodetype = "Absolute"
if Cmd.warn_on_branch_extend:
print>>sys.stderr, str(node.ppt) + ": WARNING: " \
"bra out of range, replacing with jmp"
print(str(node.ppt) + ": WARNING: " \
"bra out of range, replacing with jmp", file=sys.stderr)
else:
# Otherwise, we replace it with a 'macro' of sorts by hand:
# $branch LOC -> $reversed_branch ^+5; JMP LOC
@ -661,11 +661,11 @@ class ExtendBranches(PCTracker):
node.nodetype = 'SEQUENCE'
node.data = expansion
if Cmd.warn_on_branch_extend:
print>>sys.stderr, str(node.ppt) + ": WARNING: " + \
print(str(node.ppt) + ": WARNING: " + \
opcode + " out of range, " \
"replacing with " + \
ExtendBranches.reversed[opcode] + \
"/jmp combo"
"/jmp combo", file=sys.stderr)
self.changed = True
node.accept(self, env)
else:
@ -690,11 +690,11 @@ class ExtendBranches(PCTracker):
node.nodetype = 'SEQUENCE'
node.data = expansion
if Cmd.warn_on_branch_extend:
print>>sys.stderr, str(node.ppt) + ": WARNING: " + \
print(str(node.ppt) + ": WARNING: " + \
opcode + " out of range, " \
"replacing with " + \
ExtendBranches.reversed[opcode] + \
"/jmp combo"
"/jmp combo", file=sys.stderr)
self.changed = True
node.accept(self, env)
else:
@ -763,10 +763,10 @@ class Assembler(Pass):
self.listing.dump()
self.mapper.dump()
if Cmd.print_summary and Err.count == 0:
print>>sys.stderr, "Assembly complete: %s bytes output " \
print("Assembly complete: %s bytes output " \
"(%s code, %s data, %s filler)" \
% (len(self.output),
self.code, self.data, self.filler)
self.code, self.data, self.filler), file=sys.stderr)
def outputbyte(self, expr, env, tee=None):
'Outputs a byte, with range checking'
@ -799,7 +799,7 @@ class Assembler(Pass):
'Outputs a little-endian dword, with range checking'
if self.writeOK:
val = expr.value(env)
if val < 0x00000000 or val > 0xFFFFFFFFL:
if val < 0x00000000 or val > 0xFFFFFFFF:
Err.log("DWord constant " + str(expr) + " out of range")
val = 0
self.output.append(int(val & 0xFF))
@ -829,7 +829,7 @@ class Assembler(Pass):
'Outputs a big-endian dword, with range checking'
if self.writeOK:
val = expr.value(env)
if val < 0x00000000 or val > 0xFFFFFFFFL:
if val < 0x00000000 or val > 0xFFFFFFFF:
Err.log("DWord constant " + str(expr) + " out of range")
val = 0
self.output.append(int((val >> 24) & 0xFF))
@ -1083,7 +1083,7 @@ class Assembler(Pass):
(pc, target))
else:
created = []
for i in xrange(target - pc):
for i in range(target - pc):
self.outputbyte(node.data[1], env, created)
self.filler += target - pc
self.registerData(created, env.getPC())

View File

@ -86,9 +86,14 @@ Section "Ophis" SEC01
File "..\..\examples\structuredemo.oph"
File "..\..\examples\fibonacci.oph"
File "..\..\examples\kinematics.oph"
SetOutPath "$INSTDIR\examples\hi_stella"
File "..\..\examples\hi_stella\hi_stella.oph"
File "..\..\examples\hi_stella\README.txt"
# Remove the older copies of hi_stella if needed
Delete "$INSTDIR\examples\hi_stella\hi_stella.oph"
Delete "$INSTDIR\examples\hi_stella\README.txt"
RMDir "$INSTDIR\examples\hi_stella"
SetOutPath "$INSTDIR\examples\stella"
File "..\..\examples\stella\hi_stella.oph"
File "..\..\examples\stella\colortest.oph"
File "..\..\examples\stella\README.txt"
SetOutPath "$INSTDIR\examples\hello_nes"
File "..\..\examples\hello_nes\hello_prg.oph"
File "..\..\examples\hello_nes\hello_chr.oph"
@ -159,8 +164,9 @@ Section Uninstall
Delete "$INSTDIR\ophis.exe"
Delete "$INSTDIR\ophismanual.pdf"
Delete "$INSTDIR\README.txt"
Delete "$INSTDIR\examples\hi_stella\hi_stella.oph"
Delete "$INSTDIR\examples\hi_stella\README.txt"
Delete "$INSTDIR\examples\stella\hi_stella.oph"
Delete "$INSTDIR\examples\stella\colortest.oph"
Delete "$INSTDIR\examples\stella\README.txt"
Delete "$INSTDIR\examples\hello_nes\hello_prg.oph"
Delete "$INSTDIR\examples\hello_nes\hello_chr.oph"
Delete "$INSTDIR\examples\hello_nes\hello_ines.oph"
@ -180,7 +186,7 @@ Section Uninstall
Delete "$STARTMENU\Manual.lnk"
RMDir "$SMPROGRAMS\$ICONS_GROUP"
RMDir "$INSTDIR\examples\hi_stella"
RMDir "$INSTDIR\examples\stella"
RMDir "$INSTDIR\examples\hello_nes"
RMDir "$INSTDIR\examples"
RMDir "$INSTDIR\platform"

View File

@ -1,18 +1,18 @@
#!/usr/bin/env python
#!/usr/bin/env python3
import struct
x = ''.join([chr(x) for x in range(256)])
x = bytes(list(range(256)))
bits = struct.unpack("32s32s32s32s32s32s32s32s", x)
norm = [4, 5, 6, 7, 0, 1, 2, 3]
ivrs = [4, 1, 0, 7, 6, 5, 2, 3]
blnk = [4, 3, 2, 7, 0, 1, 6, 5]
normmap = ''.join([bits[x] for x in norm])
ivrsmap = ''.join([bits[x] for x in ivrs])
blnkmap = ''.join([bits[x] for x in blnk])
normmap = b''.join([bits[x] for x in norm])
ivrsmap = b''.join([bits[x] for x in ivrs])
blnkmap = b''.join([bits[x] for x in blnk])
def dumpfile(n, m):
f = file(n, 'wb')
f = open(n, 'wb')
f.write(m)
f.close()

View File

@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
import sys
verbose = 0
@ -80,7 +80,7 @@ def decomment(l):
def decomment_readlines(fname):
result = [decomment(x) for x in file(fname).readlines()]
result = [decomment(x) for x in open(fname, "rt").readlines()]
return [x for x in result if len(x) > 0]
@ -103,22 +103,22 @@ def parse_chipset_file(fname):
result[op] = []
result[op].append((mnem, flatmodes.index(mode)))
else:
print "Unknown mode '%s'" % s_p[1]
print("Unknown mode '%s'" % s_p[1])
except ValueError:
print "Illegal opcode '%s'" % l[0]
print("Illegal opcode '%s'" % l[0])
return result
def collate_chipset_map(cs_list, base):
result = {}
for (opcode, insts) in zip(range(256), cs_list):
for (opcode, insts) in zip(list(range(256)), cs_list):
if insts is not None:
for inst in insts:
(mnem, mode) = inst
if mnem not in result:
result[mnem] = [None] * len(modes)
if result[mnem][mode] is not None:
print "Warning: Reassigning %s - %s" % (mnem, modes[mode])
print("Warning: Reassigning %s - %s" % (mnem, modes[mode]))
result[mnem][mode] = opcode
if base is not None:
todel = []
@ -127,9 +127,9 @@ def collate_chipset_map(cs_list, base):
if result[x] == base[x]:
todel.append(x)
elif verbose != 0:
print "# Opcode %s changed" % x
print("# Opcode %s changed" % x)
elif verbose != 0:
print "# Opcode %s added" % x
print("# Opcode %s added" % x)
for x in todel:
del result[x]
return result
@ -143,14 +143,14 @@ def mapval(x):
def dump_map(m, prologue=''):
mnems = m.keys()
mnems = list(m.keys())
mnems.sort()
for mnem in mnems:
codes = [mapval(x) for x in m[mnem]]
print "%s'%s': [%s,\n%s %s]," % (prologue, mnem,
print("%s'%s': [%s,\n%s %s]," % (prologue, mnem,
', '.join(codes[:8]),
prologue + " " * len(mnem),
', '.join(codes[8:]))
', '.join(codes[8:])))
if __name__ == '__main__':
@ -165,18 +165,18 @@ if __name__ == '__main__':
for y in [z.split(':', 1) for z in decomment_readlines(x)]]
for l in ls:
if len(l) != 2:
print "Could not parse the chipset line '%s'" % ":".join(l)
print("Could not parse the chipset line '%s'" % ":".join(l))
else:
archs.append((l[0], l[1]))
except IOError:
print "Could not read file %s" % x
print prologue
print("Could not read file %s" % x)
print(prologue)
baseset = None
for (field, fname) in archs:
chipset_list = parse_chipset_file(fname)
instruction_map = collate_chipset_map(chipset_list, baseset)
if baseset is None:
baseset = instruction_map
print "%s = {" % field
print("%s = {" % field)
dump_map(instruction_map, ' ' * (len(field) + 4))
print "%s}" % (' ' * (len(field) + 3))
print("%s}" % (' ' * (len(field) + 3)))

View File

@ -193,7 +193,7 @@
C0: CPY - Immediate
C1: CMP - (Zero Page, X)
C2: CPZ - Immediate
C3: DEW - Absolute
C3: DEW - Zero Page
C4: CPY - Zero Page
C5: CMP - Zero Page
C6: DEC - Zero Page
@ -225,7 +225,7 @@
E0: CPX - Immediate
E1: SBC - (Zero Page, X)
E2: LDA - (Zero Page, SP), Y
E3: INW - Absolute
E3: INW - Zero Page
E4: CPX - Zero Page
E5: SBC - Zero Page
E6: INC - Zero Page

View File

@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
import sys
import subprocess
@ -20,14 +20,14 @@ failed = 0
def assembled(raw):
return ' '.join(["%02X" % ord(c) for c in raw])
return ' '.join(["%02X" % c for c in raw])
def assemble_raw(asm="", options=[]):
p = subprocess.Popen([pythonpath, ophispath] + options,
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
return p.communicate(asm)
return p.communicate(asm.encode("UTF-8"))
def assemble_string(asm, options=[]):
@ -37,15 +37,15 @@ def assemble_string(asm, options=[]):
def test_string(test_name, asm, expected, options=[]):
(out, err) = assemble_string(asm, options)
if out == expected:
print "%s: SUCCESS" % test_name
print("%s: SUCCESS" % test_name)
else:
global failed
failed += 1
print "%s: FAILED" % test_name
print "Assembled code: ", assembled(out)
print "Expected code: ", assembled(expected)
print("%s: FAILED" % test_name)
print("Assembled code: ", assembled(out))
print("Expected code: ", assembled(expected))
if err != '':
print "Error output:\n%s" % err
print("Error output:\n%s" % err.decode(sys.stderr.encoding))
def test_file(test_name, fname, ename, options=[]):
@ -57,7 +57,7 @@ def test_file(test_name, fname, ename, options=[]):
expected = f.read()
f.close()
else: # a test where we expect failure
expected = ''
expected = b''
test_string(test_name, asm, expected, options)
@ -66,16 +66,16 @@ def test_file(test_name, fname, ename, options=[]):
# on, then we start running through the features.
def test_basic():
print
print "==== BASIC OPERATION ===="
print()
print("==== BASIC OPERATION ====")
test_string('Basic Ophis operation', '.byte "Hello, world!"',
'Hello, world!')
b'Hello, world!')
test_string('Newline/EOF passthrough', '.byte 10,26,13,4,0,"Hi",13,10',
'\n\x1a\r\x04\x00Hi\r\n')
b'\n\x1a\r\x04\x00Hi\r\n')
# Normally these would go in Expressions but we need them to run the
# tests for relative instructions.
test_string('Program counter recognition', '.org $41\nlda #^\n', '\xa9A')
test_string('Program counter math', '.org $41\nlda #^+3\n', '\xa9D')
test_string('Program counter recognition', '.org $41\nlda #^\n', b'\xa9A')
test_string('Program counter math', '.org $41\nlda #^+3\n', b'\xa9D')
if failed == 0:
test_file('Basic instructions', 'testbase.oph', 'testbase.bin')
test_file('Basic data pragmas', 'testdata.oph', 'testdata.bin')
@ -100,13 +100,13 @@ def test_basic():
def test_outfile():
global failed
print "\n==== INPUT AND OUTPUT ===="
print("\n==== INPUT AND OUTPUT ====")
if os.path.exists("ophis.bin"):
print "TEST SUITE FAILED: unclean test environment (ophis.bin exists)"
print("TEST SUITE FAILED: unclean test environment (ophis.bin exists)")
failed += 1
return
elif os.path.exists("custom.bin"):
print "TEST SUITE FAILED: unclean test environment (custom.bin exists)"
print("TEST SUITE FAILED: unclean test environment (custom.bin exists)")
failed += 1
return
@ -114,45 +114,46 @@ def test_outfile():
try:
assemble_raw('.byte "Hello, world!", 10', ['-'])
f = open('ophis.bin', 'rb')
if f.read() != 'Hello, world!\n':
print "Default output filename: FAILED (bad output)"
if f.read() != b'Hello, world!\n':
print("Default output filename: FAILED (bad output)")
failed += 1
else:
print "Default output filename: SUCCESS"
print("Default output filename: SUCCESS")
f.close()
os.unlink('ophis.bin')
except:
print "Default output filename: FAILED (exception)"
print("Default output filename: FAILED (exception)")
failed += 1
raise
# Test 2: Command line override
try:
assemble_raw('.byte "Hello, world!", 10', ['-', '-o', 'custom.bin'])
f = open('custom.bin', 'rb')
if f.read() != 'Hello, world!\n':
print "Commandline output filename: FAILED (bad output)"
if f.read() != b'Hello, world!\n':
print("Commandline output filename: FAILED (bad output)")
failed += 1
else:
print "Commandline output filename: SUCCESS"
print("Commandline output filename: SUCCESS")
f.close()
os.unlink('custom.bin')
except:
print "Commandline output filename: FAILED (exception)"
print("Commandline output filename: FAILED (exception)")
failed += 1
# Test 3: Pragma override
try:
assemble_raw('.outfile "custom.bin"\n.byte "Hello, world!", 10', ['-'])
f = open('custom.bin', 'rb')
if f.read() != 'Hello, world!\n':
print "Commandline output filename: FAILED (bad output)"
if f.read() != b'Hello, world!\n':
print("Commandline output filename: FAILED (bad output)")
failed += 1
else:
print "Commandline output filename: SUCCESS"
print("Commandline output filename: SUCCESS")
f.close()
os.unlink('custom.bin')
except:
print "Commandline output filename: FAILED (exception)"
print("Commandline output filename: FAILED (exception)")
failed += 1
# Test 4: Command line override of .outfile
@ -160,15 +161,15 @@ def test_outfile():
assemble_raw('.outfile "custom2.bin"\n'
'.byte "Hello, world!", 10', ['-', '-o', 'custom.bin'])
f = open('custom.bin', 'rb')
if f.read() != 'Hello, world!\n':
print "Commandline override of pragma: FAILED (bad output)"
if f.read() != b'Hello, world!\n':
print("Commandline override of pragma: FAILED (bad output)")
failed += 1
else:
print "Commandline override of pragma: SUCCESS"
print("Commandline override of pragma: SUCCESS")
f.close()
os.unlink('custom.bin')
except:
print "Commandline override of pragma: FAILED (exception)"
print("Commandline override of pragma: FAILED (exception)")
failed += 1
# Test 5: Pragma repetition priority
@ -177,15 +178,15 @@ def test_outfile():
'.outfile "custom2.bin"\n'
'.byte "Hello, world!", 10', ['-'])
f = open('custom.bin', 'rb')
if f.read() != 'Hello, world!\n':
print "Pragma repetition: FAILED (bad output)"
if f.read() != b'Hello, world!\n':
print("Pragma repetition: FAILED (bad output)")
failed += 1
else:
print "Pragma repetition: SUCCESS"
print("Pragma repetition: SUCCESS")
f.close()
os.unlink('custom.bin')
except:
print "Pragma repetition: FAILED (exception)"
print("Pragma repetition: FAILED (exception)")
failed += 1
# Test 6: multiple input files
@ -200,73 +201,73 @@ def test_outfile():
s += f.read()
f.close()
if out != s:
print "Multiple input files: FAILED (bad output)"
print("Multiple input files: FAILED (bad output)")
failed += 1
else:
print "Multiple input files: SUCCESS"
print("Multiple input files: SUCCESS")
except:
print "Multiple input files: FAILED (exception)"
print("Multiple input files: FAILED (exception)")
failed += 1
def test_transforms():
print "\n==== BINARY TRANSFORM PASSES ===="
print "Simple zero page selection: SUCCESS (covered in basic tests)"
print("\n==== BINARY TRANSFORM PASSES ====")
print("Simple zero page selection: SUCCESS (covered in basic tests)")
test_string('Chained collapse', '.org $fa \n lda + \n lda ^ \n * rts \n',
'\xa5\xfe\xa5\xfc\x60')
b'\xa5\xfe\xa5\xfc\x60')
test_string('Reversible collapse', '.org $fb \n bne ^+200 \n lda ^ \n',
'\xf0\x03\x4c\xc5\x01\xad\x00\x01')
b'\xf0\x03\x4c\xc5\x01\xad\x00\x01')
def test_expressions():
print "\n==== EXPRESSIONS AND LABELS ===="
test_string('Basic addition', '.byte 3+2', '\x05')
test_string('Basic subtraction', '.byte 3-2', '\x01')
test_string('Basic multiplication', '.byte 3*2', '\x06')
test_string('Basic division', '.byte 6/2', '\x03')
test_string('Basic bit-union', '.byte 5|9', '\x0d')
test_string('Basic bit-intersection', '.byte 5&9', '\x01')
test_string('Basic bit-toggle', '.byte 5^9', '\x0c')
test_string('Division truncation', '.byte 5/2', '\x02')
test_string('Overflow', '.byte $FF*$10', '')
test_string('Multibyte overflow', '.word $FF*$10', '\xf0\x0f')
test_string('Masked overflow', '.byte $FF*$10&$FF', '\xf0')
test_string('Underflow', '.byte 2-3', '')
test_string('Masked underflow', '.byte 2-3&$FF', '\xff')
test_string('Arithmetic precedence', '.byte 2+3*4-6/2', '\x0b')
test_string('Parentheses', '.byte [2+3]*[4-6/2]', '\x05')
print("\n==== EXPRESSIONS AND LABELS ====")
test_string('Basic addition', '.byte 3+2', b'\x05')
test_string('Basic subtraction', '.byte 3-2', b'\x01')
test_string('Basic multiplication', '.byte 3*2', b'\x06')
test_string('Basic division', '.byte 6/2', b'\x03')
test_string('Basic bit-union', '.byte 5|9', b'\x0d')
test_string('Basic bit-intersection', '.byte 5&9', b'\x01')
test_string('Basic bit-toggle', '.byte 5^9', b'\x0c')
test_string('Division truncation', '.byte 5/2', b'\x02')
test_string('Overflow', '.byte $FF*$10', b'')
test_string('Multibyte overflow', '.word $FF*$10', b'\xf0\x0f')
test_string('Masked overflow', '.byte $FF*$10&$FF', b'\xf0')
test_string('Underflow', '.byte 2-3', b'')
test_string('Masked underflow', '.byte 2-3&$FF', b'\xff')
test_string('Arithmetic precedence', '.byte 2+3*4-6/2', b'\x0b')
test_string('Parentheses', '.byte [2+3]*[4-6/2]', b'\x05')
test_string('String escapes',
'.byte "The man said, \\"The \\\\ is Windowsy.\\""',
'The man said, "The \\ is Windowsy."')
b'The man said, "The \\ is Windowsy."')
test_string('Byte selector precedence',
'.byte >$d000+32,>[$d000+32],<[$D000-275]',
'\xf0\xd0\xed')
test_string('Named labels', '.org $6948\nl: .word l', 'Hi')
test_string('.alias directive (basic)', '.alias hi $6948\n.word hi', 'Hi')
b'\xf0\xd0\xed')
test_string('Named labels', '.org $6948\nl: .word l', b'Hi')
test_string('.alias directive (basic)', '.alias hi $6948\n.word hi', b'Hi')
test_string('.alias directive (derived)',
'.alias hi $6948\n.alias ho hi+$600\n.word hi,ho', 'HiHo')
'.alias hi $6948\n.alias ho hi+$600\n.word hi,ho', b'HiHo')
test_string('.alias directive (circular)',
'.alias a c+1\n.alias b a+3\n.alias c b-4\n.word a, b, c',
'')
b'')
test_string('.advance directive (basic)',
'lda #$05\n.advance $05\n.byte ^',
'\xa9\x05\x00\x00\x00\x05')
b'\xa9\x05\x00\x00\x00\x05')
test_string('.advance directive (filler)',
'lda #$05\nf: .advance $05,f+3\n.byte ^',
'\xa9\x05\x05\x05\x05\x05')
b'\xa9\x05\x05\x05\x05\x05')
test_string('.advance no-op', 'lda #$05\n.advance $02\n.byte ^',
'\xa9\x05\x02')
test_string('.advance failure', 'lda #$05\n.advance $01\n.byte ^', '')
test_string('.checkpc, space > 0', 'lda #$05\n.checkpc $10', '\xa9\x05')
test_string('.checkpc, space = 0', 'lda #$05\n.checkpc 2', '\xa9\x05')
test_string('.checkpc, space < 0', 'lda $05\n.checkpc 1', '')
b'\xa9\x05\x02')
test_string('.advance failure', 'lda #$05\n.advance $01\n.byte ^', b'')
test_string('.checkpc, space > 0', 'lda #$05\n.checkpc $10', b'\xa9\x05')
test_string('.checkpc, space = 0', 'lda #$05\n.checkpc 2', b'\xa9\x05')
test_string('.checkpc, space < 0', 'lda $05\n.checkpc 1', b'')
test_string('A X Y usable as labels',
'.alias a 1\n.alias x 2\n.alias y 3\n'
'lda (a+x+y),y\nlda (x+y,x)',
'\xb1\x06\xa1\x05')
b'\xb1\x06\xa1\x05')
test_string('Opcodes usable as labels',
'ldy #$00\n dey: dey\n bne dey',
'\xa0\x00\x88\xd0\xfd')
b'\xa0\x00\x88\xd0\xfd')
def test_segments():
@ -277,18 +278,18 @@ def test_segments():
'.org $61\n'
'd:\n'
'.text\n'
'l: .byte l, d', 'Aa')
test_string('Data cleanliness', '.byte 65\n.data\n.byte 65', '')
'l: .byte l, d', b'Aa')
test_string('Data cleanliness', '.byte 65\n.data\n.byte 65', b'')
test_string('.space directive',
'.data\n.org $41\n.space a 2\n.space b 1\n.space c 1\n'
'.text\n.byte a, b, c\n', 'ACD')
'.text\n.byte a, b, c\n', b'ACD')
test_string('Multiple named segments',
'.data\n.org $41\n.data a\n.org $61\n.data b\n.org $4a\n'
'.data\n.space q 1\n.data a\n.space r 1\n.data b\n.space s 1\n'
'.text\n.org $10\n.text a\n.org $20\n'
'.text\n.byte ^,q,r,s\n'
'.text a\n.byte ^,q,r,s\n',
'\x10AaJ\x20AaJ')
b'\x10AaJ\x20AaJ')
def test_scopes():
@ -300,13 +301,13 @@ def test_scopes():
'.scend\n'
'.scope\n'
'_l: .byte _l\n'
'.scend\n', 'AB')
'.scend\n', b'AB')
test_string('Data hiding outside of scope',
'.org $41\n'
'.scope\n'
'_l: .byte _l\n'
'.scend\n'
' .byte _l\n', '')
' .byte _l\n', b'')
test_string('Repeated labels, nested scopes',
'.org $41\n'
'.scope\n'
@ -315,17 +316,17 @@ def test_scopes():
'_l: .byte _l\n'
'.scend\n'
' .byte _l\n'
'.scend\n', 'ABA')
'.scend\n', b'ABA')
test_string('Anonymous labels (basic)',
'.org $41\n'
'* .byte -, +\n'
'* .byte -, --\n', 'ACCA')
'* .byte -, --\n', b'ACCA')
test_string('Anonymous labels (across scopes)',
'.org $41\n'
'* .byte -, +\n'
'.scope\n'
'* .byte -, --\n'
'.scend\n', 'ACCA')
'.scend\n', b'ACCA')
def test_macros():
@ -334,12 +335,12 @@ def test_macros():
'.macro greet\n'
' .byte "hi"\n'
'.macend\n'
'`greet\n.invoke greet', "hihi")
'`greet\n.invoke greet', b"hihi")
test_string('Macros with arguments',
'.macro greet\n'
' .byte "hi",_1\n'
'.macend\n'
"`greet 'A\n.invoke greet 'B", "hiAhiB")
"`greet 'A\n.invoke greet 'B", b"hiAhiB")
test_string('Macros invoking macros',
'.macro inner\n'
' .byte " there"\n'
@ -348,7 +349,7 @@ def test_macros():
' .byte "hi"\n'
' `inner\n'
'.macend\n'
"`greet", "hi there")
"`greet", b"hi there")
test_string('Macros defining macros (illegal)',
'.macro greet\n'
'.macro inner\n'
@ -357,7 +358,7 @@ def test_macros():
' .byte "hi"\n'
' `inner\n'
'.macend\n'
"`greet", "")
"`greet", b"")
test_string('Fail on infinite recursion',
'.macro inner\n'
' .byte " there"\n'
@ -367,75 +368,75 @@ def test_macros():
' .byte "hi"\n'
' `inner\n'
'.macend\n'
"`greet", "")
"`greet", b"")
def test_subfiles():
print("\n==== COMPILATION UNITS ====")
test_string(".include pragma", '.include "baseinc.oph"', 'BASIC\n')
test_string(".include pragma", '.include "baseinc.oph"', b'BASIC\n')
test_string(".include repeated",
'.include "baseinc.oph"\n.include "baseinc.oph"',
'BASIC\nBASIC\n')
test_string(".require pragma", '.require "baseinc.oph"', 'BASIC\n')
b'BASIC\nBASIC\n')
test_string(".require pragma", '.require "baseinc.oph"', b'BASIC\n')
test_string(".include before .require",
'.include "baseinc.oph"\n.require "baseinc.oph"',
'BASIC\n')
b'BASIC\n')
test_string(".require before .include",
'.require "baseinc.oph"\n.include "baseinc.oph"',
'BASIC\nBASIC\n')
b'BASIC\nBASIC\n')
test_string(".require same file twice with different paths",
'.include "baseinc.oph"\n.include "sub/baseinc.oph"',
'BASIC\nSUB 1 START\nSUB 1 END\n')
b'BASIC\nSUB 1 START\nSUB 1 END\n')
test_string(".require different files with identical paths",
'.include "sub/sub/sub.oph"',
'SUB 2 START\nSUB 1 START\nBASIC\nSUB 1 END\nSUB 2 END\n')
b'SUB 2 START\nSUB 1 START\nBASIC\nSUB 1 END\nSUB 2 END\n')
test_string(".charmap (basic)",
'.charmap \'A, "abcdefghijklmnopqrstuvwxyz"\n'
'.charmap \'a, "ABCDEFGHIJKLMNOPQRSTUVWXYZ"\n'
'.byte "hELLO, wORLD!"', "Hello, World!")
'.byte "hELLO, wORLD!"', b"Hello, World!")
test_string(".charmap (reset)",
'.charmap \'A, "abcdefghijklmnopqrstuvwxyz"\n'
'.charmap \'a, "ABCDEFGHIJKLMNOPQRSTUVWXYZ"\n'
'.byte "hELLO, wORLD!",10\n'
'.charmap\n'
'.byte "hELLO, wORLD!",10\n',
"Hello, World!\nhELLO, wORLD!\n")
b"Hello, World!\nhELLO, wORLD!\n")
test_string(".charmap (out of range)",
'.charmap 250, "ABCDEFGHIJKLM"\n.byte 250,251',
'')
b'')
test_string(".charmapbin (basic)",
'.charmapbin "../examples/petscii.map"\n.byte "hELLO, wORLD!"',
"Hello, World!")
b"Hello, World!")
test_string(".charmapbin (illegal)",
'.charmapbin "baseinc.bin"\n.byte "hELLO, wORLD!"', '')
test_string(".incbin (basic)", '.incbin "baseinc.bin"', "BASIC\n")
'.charmapbin "baseinc.bin"\n.byte "hELLO, wORLD!"', b'')
test_string(".incbin (basic)", '.incbin "baseinc.bin"', b"BASIC\n")
test_string(".incbin (hardcoded offset)",
'.incbin "baseinc.bin",3', "IC\n")
'.incbin "baseinc.bin",3', b"IC\n")
test_string(".incbin (hardcoded offset and length)",
'.incbin "baseinc.bin",3,2', "IC")
'.incbin "baseinc.bin",3,2', b"IC")
test_string(".incbin (softcoded offset and length)",
'.alias off len+1\n.alias len 2\n'
'.incbin "baseinc.bin",off,len', "IC")
'.incbin "baseinc.bin",off,len', b"IC")
test_string(".incbin (length too long)",
'.byte 65\n.incbin "baseinc.bin",3,4', "")
'.byte 65\n.incbin "baseinc.bin",3,4', b"")
test_string(".incbin (negative offset)",
'.byte 65\n.incbin "baseinc.bin",1-5,4', "")
'.byte 65\n.incbin "baseinc.bin",1-5,4', b"")
test_string(".incbin (offset = size)",
'.byte 65\n.incbin "baseinc.bin",6', "A")
'.byte 65\n.incbin "baseinc.bin",6', b"A")
test_string(".incbin (offset > size)",
'.byte 65\n.incbin "baseinc.bin",7', "")
'.byte 65\n.incbin "baseinc.bin",7', b"")
test_string(".incbin (softcoded length too long)",
'.alias off len\n.alias len 4\n'
'.byte 65\n.incbin "baseinc.bin",off,len', "")
'.byte 65\n.incbin "baseinc.bin",off,len', b"")
test_string(".incbin (softcoded negative offset)",
'.alias off 1-5\n'
'.byte 65\n.incbin "baseinc.bin",off,4', "")
'.byte 65\n.incbin "baseinc.bin",off,4', b"")
test_string(".incbin (softcoded offset = size)",
'.alias off 6\n'
'.byte 65\n.incbin "baseinc.bin",off', "A")
'.byte 65\n.incbin "baseinc.bin",off', b"A")
test_string(".incbin (softcoded offset > size)",
'.alias off 7\n'
'.byte 65\n.incbin "baseinc.bin",off', "")
'.byte 65\n.incbin "baseinc.bin",off', b"")
def test_systematic():
@ -449,18 +450,18 @@ def test_systematic():
if __name__ == '__main__':
print "Using Python interpreter:", pythonpath
print("Using Python interpreter:", pythonpath)
test_basic()
os.chdir(os.path.dirname(os.path.realpath(sys.argv[0])))
if failed == 0:
test_systematic()
else:
print "\nBasic test cases failed, aborting test."
print("\nBasic test cases failed, aborting test.")
if failed > 0:
print "\nTotal test case failures: %d" % failed
print("\nTotal test case failures: %d" % failed)
sys.exit(1)
else:
print "\nAll test cases succeeded"
print("\nAll test cases succeeded")
sys.exit(0)