git @ Cat's Eye Technologies SixtyPical / 0.15
Merge pull request #11 from catseye/develop-0.15 Develop 0.15 Chris Pressey authored 3 years ago GitHub committed 3 years ago
27 changed file(s) with 1926 addition(s) and 237 deletion(s). Raw diff Collapse all Expand all
00 History of SixtyPical
11 =====================
2
3 0.15
4 ----
5
6 * Symbolic constants can be defined with the `const` keyword, and can
7 be used in most places where literal values can be used.
8 * Added `nop` opcode, which compiles to `NOP` (mainly for timing.)
9 * Accessing zero-page with `ld` and `st` generates zero-page opcodes.
10 * A `byte` or `word` table can be initialized with a list of constants.
11 * Branching and repeating on the `n` flag is now supported.
12 * The `--optimize-fallthru` option causes the routines of the program
13 to be re-ordered to maximize the number of cases where a `goto`'ed
14 routine can be simply "falled through" to instead of `JMP`ed to.
15 * `--dump-fallthru-info` option outputs the information from the
16 fallthru analysis phase, in JSON format, to stdout.
17 * Even without fallthru optimization, `RTS` is no longer emitted after
18 the `JMP` from compiling a final `goto`.
19 * Specifying multiple SixtyPical source files will produce a single
20 compiled result from their combination.
21 * Rudimentary support for Atari 2600 prelude in a 4K cartridge image,
22 and an example program in `eg/atari2600` directory.
223
324 0.14
425 ----
00 SixtyPical
11 ==========
22
3 _Version 0.14. Work-in-progress, everything is subject to change._
3 _Version 0.15. Work-in-progress, everything is subject to change._
44
55 **SixtyPical** is a 6502-like programming language with advanced
66 static analysis.
6262 * [Literate test suite for SixtyPical syntax](tests/SixtyPical%20Syntax.md)
6363 * [Literate test suite for SixtyPical analysis](tests/SixtyPical%20Analysis.md)
6464 * [Literate test suite for SixtyPical compilation](tests/SixtyPical%20Compilation.md)
65 * [Literate test suite for SixtyPical fallthru optimization](tests/SixtyPical%20Fallthru.md)
6566 * [6502 Opcodes used/not used in SixtyPical](doc/6502%20Opcodes.md)
6667
6768 TODO
7273 This preserves them, so that, semantically, they can be used later even though they
7374 are trashed inside the block.
7475
75 ### Re-order routines and optimize tail-calls to fallthroughs
76
77 Not because it saves 3 bytes, but because it's a neat trick. Doing it optimally
78 is probably NP-complete. But doing it adequately is probably not that hard.
79
8076 ### And at some point...
8177
8278 * `low` and `high` address operators - to turn `word` type into `byte`.
83 * `const`s that can be used in defining the size of tables, etc.
8479 * Tests, and implementation, ensuring a routine can be assigned to a vector of "wider" type
8580 * Related: can we simply view a (small) part of a buffer as a byte table? If not, why not?
8681 * Related: add constant to buffer to get new buffer. (Or to table, but... well, maybe.)
87 * Check that the buffer being read or written to through pointer, appears in approporiate inputs or outputs set.
82 * Check that the buffer being read or written to through pointer, appears in appropriate inputs or outputs set.
8883 (Associate each pointer with the buffer it points into.)
8984 * `static` pointers -- currently not possible because pointers must be zero-page, thus `@`, thus uninitialized.
9085 * Question the value of the "consistent initialization" principle for `if` statement analysis.
9388 * Automatic tail-call optimization (could be tricky, w/constraints?)
9489 * Possibly `ld x, [ptr] + y`, possibly `st x, [ptr] + y`.
9590 * Maybe even `copy [ptra] + y, [ptrb] + y`, which can be compiled to indirect LDA then indirect STA!
96 * Optimize `ld a, z` and `st a, z` to zero-page operations if address of z < 256.
97 * Include files?
91 * Optimize `or|and|eor a, z` to zero-page operations if address of z < 256.
9892
9993 [VICE]: http://vice-emu.sourceforge.net/
1717 import sys
1818 import traceback
1919
20 from sixtypical.parser import Parser
20 from sixtypical.parser import Parser, ParsingContext
2121 from sixtypical.analyzer import Analyzer
2222 from sixtypical.emitter import Emitter, Byte, Word
2323 from sixtypical.compiler import Compiler
2424
2525
26 def merge_programs(programs):
27 """Assumes that the programs do not have any conflicts."""
28
29 from sixtypical.ast import Program
30
31 full = Program(1, defns=[], routines=[])
32 for p in programs:
33 full.defns.extend(p.defns)
34 full.routines.extend(p.routines)
35
36 return full
37
38
39 def process_input_files(filenames, options):
40 context = ParsingContext()
41
42 programs = []
43
44 for filename in options.filenames:
45 text = open(filename).read()
46 parser = Parser(context, text, filename)
47 if options.debug:
48 print(context)
49 program = parser.program()
50 programs.append(program)
51
52 if options.parse_only:
53 return
54
55 program = merge_programs(programs)
56
57 analyzer = Analyzer(debug=options.debug)
58 analyzer.analyze_program(program)
59
60 compilation_roster = None
61 if options.optimize_fallthru:
62 from sixtypical.fallthru import FallthruAnalyzer
63
64 def dump(data, label=None):
65 import json
66 if not options.dump_fallthru_info:
67 return
68 if label:
69 sys.stdout.write("*** {}:\n".format(label))
70 sys.stdout.write(json.dumps(data, indent=4, sort_keys=True))
71 sys.stdout.write("\n")
72
73 fa = FallthruAnalyzer(debug=options.debug)
74 fa.analyze_program(program)
75 compilation_roster = fa.serialize()
76 dump(compilation_roster)
77
78 if options.analyze_only:
79 return
80
81 fh = sys.stdout
82
83 if options.origin.startswith('0x'):
84 start_addr = int(options.origin, 16)
85 else:
86 start_addr = int(options.origin, 10)
87
88 output_format = options.output_format
89
90 prelude = []
91 if options.prelude == 'c64':
92 output_format = 'prg'
93 start_addr = 0x0801
94 prelude = [0x10, 0x08, 0xc9, 0x07, 0x9e, 0x32,
95 0x30, 0x36, 0x31, 0x00, 0x00, 0x00]
96 elif options.prelude == 'vic20':
97 output_format = 'prg'
98 start_addr = 0x1001
99 prelude = [0x0b, 0x10, 0xc9, 0x07, 0x9e, 0x34,
100 0x31, 0x30, 0x39, 0x00, 0x00, 0x00]
101 elif options.prelude == 'atari2600':
102 output_format = 'crtbb'
103 start_addr = 0xf000
104 prelude = [0x78, 0xd8, 0xa2, 0xff, 0x9a, 0xa9,
105 0x00,0x95, 0x00, 0xca, 0xd0, 0xfb]
106
107 elif options.prelude:
108 raise NotImplementedError("Unknown prelude: {}".format(options.prelude))
109
110 # If we are outputting a .PRG, we output the load address first.
111 # We don't use the Emitter for this b/c not part of addr space.
112 if output_format == 'prg':
113 fh.write(Word(start_addr).serialize(0))
114
115 emitter = Emitter(start_addr)
116 for byte in prelude:
117 emitter.emit(Byte(byte))
118 compiler = Compiler(emitter)
119 compiler.compile_program(program, compilation_roster=compilation_roster)
120
121 # If we are outputting a cartridge with boot and BRK address
122 # at the end, pad to ROM size minus 4 bytes, and emit addresses.
123 if output_format == 'crtbb':
124 emitter.pad_to_size(4096 - 4)
125 emitter.emit(Word(start_addr))
126 emitter.emit(Word(start_addr))
127
128 if options.debug:
129 pprint(emitter.accum)
130 else:
131 emitter.serialize(fh)
132
133
26134 if __name__ == '__main__':
27135 argparser = ArgumentParser(__doc__.strip())
28136
30138 'filenames', metavar='FILENAME', type=str, nargs='+',
31139 help="The SixtyPical source files to compile."
32140 )
33 argparser.add_argument(
34 "--analyze-only",
35 action="store_true",
36 help="Only parse and analyze the program; do not compile it."
37 )
141
38142 argparser.add_argument(
39143 "--origin", type=str, default='0xc000',
40144 help="Location in memory where the `main` routine will be "
42146 )
43147 argparser.add_argument(
44148 "--output-format", type=str, default='prg',
45 help="Executable format to produce. Options are: prg (.PRG file "
46 "for Commodore 8-bit). Default: prg."
149 help="Executable format to produce. Options are: prg, crtbb. "
150 "Default: prg."
47151 )
48152 argparser.add_argument(
49153 "--prelude", type=str,
50 help="Insert a snippet before the compiled program "
51 "so that it can be LOADed and RUN on a certain platforms. "
154 help="Insert a snippet of code before the compiled program so that "
155 "it can be booted automatically on a particular platform. "
52156 "Also sets the origin and format. "
53 "Options are: c64 or vic20."
157 "Options are: c64, vic20, atari2600."
158 )
159
160 argparser.add_argument(
161 "--analyze-only",
162 action="store_true",
163 help="Only parse and analyze the program; do not compile it."
164 )
165 argparser.add_argument(
166 "--optimize-fallthru",
167 action="store_true",
168 help="Reorder the routines in the program to maximize the number of tail calls "
169 "that can be removed by having execution 'fall through' to the next routine."
170 )
171 argparser.add_argument(
172 "--dump-fallthru-info",
173 action="store_true",
174 help="Dump the fallthru map and ordering to stdout after analyzing the program."
175 )
176 argparser.add_argument(
177 "--parse-only",
178 action="store_true",
179 help="Only parse the program; do not analyze or compile it."
54180 )
55181 argparser.add_argument(
56182 "--debug",
57183 action="store_true",
58184 help="Display debugging information when analyzing and compiling."
59 )
60 argparser.add_argument(
61 "--parse-only",
62 action="store_true",
63 help="Only parse the program; do not analyze or compile it."
64185 )
65186 argparser.add_argument(
66187 "--traceback",
71192 options, unknown = argparser.parse_known_args(sys.argv[1:])
72193 remainder = ' '.join(unknown)
73194
74 for filename in options.filenames:
75 text = open(filename).read()
76
77 try:
78 parser = Parser(text)
79 program = parser.program()
80 except Exception as e:
81 if options.traceback:
82 raise
83 else:
84 traceback.print_exception(e.__class__, e, None)
85 sys.exit(1)
86
87 if options.parse_only:
88 sys.exit(0)
89
90 try:
91 analyzer = Analyzer(debug=options.debug)
92 analyzer.analyze_program(program)
93 except Exception as e:
94 if options.traceback:
95 raise
96 else:
97 traceback.print_exception(e.__class__, e, None)
98 sys.exit(1)
99
100 if options.analyze_only:
101 sys.exit(0)
102
103 fh = sys.stdout
104
105 if options.origin.startswith('0x'):
106 start_addr = int(options.origin, 16)
195 try:
196 process_input_files(options.filenames, options)
197 except Exception as e:
198 if options.traceback:
199 raise
107200 else:
108 start_addr = int(options.origin, 10)
109
110 output_format = options.output_format
111
112 prelude = []
113 if options.prelude == 'c64':
114 output_format = 'prg'
115 start_addr = 0x0801
116 prelude = [0x10, 0x08, 0xc9, 0x07, 0x9e, 0x32,
117 0x30, 0x36, 0x31, 0x00, 0x00, 0x00]
118 elif options.prelude == 'vic20':
119 output_format = 'prg'
120 start_addr = 0x1001
121 prelude = [0x0b, 0x10, 0xc9, 0x07, 0x9e, 0x34,
122 0x31, 0x30, 0x39, 0x00, 0x00, 0x00]
123 elif options.prelude:
124 raise NotImplementedError("Unknown prelude: {}".format(options.prelude))
125
126 # If we are outputting a .PRG, we output the load address first.
127 # We don't use the Emitter for this b/c not part of addr space.
128 if output_format == 'prg':
129 fh.write(Word(start_addr).serialize(0))
130
131 emitter = Emitter(start_addr)
132 for byte in prelude:
133 emitter.emit(Byte(byte))
134 compiler = Compiler(emitter)
135 compiler.compile_program(program)
136 if options.debug:
137 pprint(emitter.accum)
138 else:
139 emitter.serialize(fh)
201 traceback.print_exception(e.__class__, e, None)
202 sys.exit(1)
00 SixtyPical
11 ==========
22
3 This document describes the SixtyPical programming language version 0.14,
3 This document describes the SixtyPical programming language version 0.15,
44 both its static semantics (the capabilities and limits of the static
55 analyses it defines) and its runtime semantics (with reference to the
66 semantics of 6502 machine code.)
554554 Grammar
555555 -------
556556
557 Program ::= {TypeDefn} {Defn} {Routine}.
557 Program ::= {ConstDefn | TypeDefn} {Defn} {Routine}.
558 ConstDefn::= "const" Ident<new> Const.
558559 TypeDefn::= "typedef" Type Ident<new>.
559 Defn ::= Type Ident<new> [Constraints] (":" Literal | "@" LitWord).
560 Defn ::= Type Ident<new> [Constraints] (":" Const | "@" LitWord).
560561 Type ::= TypeTerm ["table" TypeSize].
561562 TypeExpr::= "byte"
562563 | "word"
572573 | "routine" Ident<new> Constraints (Block | "@" LitWord)
573574 .
574575 LocExprs::= LocExpr {"," LocExpr}.
575 LocExpr ::= Register | Flag | Literal | Ident.
576 LocExpr ::= Register | Flag | Const | Ident.
576577 Register::= "a" | "x" | "y".
577578 Flag ::= "c" | "z" | "n" | "v".
579 Const ::= Literal | Ident<const>.
578580 Literal ::= LitByte | LitWord | LitBit.
579581 LitByte ::= "0" ... "255".
580 LitWord ::= "0" ... "65535".
582 LitWord ::= ["word"] "0" ... "65535".
581583 LitBit ::= "on" | "off".
582584 Block ::= "{" {Instr} "}".
583585 Instr ::= "ld" LocExpr "," LocExpr ["+" LocExpr]
597599 | "copy" LocExpr "," LocExpr ["+" LocExpr]
598600 | "if" ["not"] LocExpr Block ["else" Block]
599601 | "repeat" Block ("until" ["not"] LocExpr | "forever")
600 | "for" LocExpr ("up"|"down") "to" Literal Block
602 | "for" LocExpr ("up"|"down") "to" Const Block
601603 | "with" "interrupts" LitBit Block
602604 .
2929 the P65 assembler (now Ophis) and re-released on April 1st, 2008 (a
3030 hint as to its nature).
3131
32 Translated to SixtyPical (in 2018), it's 48 bytes.
32 Translated to SixtyPical (in 2018), after adding some optimizations
33 to the SixtyPical compiler, the resulting executable is still 44 bytes!
3334
3435 ### vic20
3536
3738 Commodore VIC-20. The directory itself contains some simple demos,
3839 for example [hearts.60p](vic20/hearts.60p).
3940
41 ### atari2600
42
43 In the [atari2600](atari2600/) directory are programs that run on the
44 Atari 2600 (4K cartridge). The directory itself contains a simple
45 demo, [smiley.60p](atari2600/smiley.60p) which was converted from an
46 older Atari 2600 skeleton program written in [Ophis][].
47
4048 [Ophis]: http://michaelcmartin.github.io/Ophis/
0 *.bin
1 *.disasm.txt
0 ;
1 ; atari-2600-example.oph
2 ; Skeleton code for an Atari 2600 ROM,
3 ; plus an example of reading the joystick.
4 ; By Chris Pressey, November 2, 2012.
5 ;
6 ; This work is in the public domain. See the file UNLICENSE for more info.
7 ;
8 ; Based on Chris Cracknell's Atari 2600 clock (also in the public domain):
9 ; http://everything2.com/title/An+example+of+Atari+2600+source+code
10 ;
11 ; to build and run in Stella:
12 ; ophis atari-2600-example.oph -o example.bin
13 ; stella example.bin
14 ;
15 ; More useful information can be found in the Stella Programmer's Guide:
16 ; http://alienbill.com/2600/101/docs/stella.html
17 ;
18
19 ;
20 ; Useful system addresses (TODO: briefly describe each of these.)
21 ;
22
23 .alias VSYNC $00
24 .alias VBLANK $01
25 .alias WSYNC $02
26 .alias NUSIZ0 $04
27 .alias NUSIZ1 $05
28 .alias COLUPF $08
29 .alias COLUBK $09
30 .alias PF0 $0D
31 .alias PF1 $0E
32 .alias PF2 $0F
33 .alias SWCHA $280
34 .alias INTIM $284
35 .alias TIM64T $296
36 .alias CTRLPF $0A
37 .alias COLUP0 $06
38 .alias COLUP1 $07
39 .alias GP0 $1B
40 .alias GP1 $1C
41 .alias HMOVE $2a
42 .alias RESP0 $10
43 .alias RESP1 $11
44
45 ;
46 ; Cartridge ROM occupies the top 4K of memory ($F000-$FFFF).
47 ; Thus, typically, the program will occupy all that space too.
48 ;
49 ; Zero-page RAM we can use with impunity starts at $80 and goes
50 ; upward (at least until $99, but probably further.)
51 ;
52
53 .alias colour $80
54 .alias luminosity $81
55 .alias joystick_delay $82
56
57 .org $F000
58
59 ;
60 ; Standard prelude for Atari 2600 cartridge code.
61 ;
62 ; Get various parts of the machine into a known state:
63 ;
64 ; - Disable interrupts
65 ; - Clear the Decimal flag
66 ; - Initialize the Stack Pointer
67 ; - Zero all bytes in Zero Page memory
68 ;
69
70 start:
71 sei
72 cld
73 ldx #$FF
74 txs
75 lda #$00
76
77 zero_loop:
78 sta $00, x
79 dex
80 bne zero_loop
81
82 ; and fall through to...
83
84 ;
85 ; Initialization.
86 ;
87 ; - Clear the Playfield Control register.
88 ; - Set the player (sprite) colour to light green (write to COLUP0.)
89 ; - Set the player (sprite) size/repetion to normal (write to NUSIZ0.)
90 ;
91
92 lda #$00
93 sta CTRLPF
94 lda #$0c
95 sta colour
96 lda #$0a
97 sta luminosity
98 lda #$00
99 sta NUSIZ0
100
101 ; and fall through to...
102
103 ;
104 ; Main loop.
105 ;
106 ; A typical main loop consists of:
107 ; - Waiting for the frame to start (vertical blank period)
108 ; - Displaying stuff on the screen (the _display kernel_)
109 ; - Doing any processing you like (reading joysticks, updating program state,
110 ; etc.), as long as you get it all done before the next frame starts!
111 ;
112
113 main:
114 jsr vertical_blank
115 jsr display_frame
116 jsr read_joystick
117 jmp main
118
119 ;
120 ; Vertical blank routine.
121 ;
122 ; In brief: wait until it is time for the next frame of video.
123 ; TODO: describe this in more detail.
124 ;
125
126 vertical_blank:
127 ldx #$00
128 lda #$02
129 sta WSYNC
130 sta WSYNC
131 sta WSYNC
132 sta VSYNC
133 sta WSYNC
134 sta WSYNC
135 lda #$2C
136 sta TIM64T
137 lda #$00
138 sta WSYNC
139 sta VSYNC
140 rts
141
142 ;
143 ; Display kernal.
144 ;
145 ; First, wait until it's time to display the frame.
146 ;
147
148 .scope
149 display_frame:
150 lda INTIM
151 bne display_frame
152
153 ;
154 ; (After that loop finishes, we know the accumulator must contain 0.)
155 ; Wait for the next scanline, zero HMOVE (for some reason; TODO discover
156 ; this), then turn on the screen.
157 ;
158
159 sta WSYNC
160 sta HMOVE
161 sta VBLANK
162
163 ;
164 ; Actual work in the display kernal is done here.
165 ;
166 ; This is a pathological approach to writing a display kernal.
167 ; This wouldn't be how you'd do things in a game. So be it.
168 ; One day I may improve it. For now, be happy that it displays
169 ; anything at all!
170 ;
171
172 ;
173 ; Wait for $3f (plus one?) scan lines to pass, by waiting for
174 ; WSYNC that many times.
175 ;
176
177 ldx #$3F
178 _wsync_loop:
179 sta WSYNC
180 dex
181 bpl _wsync_loop
182 sta WSYNC
183
184 ;
185 ; Delay while the raster scans across the screen. The more
186 ; we delay here, the more to the right the player will be when
187 ; we draw it.
188 ;
189
190 nop
191 nop
192 nop
193 nop
194 nop
195 nop
196 nop
197 nop
198 nop
199 nop
200 nop
201 nop
202 nop
203 nop
204 nop
205
206 ;
207 ; OK, *now* display the player.
208 ;
209
210 sta RESP0
211
212 ;
213 ; Loop over the rows of the sprite data, drawing each to the screen
214 ; over four scan lines.
215 ;
216 ; TODO understand this better and describe it!
217 ;
218
219 ldy #$07
220 _image_loop:
221 lda image_data, y
222 sta GP0
223
224 sta WSYNC
225 sta WSYNC
226 sta WSYNC
227 sta WSYNC
228 dey
229 bpl _image_loop
230
231 lda #$00
232 sta GP0
233
234 ;
235 ; Turn off screen display and clear display registers.
236 ;
237
238 lda #$02
239 sta WSYNC
240 sta VBLANK
241 lda #$00
242 sta PF0
243 sta PF1
244 sta PF2
245 sta COLUPF
246 sta COLUBK
247
248 rts
249 .scend
250
251
252 ;
253 ; Read the joystick and use it to modify the colour and luminosity
254 ; of the player.
255 ;
256
257 .scope
258 read_joystick:
259 lda joystick_delay
260 beq _continue
261
262 dec joystick_delay
263 rts
264
265 _continue:
266 lda SWCHA
267 and #$f0
268 cmp #$e0
269 beq _up
270 cmp #$d0
271 beq _down
272 cmp #$b0
273 beq _left
274 cmp #$70
275 beq _right
276 jmp _tail
277
278 _up:
279 inc luminosity
280 jmp _tail
281 _down:
282 dec luminosity
283 jmp _tail
284 _left:
285 dec colour
286 jmp _tail
287 _right:
288 inc colour
289 ;jmp _tail
290
291 _tail:
292 lda colour
293 and #$0f
294 sta colour
295
296 lda luminosity
297 and #$0f
298 sta luminosity
299
300 lda colour
301 clc
302 rol
303 rol
304 rol
305 rol
306 ora luminosity
307 sta COLUP0
308
309 lda #$06
310 sta joystick_delay
311
312 rts
313 .scend
314
315 ;
316 ; Player (sprite) data.
317 ;
318 ; Because we loop over these bytes with the Y register counting *down*,
319 ; this image is stored "upside-down".
320 ;
321
322 image_data:
323 .byte %01111110
324 .byte %10000001
325 .byte %10011001
326 .byte %10100101
327 .byte %10000001
328 .byte %10100101
329 .byte %10000001
330 .byte %01111110
331
332 ;
333 ; Standard postlude for Atari 2600 cartridge code.
334 ; Give BRK and boot vectors that point to the start of the code.
335 ;
336
337 .advance $FFFC
338 .word start
339 .word start
0 #!/bin/sh
1
2 sixtypical --prelude=atari2600 smiley.60p > smiley-60p.bin
3 if [ "x$COMPARE" != "x" ]; then
4 ophis smiley.oph -o smiley.bin
5 dcc6502 -o 0xf000 -m 200 smiley.bin > smiley.bin.disasm.txt
6 dcc6502 -o 0xf000 -m 200 smiley-60p.bin > smiley-60p.bin.disasm.txt
7 paste smiley.bin.disasm.txt smiley-60p.bin.disasm.txt | pr -t -e24
8 #diff -ru smiley.bin.disasm.txt smiley-60p.bin.disasm.txt
9 fi
0 // smiley.60p - SixtyPical translation of smiley.oph (2018),
1 // which is itself a stripped-down version of atari-2600-example.oph
2
3 byte VSYNC @ $00
4 byte VBLANK @ $01
5 byte WSYNC @ $02
6 byte NUSIZ0 @ $04
7 byte NUSIZ1 @ $05
8 byte COLUPF @ $08
9 byte COLUBK @ $09
10 byte PF0 @ $0D
11 byte PF1 @ $0E
12 byte PF2 @ $0F
13 byte SWCHA @ $280
14 byte INTIM @ $284
15 byte TIM64T @ $296
16 byte CTRLPF @ $0A
17 byte COLUP0 @ $06
18 byte COLUP1 @ $07
19 byte GP0 @ $1B
20 byte GP1 @ $1C
21 byte HMOVE @ $2a
22 byte RESP0 @ $10
23 byte RESP1 @ $11
24
25 byte colour @ $80
26 byte luminosity @ $81
27 byte joystick_delay @ $82
28
29 byte table[8] image_data : 126, 129, 153, 165, 129, 165, 129, 126
30 // %01111110
31 // %10000001
32 // %10011001
33 // %10100101
34 // %10000001
35 // %10100101
36 // %10000001
37 // %01111110
38
39
40 define vertical_blank routine
41 outputs VSYNC, WSYNC, TIM64T
42 trashes a, x, z, n
43 {
44 ld x, $00
45 ld a, $02
46 st a, WSYNC
47 st a, WSYNC
48 st a, WSYNC
49 st a, VSYNC
50 st a, WSYNC
51 st a, WSYNC
52 ld a, $2C
53 st a, TIM64T
54 ld a, $00
55 st a, WSYNC
56 st a, VSYNC
57 }
58
59 define display_frame routine
60 inputs INTIM, image_data
61 outputs WSYNC, HMOVE, VBLANK, RESP0, GP0, PF0, PF1, PF2, COLUPF, COLUBK
62 trashes a, x, y, z, n
63 {
64 repeat {
65 ld a, INTIM
66 } until z
67
68 //; (After that loop finishes, we know the accumulator must contain 0.)
69
70 st a, WSYNC
71 st a, HMOVE
72 st a, VBLANK
73
74 //;
75 //; Wait for $3f (plus one?) scan lines to pass, by waiting for
76 //; WSYNC that many times.
77 //;
78
79 ld x, $3F
80 repeat {
81 st a, WSYNC
82 dec x
83 } until n
84 st a, WSYNC
85
86 //;
87 //; Delay while the raster scans across the screen. The more
88 //; we delay here, the more to the right the player will be when
89 //; we draw it.
90 //;
91
92 nop
93 nop
94 nop
95 nop
96 nop
97 nop
98 nop
99 nop
100 nop
101 nop
102 nop
103 nop
104 nop
105 nop
106 nop
107
108 //;
109 //; OK, *now* display the player.
110 //;
111
112 st a, RESP0
113
114 //;
115 //; Loop over the rows of the sprite data, drawing each to the screen
116 //; over four scan lines.
117 //;
118 //; TODO understand this better and describe it!
119 //;
120
121 ld y, $07
122 for y down to 0 {
123 ld a, image_data + y
124 st a, GP0
125
126 st a, WSYNC
127 st a, WSYNC
128 st a, WSYNC
129 st a, WSYNC
130 } // FIXME original was "dec y; bpl _image_loop"
131
132 ld a, $00
133 st a, GP0
134
135 //;
136 //; Turn off screen display and clear display registers.
137 //;
138
139 ld a, $02
140 st a, WSYNC
141 st a, VBLANK
142 ld a, $00
143 st a, PF0
144 st a, PF1
145 st a, PF2
146 st a, COLUPF
147 st a, COLUBK
148 }
149
150 define colourize_player routine
151 inputs colour, luminosity
152 outputs COLUP0
153 trashes a, z, c, n
154 {
155 ld a, colour
156 st off, c
157 shl a
158 shl a
159 shl a
160 shl a
161 or a, luminosity
162 st a, COLUP0
163 }
164
165 define main routine
166 inputs image_data, INTIM
167 outputs CTRLPF, colour, luminosity, NUSIZ0, VSYNC, WSYNC, TIM64T, HMOVE, VBLANK, RESP0, GP0, PF0, PF1, PF2, COLUPF, COLUBK, COLUP0
168 trashes a, x, y, z, c, n
169 {
170 ld a, $00
171 st a, CTRLPF
172 ld a, $0c
173 st a, colour
174 ld a, $0a
175 st a, luminosity
176 ld a, $00
177 st a, NUSIZ0
178 repeat {
179 call vertical_blank
180 call display_frame
181 call colourize_player
182 } forever
183 }
0 ;
1 ; smiley.oph (2018)
2 ; stripped-down version of atari-2600-example.oph (2012)
3 ;
4 ; This work is in the public domain. See the file UNLICENSE for more info.
5 ;
6 ; to build and run in Stella:
7 ; ophis smiley.oph -o smiley.bin
8 ; stella smiley.bin
9
10 ;
11 ; Useful system addresses (TODO: briefly describe each of these.)
12 ;
13
14 .alias VSYNC $00
15 .alias VBLANK $01
16 .alias WSYNC $02
17 .alias NUSIZ0 $04
18 .alias NUSIZ1 $05
19 .alias COLUPF $08
20 .alias COLUBK $09
21 .alias PF0 $0D
22 .alias PF1 $0E
23 .alias PF2 $0F
24 .alias SWCHA $280
25 .alias INTIM $284
26 .alias TIM64T $296
27 .alias CTRLPF $0A
28 .alias COLUP0 $06
29 .alias COLUP1 $07
30 .alias GP0 $1B
31 .alias GP1 $1C
32 .alias HMOVE $2a
33 .alias RESP0 $10
34 .alias RESP1 $11
35
36 ;
37 ; Cartridge ROM occupies the top 4K of memory ($F000-$FFFF).
38 ; Thus, typically, the program will occupy all that space too.
39 ;
40 ; Zero-page RAM we can use with impunity starts at $80 and goes
41 ; upward (at least until $99, but probably further.)
42 ;
43
44 .alias colour $80
45 .alias luminosity $81
46 .alias joystick_delay $82
47
48 .org $F000
49
50 ;
51 ; Standard prelude for Atari 2600 cartridge code.
52 ;
53 ; Get various parts of the machine into a known state:
54 ;
55 ; - Disable interrupts
56 ; - Clear the Decimal flag
57 ; - Initialize the Stack Pointer
58 ; - Zero all bytes in Zero Page memory
59 ;
60
61 start:
62 sei
63 cld
64 ldx #$FF
65 txs
66 lda #$00
67
68 zero_loop:
69 sta $00, x
70 dex
71 bne zero_loop
72
73 ; and fall through to...
74
75 ;
76 ; Initialization.
77 ;
78 ; - Clear the Playfield Control register.
79 ; - Set the player (sprite) colour to light green (write to COLUP0.)
80 ; - Set the player (sprite) size/repetion to normal (write to NUSIZ0.)
81 ;
82
83 lda #$00
84 sta CTRLPF
85 lda #$0c
86 sta colour
87 lda #$0a
88 sta luminosity
89 lda #$00
90 sta NUSIZ0
91
92 ; and fall through to...
93
94 ;
95 ; Main loop.
96 ;
97 ; A typical main loop consists of:
98 ; - Waiting for the frame to start (vertical blank period)
99 ; - Displaying stuff on the screen (the _display kernel_)
100 ; - Doing any processing you like (reading joysticks, updating program state,
101 ; etc.), as long as you get it all done before the next frame starts!
102 ;
103
104 main:
105 jsr vertical_blank
106 jsr display_frame
107 jsr colourize_player
108 jmp main
109 rts ; NOTE just to pad out to match the SixtyPical version
110
111 ;
112 ; Vertical blank routine.
113 ;
114 ; In brief: wait until it is time for the next frame of video.
115 ; TODO: describe this in more detail.
116 ;
117
118 vertical_blank:
119 ldx #$00
120 lda #$02
121 sta WSYNC
122 sta WSYNC
123 sta WSYNC
124 sta VSYNC
125 sta WSYNC
126 sta WSYNC
127 lda #$2C
128 sta TIM64T
129 lda #$00
130 sta WSYNC
131 sta VSYNC
132 rts
133
134 ;
135 ; Display kernal.
136 ;
137 ; First, wait until it's time to display the frame.
138 ;
139
140 .scope
141 display_frame:
142 lda INTIM
143 bne display_frame
144
145 ;
146 ; (After that loop finishes, we know the accumulator must contain 0.)
147 ; Wait for the next scanline, zero HMOVE (for some reason; TODO discover
148 ; this), then turn on the screen.
149 ;
150
151 sta WSYNC
152 sta HMOVE
153 sta VBLANK
154
155 ;
156 ; Actual work in the display kernal is done here.
157 ;
158 ; This is a pathological approach to writing a display kernal.
159 ; This wouldn't be how you'd do things in a game. So be it.
160 ; One day I may improve it. For now, be happy that it displays
161 ; anything at all!
162 ;
163
164 ;
165 ; Wait for $3f (plus one?) scan lines to pass, by waiting for
166 ; WSYNC that many times.
167 ;
168
169 ldx #$3F
170 _wsync_loop:
171 sta WSYNC
172 dex
173 bpl _wsync_loop
174 sta WSYNC
175
176 ;
177 ; Delay while the raster scans across the screen. The more
178 ; we delay here, the more to the right the player will be when
179 ; we draw it.
180 ;
181
182 nop
183 nop
184 nop
185 nop
186 nop
187 nop
188 nop
189 nop
190 nop
191 nop
192 nop
193 nop
194 nop
195 nop
196 nop
197
198 ;
199 ; OK, *now* display the player.
200 ;
201
202 sta RESP0
203
204 ;
205 ; Loop over the rows of the sprite data, drawing each to the screen
206 ; over four scan lines.
207 ;
208 ; TODO understand this better and describe it!
209 ;
210
211 ldy #$07
212 _image_loop:
213 lda image_data, y
214 sta GP0
215
216 sta WSYNC
217 sta WSYNC
218 sta WSYNC
219 sta WSYNC
220 dey
221 bpl _image_loop
222
223 lda #$00
224 sta GP0
225
226 ;
227 ; Turn off screen display and clear display registers.
228 ;
229
230 lda #$02
231 sta WSYNC
232 sta VBLANK
233 lda #$00
234 sta PF0
235 sta PF1
236 sta PF2
237 sta COLUPF
238 sta COLUBK
239
240 rts
241 .scend
242
243 ;
244 ; Modify the colour and luminosity of the player.
245 ;
246
247 .scope
248 colourize_player:
249 lda colour
250 clc
251 rol
252 rol
253 rol
254 rol
255 ora luminosity
256 sta COLUP0
257 rts
258 .scend
259
260 ;
261 ; Player (sprite) data.
262 ;
263 ; Because we loop over these bytes with the Y register counting *down*,
264 ; this image is stored "upside-down".
265 ;
266
267 image_data:
268 .byte %01111110
269 .byte %10000001
270 .byte %10011001
271 .byte %10100101
272 .byte %10000001
273 .byte %10100101
274 .byte %10000001
275 .byte %01111110
276
277 ;
278 ; Standard postlude for Atari 2600 cartridge code.
279 ; Give BRK and boot vectors that point to the start of the code.
280 ;
281
282 .advance $FFFC
283 .word start
284 .word start
0 // This will not compile on its own, because there is no `main`.
1 // But this and `vector-main.60p` together will compile.
2
3 routine chrout
4 inputs a
5 trashes a
6 @ 65490
7
8 routine printa
9 trashes a, z, n
10 {
11 ld a, 65
12 call chrout
13 }
14
15 routine printb
16 trashes a, z, n
17 {
18 ld a, 66
19 call chrout
20 }
0 // This will not compile on its own, because `printa` and `printb` are not defined.
1 // But `vector-inc.60p` and this together will compile.
2
3 vector routine
4 trashes a, z, n
5 print
6
7 // routine printb
8 // trashes a, z, n
9 // {
10 // ld a, 66
11 // call chrout
12 // }
13
14 routine main
15 trashes print, a, z, n
16 {
17 copy printa, print
18 call print
19 copy printb, print
20 call print
21 }
00 #!/bin/sh
11
2 usage="Usage: loadngo.sh (c64|vic20) [--dry-run] <source.60p>"
2 usage="Usage: loadngo.sh (c64|vic20|atari2600) [--dry-run] <source.60p>"
33
44 arch="$1"
55 shift 1
1717 else
1818 emu="xvic"
1919 fi
20 elif [ "X$arch" = "Xatari2600" ]; then
21 prelude='atari2600'
22 emu='stella'
2023 else
2124 echo $usage && exit 1
2225 fi
100100 self._touched = set()
101101 self._range = dict()
102102 self._writeable = set()
103 self._has_encountered_goto = False
103 self._gotos_encountered = set()
104104
105105 for ref in inputs:
106106 if ref.is_constant():
272272 for ref in refs:
273273 self._writeable.add(ref)
274274
275 def set_encountered_goto(self):
276 self._has_encountered_goto = True
277
278 def has_encountered_goto(self):
279 return self._has_encountered_goto
275 def encounter_gotos(self, gotos):
276 self._gotos_encountered |= gotos
277
278 def encountered_gotos(self):
279 return self._gotos_encountered
280280
281281
282282 class Analyzer(object):
310310 assert isinstance(program, Program)
311311 self.routines = {r.location: r for r in program.routines}
312312 for routine in program.routines:
313 self.analyze_routine(routine)
313 context = self.analyze_routine(routine)
314 routine.encountered_gotos = list(context.encountered_gotos()) if context else []
314315
315316 def analyze_routine(self, routine):
316317 assert isinstance(routine, Routine)
345346 if ref in type_.outputs:
346347 raise UnmeaningfulOutputError(routine, ref.name)
347348
348 if not context.has_encountered_goto():
349 if not context.encountered_gotos():
349350 for ref in type_.outputs:
350351 context.assert_meaningful(ref, exception_class=UnmeaningfulOutputError)
351352 for ref in context.each_touched():
352353 if ref not in type_.outputs and ref not in type_.trashes and not routine_has_static(routine, ref):
353354 raise ForbiddenWriteError(routine, ref.name)
354355 self.current_routine = None
356 return context
355357
356358 def analyze_block(self, block, context):
357359 assert isinstance(block, Block)
378380 dest = instr.dest
379381 src = instr.src
380382
381 if context.has_encountered_goto():
383 if context.encountered_gotos():
382384 raise IllegalJumpError(instr, instr)
383385
384386 if opcode == 'ld':
594596 self.assert_affected_within('outputs', type_, current_type)
595597 self.assert_affected_within('trashes', type_, current_type)
596598
597 context.set_encountered_goto()
599 context.encounter_gotos(set([instr.location]))
598600 elif opcode == 'trash':
599601 context.set_touched(instr.dest)
600602 context.set_unmeaningful(instr.dest)
603 elif opcode == 'nop':
604 pass
601605 else:
602606 raise NotImplementedError(opcode)
603607
635639 context._touched = set(context1._touched) | set(context2._touched)
636640 context.set_meaningful(*list(outgoing_meaningful))
637641 context._writeable = set(context1._writeable) | set(context2._writeable)
638 if context1.has_encountered_goto() or context2.has_encountered_goto():
639 context.set_encountered_goto()
642 context.encounter_gotos(context1.encountered_gotos() | context2.encountered_gotos())
640643
641644 for ref in outgoing_trashes:
642645 context.set_touched(ref)
1414 CLC, SEC, ADC, SBC, ROL, ROR,
1515 INC, INX, INY, DEC, DEX, DEY,
1616 CMP, CPX, CPY, AND, ORA, EOR,
17 BCC, BCS, BNE, BEQ,
17 BCC, BCS, BNE, BEQ, BPL, BMI,
1818 JMP, JSR, RTS,
1919 SEI, CLI,
20 NOP,
2021 )
2122
2223
6566 return static_label
6667 return self.labels[name]
6768
69 def absolute_or_zero_page(self, label):
70 if label.addr is not None and label.addr < 256:
71 return ZeroPage(label)
72 else:
73 return Absolute(label)
74
6875 # visitor methods
6976
70 def compile_program(self, program):
77 def compile_program(self, program, compilation_roster=None):
7178 assert isinstance(program, Program)
7279
7380 defn_labels = []
94101 defn_labels.append((defn, label))
95102 self.routine_statics[routine.name] = static_labels
96103
97 self.compile_routine(self.routines['main'])
98 for routine in program.routines:
99 if routine.name != 'main':
100 self.compile_routine(routine)
104 if compilation_roster is None:
105 compilation_roster = [['main']] + [[routine.name] for routine in program.routines if routine.name != 'main']
106
107 for roster_row in compilation_roster:
108 for routine_name in roster_row[0:-1]:
109 self.compile_routine(self.routines[routine_name], skip_final_goto=True)
110 routine_name = roster_row[-1]
111 self.compile_routine(self.routines[routine_name])
101112
102113 for location, label in self.trampolines.iteritems():
103114 self.emitter.resolve_label(label)
114125 elif type_ == TYPE_WORD:
115126 initial_data = Word(defn.initial)
116127 elif TableType.is_a_table_type(type_, TYPE_BYTE):
117 initial_data = Table(defn.initial, type_.size)
128 initial_data = Table([Byte(i) for i in defn.initial], type_.size)
129 elif TableType.is_a_table_type(type_, TYPE_WORD):
130 initial_data = Table([Word(i) for i in defn.initial], type_.size)
118131 else:
119132 raise NotImplementedError(type_)
120133 label.set_length(initial_data.size())
126139 if defn.initial is None and defn.addr is None:
127140 self.emitter.resolve_bss_label(label)
128141
129 def compile_routine(self, routine):
142 def compile_routine(self, routine, skip_final_goto=False):
130143 self.current_routine = routine
144 self.skip_final_goto = skip_final_goto
145 self.final_goto_seen = False
131146 assert isinstance(routine, Routine)
132147 if routine.block:
133148 self.emitter.resolve_label(self.get_label(routine.name))
134149 self.compile_block(routine.block)
135 self.emitter.emit(RTS())
150 if not self.final_goto_seen:
151 self.emitter.emit(RTS())
136152 self.current_routine = None
153 self.skip_final_goto = False
137154
138155 def compile_block(self, block):
139156 assert isinstance(block, Block)
175192 elif isinstance(src, IndirectRef) and isinstance(src.ref.type, PointerType):
176193 self.emitter.emit(LDA(IndirectY(self.get_label(src.ref.name))))
177194 else:
178 self.emitter.emit(LDA(Absolute(self.get_label(src.name))))
195 self.emitter.emit(LDA(self.absolute_or_zero_page(self.get_label(src.name))))
179196 elif dest == REG_X:
180197 if src == REG_A:
181198 self.emitter.emit(TAX())
184201 elif isinstance(src, IndexedRef) and src.index == REG_Y:
185202 self.emitter.emit(LDX(AbsoluteY(self.get_label(src.ref.name))))
186203 else:
187 self.emitter.emit(LDX(Absolute(self.get_label(src.name))))
204 self.emitter.emit(LDX(self.absolute_or_zero_page(self.get_label(src.name))))
188205 elif dest == REG_Y:
189206 if src == REG_A:
190207 self.emitter.emit(TAY())
193210 elif isinstance(src, IndexedRef) and src.index == REG_X:
194211 self.emitter.emit(LDY(AbsoluteX(self.get_label(src.ref.name))))
195212 else:
196 self.emitter.emit(LDY(Absolute(self.get_label(src.name))))
213 self.emitter.emit(LDY(self.absolute_or_zero_page(self.get_label(src.name))))
197214 else:
198215 raise UnsupportedOpcodeError(instr)
199216 elif opcode == 'st':
213230 REG_X: AbsoluteX,
214231 REG_Y: AbsoluteY,
215232 }[dest.index]
216 label = self.get_label(dest.ref.name)
233 operand = mode_cls(self.get_label(dest.ref.name))
217234 elif isinstance(dest, IndirectRef) and isinstance(dest.ref.type, PointerType):
218 mode_cls = IndirectY
219 label = self.get_label(dest.ref.name)
220 else:
221 mode_cls = Absolute
222 label = self.get_label(dest.name)
223
224 if op_cls is None or mode_cls is None:
235 operand = IndirectY(self.get_label(dest.ref.name))
236 else:
237 operand = self.absolute_or_zero_page(self.get_label(dest.name))
238
239 if op_cls is None:
225240 raise UnsupportedOpcodeError(instr)
226 self.emitter.emit(op_cls(mode_cls(label)))
241 self.emitter.emit(op_cls(operand))
227242 elif opcode == 'add':
228243 if dest == REG_A:
229244 if isinstance(src, ConstantRef):
341356 else:
342357 raise NotImplementedError
343358 elif opcode == 'goto':
344 location = instr.location
345 label = self.get_label(instr.location.name)
346 if isinstance(location.type, RoutineType):
347 self.emitter.emit(JMP(Absolute(label)))
348 elif isinstance(location.type, VectorType):
349 self.emitter.emit(JMP(Indirect(label)))
350 else:
351 raise NotImplementedError
359 self.final_goto_seen = True
360 if self.skip_final_goto:
361 pass
362 else:
363 location = instr.location
364 label = self.get_label(instr.location.name)
365 if isinstance(location.type, RoutineType):
366 self.emitter.emit(JMP(Absolute(label)))
367 elif isinstance(location.type, VectorType):
368 self.emitter.emit(JMP(Indirect(label)))
369 else:
370 raise NotImplementedError
352371 elif opcode == 'copy':
353372 self.compile_copy(instr, instr.src, instr.dest)
354373 elif opcode == 'trash':
355374 pass
375 elif opcode == 'nop':
376 self.emitter.emit(NOP())
356377 else:
357378 raise NotImplementedError(opcode)
358379
508529 False: {
509530 'c': BCC,
510531 'z': BNE,
532 'n': BPL,
511533 },
512534 True: {
513535 'c': BCS,
514536 'z': BEQ,
537 'n': BMI,
515538 },
516539 }[instr.inverted].get(instr.src.name)
517540 if cls is None:
538561 False: {
539562 'c': BCC,
540563 'z': BNE,
564 'n': BPL,
541565 },
542566 True: {
543567 'c': BCS,
544568 'z': BEQ,
569 'n': BMI,
545570 },
546571 }[instr.inverted].get(instr.src.name)
547572 if cls is None:
1212
1313 class Byte(Emittable):
1414 def __init__(self, value):
15 if isinstance(value, basestring):
16 value = ord(value)
1517 if value < -127 or value > 255:
1618 raise IndexError(value)
1719 if value < 0:
4850
4951 class Table(Emittable):
5052 def __init__(self, value, size):
53 """`value` should be an iterable of Emittables."""
5154 # TODO: range-checking
5255 self.value = value
5356 self._size = size
5659 return self._size
5760
5861 def serialize(self, addr=None):
59 bytes = []
60 for b in self.value:
61 bytes.append(chr(ord(b)))
62 while len(bytes) < self.size():
63 bytes.append(chr(0))
64 return ''.join(bytes)
62 buf = ''.join([emittable.serialize() for emittable in self.value])
63 while len(buf) < self.size():
64 buf += chr(0)
65 return buf
6566
6667 def __repr__(self):
6768 return "%s()" % (self.__class__.__name__)
185186 advance the address for the next label, but don't emit anything."""
186187 self.resolve_label(label)
187188 self.addr += label.length
189
190 def size(self):
191 return sum(emittable.size() for emittable in self.accum)
192
193 def pad_to_size(self, size):
194 self_size = self.size()
195 if self_size > size:
196 raise IndexError("Emitter size {} exceeds pad size {}".format(self_size, size))
197 num_bytes = size - self_size
198 if num_bytes > 0:
199 self.accum.extend([Byte(0)] * num_bytes)
0 # encoding: UTF-8
1
2 from copy import copy
3
4 from sixtypical.model import RoutineType
5
6
7 class FallthruAnalyzer(object):
8
9 def __init__(self, debug=False):
10 self.debug = debug
11
12 def analyze_program(self, program):
13 self.program = program
14
15 self.fallthru_map = {}
16 for routine in program.routines:
17 encountered_gotos = list(routine.encountered_gotos)
18 if len(encountered_gotos) == 1 and isinstance(encountered_gotos[0].type, RoutineType):
19 self.fallthru_map[routine.name] = encountered_gotos[0].name
20 else:
21 self.fallthru_map[routine.name] = None
22
23 def find_chain(self, routine_name, available):
24 chain = [routine_name]
25 seen = set(chain)
26 while True:
27 next = self.fallthru_map.get(routine_name)
28 if next is None or next in seen or next not in available:
29 return chain
30 seen.add(next)
31 chain.append(next)
32 routine_name = next
33
34 def serialize(self):
35 pending_routines = copy(self.fallthru_map)
36 roster = []
37
38 main_chain = self.find_chain('main', pending_routines)
39 roster.append(main_chain)
40 for k in main_chain:
41 del pending_routines[k]
42
43 while pending_routines:
44 chains = [self.find_chain(k, pending_routines) for k in pending_routines.keys()]
45 chains.sort(key=len, reverse=True)
46 c = chains[0]
47 roster.append(c)
48 for k in c:
49 del pending_routines[k]
50
51 return roster
156156 class BNE(Instruction):
157157 opcodes = {
158158 Relative: 0xd0,
159 }
160
161
162 class BPL(Instruction):
163 opcodes = {
164 Relative: 0x10,
165 }
166
167
168 class BMI(Instruction):
169 opcodes = {
170 Relative: 0x30,
159171 }
160172
161173
311323 }
312324
313325
326 class NOP(Instruction):
327 opcodes = {
328 Implied: 0xEA,
329 }
330
331
314332 class SBC(Instruction):
315333 opcodes = {
316334 Immediate: 0xe9,
55 RoutineType, VectorType, TableType, BufferType, PointerType,
66 LocationRef, ConstantRef, IndirectRef, IndexedRef, AddressRef,
77 )
8 from sixtypical.scanner import Scanner, SixtyPicalSyntaxError
8 from sixtypical.scanner import Scanner
99
1010
1111 class SymEntry(object):
1717 return "%s(%r, %r)" % (self.__class__.__name__, self.ast_node, self.model)
1818
1919
20 class Parser(object):
21 def __init__(self, text):
22 self.scanner = Scanner(text)
20 class ParsingContext(object):
21 def __init__(self):
2322 self.symbols = {} # token -> SymEntry
24 self.current_statics = {} # token -> SymEntry
23 self.statics = {} # token -> SymEntry
2524 self.typedefs = {} # token -> Type AST
25 self.consts = {} # token -> Loc
26
2627 for token in ('a', 'x', 'y'):
2728 self.symbols[token] = SymEntry(None, LocationRef(TYPE_BYTE, token))
2829 for token in ('c', 'z', 'n', 'v'):
2930 self.symbols[token] = SymEntry(None, LocationRef(TYPE_BIT, token))
30 self.backpatch_instrs = []
31
32 def syntax_error(self, msg):
33 raise SixtyPicalSyntaxError(self.scanner.line_number, msg)
34
35 def soft_lookup(self, name):
36 if name in self.current_statics:
37 return self.current_statics[name].model
31
32 def __str__(self):
33 return "Symbols: {}\nStatics: {}\nTypedefs: {}\nConsts: {}".format(self.symbols, self.statics, self.typedefs, self.consts)
34
35 def lookup(self, name):
36 if name in self.statics:
37 return self.statics[name].model
3838 if name in self.symbols:
3939 return self.symbols[name].model
4040 return None
4141
42
43 class Parser(object):
44 def __init__(self, context, text, filename):
45 self.context = context
46 self.scanner = Scanner(text, filename)
47 self.backpatch_instrs = []
48
49 def syntax_error(self, msg):
50 self.scanner.syntax_error(msg)
51
4252 def lookup(self, name):
43 model = self.soft_lookup(name)
53 model = self.context.lookup(name)
4454 if model is None:
4555 self.syntax_error('Undefined symbol "{}"'.format(name))
4656 return model
5060 def program(self):
5161 defns = []
5262 routines = []
53 while self.scanner.on('typedef'):
54 typedef = self.typedef()
63 while self.scanner.on('typedef', 'const'):
64 if self.scanner.on('typedef'):
65 self.typedef()
66 if self.scanner.on('const'):
67 self.defn_const()
5568 typenames = ['byte', 'word', 'table', 'vector', 'buffer', 'pointer'] # 'routine',
56 typenames.extend(self.typedefs.keys())
69 typenames.extend(self.context.typedefs.keys())
5770 while self.scanner.on(*typenames):
5871 defn = self.defn()
5972 name = defn.name
60 if name in self.symbols:
73 if self.context.lookup(name):
6174 self.syntax_error('Symbol "%s" already declared' % name)
62 self.symbols[name] = SymEntry(defn, defn.location)
75 self.context.symbols[name] = SymEntry(defn, defn.location)
6376 defns.append(defn)
6477 while self.scanner.on('define', 'routine'):
6578 if self.scanner.consume('define'):
6982 else:
7083 routine = self.legacy_routine()
7184 name = routine.name
72 if name in self.symbols:
85 if self.context.lookup(name):
7386 self.syntax_error('Symbol "%s" already declared' % name)
74 self.symbols[name] = SymEntry(routine, routine.location)
87 self.context.symbols[name] = SymEntry(routine, routine.location)
7588 routines.append(routine)
7689 self.scanner.check_type('EOF')
7790
7891 # now backpatch the executable types.
79 #for type_name, type_ in self.typedefs.iteritems():
92 #for type_name, type_ in self.context.typedefs.iteritems():
8093 # type_.backpatch_constraint_labels(lambda w: self.lookup(w))
8194 for defn in defns:
8295 defn.location.type.backpatch_constraint_labels(lambda w: self.lookup(w))
8598 for instr in self.backpatch_instrs:
8699 if instr.opcode in ('call', 'goto'):
87100 name = instr.location
88 if name not in self.symbols:
89 self.syntax_error('Undefined routine "%s"' % name)
90 if not isinstance(self.symbols[name].model.type, (RoutineType, VectorType)):
101 model = self.lookup(name)
102 if not isinstance(model.type, (RoutineType, VectorType)):
91103 self.syntax_error('Illegal call of non-executable "%s"' % name)
92 instr.location = self.symbols[name].model
104 instr.location = model
93105 if instr.opcode in ('copy',) and isinstance(instr.src, basestring):
94106 name = instr.src
95 if name not in self.symbols:
96 self.syntax_error('Undefined routine "%s"' % name)
97 if not isinstance(self.symbols[name].model.type, (RoutineType, VectorType)):
107 model = self.lookup(name)
108 if not isinstance(model.type, (RoutineType, VectorType)):
98109 self.syntax_error('Illegal copy of non-executable "%s"' % name)
99 instr.src = self.symbols[name].model
110 instr.src = model
100111
101112 return Program(self.scanner.line_number, defns=defns, routines=routines)
102113
104115 self.scanner.expect('typedef')
105116 type_ = self.defn_type()
106117 name = self.defn_name()
107 if name in self.typedefs:
118 if name in self.context.typedefs:
108119 self.syntax_error('Type "%s" already declared' % name)
109 self.typedefs[name] = type_
120 self.context.typedefs[name] = type_
110121 return type_
122
123 def defn_const(self):
124 self.scanner.expect('const')
125 name = self.defn_name()
126 if name in self.context.consts:
127 self.syntax_error('Const "%s" already declared' % name)
128 loc = self.const()
129 self.context.consts[name] = loc
130 return loc
111131
112132 def defn(self):
113133 type_ = self.defn_type()
115135
116136 initial = None
117137 if self.scanner.consume(':'):
118 if isinstance(type_, TableType) and self.scanner.on_type('string literal'):
119 initial = self.scanner.token
138 if isinstance(type_, TableType):
139 if self.scanner.on_type('string literal'):
140 initial = self.scanner.token
141 self.scanner.scan()
142 else:
143 initial = []
144 initial.append(self.const().value)
145 while self.scanner.consume(','):
146 initial.append(self.const().value)
120147 else:
121 self.scanner.check_type('integer literal')
122 initial = int(self.scanner.token)
123 self.scanner.scan()
148 initial = self.const().value
124149
125150 addr = None
126151 if self.scanner.consume('@'):
135160
136161 return Defn(self.scanner.line_number, name=name, addr=addr, initial=initial, location=location)
137162
138 def literal_int(self):
139 self.scanner.check_type('integer literal')
140 c = int(self.scanner.token)
141 self.scanner.scan()
142 return c
143
144 def literal_int_const(self):
145 value = self.literal_int()
146 type_ = TYPE_WORD if value > 255 else TYPE_BYTE
147 loc = ConstantRef(type_, value)
148 return loc
163 def const(self):
164 if self.scanner.token in ('on', 'off'):
165 loc = ConstantRef(TYPE_BIT, 1 if self.scanner.token == 'on' else 0)
166 self.scanner.scan()
167 return loc
168 elif self.scanner.on_type('integer literal'):
169 value = int(self.scanner.token)
170 self.scanner.scan()
171 type_ = TYPE_WORD if value > 255 else TYPE_BYTE
172 loc = ConstantRef(type_, value)
173 return loc
174 elif self.scanner.consume('word'):
175 loc = ConstantRef(TYPE_WORD, int(self.scanner.token))
176 self.scanner.scan()
177 return loc
178 elif self.scanner.token in self.context.consts:
179 loc = self.context.consts[self.scanner.token]
180 self.scanner.scan()
181 return loc
182 else:
183 self.syntax_error('bad constant "%s"' % self.scanner.token)
149184
150185 def defn_size(self):
151186 self.scanner.expect('[')
152 size = self.literal_int()
187 size = self.const().value
153188 self.scanner.expect(']')
154189 return size
155190
192227 else:
193228 type_name = self.scanner.token
194229 self.scanner.scan()
195 if type_name not in self.typedefs:
230 if type_name not in self.context.typedefs:
196231 self.syntax_error("Undefined type '%s'" % type_name)
197 type_ = self.typedefs[type_name]
232 type_ = self.context.typedefs[type_name]
198233
199234 return type_
200235
250285 else:
251286 statics = self.statics()
252287
253 self.current_statics = self.compose_statics_dict(statics)
288 self.context.statics = self.compose_statics_dict(statics)
254289 block = self.block()
255 self.current_statics = {}
290 self.context.statics = {}
256291
257292 addr = None
258293 location = LocationRef(type_, name)
266301 c = {}
267302 for defn in statics:
268303 name = defn.name
269 if name in self.symbols or name in self.current_statics:
304 if self.context.lookup(name):
270305 self.syntax_error('Symbol "%s" already declared' % name)
271306 c[name] = SymEntry(defn, defn.location)
272307 return c
293328 return accum
294329
295330 def locexpr(self, forward=False):
296 if self.scanner.token in ('on', 'off'):
297 loc = ConstantRef(TYPE_BIT, 1 if self.scanner.token == 'on' else 0)
298 self.scanner.scan()
299 return loc
300 elif self.scanner.on_type('integer literal'):
301 return self.literal_int_const()
302 elif self.scanner.consume('word'):
303 loc = ConstantRef(TYPE_WORD, int(self.scanner.token))
304 self.scanner.scan()
305 return loc
331 if self.scanner.token in ('on', 'off', 'word') or self.scanner.token in self.context.consts or self.scanner.on_type('integer literal'):
332 return self.const()
306333 elif forward:
307334 name = self.scanner.token
308335 self.scanner.scan()
309 loc = self.soft_lookup(name)
336 loc = self.context.lookup(name)
310337 if loc is not None:
311338 return loc
312339 else:
386413 else:
387414 self.syntax_error('expected "up" or "down", found "%s"' % self.scanner.token)
388415 self.scanner.expect('to')
389 final = self.literal_int_const()
416 final = self.const()
390417 block = self.block()
391418 return For(self.scanner.line_number, dest=dest, direction=direction, final=final, block=block)
392419 elif self.scanner.token in ("ld",):
416443 self.scanner.scan()
417444 dest = self.locexpr()
418445 return SingleOp(self.scanner.line_number, opcode=opcode, dest=dest, src=None)
446 elif self.scanner.token in ("nop",):
447 opcode = self.scanner.token
448 self.scanner.scan()
449 return SingleOp(self.scanner.line_number, opcode=opcode, dest=None, src=None)
419450 elif self.scanner.token in ("call", "goto"):
420451 opcode = self.scanner.token
421452 self.scanner.scan()
33
44
55 class SixtyPicalSyntaxError(ValueError):
6 def __init__(self, line_number, message):
7 super(SixtyPicalSyntaxError, self).__init__(line_number, message)
6 def __init__(self, filename, line_number, message):
7 super(SixtyPicalSyntaxError, self).__init__(filename, line_number, message)
88
99 def __str__(self):
10 return "Line {}: {}".format(self.args[0], self.args[1])
10 return "{}, line {}: {}".format(self.args[0], self.args[1], self.args[2])
1111
1212
1313 class Scanner(object):
14 def __init__(self, text):
14 def __init__(self, text, filename):
1515 self.text = text
16 self.filename = filename
1617 self.token = None
1718 self.type = None
1819 self.line_number = 1
6162 if self.token == token:
6263 self.scan()
6364 else:
64 raise SixtyPicalSyntaxError(self.line_number, "Expected '{}', but found '{}'".format(
65 token, self.token
66 ))
65 self.syntax_error("Expected '{}', but found '{}'".format(token, self.token))
6766
6867 def on(self, *tokens):
6968 return self.token in tokens
7372
7473 def check_type(self, type):
7574 if not self.type == type:
76 raise SixtyPicalSyntaxError(self.line_number, "Expected {}, but found '{}'".format(
77 self.type, self.token
78 ))
75 self.syntax_error("Expected {}, but found '{}'".format(self.type, self.token))
7976
8077 def consume(self, token):
8178 if self.token == token:
8380 return True
8481 else:
8582 return False
83
84 def syntax_error(self, msg):
85 raise SixtyPicalSyntaxError(self.filename, self.line_number, msg)
22 falderal --substring-error \
33 tests/SixtyPical\ Syntax.md \
44 tests/SixtyPical\ Analysis.md \
5 tests/SixtyPical\ Fallthru.md \
56 tests/SixtyPical\ Compilation.md
10091009
10101010 ### cmp ###
10111011
1012 Some rudimentary tests for cmp.
1012 Some rudimentary tests for `cmp`.
10131013
10141014 | routine main
10151015 | inputs a
10361036
10371037 ### and ###
10381038
1039 Some rudimentary tests for and.
1039 Some rudimentary tests for `and`.
10401040
10411041 | routine main
10421042 | inputs a
10631063
10641064 ### or ###
10651065
1066 Writing unit tests on a train. Wow.
1066 Some rudimentary tests for `or`.
10671067
10681068 | routine main
10691069 | inputs a
10901090
10911091 ### xor ###
10921092
1093 Writing unit tests on a train. Wow.
1093 Some rudimentary tests for `xor`.
10941094
10951095 | routine main
10961096 | inputs a
11171117
11181118 ### shl ###
11191119
1120 Some rudimentary tests for shl.
1120 Some rudimentary tests for `shl`.
11211121
11221122 | routine main
11231123 | inputs a, c
11451145
11461146 ### shr ###
11471147
1148 Some rudimentary tests for shr.
1148 Some rudimentary tests for `shr`.
11491149
11501150 | routine main
11511151 | inputs a, c
11701170 | shr a
11711171 | }
11721172 ? UnmeaningfulReadError: c
1173
1174 ### nop ###
1175
1176 Some rudimentary tests for `nop`.
1177
1178 | routine main
1179 | {
1180 | nop
1181 | }
1182 = ok
11731183
11741184 ### call ###
11751185
16581668 | }
16591669 = ok
16601670
1671 While `repeat` is most often used with `z`, it can also be used with `n`.
1672
1673 | routine main
1674 | outputs y, n, z
1675 | {
1676 | ld y, 15
1677 | repeat {
1678 | dec y
1679 | } until n
1680 | }
1681 = ok
1682
16611683 ### for ###
16621684
16631685 Basic "open-faced for" loop. We'll start with the "upto" variant.
1616 | {
1717 | }
1818 = $080D RTS
19
20 `nop` program.
21
22 | routine main
23 | {
24 | nop
25 | }
26 = $080D NOP
27 = $080E RTS
1928
2029 Rudimentary program.
2130
101110 = $080D LDA #$64
102111 = $080F STA $0400
103112 = $0812 RTS
113
114 Accesses to memory locations in zero-page with `ld` and `st` use zero-page addressing.
115
116 | byte zp @ $00
117 | byte screen @ 100
118 |
119 | routine main
120 | inputs screen, zp
121 | outputs screen, zp
122 | trashes a, z, n
123 | {
124 | ld a, screen
125 | st a, screen
126 | ld a, zp
127 | st a, zp
128 | }
129 = $080D LDA $64
130 = $080F STA $64
131 = $0811 LDA $00
132 = $0813 STA $00
133 = $0815 RTS
104134
105135 Memory location with initial value.
106136
136166 = $081A .byte $BB
137167 = $081B .byte $0B
138168
139 Initialized byte table. Bytes allocated, but beyond the string, are 0's.
169 Initialized byte table, initialized with ASCII string. Bytes allocated, but beyond the string, are 0's.
140170
141171 | byte table[8] message : "WHAT?"
142172 |
157187 = $0818 BRK
158188 = $0819 BRK
159189 = $081A BRK
190
191 Initialized byte table, initialized with list of byte values.
192
193 | byte table[8] message : 255, 0, 129, 128, 127
194 |
195 | routine main
196 | inputs message
197 | outputs x, a, z, n
198 | {
199 | ld x, 0
200 | ld a, message + x
201 | }
202 = $080D LDX #$00
203 = $080F LDA $0813,X
204 = $0812 RTS
205 = $0813 .byte $FF
206 = $0814 BRK
207 = $0815 STA ($80,X)
208 = $0817 .byte $7F
209 = $0818 BRK
210 = $0819 BRK
211 = $081A BRK
212
213 Initialized word table, initialized with list of word values.
214
215 | word table[8] message : 65535, 0, 127
216 |
217 | routine main
218 | {
219 | }
220 = $080D RTS
221 = $080E .byte $FF
222 = $080F .byte $FF
223 = $0810 BRK
224 = $0811 BRK
225 = $0812 .byte $7F
226 = $0813 BRK
227 = $0814 BRK
228 = $0815 BRK
160229
161230 Some instructions.
162231
287356 = $0813 LDY #$01
288357 = $0815 RTS
289358
290 Compiling `repeat`.
359 Compiling `repeat ... until z`.
291360
292361 | routine main
293362 | trashes a, y, z, n, c
306375 = $0813 BNE $080F
307376 = $0815 RTS
308377
309 Compiling `repeat until not`.
378 Compiling `repeat ... until not z`.
310379
311380 | routine main
312381 | trashes a, y, z, n, c
324393 = $0811 CPY #$5B
325394 = $0813 BEQ $080F
326395 = $0815 RTS
396
397 Compiling `repeat ... until n`.
398
399 | routine main
400 | trashes a, y, z, n, c
401 | {
402 | ld y, 65
403 | repeat {
404 | ld a, y
405 | dec y
406 | } until n
407 | }
408 = $080D LDY #$41
409 = $080F TYA
410 = $0810 DEY
411 = $0811 BPL $080F
412 = $0813 RTS
413
414 Compiling `repeat ... until not n`.
415
416 | routine main
417 | trashes a, y, z, n, c
418 | {
419 | ld y, 199
420 | repeat {
421 | ld a, y
422 | inc y
423 | } until not n
424 | }
425 = $080D LDY #$C7
426 = $080F TYA
427 = $0810 INY
428 = $0811 BMI $080F
429 = $0813 RTS
327430
328431 Compiling `repeat forever`.
329432
672775 = $081E JMP ($0822)
673776 = $0821 RTS
674777
675 goto.
778 Compiling `goto`. Note that no `RTS` is emitted after the `JMP`.
676779
677780 | routine bar
678781 | inputs y
690793 | goto bar
691794 | }
692795 = $080D LDY #$C8
693 = $080F JMP $0813
694 = $0812 RTS
695 = $0813 LDX #$C8
696 = $0815 RTS
796 = $080F JMP $0812
797 = $0812 LDX #$C8
798 = $0814 RTS
697799
698800 ### Vector tables
699801
0 SixtyPical Fallthru
1 ===================
2
3 This is a test suite, written in [Falderal][] format, for SixtyPical's
4 ability to detect which routines make tail calls to other routines,
5 and thus can be re-arranged to simply "fall through" to them.
6
7 The theory is as follows.
8
9 SixtyPical supports a `goto`, but it can only appear in tail position.
10 If a routine r1 ends with a unique `goto` to a fixed routine r2 it is said
11 to *potentially fall through* to r2.
12
13 A *unique* `goto` means that there are not multiple different `goto`s in
14 tail position (which can happen if, for example, an `if` is the last thing
15 in a routine, and each branch of that `if` ends with a different `goto`.)
16
17 A *fixed* routine means, a routine which is known at compile time, not a
18 `goto` through a vector.
19
20 Consider the set R of all available routines in the program.
21
22 Every routine either potentially falls through to a single other routine
23 or it does not potentially fall through to any routine.
24
25 More formally, we can say
26
27 > fall : R → R ∪ {nil}, fall(r) ≠ r
28
29 where `nil` is an atom that represents no routine.
30
31 Now consider an operation chain() vaguely similar to a transitive closure
32 on fall(). Starting with r, we construct a list of r, fall(r),
33 fall(fall(r)), ... with the following restrictions:
34
35 - we stop when we reach `nil` (because fall(`nil`) is not defined)
36 - we stop when we see an element that is not in R.
37 - we stop when we see an element that we have already added to the
38 list (this is to prevent infinite lists due to cycles.)
39
40 With these definitions, our algorithm is something like this.
41
42 Treat R as a mutable set and start with an empty list of lists L. Then,
43
44 - For all r ∈ R, find all chain(r).
45 - Pick a longest such chain. Call it C.
46 - Append C to L.
47 - Remove all elements occurring in C, from R.
48 - Repeat until R is empty.
49
50 When times comes to generate code, generate it in the order given by L.
51 In addition, each sublist in L represents a number of routines to
52 generate; all except the final routine in such a sublist need not have
53 any jump instruction generated for its final `goto`.
54
55 The tests in this document test against the list L.
56
57 Note that this optimization is a feature of the SixtyPical's reference
58 compiler, not the language. So an implementation is not required
59 to pass these tests to be considered an implementation of SixtyPical.
60
61 [Falderal]: http://catseye.tc/node/Falderal
62
63 -> Functionality "Dump fallthru info for SixtyPical program" is implemented by
64 -> shell command "bin/sixtypical --optimize-fallthru --dump-fallthru-info --analyze-only --traceback %(test-body-file)"
65
66 -> Functionality "Compile SixtyPical program with fallthru optimization" is implemented by
67 -> shell command "bin/sixtypical --prelude=c64 --optimize-fallthru --traceback %(test-body-file) >/tmp/foo && tests/appliances/bin/dcc6502-adapter </tmp/foo"
68
69 -> Tests for functionality "Dump fallthru info for SixtyPical program"
70
71 A single routine, obviously, falls through to nothing and has nothing fall
72 through to it.
73
74 | define main routine
75 | {
76 | }
77 = [
78 = [
79 = "main"
80 = ]
81 = ]
82
83 If `main` does a `goto foo`, then it can fall through to `foo`.
84
85 | define foo routine trashes a, z, n
86 | {
87 | ld a, 0
88 | }
89 |
90 | define main routine trashes a, z, n
91 | {
92 | goto foo
93 | }
94 = [
95 = [
96 = "main",
97 = "foo"
98 = ]
99 = ]
100
101 More than one routine can fall through to a routine. We pick one
102 of them to fall through, when selecting the order of routines.
103
104 | define foo routine trashes a, z, n
105 | {
106 | ld a, 0
107 | }
108 |
109 | define bar routine trashes a, z, n
110 | {
111 | ld a, 0
112 | goto foo
113 | }
114 |
115 | define main routine trashes a, z, n
116 | {
117 | goto foo
118 | }
119 = [
120 = [
121 = "main",
122 = "foo"
123 = ],
124 = [
125 = "bar"
126 = ]
127 = ]
128
129 Because `main` is always serialized first (so that the entry
130 point of the entire program appears at the beginning of the code),
131 nothing ever falls through to `main`.
132
133 | define foo routine trashes a, z, n
134 | {
135 | ld a, 0
136 | goto main
137 | }
138 |
139 | define main routine trashes a, z, n
140 | {
141 | ld a, 1
142 | }
143 = [
144 = [
145 = "main"
146 = ],
147 = [
148 = "foo"
149 = ]
150 = ]
151
152 There is nothing stopping two routines from tail-calling each
153 other, but we will only be able to make one of them, at most,
154 fall through to the other.
155
156 | define foo routine trashes a, z, n
157 | {
158 | ld a, 0
159 | goto bar
160 | }
161 |
162 | define bar routine trashes a, z, n
163 | {
164 | ld a, 0
165 | goto foo
166 | }
167 |
168 | define main routine trashes a, z, n
169 | {
170 | }
171 = [
172 = [
173 = "main"
174 = ],
175 = [
176 = "bar",
177 = "foo"
178 = ]
179 = ]
180
181 If a routine does two tail calls (which is possible because they
182 can be in different branches of an `if`) it cannot fall through to another
183 routine.
184
185 | define foo routine trashes a, z, n
186 | {
187 | ld a, 0
188 | }
189 |
190 | define bar routine trashes a, z, n
191 | {
192 | ld a, 0
193 | }
194 |
195 | define main routine inputs z trashes a, z, n
196 | {
197 | if z {
198 | goto foo
199 | } else {
200 | goto bar
201 | }
202 | }
203 = [
204 = [
205 = "main"
206 = ],
207 = [
208 = "bar"
209 = ],
210 = [
211 = "foo"
212 = ]
213 = ]
214
215 If, however, they are the same goto, one can be optimized away.
216
217 | define foo routine trashes a, z, n
218 | {
219 | ld a, 0
220 | if z {
221 | ld a, 1
222 | goto bar
223 | } else {
224 | ld a, 2
225 | goto bar
226 | }
227 | }
228 |
229 | define bar routine trashes a, z, n
230 | {
231 | ld a, 255
232 | }
233 |
234 | define main routine trashes a, z, n
235 | {
236 | }
237 = [
238 = [
239 = "main"
240 = ],
241 = [
242 = "foo",
243 = "bar"
244 = ]
245 = ]
246
247 Similarly, a tail call to a vector can't be turned into a fallthru,
248 because we don't necessarily know what actual routine the vector contains.
249
250 | vector routine trashes a, z, n
251 | vec
252 |
253 | define foo routine trashes a, z, n
254 | {
255 | ld a, 0
256 | }
257 |
258 | define bar routine trashes a, z, n
259 | {
260 | ld a, 0
261 | }
262 |
263 | define main routine outputs vec trashes a, z, n
264 | {
265 | copy bar, vec
266 | goto vec
267 | }
268 = [
269 = [
270 = "main"
271 = ],
272 = [
273 = "bar"
274 = ],
275 = [
276 = "foo"
277 = ]
278 = ]
279
280 Our algorithm might not be strictly optimal, but it does a good job.
281
282 | define r1 routine trashes a, z, n
283 | {
284 | ld a, 0
285 | goto r2
286 | }
287 |
288 | define r2 routine trashes a, z, n
289 | {
290 | ld a, 0
291 | goto r3
292 | }
293 |
294 | define r3 routine trashes a, z, n
295 | {
296 | ld a, 0
297 | goto r4
298 | }
299 |
300 | define r4 routine trashes a, z, n
301 | {
302 | ld a, 0
303 | }
304 |
305 | define r5 routine trashes a, z, n
306 | {
307 | ld a, 0
308 | goto r6
309 | }
310 |
311 | define r6 routine trashes a, z, n
312 | {
313 | ld a, 0
314 | goto r3
315 | }
316 |
317 | define main routine trashes a, z, n
318 | {
319 | goto r1
320 | }
321 = [
322 = [
323 = "main",
324 = "r1",
325 = "r2",
326 = "r3",
327 = "r4"
328 = ],
329 = [
330 = "r5",
331 = "r6"
332 = ]
333 = ]
334
335 -> Tests for functionality "Compile SixtyPical program with fallthru optimization"
336
337 Basic test for actually applying this optimization when compiling SixtyPical programs.
338
339 | define foo routine trashes a, z, n
340 | {
341 | ld a, 0
342 | }
343 |
344 | define bar routine trashes a, z, n
345 | {
346 | ld a, 255
347 | goto foo
348 | }
349 |
350 | define main routine trashes a, z, n
351 | {
352 | goto foo
353 | }
354 = $080D LDA #$00
355 = $080F RTS
356 = $0810 LDA #$FF
357 = $0812 JMP $080D
358
359 It can optimize out one of the `goto`s if they are the same.
360
361 | define foo routine trashes a, z, n
362 | {
363 | ld a, 0
364 | if z {
365 | ld a, 1
366 | goto bar
367 | } else {
368 | ld a, 2
369 | goto bar
370 | }
371 | }
372 |
373 | define bar routine trashes a, z, n
374 | {
375 | ld a, 255
376 | }
377 |
378 | define main routine trashes a, z, n
379 | {
380 | }
381 = $080D RTS
382 = $080E LDA #$00
383 = $0810 BNE $0817
384 = $0812 LDA #$01
385 = $0814 JMP $0819
386 = $0817 LDA #$02
387 = $0819 LDA #$FF
388 = $081B RTS
389
390 It cannot optimize out the `goto`s if they are different.
391
392 Note, this currently produces unfortunately unoptimized code,
393 because generating code for the "true" branch of an `if` always
394 generates a jump out of the `if`, even if the last instruction
395 in the "true" branch is a `goto`.
396
397 | define foo routine trashes a, z, n
398 | {
399 | ld a, 0
400 | if z {
401 | ld a, 1
402 | goto bar
403 | } else {
404 | ld a, 2
405 | goto main
406 | }
407 | }
408 |
409 | define bar routine trashes a, z, n
410 | {
411 | ld a, 255
412 | }
413 |
414 | define main routine trashes a, z, n
415 | {
416 | }
417 = $080D RTS
418 = $080E LDA #$FF
419 = $0810 RTS
420 = $0811 LDA #$00
421 = $0813 BNE $081D
422 = $0815 LDA #$01
423 = $0817 JMP $080E
424 = $081A JMP $0822
425 = $081D LDA #$02
426 = $081F JMP $080D
7474 | routine main {
7575 | trash a
7676 | trash n
77 | }
78 = ok
79
80 `nop`.
81
82 | routine main
83 | {
84 | nop
7785 | }
7886 = ok
7987
227235 | }
228236 ? SyntaxError
229237
238 Constants.
239
240 | const lives 3
241 | const days lives
242 | const w1 1000
243 | const w2 word 0
244 |
245 | typedef byte table[days] them
246 |
247 | byte lark: lives
248 |
249 | routine main {
250 | ld a, lives
251 | }
252 = ok
253
254 Can't have two constants with the same name.
255
256 | const w1 1000
257 | const w1 word 0
258 |
259 | routine main {
260 | }
261 ? SyntaxError
262
230263 Explicit memory address.
231264
232265 | byte screen @ 1024
268301 | }
269302 = ok
270303
271 Initialized byte table.
304 Initialized byte table, initialized with ASCII string.
272305
273306 | byte table[32] message : "WHAT DO YOU WANT TO DO NEXT?"
274307 |
283316 | routine main {
284317 | }
285318 ? SyntaxError
319
320 Initialized byte table, initialized with list of bytes.
321
322 | byte table[8] charmap : 0, 255, 129, 192, 0, 1, 2, 4
323 |
324 | routine main {
325 | }
326 = ok
286327
287328 Can't access an undeclared memory location.
288329