mirror of
https://github.com/catseye/SixtyPical.git
synced 2025-04-06 17:39:38 +00:00
commit
9b53ed03c8
21
HISTORY.md
21
HISTORY.md
@ -1,6 +1,27 @@
|
||||
History of SixtyPical
|
||||
=====================
|
||||
|
||||
0.15
|
||||
----
|
||||
|
||||
* Symbolic constants can be defined with the `const` keyword, and can
|
||||
be used in most places where literal values can be used.
|
||||
* Added `nop` opcode, which compiles to `NOP` (mainly for timing.)
|
||||
* Accessing zero-page with `ld` and `st` generates zero-page opcodes.
|
||||
* A `byte` or `word` table can be initialized with a list of constants.
|
||||
* Branching and repeating on the `n` flag is now supported.
|
||||
* The `--optimize-fallthru` option causes the routines of the program
|
||||
to be re-ordered to maximize the number of cases where a `goto`'ed
|
||||
routine can be simply "falled through" to instead of `JMP`ed to.
|
||||
* `--dump-fallthru-info` option outputs the information from the
|
||||
fallthru analysis phase, in JSON format, to stdout.
|
||||
* Even without fallthru optimization, `RTS` is no longer emitted after
|
||||
the `JMP` from compiling a final `goto`.
|
||||
* Specifying multiple SixtyPical source files will produce a single
|
||||
compiled result from their combination.
|
||||
* Rudimentary support for Atari 2600 prelude in a 4K cartridge image,
|
||||
and an example program in `eg/atari2600` directory.
|
||||
|
||||
0.14
|
||||
----
|
||||
|
||||
|
14
README.md
14
README.md
@ -1,7 +1,7 @@
|
||||
SixtyPical
|
||||
==========
|
||||
|
||||
_Version 0.14. Work-in-progress, everything is subject to change._
|
||||
_Version 0.15. Work-in-progress, everything is subject to change._
|
||||
|
||||
**SixtyPical** is a 6502-like programming language with advanced
|
||||
static analysis.
|
||||
@ -63,6 +63,7 @@ Documentation
|
||||
* [Literate test suite for SixtyPical syntax](tests/SixtyPical%20Syntax.md)
|
||||
* [Literate test suite for SixtyPical analysis](tests/SixtyPical%20Analysis.md)
|
||||
* [Literate test suite for SixtyPical compilation](tests/SixtyPical%20Compilation.md)
|
||||
* [Literate test suite for SixtyPical fallthru optimization](tests/SixtyPical%20Fallthru.md)
|
||||
* [6502 Opcodes used/not used in SixtyPical](doc/6502%20Opcodes.md)
|
||||
|
||||
TODO
|
||||
@ -73,19 +74,13 @@ TODO
|
||||
This preserves them, so that, semantically, they can be used later even though they
|
||||
are trashed inside the block.
|
||||
|
||||
### Re-order routines and optimize tail-calls to fallthroughs
|
||||
|
||||
Not because it saves 3 bytes, but because it's a neat trick. Doing it optimally
|
||||
is probably NP-complete. But doing it adequately is probably not that hard.
|
||||
|
||||
### And at some point...
|
||||
|
||||
* `low` and `high` address operators - to turn `word` type into `byte`.
|
||||
* `const`s that can be used in defining the size of tables, etc.
|
||||
* Tests, and implementation, ensuring a routine can be assigned to a vector of "wider" type
|
||||
* Related: can we simply view a (small) part of a buffer as a byte table? If not, why not?
|
||||
* Related: add constant to buffer to get new buffer. (Or to table, but... well, maybe.)
|
||||
* Check that the buffer being read or written to through pointer, appears in approporiate inputs or outputs set.
|
||||
* Check that the buffer being read or written to through pointer, appears in appropriate inputs or outputs set.
|
||||
(Associate each pointer with the buffer it points into.)
|
||||
* `static` pointers -- currently not possible because pointers must be zero-page, thus `@`, thus uninitialized.
|
||||
* Question the value of the "consistent initialization" principle for `if` statement analysis.
|
||||
@ -94,7 +89,6 @@ is probably NP-complete. But doing it adequately is probably not that hard.
|
||||
* Automatic tail-call optimization (could be tricky, w/constraints?)
|
||||
* Possibly `ld x, [ptr] + y`, possibly `st x, [ptr] + y`.
|
||||
* Maybe even `copy [ptra] + y, [ptrb] + y`, which can be compiled to indirect LDA then indirect STA!
|
||||
* Optimize `ld a, z` and `st a, z` to zero-page operations if address of z < 256.
|
||||
* Include files?
|
||||
* Optimize `or|and|eor a, z` to zero-page operations if address of z < 256.
|
||||
|
||||
[VICE]: http://vice-emu.sourceforge.net/
|
||||
|
219
bin/sixtypical
219
bin/sixtypical
@ -18,12 +18,120 @@ from pprint import pprint
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
from sixtypical.parser import Parser
|
||||
from sixtypical.parser import Parser, ParsingContext
|
||||
from sixtypical.analyzer import Analyzer
|
||||
from sixtypical.emitter import Emitter, Byte, Word
|
||||
from sixtypical.compiler import Compiler
|
||||
|
||||
|
||||
def merge_programs(programs):
|
||||
"""Assumes that the programs do not have any conflicts."""
|
||||
|
||||
from sixtypical.ast import Program
|
||||
|
||||
full = Program(1, defns=[], routines=[])
|
||||
for p in programs:
|
||||
full.defns.extend(p.defns)
|
||||
full.routines.extend(p.routines)
|
||||
|
||||
return full
|
||||
|
||||
|
||||
def process_input_files(filenames, options):
|
||||
context = ParsingContext()
|
||||
|
||||
programs = []
|
||||
|
||||
for filename in options.filenames:
|
||||
text = open(filename).read()
|
||||
parser = Parser(context, text, filename)
|
||||
if options.debug:
|
||||
print(context)
|
||||
program = parser.program()
|
||||
programs.append(program)
|
||||
|
||||
if options.parse_only:
|
||||
return
|
||||
|
||||
program = merge_programs(programs)
|
||||
|
||||
analyzer = Analyzer(debug=options.debug)
|
||||
analyzer.analyze_program(program)
|
||||
|
||||
compilation_roster = None
|
||||
if options.optimize_fallthru:
|
||||
from sixtypical.fallthru import FallthruAnalyzer
|
||||
|
||||
def dump(data, label=None):
|
||||
import json
|
||||
if not options.dump_fallthru_info:
|
||||
return
|
||||
if label:
|
||||
sys.stdout.write("*** {}:\n".format(label))
|
||||
sys.stdout.write(json.dumps(data, indent=4, sort_keys=True))
|
||||
sys.stdout.write("\n")
|
||||
|
||||
fa = FallthruAnalyzer(debug=options.debug)
|
||||
fa.analyze_program(program)
|
||||
compilation_roster = fa.serialize()
|
||||
dump(compilation_roster)
|
||||
|
||||
if options.analyze_only:
|
||||
return
|
||||
|
||||
fh = sys.stdout
|
||||
|
||||
if options.origin.startswith('0x'):
|
||||
start_addr = int(options.origin, 16)
|
||||
else:
|
||||
start_addr = int(options.origin, 10)
|
||||
|
||||
output_format = options.output_format
|
||||
|
||||
prelude = []
|
||||
if options.prelude == 'c64':
|
||||
output_format = 'prg'
|
||||
start_addr = 0x0801
|
||||
prelude = [0x10, 0x08, 0xc9, 0x07, 0x9e, 0x32,
|
||||
0x30, 0x36, 0x31, 0x00, 0x00, 0x00]
|
||||
elif options.prelude == 'vic20':
|
||||
output_format = 'prg'
|
||||
start_addr = 0x1001
|
||||
prelude = [0x0b, 0x10, 0xc9, 0x07, 0x9e, 0x34,
|
||||
0x31, 0x30, 0x39, 0x00, 0x00, 0x00]
|
||||
elif options.prelude == 'atari2600':
|
||||
output_format = 'crtbb'
|
||||
start_addr = 0xf000
|
||||
prelude = [0x78, 0xd8, 0xa2, 0xff, 0x9a, 0xa9,
|
||||
0x00,0x95, 0x00, 0xca, 0xd0, 0xfb]
|
||||
|
||||
elif options.prelude:
|
||||
raise NotImplementedError("Unknown prelude: {}".format(options.prelude))
|
||||
|
||||
# If we are outputting a .PRG, we output the load address first.
|
||||
# We don't use the Emitter for this b/c not part of addr space.
|
||||
if output_format == 'prg':
|
||||
fh.write(Word(start_addr).serialize(0))
|
||||
|
||||
emitter = Emitter(start_addr)
|
||||
for byte in prelude:
|
||||
emitter.emit(Byte(byte))
|
||||
compiler = Compiler(emitter)
|
||||
compiler.compile_program(program, compilation_roster=compilation_roster)
|
||||
|
||||
# If we are outputting a cartridge with boot and BRK address
|
||||
# at the end, pad to ROM size minus 4 bytes, and emit addresses.
|
||||
if output_format == 'crtbb':
|
||||
emitter.pad_to_size(4096 - 4)
|
||||
emitter.emit(Word(start_addr))
|
||||
emitter.emit(Word(start_addr))
|
||||
|
||||
if options.debug:
|
||||
pprint(emitter.accum)
|
||||
else:
|
||||
emitter.serialize(fh)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
argparser = ArgumentParser(__doc__.strip())
|
||||
|
||||
@ -31,11 +139,7 @@ if __name__ == '__main__':
|
||||
'filenames', metavar='FILENAME', type=str, nargs='+',
|
||||
help="The SixtyPical source files to compile."
|
||||
)
|
||||
argparser.add_argument(
|
||||
"--analyze-only",
|
||||
action="store_true",
|
||||
help="Only parse and analyze the program; do not compile it."
|
||||
)
|
||||
|
||||
argparser.add_argument(
|
||||
"--origin", type=str, default='0xc000',
|
||||
help="Location in memory where the `main` routine will be "
|
||||
@ -43,26 +147,43 @@ if __name__ == '__main__':
|
||||
)
|
||||
argparser.add_argument(
|
||||
"--output-format", type=str, default='prg',
|
||||
help="Executable format to produce. Options are: prg (.PRG file "
|
||||
"for Commodore 8-bit). Default: prg."
|
||||
help="Executable format to produce. Options are: prg, crtbb. "
|
||||
"Default: prg."
|
||||
)
|
||||
argparser.add_argument(
|
||||
"--prelude", type=str,
|
||||
help="Insert a snippet before the compiled program "
|
||||
"so that it can be LOADed and RUN on a certain platforms. "
|
||||
help="Insert a snippet of code before the compiled program so that "
|
||||
"it can be booted automatically on a particular platform. "
|
||||
"Also sets the origin and format. "
|
||||
"Options are: c64 or vic20."
|
||||
"Options are: c64, vic20, atari2600."
|
||||
)
|
||||
|
||||
argparser.add_argument(
|
||||
"--analyze-only",
|
||||
action="store_true",
|
||||
help="Only parse and analyze the program; do not compile it."
|
||||
)
|
||||
argparser.add_argument(
|
||||
"--debug",
|
||||
"--optimize-fallthru",
|
||||
action="store_true",
|
||||
help="Display debugging information when analyzing and compiling."
|
||||
help="Reorder the routines in the program to maximize the number of tail calls "
|
||||
"that can be removed by having execution 'fall through' to the next routine."
|
||||
)
|
||||
argparser.add_argument(
|
||||
"--dump-fallthru-info",
|
||||
action="store_true",
|
||||
help="Dump the fallthru map and ordering to stdout after analyzing the program."
|
||||
)
|
||||
argparser.add_argument(
|
||||
"--parse-only",
|
||||
action="store_true",
|
||||
help="Only parse the program; do not analyze or compile it."
|
||||
)
|
||||
argparser.add_argument(
|
||||
"--debug",
|
||||
action="store_true",
|
||||
help="Display debugging information when analyzing and compiling."
|
||||
)
|
||||
argparser.add_argument(
|
||||
"--traceback",
|
||||
action="store_true",
|
||||
@ -72,69 +193,11 @@ if __name__ == '__main__':
|
||||
options, unknown = argparser.parse_known_args(sys.argv[1:])
|
||||
remainder = ' '.join(unknown)
|
||||
|
||||
for filename in options.filenames:
|
||||
text = open(filename).read()
|
||||
|
||||
try:
|
||||
parser = Parser(text)
|
||||
program = parser.program()
|
||||
except Exception as e:
|
||||
if options.traceback:
|
||||
raise
|
||||
else:
|
||||
traceback.print_exception(e.__class__, e, None)
|
||||
sys.exit(1)
|
||||
|
||||
if options.parse_only:
|
||||
sys.exit(0)
|
||||
|
||||
try:
|
||||
analyzer = Analyzer(debug=options.debug)
|
||||
analyzer.analyze_program(program)
|
||||
except Exception as e:
|
||||
if options.traceback:
|
||||
raise
|
||||
else:
|
||||
traceback.print_exception(e.__class__, e, None)
|
||||
sys.exit(1)
|
||||
|
||||
if options.analyze_only:
|
||||
sys.exit(0)
|
||||
|
||||
fh = sys.stdout
|
||||
|
||||
if options.origin.startswith('0x'):
|
||||
start_addr = int(options.origin, 16)
|
||||
try:
|
||||
process_input_files(options.filenames, options)
|
||||
except Exception as e:
|
||||
if options.traceback:
|
||||
raise
|
||||
else:
|
||||
start_addr = int(options.origin, 10)
|
||||
|
||||
output_format = options.output_format
|
||||
|
||||
prelude = []
|
||||
if options.prelude == 'c64':
|
||||
output_format = 'prg'
|
||||
start_addr = 0x0801
|
||||
prelude = [0x10, 0x08, 0xc9, 0x07, 0x9e, 0x32,
|
||||
0x30, 0x36, 0x31, 0x00, 0x00, 0x00]
|
||||
elif options.prelude == 'vic20':
|
||||
output_format = 'prg'
|
||||
start_addr = 0x1001
|
||||
prelude = [0x0b, 0x10, 0xc9, 0x07, 0x9e, 0x34,
|
||||
0x31, 0x30, 0x39, 0x00, 0x00, 0x00]
|
||||
elif options.prelude:
|
||||
raise NotImplementedError("Unknown prelude: {}".format(options.prelude))
|
||||
|
||||
# If we are outputting a .PRG, we output the load address first.
|
||||
# We don't use the Emitter for this b/c not part of addr space.
|
||||
if output_format == 'prg':
|
||||
fh.write(Word(start_addr).serialize(0))
|
||||
|
||||
emitter = Emitter(start_addr)
|
||||
for byte in prelude:
|
||||
emitter.emit(Byte(byte))
|
||||
compiler = Compiler(emitter)
|
||||
compiler.compile_program(program)
|
||||
if options.debug:
|
||||
pprint(emitter.accum)
|
||||
else:
|
||||
emitter.serialize(fh)
|
||||
traceback.print_exception(e.__class__, e, None)
|
||||
sys.exit(1)
|
||||
|
@ -1,7 +1,7 @@
|
||||
SixtyPical
|
||||
==========
|
||||
|
||||
This document describes the SixtyPical programming language version 0.14,
|
||||
This document describes the SixtyPical programming language version 0.15,
|
||||
both its static semantics (the capabilities and limits of the static
|
||||
analyses it defines) and its runtime semantics (with reference to the
|
||||
semantics of 6502 machine code.)
|
||||
@ -555,9 +555,10 @@ The block is always executed as least once.
|
||||
Grammar
|
||||
-------
|
||||
|
||||
Program ::= {TypeDefn} {Defn} {Routine}.
|
||||
Program ::= {ConstDefn | TypeDefn} {Defn} {Routine}.
|
||||
ConstDefn::= "const" Ident<new> Const.
|
||||
TypeDefn::= "typedef" Type Ident<new>.
|
||||
Defn ::= Type Ident<new> [Constraints] (":" Literal | "@" LitWord).
|
||||
Defn ::= Type Ident<new> [Constraints] (":" Const | "@" LitWord).
|
||||
Type ::= TypeTerm ["table" TypeSize].
|
||||
TypeExpr::= "byte"
|
||||
| "word"
|
||||
@ -573,12 +574,13 @@ Grammar
|
||||
| "routine" Ident<new> Constraints (Block | "@" LitWord)
|
||||
.
|
||||
LocExprs::= LocExpr {"," LocExpr}.
|
||||
LocExpr ::= Register | Flag | Literal | Ident.
|
||||
LocExpr ::= Register | Flag | Const | Ident.
|
||||
Register::= "a" | "x" | "y".
|
||||
Flag ::= "c" | "z" | "n" | "v".
|
||||
Const ::= Literal | Ident<const>.
|
||||
Literal ::= LitByte | LitWord | LitBit.
|
||||
LitByte ::= "0" ... "255".
|
||||
LitWord ::= "0" ... "65535".
|
||||
LitWord ::= ["word"] "0" ... "65535".
|
||||
LitBit ::= "on" | "off".
|
||||
Block ::= "{" {Instr} "}".
|
||||
Instr ::= "ld" LocExpr "," LocExpr ["+" LocExpr]
|
||||
@ -598,6 +600,6 @@ Grammar
|
||||
| "copy" LocExpr "," LocExpr ["+" LocExpr]
|
||||
| "if" ["not"] LocExpr Block ["else" Block]
|
||||
| "repeat" Block ("until" ["not"] LocExpr | "forever")
|
||||
| "for" LocExpr ("up"|"down") "to" Literal Block
|
||||
| "for" LocExpr ("up"|"down") "to" Const Block
|
||||
| "with" "interrupts" LitBit Block
|
||||
.
|
||||
|
10
eg/README.md
10
eg/README.md
@ -30,7 +30,8 @@ elaborate demos:
|
||||
the P65 assembler (now Ophis) and re-released on April 1st, 2008 (a
|
||||
hint as to its nature).
|
||||
|
||||
Translated to SixtyPical (in 2018), it's 48 bytes.
|
||||
Translated to SixtyPical (in 2018), after adding some optimizations
|
||||
to the SixtyPical compiler, the resulting executable is still 44 bytes!
|
||||
|
||||
### vic20
|
||||
|
||||
@ -38,4 +39,11 @@ In the [vic20](vic20/) directory are programs that run on the
|
||||
Commodore VIC-20. The directory itself contains some simple demos,
|
||||
for example [hearts.60p](vic20/hearts.60p).
|
||||
|
||||
### atari2600
|
||||
|
||||
In the [atari2600](atari2600/) directory are programs that run on the
|
||||
Atari 2600 (4K cartridge). The directory itself contains a simple
|
||||
demo, [smiley.60p](atari2600/smiley.60p) which was converted from an
|
||||
older Atari 2600 skeleton program written in [Ophis][].
|
||||
|
||||
[Ophis]: http://michaelcmartin.github.io/Ophis/
|
||||
|
2
eg/atari2600/.gitignore
vendored
Normal file
2
eg/atari2600/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
*.bin
|
||||
*.disasm.txt
|
340
eg/atari2600/atari-2600-example.oph
Normal file
340
eg/atari2600/atari-2600-example.oph
Normal file
@ -0,0 +1,340 @@
|
||||
;
|
||||
; atari-2600-example.oph
|
||||
; Skeleton code for an Atari 2600 ROM,
|
||||
; plus an example of reading the joystick.
|
||||
; By Chris Pressey, November 2, 2012.
|
||||
;
|
||||
; This work is in the public domain. See the file UNLICENSE for more info.
|
||||
;
|
||||
; Based on Chris Cracknell's Atari 2600 clock (also in the public domain):
|
||||
; http://everything2.com/title/An+example+of+Atari+2600+source+code
|
||||
;
|
||||
; to build and run in Stella:
|
||||
; ophis atari-2600-example.oph -o example.bin
|
||||
; stella example.bin
|
||||
;
|
||||
; More useful information can be found in the Stella Programmer's Guide:
|
||||
; http://alienbill.com/2600/101/docs/stella.html
|
||||
;
|
||||
|
||||
;
|
||||
; Useful system addresses (TODO: briefly describe each of these.)
|
||||
;
|
||||
|
||||
.alias VSYNC $00
|
||||
.alias VBLANK $01
|
||||
.alias WSYNC $02
|
||||
.alias NUSIZ0 $04
|
||||
.alias NUSIZ1 $05
|
||||
.alias COLUPF $08
|
||||
.alias COLUBK $09
|
||||
.alias PF0 $0D
|
||||
.alias PF1 $0E
|
||||
.alias PF2 $0F
|
||||
.alias SWCHA $280
|
||||
.alias INTIM $284
|
||||
.alias TIM64T $296
|
||||
.alias CTRLPF $0A
|
||||
.alias COLUP0 $06
|
||||
.alias COLUP1 $07
|
||||
.alias GP0 $1B
|
||||
.alias GP1 $1C
|
||||
.alias HMOVE $2a
|
||||
.alias RESP0 $10
|
||||
.alias RESP1 $11
|
||||
|
||||
;
|
||||
; Cartridge ROM occupies the top 4K of memory ($F000-$FFFF).
|
||||
; Thus, typically, the program will occupy all that space too.
|
||||
;
|
||||
; Zero-page RAM we can use with impunity starts at $80 and goes
|
||||
; upward (at least until $99, but probably further.)
|
||||
;
|
||||
|
||||
.alias colour $80
|
||||
.alias luminosity $81
|
||||
.alias joystick_delay $82
|
||||
|
||||
.org $F000
|
||||
|
||||
;
|
||||
; Standard prelude for Atari 2600 cartridge code.
|
||||
;
|
||||
; Get various parts of the machine into a known state:
|
||||
;
|
||||
; - Disable interrupts
|
||||
; - Clear the Decimal flag
|
||||
; - Initialize the Stack Pointer
|
||||
; - Zero all bytes in Zero Page memory
|
||||
;
|
||||
|
||||
start:
|
||||
sei
|
||||
cld
|
||||
ldx #$FF
|
||||
txs
|
||||
lda #$00
|
||||
|
||||
zero_loop:
|
||||
sta $00, x
|
||||
dex
|
||||
bne zero_loop
|
||||
|
||||
; and fall through to...
|
||||
|
||||
;
|
||||
; Initialization.
|
||||
;
|
||||
; - Clear the Playfield Control register.
|
||||
; - Set the player (sprite) colour to light green (write to COLUP0.)
|
||||
; - Set the player (sprite) size/repetion to normal (write to NUSIZ0.)
|
||||
;
|
||||
|
||||
lda #$00
|
||||
sta CTRLPF
|
||||
lda #$0c
|
||||
sta colour
|
||||
lda #$0a
|
||||
sta luminosity
|
||||
lda #$00
|
||||
sta NUSIZ0
|
||||
|
||||
; and fall through to...
|
||||
|
||||
;
|
||||
; Main loop.
|
||||
;
|
||||
; A typical main loop consists of:
|
||||
; - Waiting for the frame to start (vertical blank period)
|
||||
; - Displaying stuff on the screen (the _display kernel_)
|
||||
; - Doing any processing you like (reading joysticks, updating program state,
|
||||
; etc.), as long as you get it all done before the next frame starts!
|
||||
;
|
||||
|
||||
main:
|
||||
jsr vertical_blank
|
||||
jsr display_frame
|
||||
jsr read_joystick
|
||||
jmp main
|
||||
|
||||
;
|
||||
; Vertical blank routine.
|
||||
;
|
||||
; In brief: wait until it is time for the next frame of video.
|
||||
; TODO: describe this in more detail.
|
||||
;
|
||||
|
||||
vertical_blank:
|
||||
ldx #$00
|
||||
lda #$02
|
||||
sta WSYNC
|
||||
sta WSYNC
|
||||
sta WSYNC
|
||||
sta VSYNC
|
||||
sta WSYNC
|
||||
sta WSYNC
|
||||
lda #$2C
|
||||
sta TIM64T
|
||||
lda #$00
|
||||
sta WSYNC
|
||||
sta VSYNC
|
||||
rts
|
||||
|
||||
;
|
||||
; Display kernal.
|
||||
;
|
||||
; First, wait until it's time to display the frame.
|
||||
;
|
||||
|
||||
.scope
|
||||
display_frame:
|
||||
lda INTIM
|
||||
bne display_frame
|
||||
|
||||
;
|
||||
; (After that loop finishes, we know the accumulator must contain 0.)
|
||||
; Wait for the next scanline, zero HMOVE (for some reason; TODO discover
|
||||
; this), then turn on the screen.
|
||||
;
|
||||
|
||||
sta WSYNC
|
||||
sta HMOVE
|
||||
sta VBLANK
|
||||
|
||||
;
|
||||
; Actual work in the display kernal is done here.
|
||||
;
|
||||
; This is a pathological approach to writing a display kernal.
|
||||
; This wouldn't be how you'd do things in a game. So be it.
|
||||
; One day I may improve it. For now, be happy that it displays
|
||||
; anything at all!
|
||||
;
|
||||
|
||||
;
|
||||
; Wait for $3f (plus one?) scan lines to pass, by waiting for
|
||||
; WSYNC that many times.
|
||||
;
|
||||
|
||||
ldx #$3F
|
||||
_wsync_loop:
|
||||
sta WSYNC
|
||||
dex
|
||||
bpl _wsync_loop
|
||||
sta WSYNC
|
||||
|
||||
;
|
||||
; Delay while the raster scans across the screen. The more
|
||||
; we delay here, the more to the right the player will be when
|
||||
; we draw it.
|
||||
;
|
||||
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
|
||||
;
|
||||
; OK, *now* display the player.
|
||||
;
|
||||
|
||||
sta RESP0
|
||||
|
||||
;
|
||||
; Loop over the rows of the sprite data, drawing each to the screen
|
||||
; over four scan lines.
|
||||
;
|
||||
; TODO understand this better and describe it!
|
||||
;
|
||||
|
||||
ldy #$07
|
||||
_image_loop:
|
||||
lda image_data, y
|
||||
sta GP0
|
||||
|
||||
sta WSYNC
|
||||
sta WSYNC
|
||||
sta WSYNC
|
||||
sta WSYNC
|
||||
dey
|
||||
bpl _image_loop
|
||||
|
||||
lda #$00
|
||||
sta GP0
|
||||
|
||||
;
|
||||
; Turn off screen display and clear display registers.
|
||||
;
|
||||
|
||||
lda #$02
|
||||
sta WSYNC
|
||||
sta VBLANK
|
||||
lda #$00
|
||||
sta PF0
|
||||
sta PF1
|
||||
sta PF2
|
||||
sta COLUPF
|
||||
sta COLUBK
|
||||
|
||||
rts
|
||||
.scend
|
||||
|
||||
|
||||
;
|
||||
; Read the joystick and use it to modify the colour and luminosity
|
||||
; of the player.
|
||||
;
|
||||
|
||||
.scope
|
||||
read_joystick:
|
||||
lda joystick_delay
|
||||
beq _continue
|
||||
|
||||
dec joystick_delay
|
||||
rts
|
||||
|
||||
_continue:
|
||||
lda SWCHA
|
||||
and #$f0
|
||||
cmp #$e0
|
||||
beq _up
|
||||
cmp #$d0
|
||||
beq _down
|
||||
cmp #$b0
|
||||
beq _left
|
||||
cmp #$70
|
||||
beq _right
|
||||
jmp _tail
|
||||
|
||||
_up:
|
||||
inc luminosity
|
||||
jmp _tail
|
||||
_down:
|
||||
dec luminosity
|
||||
jmp _tail
|
||||
_left:
|
||||
dec colour
|
||||
jmp _tail
|
||||
_right:
|
||||
inc colour
|
||||
;jmp _tail
|
||||
|
||||
_tail:
|
||||
lda colour
|
||||
and #$0f
|
||||
sta colour
|
||||
|
||||
lda luminosity
|
||||
and #$0f
|
||||
sta luminosity
|
||||
|
||||
lda colour
|
||||
clc
|
||||
rol
|
||||
rol
|
||||
rol
|
||||
rol
|
||||
ora luminosity
|
||||
sta COLUP0
|
||||
|
||||
lda #$06
|
||||
sta joystick_delay
|
||||
|
||||
rts
|
||||
.scend
|
||||
|
||||
;
|
||||
; Player (sprite) data.
|
||||
;
|
||||
; Because we loop over these bytes with the Y register counting *down*,
|
||||
; this image is stored "upside-down".
|
||||
;
|
||||
|
||||
image_data:
|
||||
.byte %01111110
|
||||
.byte %10000001
|
||||
.byte %10011001
|
||||
.byte %10100101
|
||||
.byte %10000001
|
||||
.byte %10100101
|
||||
.byte %10000001
|
||||
.byte %01111110
|
||||
|
||||
;
|
||||
; Standard postlude for Atari 2600 cartridge code.
|
||||
; Give BRK and boot vectors that point to the start of the code.
|
||||
;
|
||||
|
||||
.advance $FFFC
|
||||
.word start
|
||||
.word start
|
10
eg/atari2600/build.sh
Executable file
10
eg/atari2600/build.sh
Executable file
@ -0,0 +1,10 @@
|
||||
#!/bin/sh
|
||||
|
||||
sixtypical --prelude=atari2600 smiley.60p > smiley-60p.bin
|
||||
if [ "x$COMPARE" != "x" ]; then
|
||||
ophis smiley.oph -o smiley.bin
|
||||
dcc6502 -o 0xf000 -m 200 smiley.bin > smiley.bin.disasm.txt
|
||||
dcc6502 -o 0xf000 -m 200 smiley-60p.bin > smiley-60p.bin.disasm.txt
|
||||
paste smiley.bin.disasm.txt smiley-60p.bin.disasm.txt | pr -t -e24
|
||||
#diff -ru smiley.bin.disasm.txt smiley-60p.bin.disasm.txt
|
||||
fi
|
184
eg/atari2600/smiley.60p
Normal file
184
eg/atari2600/smiley.60p
Normal file
@ -0,0 +1,184 @@
|
||||
// smiley.60p - SixtyPical translation of smiley.oph (2018),
|
||||
// which is itself a stripped-down version of atari-2600-example.oph
|
||||
|
||||
byte VSYNC @ $00
|
||||
byte VBLANK @ $01
|
||||
byte WSYNC @ $02
|
||||
byte NUSIZ0 @ $04
|
||||
byte NUSIZ1 @ $05
|
||||
byte COLUPF @ $08
|
||||
byte COLUBK @ $09
|
||||
byte PF0 @ $0D
|
||||
byte PF1 @ $0E
|
||||
byte PF2 @ $0F
|
||||
byte SWCHA @ $280
|
||||
byte INTIM @ $284
|
||||
byte TIM64T @ $296
|
||||
byte CTRLPF @ $0A
|
||||
byte COLUP0 @ $06
|
||||
byte COLUP1 @ $07
|
||||
byte GP0 @ $1B
|
||||
byte GP1 @ $1C
|
||||
byte HMOVE @ $2a
|
||||
byte RESP0 @ $10
|
||||
byte RESP1 @ $11
|
||||
|
||||
byte colour @ $80
|
||||
byte luminosity @ $81
|
||||
byte joystick_delay @ $82
|
||||
|
||||
byte table[8] image_data : 126, 129, 153, 165, 129, 165, 129, 126
|
||||
// %01111110
|
||||
// %10000001
|
||||
// %10011001
|
||||
// %10100101
|
||||
// %10000001
|
||||
// %10100101
|
||||
// %10000001
|
||||
// %01111110
|
||||
|
||||
|
||||
define vertical_blank routine
|
||||
outputs VSYNC, WSYNC, TIM64T
|
||||
trashes a, x, z, n
|
||||
{
|
||||
ld x, $00
|
||||
ld a, $02
|
||||
st a, WSYNC
|
||||
st a, WSYNC
|
||||
st a, WSYNC
|
||||
st a, VSYNC
|
||||
st a, WSYNC
|
||||
st a, WSYNC
|
||||
ld a, $2C
|
||||
st a, TIM64T
|
||||
ld a, $00
|
||||
st a, WSYNC
|
||||
st a, VSYNC
|
||||
}
|
||||
|
||||
define display_frame routine
|
||||
inputs INTIM, image_data
|
||||
outputs WSYNC, HMOVE, VBLANK, RESP0, GP0, PF0, PF1, PF2, COLUPF, COLUBK
|
||||
trashes a, x, y, z, n
|
||||
{
|
||||
repeat {
|
||||
ld a, INTIM
|
||||
} until z
|
||||
|
||||
//; (After that loop finishes, we know the accumulator must contain 0.)
|
||||
|
||||
st a, WSYNC
|
||||
st a, HMOVE
|
||||
st a, VBLANK
|
||||
|
||||
//;
|
||||
//; Wait for $3f (plus one?) scan lines to pass, by waiting for
|
||||
//; WSYNC that many times.
|
||||
//;
|
||||
|
||||
ld x, $3F
|
||||
repeat {
|
||||
st a, WSYNC
|
||||
dec x
|
||||
} until n
|
||||
st a, WSYNC
|
||||
|
||||
//;
|
||||
//; Delay while the raster scans across the screen. The more
|
||||
//; we delay here, the more to the right the player will be when
|
||||
//; we draw it.
|
||||
//;
|
||||
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
|
||||
//;
|
||||
//; OK, *now* display the player.
|
||||
//;
|
||||
|
||||
st a, RESP0
|
||||
|
||||
//;
|
||||
//; Loop over the rows of the sprite data, drawing each to the screen
|
||||
//; over four scan lines.
|
||||
//;
|
||||
//; TODO understand this better and describe it!
|
||||
//;
|
||||
|
||||
ld y, $07
|
||||
for y down to 0 {
|
||||
ld a, image_data + y
|
||||
st a, GP0
|
||||
|
||||
st a, WSYNC
|
||||
st a, WSYNC
|
||||
st a, WSYNC
|
||||
st a, WSYNC
|
||||
} // FIXME original was "dec y; bpl _image_loop"
|
||||
|
||||
ld a, $00
|
||||
st a, GP0
|
||||
|
||||
//;
|
||||
//; Turn off screen display and clear display registers.
|
||||
//;
|
||||
|
||||
ld a, $02
|
||||
st a, WSYNC
|
||||
st a, VBLANK
|
||||
ld a, $00
|
||||
st a, PF0
|
||||
st a, PF1
|
||||
st a, PF2
|
||||
st a, COLUPF
|
||||
st a, COLUBK
|
||||
}
|
||||
|
||||
define colourize_player routine
|
||||
inputs colour, luminosity
|
||||
outputs COLUP0
|
||||
trashes a, z, c, n
|
||||
{
|
||||
ld a, colour
|
||||
st off, c
|
||||
shl a
|
||||
shl a
|
||||
shl a
|
||||
shl a
|
||||
or a, luminosity
|
||||
st a, COLUP0
|
||||
}
|
||||
|
||||
define main routine
|
||||
inputs image_data, INTIM
|
||||
outputs CTRLPF, colour, luminosity, NUSIZ0, VSYNC, WSYNC, TIM64T, HMOVE, VBLANK, RESP0, GP0, PF0, PF1, PF2, COLUPF, COLUBK, COLUP0
|
||||
trashes a, x, y, z, c, n
|
||||
{
|
||||
ld a, $00
|
||||
st a, CTRLPF
|
||||
ld a, $0c
|
||||
st a, colour
|
||||
ld a, $0a
|
||||
st a, luminosity
|
||||
ld a, $00
|
||||
st a, NUSIZ0
|
||||
repeat {
|
||||
call vertical_blank
|
||||
call display_frame
|
||||
call colourize_player
|
||||
} forever
|
||||
}
|
285
eg/atari2600/smiley.oph
Normal file
285
eg/atari2600/smiley.oph
Normal file
@ -0,0 +1,285 @@
|
||||
;
|
||||
; smiley.oph (2018)
|
||||
; stripped-down version of atari-2600-example.oph (2012)
|
||||
;
|
||||
; This work is in the public domain. See the file UNLICENSE for more info.
|
||||
;
|
||||
; to build and run in Stella:
|
||||
; ophis smiley.oph -o smiley.bin
|
||||
; stella smiley.bin
|
||||
|
||||
;
|
||||
; Useful system addresses (TODO: briefly describe each of these.)
|
||||
;
|
||||
|
||||
.alias VSYNC $00
|
||||
.alias VBLANK $01
|
||||
.alias WSYNC $02
|
||||
.alias NUSIZ0 $04
|
||||
.alias NUSIZ1 $05
|
||||
.alias COLUPF $08
|
||||
.alias COLUBK $09
|
||||
.alias PF0 $0D
|
||||
.alias PF1 $0E
|
||||
.alias PF2 $0F
|
||||
.alias SWCHA $280
|
||||
.alias INTIM $284
|
||||
.alias TIM64T $296
|
||||
.alias CTRLPF $0A
|
||||
.alias COLUP0 $06
|
||||
.alias COLUP1 $07
|
||||
.alias GP0 $1B
|
||||
.alias GP1 $1C
|
||||
.alias HMOVE $2a
|
||||
.alias RESP0 $10
|
||||
.alias RESP1 $11
|
||||
|
||||
;
|
||||
; Cartridge ROM occupies the top 4K of memory ($F000-$FFFF).
|
||||
; Thus, typically, the program will occupy all that space too.
|
||||
;
|
||||
; Zero-page RAM we can use with impunity starts at $80 and goes
|
||||
; upward (at least until $99, but probably further.)
|
||||
;
|
||||
|
||||
.alias colour $80
|
||||
.alias luminosity $81
|
||||
.alias joystick_delay $82
|
||||
|
||||
.org $F000
|
||||
|
||||
;
|
||||
; Standard prelude for Atari 2600 cartridge code.
|
||||
;
|
||||
; Get various parts of the machine into a known state:
|
||||
;
|
||||
; - Disable interrupts
|
||||
; - Clear the Decimal flag
|
||||
; - Initialize the Stack Pointer
|
||||
; - Zero all bytes in Zero Page memory
|
||||
;
|
||||
|
||||
start:
|
||||
sei
|
||||
cld
|
||||
ldx #$FF
|
||||
txs
|
||||
lda #$00
|
||||
|
||||
zero_loop:
|
||||
sta $00, x
|
||||
dex
|
||||
bne zero_loop
|
||||
|
||||
; and fall through to...
|
||||
|
||||
;
|
||||
; Initialization.
|
||||
;
|
||||
; - Clear the Playfield Control register.
|
||||
; - Set the player (sprite) colour to light green (write to COLUP0.)
|
||||
; - Set the player (sprite) size/repetion to normal (write to NUSIZ0.)
|
||||
;
|
||||
|
||||
lda #$00
|
||||
sta CTRLPF
|
||||
lda #$0c
|
||||
sta colour
|
||||
lda #$0a
|
||||
sta luminosity
|
||||
lda #$00
|
||||
sta NUSIZ0
|
||||
|
||||
; and fall through to...
|
||||
|
||||
;
|
||||
; Main loop.
|
||||
;
|
||||
; A typical main loop consists of:
|
||||
; - Waiting for the frame to start (vertical blank period)
|
||||
; - Displaying stuff on the screen (the _display kernel_)
|
||||
; - Doing any processing you like (reading joysticks, updating program state,
|
||||
; etc.), as long as you get it all done before the next frame starts!
|
||||
;
|
||||
|
||||
main:
|
||||
jsr vertical_blank
|
||||
jsr display_frame
|
||||
jsr colourize_player
|
||||
jmp main
|
||||
rts ; NOTE just to pad out to match the SixtyPical version
|
||||
|
||||
;
|
||||
; Vertical blank routine.
|
||||
;
|
||||
; In brief: wait until it is time for the next frame of video.
|
||||
; TODO: describe this in more detail.
|
||||
;
|
||||
|
||||
vertical_blank:
|
||||
ldx #$00
|
||||
lda #$02
|
||||
sta WSYNC
|
||||
sta WSYNC
|
||||
sta WSYNC
|
||||
sta VSYNC
|
||||
sta WSYNC
|
||||
sta WSYNC
|
||||
lda #$2C
|
||||
sta TIM64T
|
||||
lda #$00
|
||||
sta WSYNC
|
||||
sta VSYNC
|
||||
rts
|
||||
|
||||
;
|
||||
; Display kernal.
|
||||
;
|
||||
; First, wait until it's time to display the frame.
|
||||
;
|
||||
|
||||
.scope
|
||||
display_frame:
|
||||
lda INTIM
|
||||
bne display_frame
|
||||
|
||||
;
|
||||
; (After that loop finishes, we know the accumulator must contain 0.)
|
||||
; Wait for the next scanline, zero HMOVE (for some reason; TODO discover
|
||||
; this), then turn on the screen.
|
||||
;
|
||||
|
||||
sta WSYNC
|
||||
sta HMOVE
|
||||
sta VBLANK
|
||||
|
||||
;
|
||||
; Actual work in the display kernal is done here.
|
||||
;
|
||||
; This is a pathological approach to writing a display kernal.
|
||||
; This wouldn't be how you'd do things in a game. So be it.
|
||||
; One day I may improve it. For now, be happy that it displays
|
||||
; anything at all!
|
||||
;
|
||||
|
||||
;
|
||||
; Wait for $3f (plus one?) scan lines to pass, by waiting for
|
||||
; WSYNC that many times.
|
||||
;
|
||||
|
||||
ldx #$3F
|
||||
_wsync_loop:
|
||||
sta WSYNC
|
||||
dex
|
||||
bpl _wsync_loop
|
||||
sta WSYNC
|
||||
|
||||
;
|
||||
; Delay while the raster scans across the screen. The more
|
||||
; we delay here, the more to the right the player will be when
|
||||
; we draw it.
|
||||
;
|
||||
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
|
||||
;
|
||||
; OK, *now* display the player.
|
||||
;
|
||||
|
||||
sta RESP0
|
||||
|
||||
;
|
||||
; Loop over the rows of the sprite data, drawing each to the screen
|
||||
; over four scan lines.
|
||||
;
|
||||
; TODO understand this better and describe it!
|
||||
;
|
||||
|
||||
ldy #$07
|
||||
_image_loop:
|
||||
lda image_data, y
|
||||
sta GP0
|
||||
|
||||
sta WSYNC
|
||||
sta WSYNC
|
||||
sta WSYNC
|
||||
sta WSYNC
|
||||
dey
|
||||
bpl _image_loop
|
||||
|
||||
lda #$00
|
||||
sta GP0
|
||||
|
||||
;
|
||||
; Turn off screen display and clear display registers.
|
||||
;
|
||||
|
||||
lda #$02
|
||||
sta WSYNC
|
||||
sta VBLANK
|
||||
lda #$00
|
||||
sta PF0
|
||||
sta PF1
|
||||
sta PF2
|
||||
sta COLUPF
|
||||
sta COLUBK
|
||||
|
||||
rts
|
||||
.scend
|
||||
|
||||
;
|
||||
; Modify the colour and luminosity of the player.
|
||||
;
|
||||
|
||||
.scope
|
||||
colourize_player:
|
||||
lda colour
|
||||
clc
|
||||
rol
|
||||
rol
|
||||
rol
|
||||
rol
|
||||
ora luminosity
|
||||
sta COLUP0
|
||||
rts
|
||||
.scend
|
||||
|
||||
;
|
||||
; Player (sprite) data.
|
||||
;
|
||||
; Because we loop over these bytes with the Y register counting *down*,
|
||||
; this image is stored "upside-down".
|
||||
;
|
||||
|
||||
image_data:
|
||||
.byte %01111110
|
||||
.byte %10000001
|
||||
.byte %10011001
|
||||
.byte %10100101
|
||||
.byte %10000001
|
||||
.byte %10100101
|
||||
.byte %10000001
|
||||
.byte %01111110
|
||||
|
||||
;
|
||||
; Standard postlude for Atari 2600 cartridge code.
|
||||
; Give BRK and boot vectors that point to the start of the code.
|
||||
;
|
||||
|
||||
.advance $FFFC
|
||||
.word start
|
||||
.word start
|
Binary file not shown.
Binary file not shown.
21
eg/rudiments/vector-inc.60p
Normal file
21
eg/rudiments/vector-inc.60p
Normal file
@ -0,0 +1,21 @@
|
||||
// This will not compile on its own, because there is no `main`.
|
||||
// But this and `vector-main.60p` together will compile.
|
||||
|
||||
routine chrout
|
||||
inputs a
|
||||
trashes a
|
||||
@ 65490
|
||||
|
||||
routine printa
|
||||
trashes a, z, n
|
||||
{
|
||||
ld a, 65
|
||||
call chrout
|
||||
}
|
||||
|
||||
routine printb
|
||||
trashes a, z, n
|
||||
{
|
||||
ld a, 66
|
||||
call chrout
|
||||
}
|
22
eg/rudiments/vector-main.60p
Normal file
22
eg/rudiments/vector-main.60p
Normal file
@ -0,0 +1,22 @@
|
||||
// This will not compile on its own, because `printa` and `printb` are not defined.
|
||||
// But `vector-inc.60p` and this together will compile.
|
||||
|
||||
vector routine
|
||||
trashes a, z, n
|
||||
print
|
||||
|
||||
// routine printb
|
||||
// trashes a, z, n
|
||||
// {
|
||||
// ld a, 66
|
||||
// call chrout
|
||||
// }
|
||||
|
||||
routine main
|
||||
trashes print, a, z, n
|
||||
{
|
||||
copy printa, print
|
||||
call print
|
||||
copy printb, print
|
||||
call print
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
#!/bin/sh
|
||||
|
||||
usage="Usage: loadngo.sh (c64|vic20) [--dry-run] <source.60p>"
|
||||
usage="Usage: loadngo.sh (c64|vic20|atari2600) [--dry-run] <source.60p>"
|
||||
|
||||
arch="$1"
|
||||
shift 1
|
||||
@ -18,6 +18,9 @@ elif [ "X$arch" = "Xvic20" ]; then
|
||||
else
|
||||
emu="xvic"
|
||||
fi
|
||||
elif [ "X$arch" = "Xatari2600" ]; then
|
||||
prelude='atari2600'
|
||||
emu='stella'
|
||||
else
|
||||
echo $usage && exit 1
|
||||
fi
|
||||
|
@ -101,7 +101,7 @@ class Context(object):
|
||||
self._touched = set()
|
||||
self._range = dict()
|
||||
self._writeable = set()
|
||||
self._has_encountered_goto = False
|
||||
self._gotos_encountered = set()
|
||||
|
||||
for ref in inputs:
|
||||
if ref.is_constant():
|
||||
@ -273,11 +273,11 @@ class Context(object):
|
||||
for ref in refs:
|
||||
self._writeable.add(ref)
|
||||
|
||||
def set_encountered_goto(self):
|
||||
self._has_encountered_goto = True
|
||||
def encounter_gotos(self, gotos):
|
||||
self._gotos_encountered |= gotos
|
||||
|
||||
def has_encountered_goto(self):
|
||||
return self._has_encountered_goto
|
||||
def encountered_gotos(self):
|
||||
return self._gotos_encountered
|
||||
|
||||
|
||||
class Analyzer(object):
|
||||
@ -311,7 +311,8 @@ class Analyzer(object):
|
||||
assert isinstance(program, Program)
|
||||
self.routines = {r.location: r for r in program.routines}
|
||||
for routine in program.routines:
|
||||
self.analyze_routine(routine)
|
||||
context = self.analyze_routine(routine)
|
||||
routine.encountered_gotos = list(context.encountered_gotos()) if context else []
|
||||
|
||||
def analyze_routine(self, routine):
|
||||
assert isinstance(routine, Routine)
|
||||
@ -346,13 +347,14 @@ class Analyzer(object):
|
||||
if ref in type_.outputs:
|
||||
raise UnmeaningfulOutputError(routine, ref.name)
|
||||
|
||||
if not context.has_encountered_goto():
|
||||
if not context.encountered_gotos():
|
||||
for ref in type_.outputs:
|
||||
context.assert_meaningful(ref, exception_class=UnmeaningfulOutputError)
|
||||
for ref in context.each_touched():
|
||||
if ref not in type_.outputs and ref not in type_.trashes and not routine_has_static(routine, ref):
|
||||
raise ForbiddenWriteError(routine, ref.name)
|
||||
self.current_routine = None
|
||||
return context
|
||||
|
||||
def analyze_block(self, block, context):
|
||||
assert isinstance(block, Block)
|
||||
@ -379,7 +381,7 @@ class Analyzer(object):
|
||||
dest = instr.dest
|
||||
src = instr.src
|
||||
|
||||
if context.has_encountered_goto():
|
||||
if context.encountered_gotos():
|
||||
raise IllegalJumpError(instr, instr)
|
||||
|
||||
if opcode == 'ld':
|
||||
@ -595,10 +597,12 @@ class Analyzer(object):
|
||||
self.assert_affected_within('outputs', type_, current_type)
|
||||
self.assert_affected_within('trashes', type_, current_type)
|
||||
|
||||
context.set_encountered_goto()
|
||||
context.encounter_gotos(set([instr.location]))
|
||||
elif opcode == 'trash':
|
||||
context.set_touched(instr.dest)
|
||||
context.set_unmeaningful(instr.dest)
|
||||
elif opcode == 'nop':
|
||||
pass
|
||||
else:
|
||||
raise NotImplementedError(opcode)
|
||||
|
||||
@ -636,8 +640,7 @@ class Analyzer(object):
|
||||
context._touched = set(context1._touched) | set(context2._touched)
|
||||
context.set_meaningful(*list(outgoing_meaningful))
|
||||
context._writeable = set(context1._writeable) | set(context2._writeable)
|
||||
if context1.has_encountered_goto() or context2.has_encountered_goto():
|
||||
context.set_encountered_goto()
|
||||
context.encounter_gotos(context1.encountered_gotos() | context2.encountered_gotos())
|
||||
|
||||
for ref in outgoing_trashes:
|
||||
context.set_touched(ref)
|
||||
|
@ -15,9 +15,10 @@ from sixtypical.gen6502 import (
|
||||
CLC, SEC, ADC, SBC, ROL, ROR,
|
||||
INC, INX, INY, DEC, DEX, DEY,
|
||||
CMP, CPX, CPY, AND, ORA, EOR,
|
||||
BCC, BCS, BNE, BEQ,
|
||||
BCC, BCS, BNE, BEQ, BPL, BMI,
|
||||
JMP, JSR, RTS,
|
||||
SEI, CLI,
|
||||
NOP,
|
||||
)
|
||||
|
||||
|
||||
@ -66,9 +67,15 @@ class Compiler(object):
|
||||
return static_label
|
||||
return self.labels[name]
|
||||
|
||||
def absolute_or_zero_page(self, label):
|
||||
if label.addr is not None and label.addr < 256:
|
||||
return ZeroPage(label)
|
||||
else:
|
||||
return Absolute(label)
|
||||
|
||||
# visitor methods
|
||||
|
||||
def compile_program(self, program):
|
||||
def compile_program(self, program, compilation_roster=None):
|
||||
assert isinstance(program, Program)
|
||||
|
||||
defn_labels = []
|
||||
@ -95,10 +102,14 @@ class Compiler(object):
|
||||
defn_labels.append((defn, label))
|
||||
self.routine_statics[routine.name] = static_labels
|
||||
|
||||
self.compile_routine(self.routines['main'])
|
||||
for routine in program.routines:
|
||||
if routine.name != 'main':
|
||||
self.compile_routine(routine)
|
||||
if compilation_roster is None:
|
||||
compilation_roster = [['main']] + [[routine.name] for routine in program.routines if routine.name != 'main']
|
||||
|
||||
for roster_row in compilation_roster:
|
||||
for routine_name in roster_row[0:-1]:
|
||||
self.compile_routine(self.routines[routine_name], skip_final_goto=True)
|
||||
routine_name = roster_row[-1]
|
||||
self.compile_routine(self.routines[routine_name])
|
||||
|
||||
for location, label in self.trampolines.iteritems():
|
||||
self.emitter.resolve_label(label)
|
||||
@ -115,7 +126,9 @@ class Compiler(object):
|
||||
elif type_ == TYPE_WORD:
|
||||
initial_data = Word(defn.initial)
|
||||
elif TableType.is_a_table_type(type_, TYPE_BYTE):
|
||||
initial_data = Table(defn.initial, type_.size)
|
||||
initial_data = Table([Byte(i) for i in defn.initial], type_.size)
|
||||
elif TableType.is_a_table_type(type_, TYPE_WORD):
|
||||
initial_data = Table([Word(i) for i in defn.initial], type_.size)
|
||||
else:
|
||||
raise NotImplementedError(type_)
|
||||
label.set_length(initial_data.size())
|
||||
@ -127,14 +140,18 @@ class Compiler(object):
|
||||
if defn.initial is None and defn.addr is None:
|
||||
self.emitter.resolve_bss_label(label)
|
||||
|
||||
def compile_routine(self, routine):
|
||||
def compile_routine(self, routine, skip_final_goto=False):
|
||||
self.current_routine = routine
|
||||
self.skip_final_goto = skip_final_goto
|
||||
self.final_goto_seen = False
|
||||
assert isinstance(routine, Routine)
|
||||
if routine.block:
|
||||
self.emitter.resolve_label(self.get_label(routine.name))
|
||||
self.compile_block(routine.block)
|
||||
self.emitter.emit(RTS())
|
||||
if not self.final_goto_seen:
|
||||
self.emitter.emit(RTS())
|
||||
self.current_routine = None
|
||||
self.skip_final_goto = False
|
||||
|
||||
def compile_block(self, block):
|
||||
assert isinstance(block, Block)
|
||||
@ -176,7 +193,7 @@ class Compiler(object):
|
||||
elif isinstance(src, IndirectRef) and isinstance(src.ref.type, PointerType):
|
||||
self.emitter.emit(LDA(IndirectY(self.get_label(src.ref.name))))
|
||||
else:
|
||||
self.emitter.emit(LDA(Absolute(self.get_label(src.name))))
|
||||
self.emitter.emit(LDA(self.absolute_or_zero_page(self.get_label(src.name))))
|
||||
elif dest == REG_X:
|
||||
if src == REG_A:
|
||||
self.emitter.emit(TAX())
|
||||
@ -185,7 +202,7 @@ class Compiler(object):
|
||||
elif isinstance(src, IndexedRef) and src.index == REG_Y:
|
||||
self.emitter.emit(LDX(AbsoluteY(self.get_label(src.ref.name))))
|
||||
else:
|
||||
self.emitter.emit(LDX(Absolute(self.get_label(src.name))))
|
||||
self.emitter.emit(LDX(self.absolute_or_zero_page(self.get_label(src.name))))
|
||||
elif dest == REG_Y:
|
||||
if src == REG_A:
|
||||
self.emitter.emit(TAY())
|
||||
@ -194,7 +211,7 @@ class Compiler(object):
|
||||
elif isinstance(src, IndexedRef) and src.index == REG_X:
|
||||
self.emitter.emit(LDY(AbsoluteX(self.get_label(src.ref.name))))
|
||||
else:
|
||||
self.emitter.emit(LDY(Absolute(self.get_label(src.name))))
|
||||
self.emitter.emit(LDY(self.absolute_or_zero_page(self.get_label(src.name))))
|
||||
else:
|
||||
raise UnsupportedOpcodeError(instr)
|
||||
elif opcode == 'st':
|
||||
@ -214,17 +231,15 @@ class Compiler(object):
|
||||
REG_X: AbsoluteX,
|
||||
REG_Y: AbsoluteY,
|
||||
}[dest.index]
|
||||
label = self.get_label(dest.ref.name)
|
||||
operand = mode_cls(self.get_label(dest.ref.name))
|
||||
elif isinstance(dest, IndirectRef) and isinstance(dest.ref.type, PointerType):
|
||||
mode_cls = IndirectY
|
||||
label = self.get_label(dest.ref.name)
|
||||
operand = IndirectY(self.get_label(dest.ref.name))
|
||||
else:
|
||||
mode_cls = Absolute
|
||||
label = self.get_label(dest.name)
|
||||
operand = self.absolute_or_zero_page(self.get_label(dest.name))
|
||||
|
||||
if op_cls is None or mode_cls is None:
|
||||
if op_cls is None:
|
||||
raise UnsupportedOpcodeError(instr)
|
||||
self.emitter.emit(op_cls(mode_cls(label)))
|
||||
self.emitter.emit(op_cls(operand))
|
||||
elif opcode == 'add':
|
||||
if dest == REG_A:
|
||||
if isinstance(src, ConstantRef):
|
||||
@ -342,18 +357,24 @@ class Compiler(object):
|
||||
else:
|
||||
raise NotImplementedError
|
||||
elif opcode == 'goto':
|
||||
location = instr.location
|
||||
label = self.get_label(instr.location.name)
|
||||
if isinstance(location.type, RoutineType):
|
||||
self.emitter.emit(JMP(Absolute(label)))
|
||||
elif isinstance(location.type, VectorType):
|
||||
self.emitter.emit(JMP(Indirect(label)))
|
||||
self.final_goto_seen = True
|
||||
if self.skip_final_goto:
|
||||
pass
|
||||
else:
|
||||
raise NotImplementedError
|
||||
location = instr.location
|
||||
label = self.get_label(instr.location.name)
|
||||
if isinstance(location.type, RoutineType):
|
||||
self.emitter.emit(JMP(Absolute(label)))
|
||||
elif isinstance(location.type, VectorType):
|
||||
self.emitter.emit(JMP(Indirect(label)))
|
||||
else:
|
||||
raise NotImplementedError
|
||||
elif opcode == 'copy':
|
||||
self.compile_copy(instr, instr.src, instr.dest)
|
||||
elif opcode == 'trash':
|
||||
pass
|
||||
elif opcode == 'nop':
|
||||
self.emitter.emit(NOP())
|
||||
else:
|
||||
raise NotImplementedError(opcode)
|
||||
|
||||
@ -509,10 +530,12 @@ class Compiler(object):
|
||||
False: {
|
||||
'c': BCC,
|
||||
'z': BNE,
|
||||
'n': BPL,
|
||||
},
|
||||
True: {
|
||||
'c': BCS,
|
||||
'z': BEQ,
|
||||
'n': BMI,
|
||||
},
|
||||
}[instr.inverted].get(instr.src.name)
|
||||
if cls is None:
|
||||
@ -539,10 +562,12 @@ class Compiler(object):
|
||||
False: {
|
||||
'c': BCC,
|
||||
'z': BNE,
|
||||
'n': BPL,
|
||||
},
|
||||
True: {
|
||||
'c': BCS,
|
||||
'z': BEQ,
|
||||
'n': BMI,
|
||||
},
|
||||
}[instr.inverted].get(instr.src.name)
|
||||
if cls is None:
|
||||
|
@ -13,6 +13,8 @@ class Emittable(object):
|
||||
|
||||
class Byte(Emittable):
|
||||
def __init__(self, value):
|
||||
if isinstance(value, basestring):
|
||||
value = ord(value)
|
||||
if value < -127 or value > 255:
|
||||
raise IndexError(value)
|
||||
if value < 0:
|
||||
@ -49,6 +51,7 @@ class Word(Emittable):
|
||||
|
||||
class Table(Emittable):
|
||||
def __init__(self, value, size):
|
||||
"""`value` should be an iterable of Emittables."""
|
||||
# TODO: range-checking
|
||||
self.value = value
|
||||
self._size = size
|
||||
@ -57,12 +60,10 @@ class Table(Emittable):
|
||||
return self._size
|
||||
|
||||
def serialize(self, addr=None):
|
||||
bytes = []
|
||||
for b in self.value:
|
||||
bytes.append(chr(ord(b)))
|
||||
while len(bytes) < self.size():
|
||||
bytes.append(chr(0))
|
||||
return ''.join(bytes)
|
||||
buf = ''.join([emittable.serialize() for emittable in self.value])
|
||||
while len(buf) < self.size():
|
||||
buf += chr(0)
|
||||
return buf
|
||||
|
||||
def __repr__(self):
|
||||
return "%s()" % (self.__class__.__name__)
|
||||
@ -186,3 +187,14 @@ class Emitter(object):
|
||||
advance the address for the next label, but don't emit anything."""
|
||||
self.resolve_label(label)
|
||||
self.addr += label.length
|
||||
|
||||
def size(self):
|
||||
return sum(emittable.size() for emittable in self.accum)
|
||||
|
||||
def pad_to_size(self, size):
|
||||
self_size = self.size()
|
||||
if self_size > size:
|
||||
raise IndexError("Emitter size {} exceeds pad size {}".format(self_size, size))
|
||||
num_bytes = size - self_size
|
||||
if num_bytes > 0:
|
||||
self.accum.extend([Byte(0)] * num_bytes)
|
||||
|
52
src/sixtypical/fallthru.py
Normal file
52
src/sixtypical/fallthru.py
Normal file
@ -0,0 +1,52 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
from copy import copy
|
||||
|
||||
from sixtypical.model import RoutineType
|
||||
|
||||
|
||||
class FallthruAnalyzer(object):
|
||||
|
||||
def __init__(self, debug=False):
|
||||
self.debug = debug
|
||||
|
||||
def analyze_program(self, program):
|
||||
self.program = program
|
||||
|
||||
self.fallthru_map = {}
|
||||
for routine in program.routines:
|
||||
encountered_gotos = list(routine.encountered_gotos)
|
||||
if len(encountered_gotos) == 1 and isinstance(encountered_gotos[0].type, RoutineType):
|
||||
self.fallthru_map[routine.name] = encountered_gotos[0].name
|
||||
else:
|
||||
self.fallthru_map[routine.name] = None
|
||||
|
||||
def find_chain(self, routine_name, available):
|
||||
chain = [routine_name]
|
||||
seen = set(chain)
|
||||
while True:
|
||||
next = self.fallthru_map.get(routine_name)
|
||||
if next is None or next in seen or next not in available:
|
||||
return chain
|
||||
seen.add(next)
|
||||
chain.append(next)
|
||||
routine_name = next
|
||||
|
||||
def serialize(self):
|
||||
pending_routines = copy(self.fallthru_map)
|
||||
roster = []
|
||||
|
||||
main_chain = self.find_chain('main', pending_routines)
|
||||
roster.append(main_chain)
|
||||
for k in main_chain:
|
||||
del pending_routines[k]
|
||||
|
||||
while pending_routines:
|
||||
chains = [self.find_chain(k, pending_routines) for k in pending_routines.keys()]
|
||||
chains.sort(key=len, reverse=True)
|
||||
c = chains[0]
|
||||
roster.append(c)
|
||||
for k in c:
|
||||
del pending_routines[k]
|
||||
|
||||
return roster
|
@ -160,6 +160,18 @@ class BNE(Instruction):
|
||||
}
|
||||
|
||||
|
||||
class BPL(Instruction):
|
||||
opcodes = {
|
||||
Relative: 0x10,
|
||||
}
|
||||
|
||||
|
||||
class BMI(Instruction):
|
||||
opcodes = {
|
||||
Relative: 0x30,
|
||||
}
|
||||
|
||||
|
||||
class CLC(Instruction):
|
||||
opcodes = {
|
||||
Implied: 0x18
|
||||
@ -312,6 +324,12 @@ class RTS(Instruction):
|
||||
}
|
||||
|
||||
|
||||
class NOP(Instruction):
|
||||
opcodes = {
|
||||
Implied: 0xEA,
|
||||
}
|
||||
|
||||
|
||||
class SBC(Instruction):
|
||||
opcodes = {
|
||||
Immediate: 0xe9,
|
||||
|
@ -6,7 +6,7 @@ from sixtypical.model import (
|
||||
RoutineType, VectorType, TableType, BufferType, PointerType,
|
||||
LocationRef, ConstantRef, IndirectRef, IndexedRef, AddressRef,
|
||||
)
|
||||
from sixtypical.scanner import Scanner, SixtyPicalSyntaxError
|
||||
from sixtypical.scanner import Scanner
|
||||
|
||||
|
||||
class SymEntry(object):
|
||||
@ -18,30 +18,40 @@ class SymEntry(object):
|
||||
return "%s(%r, %r)" % (self.__class__.__name__, self.ast_node, self.model)
|
||||
|
||||
|
||||
class Parser(object):
|
||||
def __init__(self, text):
|
||||
self.scanner = Scanner(text)
|
||||
class ParsingContext(object):
|
||||
def __init__(self):
|
||||
self.symbols = {} # token -> SymEntry
|
||||
self.current_statics = {} # token -> SymEntry
|
||||
self.statics = {} # token -> SymEntry
|
||||
self.typedefs = {} # token -> Type AST
|
||||
self.consts = {} # token -> Loc
|
||||
|
||||
for token in ('a', 'x', 'y'):
|
||||
self.symbols[token] = SymEntry(None, LocationRef(TYPE_BYTE, token))
|
||||
for token in ('c', 'z', 'n', 'v'):
|
||||
self.symbols[token] = SymEntry(None, LocationRef(TYPE_BIT, token))
|
||||
self.backpatch_instrs = []
|
||||
|
||||
def syntax_error(self, msg):
|
||||
raise SixtyPicalSyntaxError(self.scanner.line_number, msg)
|
||||
def __str__(self):
|
||||
return "Symbols: {}\nStatics: {}\nTypedefs: {}\nConsts: {}".format(self.symbols, self.statics, self.typedefs, self.consts)
|
||||
|
||||
def soft_lookup(self, name):
|
||||
if name in self.current_statics:
|
||||
return self.current_statics[name].model
|
||||
def lookup(self, name):
|
||||
if name in self.statics:
|
||||
return self.statics[name].model
|
||||
if name in self.symbols:
|
||||
return self.symbols[name].model
|
||||
return None
|
||||
|
||||
|
||||
class Parser(object):
|
||||
def __init__(self, context, text, filename):
|
||||
self.context = context
|
||||
self.scanner = Scanner(text, filename)
|
||||
self.backpatch_instrs = []
|
||||
|
||||
def syntax_error(self, msg):
|
||||
self.scanner.syntax_error(msg)
|
||||
|
||||
def lookup(self, name):
|
||||
model = self.soft_lookup(name)
|
||||
model = self.context.lookup(name)
|
||||
if model is None:
|
||||
self.syntax_error('Undefined symbol "{}"'.format(name))
|
||||
return model
|
||||
@ -51,16 +61,19 @@ class Parser(object):
|
||||
def program(self):
|
||||
defns = []
|
||||
routines = []
|
||||
while self.scanner.on('typedef'):
|
||||
typedef = self.typedef()
|
||||
while self.scanner.on('typedef', 'const'):
|
||||
if self.scanner.on('typedef'):
|
||||
self.typedef()
|
||||
if self.scanner.on('const'):
|
||||
self.defn_const()
|
||||
typenames = ['byte', 'word', 'table', 'vector', 'buffer', 'pointer'] # 'routine',
|
||||
typenames.extend(self.typedefs.keys())
|
||||
typenames.extend(self.context.typedefs.keys())
|
||||
while self.scanner.on(*typenames):
|
||||
defn = self.defn()
|
||||
name = defn.name
|
||||
if name in self.symbols:
|
||||
if self.context.lookup(name):
|
||||
self.syntax_error('Symbol "%s" already declared' % name)
|
||||
self.symbols[name] = SymEntry(defn, defn.location)
|
||||
self.context.symbols[name] = SymEntry(defn, defn.location)
|
||||
defns.append(defn)
|
||||
while self.scanner.on('define', 'routine'):
|
||||
if self.scanner.consume('define'):
|
||||
@ -70,14 +83,14 @@ class Parser(object):
|
||||
else:
|
||||
routine = self.legacy_routine()
|
||||
name = routine.name
|
||||
if name in self.symbols:
|
||||
if self.context.lookup(name):
|
||||
self.syntax_error('Symbol "%s" already declared' % name)
|
||||
self.symbols[name] = SymEntry(routine, routine.location)
|
||||
self.context.symbols[name] = SymEntry(routine, routine.location)
|
||||
routines.append(routine)
|
||||
self.scanner.check_type('EOF')
|
||||
|
||||
# now backpatch the executable types.
|
||||
#for type_name, type_ in self.typedefs.iteritems():
|
||||
#for type_name, type_ in self.context.typedefs.iteritems():
|
||||
# type_.backpatch_constraint_labels(lambda w: self.lookup(w))
|
||||
for defn in defns:
|
||||
defn.location.type.backpatch_constraint_labels(lambda w: self.lookup(w))
|
||||
@ -86,18 +99,16 @@ class Parser(object):
|
||||
for instr in self.backpatch_instrs:
|
||||
if instr.opcode in ('call', 'goto'):
|
||||
name = instr.location
|
||||
if name not in self.symbols:
|
||||
self.syntax_error('Undefined routine "%s"' % name)
|
||||
if not isinstance(self.symbols[name].model.type, (RoutineType, VectorType)):
|
||||
model = self.lookup(name)
|
||||
if not isinstance(model.type, (RoutineType, VectorType)):
|
||||
self.syntax_error('Illegal call of non-executable "%s"' % name)
|
||||
instr.location = self.symbols[name].model
|
||||
instr.location = model
|
||||
if instr.opcode in ('copy',) and isinstance(instr.src, basestring):
|
||||
name = instr.src
|
||||
if name not in self.symbols:
|
||||
self.syntax_error('Undefined routine "%s"' % name)
|
||||
if not isinstance(self.symbols[name].model.type, (RoutineType, VectorType)):
|
||||
model = self.lookup(name)
|
||||
if not isinstance(model.type, (RoutineType, VectorType)):
|
||||
self.syntax_error('Illegal copy of non-executable "%s"' % name)
|
||||
instr.src = self.symbols[name].model
|
||||
instr.src = model
|
||||
|
||||
return Program(self.scanner.line_number, defns=defns, routines=routines)
|
||||
|
||||
@ -105,23 +116,37 @@ class Parser(object):
|
||||
self.scanner.expect('typedef')
|
||||
type_ = self.defn_type()
|
||||
name = self.defn_name()
|
||||
if name in self.typedefs:
|
||||
if name in self.context.typedefs:
|
||||
self.syntax_error('Type "%s" already declared' % name)
|
||||
self.typedefs[name] = type_
|
||||
self.context.typedefs[name] = type_
|
||||
return type_
|
||||
|
||||
def defn_const(self):
|
||||
self.scanner.expect('const')
|
||||
name = self.defn_name()
|
||||
if name in self.context.consts:
|
||||
self.syntax_error('Const "%s" already declared' % name)
|
||||
loc = self.const()
|
||||
self.context.consts[name] = loc
|
||||
return loc
|
||||
|
||||
def defn(self):
|
||||
type_ = self.defn_type()
|
||||
name = self.defn_name()
|
||||
|
||||
initial = None
|
||||
if self.scanner.consume(':'):
|
||||
if isinstance(type_, TableType) and self.scanner.on_type('string literal'):
|
||||
initial = self.scanner.token
|
||||
if isinstance(type_, TableType):
|
||||
if self.scanner.on_type('string literal'):
|
||||
initial = self.scanner.token
|
||||
self.scanner.scan()
|
||||
else:
|
||||
initial = []
|
||||
initial.append(self.const().value)
|
||||
while self.scanner.consume(','):
|
||||
initial.append(self.const().value)
|
||||
else:
|
||||
self.scanner.check_type('integer literal')
|
||||
initial = int(self.scanner.token)
|
||||
self.scanner.scan()
|
||||
initial = self.const().value
|
||||
|
||||
addr = None
|
||||
if self.scanner.consume('@'):
|
||||
@ -136,21 +161,31 @@ class Parser(object):
|
||||
|
||||
return Defn(self.scanner.line_number, name=name, addr=addr, initial=initial, location=location)
|
||||
|
||||
def literal_int(self):
|
||||
self.scanner.check_type('integer literal')
|
||||
c = int(self.scanner.token)
|
||||
self.scanner.scan()
|
||||
return c
|
||||
|
||||
def literal_int_const(self):
|
||||
value = self.literal_int()
|
||||
type_ = TYPE_WORD if value > 255 else TYPE_BYTE
|
||||
loc = ConstantRef(type_, value)
|
||||
return loc
|
||||
def const(self):
|
||||
if self.scanner.token in ('on', 'off'):
|
||||
loc = ConstantRef(TYPE_BIT, 1 if self.scanner.token == 'on' else 0)
|
||||
self.scanner.scan()
|
||||
return loc
|
||||
elif self.scanner.on_type('integer literal'):
|
||||
value = int(self.scanner.token)
|
||||
self.scanner.scan()
|
||||
type_ = TYPE_WORD if value > 255 else TYPE_BYTE
|
||||
loc = ConstantRef(type_, value)
|
||||
return loc
|
||||
elif self.scanner.consume('word'):
|
||||
loc = ConstantRef(TYPE_WORD, int(self.scanner.token))
|
||||
self.scanner.scan()
|
||||
return loc
|
||||
elif self.scanner.token in self.context.consts:
|
||||
loc = self.context.consts[self.scanner.token]
|
||||
self.scanner.scan()
|
||||
return loc
|
||||
else:
|
||||
self.syntax_error('bad constant "%s"' % self.scanner.token)
|
||||
|
||||
def defn_size(self):
|
||||
self.scanner.expect('[')
|
||||
size = self.literal_int()
|
||||
size = self.const().value
|
||||
self.scanner.expect(']')
|
||||
return size
|
||||
|
||||
@ -193,9 +228,9 @@ class Parser(object):
|
||||
else:
|
||||
type_name = self.scanner.token
|
||||
self.scanner.scan()
|
||||
if type_name not in self.typedefs:
|
||||
if type_name not in self.context.typedefs:
|
||||
self.syntax_error("Undefined type '%s'" % type_name)
|
||||
type_ = self.typedefs[type_name]
|
||||
type_ = self.context.typedefs[type_name]
|
||||
|
||||
return type_
|
||||
|
||||
@ -251,9 +286,9 @@ class Parser(object):
|
||||
else:
|
||||
statics = self.statics()
|
||||
|
||||
self.current_statics = self.compose_statics_dict(statics)
|
||||
self.context.statics = self.compose_statics_dict(statics)
|
||||
block = self.block()
|
||||
self.current_statics = {}
|
||||
self.context.statics = {}
|
||||
|
||||
addr = None
|
||||
location = LocationRef(type_, name)
|
||||
@ -267,7 +302,7 @@ class Parser(object):
|
||||
c = {}
|
||||
for defn in statics:
|
||||
name = defn.name
|
||||
if name in self.symbols or name in self.current_statics:
|
||||
if self.context.lookup(name):
|
||||
self.syntax_error('Symbol "%s" already declared' % name)
|
||||
c[name] = SymEntry(defn, defn.location)
|
||||
return c
|
||||
@ -294,20 +329,12 @@ class Parser(object):
|
||||
return accum
|
||||
|
||||
def locexpr(self, forward=False):
|
||||
if self.scanner.token in ('on', 'off'):
|
||||
loc = ConstantRef(TYPE_BIT, 1 if self.scanner.token == 'on' else 0)
|
||||
self.scanner.scan()
|
||||
return loc
|
||||
elif self.scanner.on_type('integer literal'):
|
||||
return self.literal_int_const()
|
||||
elif self.scanner.consume('word'):
|
||||
loc = ConstantRef(TYPE_WORD, int(self.scanner.token))
|
||||
self.scanner.scan()
|
||||
return loc
|
||||
if self.scanner.token in ('on', 'off', 'word') or self.scanner.token in self.context.consts or self.scanner.on_type('integer literal'):
|
||||
return self.const()
|
||||
elif forward:
|
||||
name = self.scanner.token
|
||||
self.scanner.scan()
|
||||
loc = self.soft_lookup(name)
|
||||
loc = self.context.lookup(name)
|
||||
if loc is not None:
|
||||
return loc
|
||||
else:
|
||||
@ -387,7 +414,7 @@ class Parser(object):
|
||||
else:
|
||||
self.syntax_error('expected "up" or "down", found "%s"' % self.scanner.token)
|
||||
self.scanner.expect('to')
|
||||
final = self.literal_int_const()
|
||||
final = self.const()
|
||||
block = self.block()
|
||||
return For(self.scanner.line_number, dest=dest, direction=direction, final=final, block=block)
|
||||
elif self.scanner.token in ("ld",):
|
||||
@ -417,6 +444,10 @@ class Parser(object):
|
||||
self.scanner.scan()
|
||||
dest = self.locexpr()
|
||||
return SingleOp(self.scanner.line_number, opcode=opcode, dest=dest, src=None)
|
||||
elif self.scanner.token in ("nop",):
|
||||
opcode = self.scanner.token
|
||||
self.scanner.scan()
|
||||
return SingleOp(self.scanner.line_number, opcode=opcode, dest=None, src=None)
|
||||
elif self.scanner.token in ("call", "goto"):
|
||||
opcode = self.scanner.token
|
||||
self.scanner.scan()
|
||||
|
@ -4,16 +4,17 @@ import re
|
||||
|
||||
|
||||
class SixtyPicalSyntaxError(ValueError):
|
||||
def __init__(self, line_number, message):
|
||||
super(SixtyPicalSyntaxError, self).__init__(line_number, message)
|
||||
def __init__(self, filename, line_number, message):
|
||||
super(SixtyPicalSyntaxError, self).__init__(filename, line_number, message)
|
||||
|
||||
def __str__(self):
|
||||
return "Line {}: {}".format(self.args[0], self.args[1])
|
||||
return "{}, line {}: {}".format(self.args[0], self.args[1], self.args[2])
|
||||
|
||||
|
||||
class Scanner(object):
|
||||
def __init__(self, text):
|
||||
def __init__(self, text, filename):
|
||||
self.text = text
|
||||
self.filename = filename
|
||||
self.token = None
|
||||
self.type = None
|
||||
self.line_number = 1
|
||||
@ -62,9 +63,7 @@ class Scanner(object):
|
||||
if self.token == token:
|
||||
self.scan()
|
||||
else:
|
||||
raise SixtyPicalSyntaxError(self.line_number, "Expected '{}', but found '{}'".format(
|
||||
token, self.token
|
||||
))
|
||||
self.syntax_error("Expected '{}', but found '{}'".format(token, self.token))
|
||||
|
||||
def on(self, *tokens):
|
||||
return self.token in tokens
|
||||
@ -74,9 +73,7 @@ class Scanner(object):
|
||||
|
||||
def check_type(self, type):
|
||||
if not self.type == type:
|
||||
raise SixtyPicalSyntaxError(self.line_number, "Expected {}, but found '{}'".format(
|
||||
self.type, self.token
|
||||
))
|
||||
self.syntax_error("Expected {}, but found '{}'".format(self.type, self.token))
|
||||
|
||||
def consume(self, token):
|
||||
if self.token == token:
|
||||
@ -84,3 +81,6 @@ class Scanner(object):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def syntax_error(self, msg):
|
||||
raise SixtyPicalSyntaxError(self.filename, self.line_number, msg)
|
||||
|
1
test.sh
1
test.sh
@ -3,4 +3,5 @@
|
||||
falderal --substring-error \
|
||||
tests/SixtyPical\ Syntax.md \
|
||||
tests/SixtyPical\ Analysis.md \
|
||||
tests/SixtyPical\ Fallthru.md \
|
||||
tests/SixtyPical\ Compilation.md
|
||||
|
@ -1010,7 +1010,7 @@ Can't `dec` a `word` type.
|
||||
|
||||
### cmp ###
|
||||
|
||||
Some rudimentary tests for cmp.
|
||||
Some rudimentary tests for `cmp`.
|
||||
|
||||
| routine main
|
||||
| inputs a
|
||||
@ -1037,7 +1037,7 @@ Some rudimentary tests for cmp.
|
||||
|
||||
### and ###
|
||||
|
||||
Some rudimentary tests for and.
|
||||
Some rudimentary tests for `and`.
|
||||
|
||||
| routine main
|
||||
| inputs a
|
||||
@ -1064,7 +1064,7 @@ Some rudimentary tests for and.
|
||||
|
||||
### or ###
|
||||
|
||||
Writing unit tests on a train. Wow.
|
||||
Some rudimentary tests for `or`.
|
||||
|
||||
| routine main
|
||||
| inputs a
|
||||
@ -1091,7 +1091,7 @@ Writing unit tests on a train. Wow.
|
||||
|
||||
### xor ###
|
||||
|
||||
Writing unit tests on a train. Wow.
|
||||
Some rudimentary tests for `xor`.
|
||||
|
||||
| routine main
|
||||
| inputs a
|
||||
@ -1118,7 +1118,7 @@ Writing unit tests on a train. Wow.
|
||||
|
||||
### shl ###
|
||||
|
||||
Some rudimentary tests for shl.
|
||||
Some rudimentary tests for `shl`.
|
||||
|
||||
| routine main
|
||||
| inputs a, c
|
||||
@ -1146,7 +1146,7 @@ Some rudimentary tests for shl.
|
||||
|
||||
### shr ###
|
||||
|
||||
Some rudimentary tests for shr.
|
||||
Some rudimentary tests for `shr`.
|
||||
|
||||
| routine main
|
||||
| inputs a, c
|
||||
@ -1172,6 +1172,16 @@ Some rudimentary tests for shr.
|
||||
| }
|
||||
? UnmeaningfulReadError: c
|
||||
|
||||
### nop ###
|
||||
|
||||
Some rudimentary tests for `nop`.
|
||||
|
||||
| routine main
|
||||
| {
|
||||
| nop
|
||||
| }
|
||||
= ok
|
||||
|
||||
### call ###
|
||||
|
||||
When calling a routine, all of the locations it lists as inputs must be
|
||||
@ -1659,6 +1669,18 @@ The body of `repeat forever` can be empty.
|
||||
| }
|
||||
= ok
|
||||
|
||||
While `repeat` is most often used with `z`, it can also be used with `n`.
|
||||
|
||||
| routine main
|
||||
| outputs y, n, z
|
||||
| {
|
||||
| ld y, 15
|
||||
| repeat {
|
||||
| dec y
|
||||
| } until n
|
||||
| }
|
||||
= ok
|
||||
|
||||
### for ###
|
||||
|
||||
Basic "open-faced for" loop. We'll start with the "upto" variant.
|
||||
|
@ -18,6 +18,15 @@ Null program.
|
||||
| }
|
||||
= $080D RTS
|
||||
|
||||
`nop` program.
|
||||
|
||||
| routine main
|
||||
| {
|
||||
| nop
|
||||
| }
|
||||
= $080D NOP
|
||||
= $080E RTS
|
||||
|
||||
Rudimentary program.
|
||||
|
||||
| routine main
|
||||
@ -103,6 +112,27 @@ Memory location with explicit address.
|
||||
= $080F STA $0400
|
||||
= $0812 RTS
|
||||
|
||||
Accesses to memory locations in zero-page with `ld` and `st` use zero-page addressing.
|
||||
|
||||
| byte zp @ $00
|
||||
| byte screen @ 100
|
||||
|
|
||||
| routine main
|
||||
| inputs screen, zp
|
||||
| outputs screen, zp
|
||||
| trashes a, z, n
|
||||
| {
|
||||
| ld a, screen
|
||||
| st a, screen
|
||||
| ld a, zp
|
||||
| st a, zp
|
||||
| }
|
||||
= $080D LDA $64
|
||||
= $080F STA $64
|
||||
= $0811 LDA $00
|
||||
= $0813 STA $00
|
||||
= $0815 RTS
|
||||
|
||||
Memory location with initial value.
|
||||
|
||||
| byte lives : 3
|
||||
@ -137,7 +167,7 @@ Word memory locations with explicit address, initial value.
|
||||
= $081A .byte $BB
|
||||
= $081B .byte $0B
|
||||
|
||||
Initialized byte table. Bytes allocated, but beyond the string, are 0's.
|
||||
Initialized byte table, initialized with ASCII string. Bytes allocated, but beyond the string, are 0's.
|
||||
|
||||
| byte table[8] message : "WHAT?"
|
||||
|
|
||||
@ -159,6 +189,45 @@ Initialized byte table. Bytes allocated, but beyond the string, are 0's.
|
||||
= $0819 BRK
|
||||
= $081A BRK
|
||||
|
||||
Initialized byte table, initialized with list of byte values.
|
||||
|
||||
| byte table[8] message : 255, 0, 129, 128, 127
|
||||
|
|
||||
| routine main
|
||||
| inputs message
|
||||
| outputs x, a, z, n
|
||||
| {
|
||||
| ld x, 0
|
||||
| ld a, message + x
|
||||
| }
|
||||
= $080D LDX #$00
|
||||
= $080F LDA $0813,X
|
||||
= $0812 RTS
|
||||
= $0813 .byte $FF
|
||||
= $0814 BRK
|
||||
= $0815 STA ($80,X)
|
||||
= $0817 .byte $7F
|
||||
= $0818 BRK
|
||||
= $0819 BRK
|
||||
= $081A BRK
|
||||
|
||||
Initialized word table, initialized with list of word values.
|
||||
|
||||
| word table[8] message : 65535, 0, 127
|
||||
|
|
||||
| routine main
|
||||
| {
|
||||
| }
|
||||
= $080D RTS
|
||||
= $080E .byte $FF
|
||||
= $080F .byte $FF
|
||||
= $0810 BRK
|
||||
= $0811 BRK
|
||||
= $0812 .byte $7F
|
||||
= $0813 BRK
|
||||
= $0814 BRK
|
||||
= $0815 BRK
|
||||
|
||||
Some instructions.
|
||||
|
||||
| byte foo
|
||||
@ -288,7 +357,7 @@ Compiling `if` without `else`.
|
||||
= $0813 LDY #$01
|
||||
= $0815 RTS
|
||||
|
||||
Compiling `repeat`.
|
||||
Compiling `repeat ... until z`.
|
||||
|
||||
| routine main
|
||||
| trashes a, y, z, n, c
|
||||
@ -307,7 +376,7 @@ Compiling `repeat`.
|
||||
= $0813 BNE $080F
|
||||
= $0815 RTS
|
||||
|
||||
Compiling `repeat until not`.
|
||||
Compiling `repeat ... until not z`.
|
||||
|
||||
| routine main
|
||||
| trashes a, y, z, n, c
|
||||
@ -326,6 +395,40 @@ Compiling `repeat until not`.
|
||||
= $0813 BEQ $080F
|
||||
= $0815 RTS
|
||||
|
||||
Compiling `repeat ... until n`.
|
||||
|
||||
| routine main
|
||||
| trashes a, y, z, n, c
|
||||
| {
|
||||
| ld y, 65
|
||||
| repeat {
|
||||
| ld a, y
|
||||
| dec y
|
||||
| } until n
|
||||
| }
|
||||
= $080D LDY #$41
|
||||
= $080F TYA
|
||||
= $0810 DEY
|
||||
= $0811 BPL $080F
|
||||
= $0813 RTS
|
||||
|
||||
Compiling `repeat ... until not n`.
|
||||
|
||||
| routine main
|
||||
| trashes a, y, z, n, c
|
||||
| {
|
||||
| ld y, 199
|
||||
| repeat {
|
||||
| ld a, y
|
||||
| inc y
|
||||
| } until not n
|
||||
| }
|
||||
= $080D LDY #$C7
|
||||
= $080F TYA
|
||||
= $0810 INY
|
||||
= $0811 BMI $080F
|
||||
= $0813 RTS
|
||||
|
||||
Compiling `repeat forever`.
|
||||
|
||||
| routine main
|
||||
@ -673,7 +776,7 @@ Indirect call.
|
||||
= $081E JMP ($0822)
|
||||
= $0821 RTS
|
||||
|
||||
goto.
|
||||
Compiling `goto`. Note that no `RTS` is emitted after the `JMP`.
|
||||
|
||||
| routine bar
|
||||
| inputs y
|
||||
@ -691,10 +794,9 @@ goto.
|
||||
| goto bar
|
||||
| }
|
||||
= $080D LDY #$C8
|
||||
= $080F JMP $0813
|
||||
= $0812 RTS
|
||||
= $0813 LDX #$C8
|
||||
= $0815 RTS
|
||||
= $080F JMP $0812
|
||||
= $0812 LDX #$C8
|
||||
= $0814 RTS
|
||||
|
||||
### Vector tables
|
||||
|
||||
|
427
tests/SixtyPical Fallthru.md
Normal file
427
tests/SixtyPical Fallthru.md
Normal file
@ -0,0 +1,427 @@
|
||||
SixtyPical Fallthru
|
||||
===================
|
||||
|
||||
This is a test suite, written in [Falderal][] format, for SixtyPical's
|
||||
ability to detect which routines make tail calls to other routines,
|
||||
and thus can be re-arranged to simply "fall through" to them.
|
||||
|
||||
The theory is as follows.
|
||||
|
||||
SixtyPical supports a `goto`, but it can only appear in tail position.
|
||||
If a routine r1 ends with a unique `goto` to a fixed routine r2 it is said
|
||||
to *potentially fall through* to r2.
|
||||
|
||||
A *unique* `goto` means that there are not multiple different `goto`s in
|
||||
tail position (which can happen if, for example, an `if` is the last thing
|
||||
in a routine, and each branch of that `if` ends with a different `goto`.)
|
||||
|
||||
A *fixed* routine means, a routine which is known at compile time, not a
|
||||
`goto` through a vector.
|
||||
|
||||
Consider the set R of all available routines in the program.
|
||||
|
||||
Every routine either potentially falls through to a single other routine
|
||||
or it does not potentially fall through to any routine.
|
||||
|
||||
More formally, we can say
|
||||
|
||||
> fall : R → R ∪ {nil}, fall(r) ≠ r
|
||||
|
||||
where `nil` is an atom that represents no routine.
|
||||
|
||||
Now consider an operation chain() vaguely similar to a transitive closure
|
||||
on fall(). Starting with r, we construct a list of r, fall(r),
|
||||
fall(fall(r)), ... with the following restrictions:
|
||||
|
||||
- we stop when we reach `nil` (because fall(`nil`) is not defined)
|
||||
- we stop when we see an element that is not in R.
|
||||
- we stop when we see an element that we have already added to the
|
||||
list (this is to prevent infinite lists due to cycles.)
|
||||
|
||||
With these definitions, our algorithm is something like this.
|
||||
|
||||
Treat R as a mutable set and start with an empty list of lists L. Then,
|
||||
|
||||
- For all r ∈ R, find all chain(r).
|
||||
- Pick a longest such chain. Call it C.
|
||||
- Append C to L.
|
||||
- Remove all elements occurring in C, from R.
|
||||
- Repeat until R is empty.
|
||||
|
||||
When times comes to generate code, generate it in the order given by L.
|
||||
In addition, each sublist in L represents a number of routines to
|
||||
generate; all except the final routine in such a sublist need not have
|
||||
any jump instruction generated for its final `goto`.
|
||||
|
||||
The tests in this document test against the list L.
|
||||
|
||||
Note that this optimization is a feature of the SixtyPical's reference
|
||||
compiler, not the language. So an implementation is not required
|
||||
to pass these tests to be considered an implementation of SixtyPical.
|
||||
|
||||
[Falderal]: http://catseye.tc/node/Falderal
|
||||
|
||||
-> Functionality "Dump fallthru info for SixtyPical program" is implemented by
|
||||
-> shell command "bin/sixtypical --optimize-fallthru --dump-fallthru-info --analyze-only --traceback %(test-body-file)"
|
||||
|
||||
-> Functionality "Compile SixtyPical program with fallthru optimization" is implemented by
|
||||
-> shell command "bin/sixtypical --prelude=c64 --optimize-fallthru --traceback %(test-body-file) >/tmp/foo && tests/appliances/bin/dcc6502-adapter </tmp/foo"
|
||||
|
||||
-> Tests for functionality "Dump fallthru info for SixtyPical program"
|
||||
|
||||
A single routine, obviously, falls through to nothing and has nothing fall
|
||||
through to it.
|
||||
|
||||
| define main routine
|
||||
| {
|
||||
| }
|
||||
= [
|
||||
= [
|
||||
= "main"
|
||||
= ]
|
||||
= ]
|
||||
|
||||
If `main` does a `goto foo`, then it can fall through to `foo`.
|
||||
|
||||
| define foo routine trashes a, z, n
|
||||
| {
|
||||
| ld a, 0
|
||||
| }
|
||||
|
|
||||
| define main routine trashes a, z, n
|
||||
| {
|
||||
| goto foo
|
||||
| }
|
||||
= [
|
||||
= [
|
||||
= "main",
|
||||
= "foo"
|
||||
= ]
|
||||
= ]
|
||||
|
||||
More than one routine can fall through to a routine. We pick one
|
||||
of them to fall through, when selecting the order of routines.
|
||||
|
||||
| define foo routine trashes a, z, n
|
||||
| {
|
||||
| ld a, 0
|
||||
| }
|
||||
|
|
||||
| define bar routine trashes a, z, n
|
||||
| {
|
||||
| ld a, 0
|
||||
| goto foo
|
||||
| }
|
||||
|
|
||||
| define main routine trashes a, z, n
|
||||
| {
|
||||
| goto foo
|
||||
| }
|
||||
= [
|
||||
= [
|
||||
= "main",
|
||||
= "foo"
|
||||
= ],
|
||||
= [
|
||||
= "bar"
|
||||
= ]
|
||||
= ]
|
||||
|
||||
Because `main` is always serialized first (so that the entry
|
||||
point of the entire program appears at the beginning of the code),
|
||||
nothing ever falls through to `main`.
|
||||
|
||||
| define foo routine trashes a, z, n
|
||||
| {
|
||||
| ld a, 0
|
||||
| goto main
|
||||
| }
|
||||
|
|
||||
| define main routine trashes a, z, n
|
||||
| {
|
||||
| ld a, 1
|
||||
| }
|
||||
= [
|
||||
= [
|
||||
= "main"
|
||||
= ],
|
||||
= [
|
||||
= "foo"
|
||||
= ]
|
||||
= ]
|
||||
|
||||
There is nothing stopping two routines from tail-calling each
|
||||
other, but we will only be able to make one of them, at most,
|
||||
fall through to the other.
|
||||
|
||||
| define foo routine trashes a, z, n
|
||||
| {
|
||||
| ld a, 0
|
||||
| goto bar
|
||||
| }
|
||||
|
|
||||
| define bar routine trashes a, z, n
|
||||
| {
|
||||
| ld a, 0
|
||||
| goto foo
|
||||
| }
|
||||
|
|
||||
| define main routine trashes a, z, n
|
||||
| {
|
||||
| }
|
||||
= [
|
||||
= [
|
||||
= "main"
|
||||
= ],
|
||||
= [
|
||||
= "bar",
|
||||
= "foo"
|
||||
= ]
|
||||
= ]
|
||||
|
||||
If a routine does two tail calls (which is possible because they
|
||||
can be in different branches of an `if`) it cannot fall through to another
|
||||
routine.
|
||||
|
||||
| define foo routine trashes a, z, n
|
||||
| {
|
||||
| ld a, 0
|
||||
| }
|
||||
|
|
||||
| define bar routine trashes a, z, n
|
||||
| {
|
||||
| ld a, 0
|
||||
| }
|
||||
|
|
||||
| define main routine inputs z trashes a, z, n
|
||||
| {
|
||||
| if z {
|
||||
| goto foo
|
||||
| } else {
|
||||
| goto bar
|
||||
| }
|
||||
| }
|
||||
= [
|
||||
= [
|
||||
= "main"
|
||||
= ],
|
||||
= [
|
||||
= "bar"
|
||||
= ],
|
||||
= [
|
||||
= "foo"
|
||||
= ]
|
||||
= ]
|
||||
|
||||
If, however, they are the same goto, one can be optimized away.
|
||||
|
||||
| define foo routine trashes a, z, n
|
||||
| {
|
||||
| ld a, 0
|
||||
| if z {
|
||||
| ld a, 1
|
||||
| goto bar
|
||||
| } else {
|
||||
| ld a, 2
|
||||
| goto bar
|
||||
| }
|
||||
| }
|
||||
|
|
||||
| define bar routine trashes a, z, n
|
||||
| {
|
||||
| ld a, 255
|
||||
| }
|
||||
|
|
||||
| define main routine trashes a, z, n
|
||||
| {
|
||||
| }
|
||||
= [
|
||||
= [
|
||||
= "main"
|
||||
= ],
|
||||
= [
|
||||
= "foo",
|
||||
= "bar"
|
||||
= ]
|
||||
= ]
|
||||
|
||||
Similarly, a tail call to a vector can't be turned into a fallthru,
|
||||
because we don't necessarily know what actual routine the vector contains.
|
||||
|
||||
| vector routine trashes a, z, n
|
||||
| vec
|
||||
|
|
||||
| define foo routine trashes a, z, n
|
||||
| {
|
||||
| ld a, 0
|
||||
| }
|
||||
|
|
||||
| define bar routine trashes a, z, n
|
||||
| {
|
||||
| ld a, 0
|
||||
| }
|
||||
|
|
||||
| define main routine outputs vec trashes a, z, n
|
||||
| {
|
||||
| copy bar, vec
|
||||
| goto vec
|
||||
| }
|
||||
= [
|
||||
= [
|
||||
= "main"
|
||||
= ],
|
||||
= [
|
||||
= "bar"
|
||||
= ],
|
||||
= [
|
||||
= "foo"
|
||||
= ]
|
||||
= ]
|
||||
|
||||
Our algorithm might not be strictly optimal, but it does a good job.
|
||||
|
||||
| define r1 routine trashes a, z, n
|
||||
| {
|
||||
| ld a, 0
|
||||
| goto r2
|
||||
| }
|
||||
|
|
||||
| define r2 routine trashes a, z, n
|
||||
| {
|
||||
| ld a, 0
|
||||
| goto r3
|
||||
| }
|
||||
|
|
||||
| define r3 routine trashes a, z, n
|
||||
| {
|
||||
| ld a, 0
|
||||
| goto r4
|
||||
| }
|
||||
|
|
||||
| define r4 routine trashes a, z, n
|
||||
| {
|
||||
| ld a, 0
|
||||
| }
|
||||
|
|
||||
| define r5 routine trashes a, z, n
|
||||
| {
|
||||
| ld a, 0
|
||||
| goto r6
|
||||
| }
|
||||
|
|
||||
| define r6 routine trashes a, z, n
|
||||
| {
|
||||
| ld a, 0
|
||||
| goto r3
|
||||
| }
|
||||
|
|
||||
| define main routine trashes a, z, n
|
||||
| {
|
||||
| goto r1
|
||||
| }
|
||||
= [
|
||||
= [
|
||||
= "main",
|
||||
= "r1",
|
||||
= "r2",
|
||||
= "r3",
|
||||
= "r4"
|
||||
= ],
|
||||
= [
|
||||
= "r5",
|
||||
= "r6"
|
||||
= ]
|
||||
= ]
|
||||
|
||||
-> Tests for functionality "Compile SixtyPical program with fallthru optimization"
|
||||
|
||||
Basic test for actually applying this optimization when compiling SixtyPical programs.
|
||||
|
||||
| define foo routine trashes a, z, n
|
||||
| {
|
||||
| ld a, 0
|
||||
| }
|
||||
|
|
||||
| define bar routine trashes a, z, n
|
||||
| {
|
||||
| ld a, 255
|
||||
| goto foo
|
||||
| }
|
||||
|
|
||||
| define main routine trashes a, z, n
|
||||
| {
|
||||
| goto foo
|
||||
| }
|
||||
= $080D LDA #$00
|
||||
= $080F RTS
|
||||
= $0810 LDA #$FF
|
||||
= $0812 JMP $080D
|
||||
|
||||
It can optimize out one of the `goto`s if they are the same.
|
||||
|
||||
| define foo routine trashes a, z, n
|
||||
| {
|
||||
| ld a, 0
|
||||
| if z {
|
||||
| ld a, 1
|
||||
| goto bar
|
||||
| } else {
|
||||
| ld a, 2
|
||||
| goto bar
|
||||
| }
|
||||
| }
|
||||
|
|
||||
| define bar routine trashes a, z, n
|
||||
| {
|
||||
| ld a, 255
|
||||
| }
|
||||
|
|
||||
| define main routine trashes a, z, n
|
||||
| {
|
||||
| }
|
||||
= $080D RTS
|
||||
= $080E LDA #$00
|
||||
= $0810 BNE $0817
|
||||
= $0812 LDA #$01
|
||||
= $0814 JMP $0819
|
||||
= $0817 LDA #$02
|
||||
= $0819 LDA #$FF
|
||||
= $081B RTS
|
||||
|
||||
It cannot optimize out the `goto`s if they are different.
|
||||
|
||||
Note, this currently produces unfortunately unoptimized code,
|
||||
because generating code for the "true" branch of an `if` always
|
||||
generates a jump out of the `if`, even if the last instruction
|
||||
in the "true" branch is a `goto`.
|
||||
|
||||
| define foo routine trashes a, z, n
|
||||
| {
|
||||
| ld a, 0
|
||||
| if z {
|
||||
| ld a, 1
|
||||
| goto bar
|
||||
| } else {
|
||||
| ld a, 2
|
||||
| goto main
|
||||
| }
|
||||
| }
|
||||
|
|
||||
| define bar routine trashes a, z, n
|
||||
| {
|
||||
| ld a, 255
|
||||
| }
|
||||
|
|
||||
| define main routine trashes a, z, n
|
||||
| {
|
||||
| }
|
||||
= $080D RTS
|
||||
= $080E LDA #$FF
|
||||
= $0810 RTS
|
||||
= $0811 LDA #$00
|
||||
= $0813 BNE $081D
|
||||
= $0815 LDA #$01
|
||||
= $0817 JMP $080E
|
||||
= $081A JMP $0822
|
||||
= $081D LDA #$02
|
||||
= $081F JMP $080D
|
@ -78,6 +78,14 @@ Trash.
|
||||
| }
|
||||
= ok
|
||||
|
||||
`nop`.
|
||||
|
||||
| routine main
|
||||
| {
|
||||
| nop
|
||||
| }
|
||||
= ok
|
||||
|
||||
If with not
|
||||
|
||||
| routine foo {
|
||||
@ -228,6 +236,31 @@ Can't have two typedefs with the same name.
|
||||
| }
|
||||
? SyntaxError
|
||||
|
||||
Constants.
|
||||
|
||||
| const lives 3
|
||||
| const days lives
|
||||
| const w1 1000
|
||||
| const w2 word 0
|
||||
|
|
||||
| typedef byte table[days] them
|
||||
|
|
||||
| byte lark: lives
|
||||
|
|
||||
| routine main {
|
||||
| ld a, lives
|
||||
| }
|
||||
= ok
|
||||
|
||||
Can't have two constants with the same name.
|
||||
|
||||
| const w1 1000
|
||||
| const w1 word 0
|
||||
|
|
||||
| routine main {
|
||||
| }
|
||||
? SyntaxError
|
||||
|
||||
Explicit memory address.
|
||||
|
||||
| byte screen @ 1024
|
||||
@ -269,7 +302,7 @@ User-defined locations of other types.
|
||||
| }
|
||||
= ok
|
||||
|
||||
Initialized byte table.
|
||||
Initialized byte table, initialized with ASCII string.
|
||||
|
||||
| byte table[32] message : "WHAT DO YOU WANT TO DO NEXT?"
|
||||
|
|
||||
@ -285,6 +318,14 @@ Can't initialize anything but a byte table with a string.
|
||||
| }
|
||||
? SyntaxError
|
||||
|
||||
Initialized byte table, initialized with list of bytes.
|
||||
|
||||
| byte table[8] charmap : 0, 255, 129, 192, 0, 1, 2, 4
|
||||
|
|
||||
| routine main {
|
||||
| }
|
||||
= ok
|
||||
|
||||
Can't access an undeclared memory location.
|
||||
|
||||
| routine main {
|
||||
|
Loading…
x
Reference in New Issue
Block a user