Merge pull request #4 from catseye/develop-0.10
Develop 0.10
Chris Pressey authored 5 years ago
GitHub committed 5 years ago
0 | 0 | History of SixtyPical |
1 | 1 | ===================== |
2 | ||
3 | 0.10 | |
4 | ---- | |
5 | ||
6 | * Can `call` and `goto` routines that are defined further down in the source code. | |
7 | * The `forward` modifier can also be used to indicate that the symbol being copied | |
8 | in a `copy` to a vector is a routine that is defined further down in the source. | |
9 | * Initialized `word` memory locations. | |
10 | * Can `copy` a literal word to a word table. | |
11 | * Subtract word (constant or memory location) from word memory location. | |
12 | * `trash` instruction explicitly indicates a value is no longer considered meaningful. | |
13 | * `copy []+y, a` can indirectly read a byte value into the `a` register. | |
14 | * Fixed bug which was preventing `if` branches to diverge in what they initialized, | |
15 | if it was already initialized when going into the `if`. | |
16 | * Fixed a bug which was making it crash when trying to analyze `repeat forever` loops. | |
2 | 17 | |
3 | 18 | 0.9 |
4 | 19 | --- |
22 | 22 | programs to 6502 machine code. |
23 | 23 | |
24 | 24 | SixtyPical is a work in progress. The current released version of SixtyPical |
25 | is 0.9. | |
25 | is 0.10. | |
26 | 26 | |
27 | 27 | Documentation |
28 | 28 | ------------- |
45 | 45 | the joystick (i.e. bring it up to par with the original demo game that was written |
46 | 46 | for SixtyPical) |
47 | 47 | |
48 | ### `call` routines that are defined further down in the source code | |
49 | ||
50 | We might have a graph of states that refer to each other and that want to `goto` | |
51 | each other. Thus we need this. We have it for vectors, but we need it for `call`. | |
52 | ||
53 | ### Allow branches to diverge in what they touch | |
54 | ||
55 | For example, if the routine inputs and outputs `foo`, and one branch of an `if` | |
56 | sets `foo` and the other does not touch it, that should be OK. | |
57 | ||
58 | 48 | ### `vector table` type |
59 | 49 | |
60 | 50 | ### `low` and `high` address operators |
74 | 64 | This should be tracked in the abstract interpretation. |
75 | 65 | (If only because abstract interpretation is the major point of this project!) |
76 | 66 | |
67 | ### Routine-local static memory locations | |
68 | ||
69 | These would not need to appear in the inputs/outputs/trashes sets of the routines | |
70 | that call this routine. | |
71 | ||
72 | These might be forced to specify an initial value so that they can always be | |
73 | assumed to be meaningful. | |
74 | ||
75 | ### More modes for `copy` | |
76 | ||
77 | * don't allow `copy foo, a` probably. insist on `ld a, foo` for this. | |
78 | * have `copy` instruction able to copy a byte to a user-def mem loc, etc. | |
79 | * `copy x, [ptr] + y` | |
80 | * Maybe even `copy [ptra] + y, [ptrb] + y`, which can be compiled to indirect LDA then indirect STA! | |
81 | ||
77 | 82 | ### And at some point... |
78 | 83 | |
79 | * Compare word (constant or memory location) with memory location or pointer. (Maybe?) | |
80 | * `copy x, [ptr] + y` | |
81 | * Maybe even `copy [ptra] + y, [ptrb] + y`, which can be compiled to indirect LDA then indirect STA! | |
82 | 84 | * Check that the buffer being read or written to through pointer, appears in approporiate inputs or outputs set. |
83 | 85 | * `byte table` and `word table` of sizes other than 256 |
84 | * initialized `byte table` memory locations | |
85 | 86 | * always analyze before executing or compiling, unless told not to |
86 | * `trash` instruction. | |
87 | 87 | * `interrupt` routines -- to indicate that "the supervisor" has stored values on the stack, so we can trash them. |
88 | * pre-initialized `word` variables | |
89 | 88 | * error messages that include the line number of the source code |
90 | * have `copy` instruction able to copy a byte to a user-def mem loc, etc. | |
91 | 89 | * add absolute addressing in shl/shr, absolute-indexed for add, sub, etc. |
92 | 90 | * check and disallow recursion. |
93 | 91 | * automatic tail-call optimization (could be tricky, w/constraints?) |
0 | 0 | SixtyPical |
1 | 1 | ========== |
2 | 2 | |
3 | This document describes the SixtyPical programming language version 0.9, | |
3 | This document describes the SixtyPical programming language version 0.10, | |
4 | 4 | both its execution aspect and its static analysis aspect (even though |
5 | 5 | these are, technically speaking, separate concepts.) |
6 | 6 |
26 | 26 | // ---------------------------------------------------------------- |
27 | 27 | |
28 | 28 | pointer ptr @ 254 |
29 | ||
30 | word table actor_pos | |
29 | 31 | word pos |
32 | word new_pos | |
33 | ||
34 | word table actor_delta | |
30 | 35 | word delta |
36 | ||
31 | 37 | byte button_down : 0 // effectively static-local to check_button |
38 | byte table press_fire_msg: "PRESS`FIRE`TO`PLAY" | |
39 | ||
40 | byte save_x | |
41 | word compare_target | |
32 | 42 | |
33 | 43 | // |
34 | 44 | // Points to the routine that implements the current game state. |
35 | 45 | // |
46 | // It's very arguable that screen1/2/3/4 and colormap1/2/3/4 are not REALLY inputs. | |
47 | // They're only there to support the fact that game states sometimes clear the | |
48 | // screen, and sometimes don't. When they don't, they preserve the screen, and | |
49 | // currently the way to say "we preserve the screen" is to have it as both input | |
50 | // and output. There is probably a better way to do this, but it needs thought. | |
51 | // | |
36 | 52 | |
37 | 53 | vector dispatch_game_state |
38 | inputs joy2, pos, button_down, dispatch_game_state | |
39 | outputs delta, pos, screen, screen1, button_down, dispatch_game_state | |
40 | trashes a, x, y, c, z, n, v, ptr | |
54 | inputs joy2, button_down, press_fire_msg, dispatch_game_state, save_x, | |
55 | actor_pos, pos, new_pos, actor_delta, delta, | |
56 | screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 | |
57 | outputs button_down, dispatch_game_state, | |
58 | actor_pos, pos, new_pos, actor_delta, delta, | |
59 | screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 | |
60 | trashes a, x, y, c, z, n, v, ptr, save_x, compare_target | |
41 | 61 | |
42 | 62 | // |
43 | 63 | // The constraints on these 2 vectors are kind-of sort-of big fibs. |
52 | 72 | // |
53 | 73 | |
54 | 74 | vector cinv |
55 | inputs joy2, pos, button_down, dispatch_game_state | |
56 | outputs delta, pos, screen, screen1, button_down, dispatch_game_state | |
57 | trashes a, x, y, c, z, n, v, ptr | |
75 | inputs joy2, button_down, press_fire_msg, dispatch_game_state, save_x, | |
76 | actor_pos, pos, new_pos, actor_delta, delta, | |
77 | screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 | |
78 | outputs button_down, dispatch_game_state, | |
79 | actor_pos, pos, new_pos, actor_delta, delta, | |
80 | screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 | |
81 | trashes a, x, y, c, z, n, v, ptr, save_x, compare_target | |
58 | 82 | @ 788 |
59 | 83 | |
60 | 84 | vector save_cinv |
61 | inputs joy2, pos, button_down, dispatch_game_state | |
62 | outputs delta, pos, screen, screen1, button_down, dispatch_game_state | |
63 | trashes a, x, y, c, z, n, v, ptr | |
85 | inputs joy2, button_down, press_fire_msg, dispatch_game_state, save_x, | |
86 | actor_pos, pos, new_pos, actor_delta, delta, | |
87 | screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 | |
88 | outputs button_down, dispatch_game_state, | |
89 | actor_pos, pos, new_pos, actor_delta, delta, | |
90 | screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 | |
91 | trashes a, x, y, c, z, n, v, ptr, save_x, compare_target | |
64 | 92 | |
65 | 93 | // ---------------------------------------------------------------- |
66 | 94 | // Utility Routines |
153 | 181 | } until z |
154 | 182 | } |
155 | 183 | |
184 | routine calculate_new_position | |
185 | inputs pos, delta | |
186 | outputs new_pos | |
187 | trashes a, c, n, z, v | |
188 | { | |
189 | copy pos, new_pos | |
190 | st off, c | |
191 | add new_pos, delta | |
192 | } | |
193 | ||
194 | routine check_new_position_in_bounds | |
195 | inputs new_pos | |
196 | outputs c | |
197 | trashes compare_target, a, z, n, v | |
198 | { | |
199 | copy 1000, compare_target | |
200 | st on, c | |
201 | sub compare_target, new_pos | |
202 | ||
203 | if not c { | |
204 | copy word 0, compare_target | |
205 | st on, c | |
206 | sub compare_target, new_pos | |
207 | if not c { | |
208 | st off, c | |
209 | } else { | |
210 | st on, c | |
211 | } | |
212 | } else { | |
213 | st on, c | |
214 | } | |
215 | } | |
216 | ||
217 | routine init_game | |
218 | inputs actor_pos, actor_delta | |
219 | outputs actor_pos, actor_delta, pos | |
220 | trashes a, y, z, n, c, v | |
221 | { | |
222 | ld y, 0 | |
223 | copy word 0, pos | |
224 | repeat { | |
225 | copy pos, actor_pos + y | |
226 | copy word 40, actor_delta + y | |
227 | ||
228 | st off, c | |
229 | add pos, word 7 | |
230 | ||
231 | inc y | |
232 | cmp y, 16 | |
233 | } until z | |
234 | ||
235 | ld y, 0 | |
236 | copy word 0, actor_pos + y | |
237 | copy word 0, actor_delta + y | |
238 | } | |
239 | ||
240 | // ---------------------------------------------------------------- | |
241 | // Actor Logics | |
242 | // ---------------------------------------------------------------- | |
243 | ||
244 | // | |
245 | // Sets carry if the player perished. Carry clear otherwise. | |
246 | // | |
247 | ||
248 | routine player_logic | |
249 | inputs pos, delta, joy2, screen | |
250 | outputs pos, delta, new_pos, screen, c | |
251 | trashes a, x, y, z, n, v, ptr, compare_target | |
252 | { | |
253 | call read_stick | |
254 | ||
255 | call calculate_new_position | |
256 | call check_new_position_in_bounds | |
257 | ||
258 | if c { | |
259 | copy ^screen, ptr | |
260 | st off, c | |
261 | add ptr, new_pos | |
262 | ld y, 0 | |
263 | ||
264 | // check collision. | |
265 | copy [ptr] + y, a | |
266 | // if "collision" is with your own self, treat it as if it's blank space! | |
267 | cmp a, 81 | |
268 | if z { | |
269 | ld a, 32 | |
270 | } | |
271 | cmp a, 32 | |
272 | if z { | |
273 | copy ^screen, ptr | |
274 | st off, c | |
275 | add ptr, pos | |
276 | copy 32, [ptr] + y | |
277 | ||
278 | copy new_pos, pos | |
279 | ||
280 | copy ^screen, ptr | |
281 | st off, c | |
282 | add ptr, pos | |
283 | copy 81, [ptr] + y | |
284 | ||
285 | st off, c | |
286 | } else { | |
287 | st on, c | |
288 | trash n | |
289 | trash a | |
290 | trash z | |
291 | } | |
292 | ||
293 | // FIXME these trashes, strictly speaking, probably shouldn't be needed, | |
294 | // but currently the compiler cares too much about values that are | |
295 | // initialized in one branch of an `if`, but not the other, but trashed | |
296 | // at the end of the routine anyway. | |
297 | trash ptr | |
298 | trash y | |
299 | trash a | |
300 | trash v | |
301 | } else { | |
302 | st off, c | |
303 | } | |
304 | } | |
305 | ||
306 | // | |
307 | // Sets carry if the player perished. Carry clear otherwise. | |
308 | // | |
309 | ||
310 | routine enemy_logic | |
311 | inputs pos, delta, screen | |
312 | outputs pos, delta, new_pos, screen, c | |
313 | trashes a, x, y, z, n, v, ptr, compare_target | |
314 | { | |
315 | call calculate_new_position | |
316 | call check_new_position_in_bounds | |
317 | ||
318 | if c { | |
319 | copy ^screen, ptr | |
320 | st off, c | |
321 | add ptr, new_pos | |
322 | ld y, 0 | |
323 | ||
324 | // check collision. | |
325 | copy [ptr] + y, a | |
326 | // if "collision" is with your own self, treat it as if it's blank space! | |
327 | cmp a, 82 | |
328 | if z { | |
329 | ld a, 32 | |
330 | } | |
331 | cmp a, 32 | |
332 | if z { | |
333 | copy ^screen, ptr | |
334 | st off, c | |
335 | add ptr, pos | |
336 | copy 32, [ptr] + y | |
337 | ||
338 | copy new_pos, pos | |
339 | ||
340 | copy ^screen, ptr | |
341 | st off, c | |
342 | add ptr, pos | |
343 | copy 82, [ptr] + y | |
344 | ||
345 | st off, c | |
346 | } else { | |
347 | st on, c | |
348 | trash n | |
349 | trash a | |
350 | trash z | |
351 | } | |
352 | ||
353 | // FIXME these trashes, strictly speaking, probably shouldn't be needed, | |
354 | // but currently the compiler cares too much about values that are | |
355 | // initialized in one branch of an `if`, but not the other, but trashed | |
356 | // at the end of the routine anyway. | |
357 | trash ptr | |
358 | trash y | |
359 | trash a | |
360 | } else { | |
361 | copy delta, compare_target | |
362 | st on, c | |
363 | sub compare_target, word 40 | |
364 | if not z { | |
365 | copy word 40, delta | |
366 | } else { | |
367 | copy $ffd8, delta | |
368 | } | |
369 | trash compare_target | |
370 | } | |
371 | ||
372 | st off, c | |
373 | } | |
374 | ||
156 | 375 | // ---------------------------------------------------------------- |
157 | 376 | // Game States |
158 | 377 | // ---------------------------------------------------------------- |
161 | 380 | // Because these all `goto save_cinv` at the end, they must have the same signature as that routine. |
162 | 381 | // |
163 | 382 | |
164 | routine game_state_play | |
165 | inputs joy2, pos, button_down, dispatch_game_state | |
166 | outputs delta, pos, screen, screen1, button_down, dispatch_game_state | |
167 | trashes a, x, y, c, z, n, v, ptr | |
168 | { | |
169 | call read_stick | |
170 | ||
171 | st off, c | |
172 | add pos, delta | |
173 | ||
174 | copy ^screen, ptr | |
175 | st off, c | |
176 | add ptr, pos | |
177 | ||
383 | routine game_state_title_screen | |
384 | inputs joy2, button_down, press_fire_msg, dispatch_game_state, save_x, | |
385 | actor_pos, pos, new_pos, actor_delta, delta, | |
386 | screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 | |
387 | outputs button_down, dispatch_game_state, | |
388 | actor_pos, pos, new_pos, actor_delta, delta, | |
389 | screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 | |
390 | trashes a, x, y, c, z, n, v, ptr, save_x, compare_target | |
391 | { | |
178 | 392 | ld y, 0 |
179 | copy 81, [ptr] + y | |
180 | ||
181 | goto save_cinv | |
182 | } | |
183 | ||
184 | routine game_state_title_screen | |
185 | inputs joy2, pos, button_down, dispatch_game_state | |
186 | outputs delta, pos, screen, screen1, button_down, dispatch_game_state | |
187 | trashes a, x, y, c, z, n, v, ptr | |
188 | { | |
189 | ld y, 10 | |
190 | 393 | repeat { |
191 | ld a, 90 | |
394 | ld a, press_fire_msg + y | |
395 | ||
396 | st on, c | |
397 | sub a, 64 // yuck. oh well | |
398 | ||
192 | 399 | st a, screen1 + y |
193 | 400 | inc y |
194 | cmp y, 20 | |
401 | cmp y, 18 | |
195 | 402 | } until z |
196 | 403 | |
197 | 404 | st off, c |
198 | 405 | call check_button |
199 | 406 | |
200 | 407 | if c { |
201 | // call clear_screen | |
202 | // call init_game | |
203 | copy game_state_play, dispatch_game_state | |
204 | } else { | |
205 | // This is sort of a hack. FIXME: let `if` branches diverge this much. | |
206 | copy dispatch_game_state, dispatch_game_state | |
408 | call clear_screen | |
409 | call init_game | |
410 | copy forward game_state_play, dispatch_game_state | |
411 | ||
412 | // FIXME these trashes, strictly speaking, probably shouldn't be needed, | |
413 | // but currently the compiler cares too much about values that are | |
414 | // initialized in one branch of an `if`, but not the other, but trashed | |
415 | // at the end of the routine anyway. | |
416 | trash a | |
417 | trash n | |
418 | trash z | |
419 | } else { | |
420 | trash y | |
421 | trash c | |
422 | trash v | |
423 | } | |
424 | ||
425 | goto save_cinv | |
426 | } | |
427 | ||
428 | routine game_state_play | |
429 | inputs joy2, button_down, press_fire_msg, dispatch_game_state, save_x, | |
430 | actor_pos, pos, new_pos, actor_delta, delta, | |
431 | screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 | |
432 | outputs button_down, dispatch_game_state, | |
433 | actor_pos, pos, new_pos, actor_delta, delta, | |
434 | screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 | |
435 | trashes a, x, y, c, z, n, v, ptr, save_x, compare_target | |
436 | { | |
437 | ld x, 0 | |
438 | repeat { | |
439 | copy actor_pos + x, pos | |
440 | copy actor_delta + x, delta | |
441 | ||
442 | st x, save_x | |
443 | ||
444 | // FIXME need VECTOR TABLEs to make this happen: | |
445 | // copy actor_logic, x dispatch_logic | |
446 | // call indirect_jsr_logic | |
447 | // For now, just check the actor ID to see what type it is, and go from there. | |
448 | ||
449 | cmp x, 0 | |
450 | if z { | |
451 | call player_logic | |
452 | } else { | |
453 | call enemy_logic | |
454 | } | |
455 | ||
456 | if c { | |
457 | // Player died! Want no dead! Break out of the loop (this is a bit awkward.) | |
458 | call clear_screen | |
459 | copy forward game_state_game_over, dispatch_game_state | |
460 | ld x, 15 | |
461 | st x, save_x | |
462 | trash n | |
463 | trash z | |
464 | trash x | |
465 | } else { | |
466 | trash c | |
467 | } | |
468 | ||
469 | ld x, save_x | |
470 | ||
471 | copy pos, actor_pos + x | |
472 | copy delta, actor_delta + x | |
473 | ||
474 | inc x | |
475 | cmp x, 16 | |
476 | } until z | |
477 | ||
478 | goto save_cinv | |
479 | } | |
480 | ||
481 | routine game_state_game_over | |
482 | inputs joy2, button_down, press_fire_msg, dispatch_game_state, save_x, | |
483 | actor_pos, pos, new_pos, actor_delta, delta, | |
484 | screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 | |
485 | outputs button_down, dispatch_game_state, | |
486 | actor_pos, pos, new_pos, actor_delta, delta, | |
487 | screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 | |
488 | trashes a, x, y, c, z, n, v, ptr, save_x, compare_target | |
489 | { | |
490 | st off, c | |
491 | call check_button | |
492 | ||
493 | if c { | |
494 | call clear_screen | |
495 | call init_game | |
496 | copy game_state_title_screen, dispatch_game_state | |
497 | ||
498 | // FIXME these trashes, strictly speaking, probably shouldn't be needed, | |
499 | // but currently the compiler cares too much about values that are | |
500 | // initialized in one branch of an `if`, but not the other, but trashed | |
501 | // at the end of the routine anyway. | |
502 | trash a | |
503 | trash n | |
504 | trash z | |
505 | } else { | |
506 | trash y | |
507 | trash c | |
508 | trash v | |
207 | 509 | } |
208 | 510 | |
209 | 511 | goto save_cinv |
214 | 516 | // ************************* |
215 | 517 | |
216 | 518 | routine our_cinv |
217 | inputs joy2, pos, button_down, dispatch_game_state | |
218 | outputs delta, pos, screen, screen1, button_down, dispatch_game_state | |
219 | trashes a, x, y, c, z, n, v, ptr | |
519 | inputs joy2, button_down, press_fire_msg, dispatch_game_state, save_x, | |
520 | actor_pos, pos, new_pos, actor_delta, delta, | |
521 | screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 | |
522 | outputs button_down, dispatch_game_state, | |
523 | actor_pos, pos, new_pos, actor_delta, delta, | |
524 | screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 | |
525 | trashes a, x, y, c, z, n, v, ptr, save_x, compare_target | |
220 | 526 | { |
221 | 527 | goto dispatch_game_state |
222 | 528 | } |
242 | 548 | copy cinv, save_cinv |
243 | 549 | copy our_cinv, cinv |
244 | 550 | } |
245 | // FIXME: find out why `repeat { } forever` does not analyze OK | |
246 | repeat { | |
247 | ld a, 0 | |
248 | } until not z | |
249 | } | |
551 | ||
552 | repeat { } forever | |
553 | } |
88 | 88 | self._writeable.add(ref) |
89 | 89 | |
90 | 90 | def __str__(self): |
91 | def locstr(loc): | |
92 | if isinstance(loc, LocationRef): | |
93 | return "{}:{}".format(loc.name, loc.type) | |
94 | else: | |
95 | return str(loc) | |
96 | ||
97 | def locsetstr(s): | |
98 | return '{' + ', '.join([locstr(loc) for loc in list(s)]) + '}' | |
99 | ||
100 | 91 | return "Context(\n _touched={},\n _meaningful={},\n _writeable={}\n)".format( |
101 | locsetstr(self._touched), locsetstr(self._meaningful), locsetstr(self._writeable) | |
92 | LocationRef.format_set(self._touched), LocationRef.format_set(self._meaningful), LocationRef.format_set(self._writeable) | |
102 | 93 | ) |
103 | 94 | |
104 | 95 | def clone(self): |
184 | 175 | ) |
185 | 176 | |
186 | 177 | def assert_affected_within(self, name, affected, limited_to): |
187 | # We reduce the set of LocationRefs to a set of strings (their labels). | |
188 | # This is necessary because currently, two LocationRefs that refer to the | |
189 | # same location are not considered euqal. (But two LocationRefs with the | |
190 | # same label should always be the same type.) | |
191 | ||
192 | affected = set([loc.name for loc in affected]) | |
193 | limited_to = set([loc.name for loc in limited_to]) | |
194 | ||
195 | def loc_list(label_set): | |
196 | return ', '.join(sorted(label_set)) | |
197 | ||
198 | 178 | overage = affected - limited_to |
199 | 179 | if not overage: |
200 | 180 | return |
201 | message = 'in %s: %s are {%s} but affects {%s} which exceeds by: {%s} ' % ( | |
202 | self.current_routine.name, name, loc_list(limited_to), loc_list(affected), loc_list(overage) | |
181 | message = 'in %s: %s are %s but affects %s which exceeds it by: %s ' % ( | |
182 | self.current_routine.name, name, | |
183 | LocationRef.format_set(limited_to), LocationRef.format_set(affected), LocationRef.format_set(overage) | |
203 | 184 | ) |
204 | 185 | raise IncompatibleConstraintsError(message) |
205 | 186 | |
291 | 272 | else: |
292 | 273 | self.assert_type(TYPE_WORD, dest) |
293 | 274 | elif opcode == 'sub': |
294 | self.assert_type(TYPE_BYTE, src, dest) | |
295 | 275 | context.assert_meaningful(src, dest, FLAG_C) |
296 | context.set_written(dest, FLAG_Z, FLAG_N, FLAG_C, FLAG_V) | |
276 | if src.type == TYPE_BYTE: | |
277 | self.assert_type(TYPE_BYTE, src, dest) | |
278 | context.set_written(dest, FLAG_Z, FLAG_N, FLAG_C, FLAG_V) | |
279 | else: | |
280 | self.assert_type(TYPE_WORD, src, dest) | |
281 | context.set_written(dest, FLAG_Z, FLAG_N, FLAG_C, FLAG_V) | |
282 | context.set_touched(REG_A) | |
283 | context.set_unmeaningful(REG_A) | |
297 | 284 | elif opcode in ('inc', 'dec'): |
298 | 285 | self.assert_type(TYPE_BYTE, dest) |
299 | 286 | context.assert_meaningful(dest) |
331 | 318 | # doesn't really matter if you modified it or not, coming out. |
332 | 319 | for ref in context1.each_meaningful(): |
333 | 320 | context2.assert_meaningful( |
334 | ref, exception_class=InconsistentInitializationError, message='initialized in block 1 but not in block 2' | |
321 | ref, exception_class=InconsistentInitializationError, | |
322 | message='initialized in block 1 but not in block 2 of `if {}`'.format(src) | |
335 | 323 | ) |
336 | 324 | for ref in context2.each_meaningful(): |
337 | 325 | context1.assert_meaningful( |
338 | ref, exception_class=InconsistentInitializationError, message='initialized in block 2 but not in block 1' | |
326 | ref, exception_class=InconsistentInitializationError, | |
327 | message='initialized in block 2 but not in block 1 of `if {}`'.format(src) | |
339 | 328 | ) |
340 | 329 | context.set_from(context1) |
341 | 330 | elif opcode == 'repeat': |
342 | 331 | # it will always be executed at least once, so analyze it having |
343 | 332 | # been executed the first time. |
344 | 333 | self.analyze_block(instr.block, context) |
345 | context.assert_meaningful(src) | |
334 | if src is not None: # None indicates 'repeat forever' | |
335 | context.assert_meaningful(src) | |
346 | 336 | |
347 | 337 | # now analyze it having been executed a second time, with the context |
348 | 338 | # of it having already been executed. |
349 | 339 | self.analyze_block(instr.block, context) |
350 | context.assert_meaningful(src) | |
340 | if src is not None: | |
341 | context.assert_meaningful(src) | |
351 | 342 | |
352 | 343 | elif opcode == 'copy': |
353 | 344 | # 1. check that their types are compatible |
368 | 359 | else: |
369 | 360 | raise TypeMismatchError((src, dest)) |
370 | 361 | |
371 | elif isinstance(src, LocationRef) and isinstance(dest, IndexedRef): | |
362 | elif isinstance(src, (LocationRef, ConstantRef)) and isinstance(dest, IndexedRef): | |
372 | 363 | if src.type == TYPE_WORD and dest.ref.type == TYPE_WORD_TABLE: |
373 | 364 | pass |
374 | 365 | else: |
402 | 393 | context.assert_meaningful(src.ref, REG_Y) |
403 | 394 | # TODO this will need to be more sophisticated. the thing ref points to is touched, as well. |
404 | 395 | context.set_touched(src.ref) # TODO and REG_Y? if not, why not? |
396 | context.set_touched(dest) | |
405 | 397 | context.set_written(dest) |
406 | 398 | elif isinstance(src, LocationRef) and isinstance(dest, IndexedRef): |
407 | 399 | context.assert_meaningful(src, dest.ref, dest.index) |
408 | 400 | context.set_touched(src) # TODO and dest.index? |
409 | 401 | context.set_written(dest.ref) |
402 | elif isinstance(src, ConstantRef) and isinstance(dest, IndexedRef): | |
403 | context.assert_meaningful(src, dest.ref, dest.index) | |
404 | context.set_written(dest.ref) | |
410 | 405 | elif isinstance(src, IndexedRef) and isinstance(dest, LocationRef): |
411 | 406 | context.assert_meaningful(src.ref, src.index, dest) |
412 | 407 | context.set_touched(dest) # TODO and src.index? |
416 | 411 | context.set_written(dest) |
417 | 412 | |
418 | 413 | context.set_touched(REG_A, FLAG_Z, FLAG_N) |
419 | context.set_unmeaningful(REG_A, FLAG_Z, FLAG_N) | |
414 | context.set_unmeaningful(FLAG_Z, FLAG_N) | |
415 | ||
416 | # FIXME: this is just to support "copy [foo] + y, a". consider disallowing `a` as something | |
417 | # that can be used in `copy`. should be using `st` or `ld` instead, probably. | |
418 | if dest == REG_A: | |
419 | context.set_touched(REG_A) | |
420 | context.set_written(REG_A) | |
421 | else: | |
422 | context.set_unmeaningful(REG_A) | |
420 | 423 | |
421 | 424 | elif opcode == 'with-sei': |
422 | 425 | self.analyze_block(instr.block, context) |
438 | 441 | self.assert_affected_within('trashes', type_.trashes, current_type.trashes) |
439 | 442 | |
440 | 443 | self.has_encountered_goto = True |
444 | elif opcode == 'trash': | |
445 | context.set_unmeaningful(instr.dest) | |
441 | 446 | else: |
442 | 447 | raise NotImplementedError(opcode) |
5 | 5 | TYPE_BIT, TYPE_BYTE, TYPE_BYTE_TABLE, TYPE_WORD, TYPE_WORD_TABLE, BufferType, PointerType, RoutineType, VectorType, |
6 | 6 | REG_A, REG_X, REG_Y, FLAG_C |
7 | 7 | ) |
8 | from sixtypical.emitter import Byte, Label, Offset, LowAddressByte, HighAddressByte | |
8 | from sixtypical.emitter import Byte, Word, Table, Label, Offset, LowAddressByte, HighAddressByte | |
9 | 9 | from sixtypical.gen6502 import ( |
10 | 10 | Immediate, Absolute, AbsoluteX, AbsoluteY, ZeroPage, Indirect, IndirectY, Relative, |
11 | 11 | LDA, LDX, LDY, STA, STX, STY, |
29 | 29 | self.routines = {} |
30 | 30 | self.labels = {} |
31 | 31 | self.trampolines = {} # Location -> Label |
32 | ||
33 | # helper methods | |
34 | ||
35 | def addressing_mode_for_index(self, index): | |
36 | if index == REG_X: | |
37 | return AbsoluteX | |
38 | elif index == REG_Y: | |
39 | return AbsoluteY | |
40 | else: | |
41 | raise NotImplementedError(index) | |
42 | ||
43 | # visitor methods | |
32 | 44 | |
33 | 45 | def compile_program(self, program): |
34 | 46 | assert isinstance(program, Program) |
71 | 83 | for defn in program.defns: |
72 | 84 | if defn.initial is not None: |
73 | 85 | label = self.labels[defn.name] |
74 | initial_data = Byte(defn.initial) # TODO: support other types than Byte | |
86 | initial_data = None | |
87 | type_ = defn.location.type | |
88 | if type_ == TYPE_BYTE: | |
89 | initial_data = Byte(defn.initial) | |
90 | elif type_ == TYPE_WORD: | |
91 | initial_data = Word(defn.initial) | |
92 | elif type_ == TYPE_BYTE_TABLE: | |
93 | initial_data = Table(defn.initial) | |
94 | else: | |
95 | raise NotImplementedError(type_) | |
75 | 96 | label.set_length(initial_data.size()) |
76 | 97 | self.emitter.resolve_label(label) |
77 | 98 | self.emitter.emit(initial_data) |
208 | 229 | self.emitter.emit(SBC(Immediate(Byte(src.value)))) |
209 | 230 | else: |
210 | 231 | self.emitter.emit(SBC(Absolute(self.labels[src.name]))) |
232 | elif isinstance(dest, LocationRef) and src.type == TYPE_WORD and dest.type == TYPE_WORD: | |
233 | if isinstance(src, ConstantRef): | |
234 | dest_label = self.labels[dest.name] | |
235 | self.emitter.emit(LDA(Absolute(dest_label))) | |
236 | self.emitter.emit(SBC(Immediate(Byte(src.low_byte())))) | |
237 | self.emitter.emit(STA(Absolute(dest_label))) | |
238 | self.emitter.emit(LDA(Absolute(Offset(dest_label, 1)))) | |
239 | self.emitter.emit(SBC(Immediate(Byte(src.high_byte())))) | |
240 | self.emitter.emit(STA(Absolute(Offset(dest_label, 1)))) | |
241 | elif isinstance(src, LocationRef): | |
242 | src_label = self.labels[src.name] | |
243 | dest_label = self.labels[dest.name] | |
244 | self.emitter.emit(LDA(Absolute(dest_label))) | |
245 | self.emitter.emit(SBC(Absolute(src_label))) | |
246 | self.emitter.emit(STA(Absolute(dest_label))) | |
247 | self.emitter.emit(LDA(Absolute(Offset(dest_label, 1)))) | |
248 | self.emitter.emit(SBC(Absolute(Offset(src_label, 1)))) | |
249 | self.emitter.emit(STA(Absolute(Offset(dest_label, 1)))) | |
250 | else: | |
251 | raise UnsupportedOpcodeError(instr) | |
211 | 252 | else: |
212 | 253 | raise UnsupportedOpcodeError(instr) |
213 | 254 | elif opcode == 'inc': |
306 | 347 | elif opcode == 'repeat': |
307 | 348 | top_label = self.emitter.make_label() |
308 | 349 | self.compile_block(instr.block) |
309 | if src is None: | |
350 | if src is None: # indicates 'repeat forever' | |
310 | 351 | self.emitter.emit(JMP(Absolute(top_label))) |
311 | 352 | else: |
312 | 353 | cls = { |
343 | 384 | else: |
344 | 385 | raise NotImplementedError((src, dest)) |
345 | 386 | elif isinstance(src, IndirectRef) and isinstance(dest, LocationRef): |
346 | if dest.type == TYPE_BYTE and isinstance(src.ref.type, PointerType): | |
387 | if dest == REG_A and isinstance(src.ref.type, PointerType): | |
388 | src_label = self.labels[src.ref.name] | |
389 | self.emitter.emit(LDA(IndirectY(src_label))) | |
390 | elif dest.type == TYPE_BYTE and isinstance(src.ref.type, PointerType): | |
347 | 391 | src_label = self.labels[src.ref.name] |
348 | 392 | dest_label = self.labels[dest.name] |
349 | 393 | self.emitter.emit(LDA(IndirectY(src_label))) |
362 | 406 | if src.type == TYPE_WORD and dest.ref.type == TYPE_WORD_TABLE: |
363 | 407 | src_label = self.labels[src.name] |
364 | 408 | dest_label = self.labels[dest.ref.name] |
365 | addressing_mode = None | |
366 | if dest.index == REG_X: | |
367 | addressing_mode = AbsoluteX | |
368 | elif dest.index == REG_Y: | |
369 | addressing_mode = AbsoluteY | |
370 | else: | |
371 | raise NotImplementedError(dest) | |
372 | 409 | self.emitter.emit(LDA(Absolute(src_label))) |
373 | self.emitter.emit(STA(addressing_mode(dest_label))) | |
410 | self.emitter.emit(STA(self.addressing_mode_for_index(dest.index)(dest_label))) | |
374 | 411 | self.emitter.emit(LDA(Absolute(Offset(src_label, 1)))) |
375 | self.emitter.emit(STA(addressing_mode(Offset(dest_label, 256)))) | |
412 | self.emitter.emit(STA(self.addressing_mode_for_index(dest.index)(Offset(dest_label, 256)))) | |
413 | else: | |
414 | raise NotImplementedError | |
415 | elif isinstance(src, ConstantRef) and isinstance(dest, IndexedRef): | |
416 | if src.type == TYPE_WORD and dest.ref.type == TYPE_WORD_TABLE: | |
417 | dest_label = self.labels[dest.ref.name] | |
418 | self.emitter.emit(LDA(Immediate(Byte(src.low_byte())))) | |
419 | self.emitter.emit(STA(self.addressing_mode_for_index(dest.index)(dest_label))) | |
420 | self.emitter.emit(LDA(Immediate(Byte(src.high_byte())))) | |
421 | self.emitter.emit(STA(self.addressing_mode_for_index(dest.index)(Offset(dest_label, 256)))) | |
376 | 422 | else: |
377 | 423 | raise NotImplementedError |
378 | 424 | elif isinstance(src, IndexedRef) and isinstance(dest, LocationRef): |
379 | 425 | if src.ref.type == TYPE_WORD_TABLE and dest.type == TYPE_WORD: |
380 | 426 | src_label = self.labels[src.ref.name] |
381 | 427 | dest_label = self.labels[dest.name] |
382 | addressing_mode = None | |
383 | if src.index == REG_X: | |
384 | addressing_mode = AbsoluteX | |
385 | elif src.index == REG_Y: | |
386 | addressing_mode = AbsoluteY | |
387 | else: | |
388 | raise NotImplementedError(src) | |
389 | self.emitter.emit(LDA(addressing_mode(src_label))) | |
390 | self.emitter.emit(STA(Absolute(dest_label))) | |
391 | self.emitter.emit(LDA(addressing_mode(Offset(src_label, 256)))) | |
428 | self.emitter.emit(LDA(self.addressing_mode_for_index(src.index)(src_label))) | |
429 | self.emitter.emit(STA(Absolute(dest_label))) | |
430 | self.emitter.emit(LDA(self.addressing_mode_for_index(src.index)(Offset(src_label, 256)))) | |
392 | 431 | self.emitter.emit(STA(Absolute(Offset(dest_label, 1)))) |
393 | 432 | else: |
394 | 433 | raise NotImplementedError |
433 | 472 | self.emitter.emit(STA(Absolute(Offset(dest_label, 1)))) |
434 | 473 | else: |
435 | 474 | raise NotImplementedError(src.type) |
475 | elif opcode == 'trash': | |
476 | pass | |
436 | 477 | else: |
437 | 478 | raise NotImplementedError(opcode) |
47 | 47 | |
48 | 48 | |
49 | 49 | class Table(Emittable): |
50 | def __init__(self, value): | |
51 | # TODO: range-checking | |
52 | self.value = value | |
53 | ||
50 | 54 | def size(self): |
51 | 55 | return 256 |
52 | 56 | |
53 | 57 | def serialize(self, addr=None): |
54 | return chr(0) * self.size() | |
58 | bytes = [] | |
59 | for b in self.value: | |
60 | bytes.append(chr(ord(b))) | |
61 | while len(bytes) < self.size(): | |
62 | bytes.append(chr(0)) | |
63 | return ''.join(bytes) | |
55 | 64 | |
56 | 65 | def __repr__(self): |
57 | 66 | return "%s()" % (self.__class__.__name__) |
6 | 6 | |
7 | 7 | def __repr__(self): |
8 | 8 | return 'Type(%r)' % self.name |
9 | ||
10 | def __str__(self): | |
11 | return self.name | |
9 | 12 | |
10 | 13 | def __eq__(self, other): |
11 | 14 | return isinstance(other, Type) and other.name == self.name |
85 | 88 | def __eq__(self, other): |
86 | 89 | # Ordinarily there will only be one ref with a given name, |
87 | 90 | # but because we store the type in here and we want to treat |
88 | # these objects as immutable, we compare the types, too. | |
89 | # Not sure if very wise. | |
90 | return isinstance(other, self.__class__) and ( | |
91 | other.name == self.name and other.type == self.type | |
92 | ) | |
91 | # these objects as immutable, we compare the types, too, | |
92 | # just to be sure. | |
93 | equal = isinstance(other, self.__class__) and other.name == self.name | |
94 | if equal: | |
95 | assert other.type == self.type | |
96 | return equal | |
93 | 97 | |
94 | 98 | def __hash__(self): |
95 | 99 | return hash(self.name + str(self.type)) |
97 | 101 | def __repr__(self): |
98 | 102 | return '%s(%r, %r)' % (self.__class__.__name__, self.type, self.name) |
99 | 103 | |
104 | def __str__(self): | |
105 | return "{}:{}".format(self.name, self.type) | |
106 | ||
100 | 107 | def is_constant(self): |
101 | 108 | return isinstance(self.type, RoutineType) |
109 | ||
110 | def backpatch_vector_labels(self, resolver): | |
111 | if isinstance(self.type, ExecutableType): | |
112 | t = self.type | |
113 | t.inputs = set([resolver(w) for w in t.inputs]) | |
114 | t.outputs = set([resolver(w) for w in t.outputs]) | |
115 | t.trashes = set([resolver(w) for w in t.trashes]) | |
116 | ||
117 | @classmethod | |
118 | def format_set(cls, location_refs): | |
119 | return '{%s}' % ', '.join([str(loc) for loc in sorted(location_refs)]) | |
102 | 120 | |
103 | 121 | |
104 | 122 | class IndirectRef(Ref): |
22 | 22 | self.symbols[token] = SymEntry(None, LocationRef(TYPE_BYTE, token)) |
23 | 23 | for token in ('c', 'z', 'n', 'v'): |
24 | 24 | self.symbols[token] = SymEntry(None, LocationRef(TYPE_BIT, token)) |
25 | self.backpatch_instrs = [] | |
25 | 26 | |
26 | 27 | def lookup(self, name): |
27 | 28 | if name not in self.symbols: |
48 | 49 | self.symbols[name] = SymEntry(routine, routine.location) |
49 | 50 | routines.append(routine) |
50 | 51 | self.scanner.check_type('EOF') |
52 | ||
51 | 53 | # now backpatch the executable types. |
52 | 54 | for defn in defns: |
53 | if isinstance(defn.location.type, VectorType): | |
54 | t = defn.location.type | |
55 | t.inputs = set([self.lookup(w) for w in t.inputs]) | |
56 | t.outputs = set([self.lookup(w) for w in t.outputs]) | |
57 | t.trashes = set([self.lookup(w) for w in t.trashes]) | |
55 | defn.location.backpatch_vector_labels(lambda w: self.lookup(w)) | |
58 | 56 | for routine in routines: |
59 | if isinstance(routine.location.type, ExecutableType): | |
60 | t = routine.location.type | |
61 | t.inputs = set([self.lookup(w) for w in t.inputs]) | |
62 | t.outputs = set([self.lookup(w) for w in t.outputs]) | |
63 | t.trashes = set([self.lookup(w) for w in t.trashes]) | |
57 | routine.location.backpatch_vector_labels(lambda w: self.lookup(w)) | |
58 | for instr in self.backpatch_instrs: | |
59 | if instr.opcode in ('call', 'goto'): | |
60 | name = instr.location | |
61 | if name not in self.symbols: | |
62 | raise SyntaxError('Undefined routine "%s"' % name) | |
63 | if not isinstance(self.symbols[name].model.type, ExecutableType): | |
64 | raise SyntaxError('Illegal call of non-executable "%s"' % name) | |
65 | instr.location = self.symbols[name].model | |
66 | if instr.opcode in ('copy',) and isinstance(instr.src, basestring): | |
67 | name = instr.src | |
68 | if name not in self.symbols: | |
69 | raise SyntaxError('Undefined routine "%s"' % name) | |
70 | if not isinstance(self.symbols[name].model.type, ExecutableType): | |
71 | raise SyntaxError('Illegal copy of non-executable "%s"' % name) | |
72 | instr.src = self.symbols[name].model | |
73 | ||
64 | 74 | return Program(defns=defns, routines=routines) |
65 | 75 | |
66 | 76 | def defn(self): |
78 | 88 | |
79 | 89 | initial = None |
80 | 90 | if self.scanner.consume(':'): |
81 | self.scanner.check_type('integer literal') | |
82 | initial = int(self.scanner.token) | |
91 | if type_ == TYPE_BYTE_TABLE and self.scanner.on_type('string literal'): | |
92 | initial = self.scanner.token | |
93 | else: | |
94 | self.scanner.check_type('integer literal') | |
95 | initial = int(self.scanner.token) | |
83 | 96 | self.scanner.scan() |
84 | 97 | |
85 | 98 | addr = None |
193 | 206 | return loc |
194 | 207 | |
195 | 208 | def indlocexpr(self): |
196 | if self.scanner.consume('['): | |
209 | if self.scanner.consume('forward'): | |
210 | return self.label() | |
211 | elif self.scanner.consume('['): | |
197 | 212 | loc = self.locexpr() |
198 | 213 | self.scanner.expect(']') |
199 | 214 | self.scanner.expect('+') |
272 | 287 | self.scanner.scan() |
273 | 288 | name = self.scanner.token |
274 | 289 | self.scanner.scan() |
275 | if name not in self.symbols: | |
276 | raise SyntaxError('Undefined routine "%s"' % name) | |
277 | if not isinstance(self.symbols[name].model.type, ExecutableType): | |
278 | raise SyntaxError('Illegal call of non-executable "%s"' % name) | |
279 | return Instr(opcode=opcode, location=self.symbols[name].model, dest=None, src=None) | |
290 | instr = Instr(opcode=opcode, location=name, dest=None, src=None) | |
291 | self.backpatch_instrs.append(instr) | |
292 | return instr | |
280 | 293 | elif self.scanner.token in ("copy",): |
281 | 294 | opcode = self.scanner.token |
282 | 295 | self.scanner.scan() |
283 | 296 | src = self.indlocexpr() |
284 | 297 | self.scanner.expect(',') |
285 | 298 | dest = self.indlocexpr() |
286 | return Instr(opcode=opcode, dest=dest, src=src) | |
299 | instr = Instr(opcode=opcode, dest=dest, src=src) | |
300 | self.backpatch_instrs.append(instr) | |
301 | return instr | |
287 | 302 | elif self.scanner.consume("with"): |
288 | 303 | self.scanner.expect("interrupts") |
289 | 304 | self.scanner.expect("off") |
290 | 305 | block = self.block() |
291 | 306 | return Instr(opcode='with-sei', dest=None, src=None, block=block) |
307 | elif self.scanner.consume("trash"): | |
308 | dest = self.locexpr() | |
309 | return Instr(opcode='trash', src=None, dest=dest) | |
292 | 310 | else: |
293 | 311 | raise ValueError('bad opcode "%s"' % self.scanner.token) |
85 | 85 | | trashes x, z, n |
86 | 86 | | { |
87 | 87 | | ld x, 0 |
88 | | } | |
89 | = ok | |
90 | ||
91 | If a routine reads or writes a user-define memory location, it needs to declare that too. | |
92 | ||
93 | | byte b1 @ 60000 | |
94 | | byte b2 : 3 | |
95 | | word w1 @ 60001 | |
96 | | word w2 : 2000 | |
97 | | | |
98 | | routine main | |
99 | | inputs b1, w1 | |
100 | | outputs b2, w2 | |
101 | | trashes a, z, n | |
102 | | { | |
103 | | ld a, b1 | |
104 | | st a, b2 | |
105 | | copy w1, w2 | |
88 | 106 | | } |
89 | 107 | = ok |
90 | 108 | |
369 | 387 | | } |
370 | 388 | ? TypeMismatchError |
371 | 389 | |
390 | You can also copy a literal word to a word table. | |
391 | ||
392 | | word table many | |
393 | | | |
394 | | routine main | |
395 | | inputs many | |
396 | | outputs many | |
397 | | trashes a, x, n, z | |
398 | | { | |
399 | | ld x, 0 | |
400 | | copy 9999, many + x | |
401 | | } | |
402 | = ok | |
403 | ||
372 | 404 | ### add ### |
373 | 405 | |
374 | 406 | Can't `add` from or to a memory location that isn't initialized. |
560 | 592 | | } |
561 | 593 | ? ForbiddenWriteError: a in main |
562 | 594 | |
595 | You can `sub` a word constant from a word memory location. | |
596 | ||
597 | | word score | |
598 | | routine main | |
599 | | inputs a, score | |
600 | | outputs score | |
601 | | trashes a, c, z, v, n | |
602 | | { | |
603 | | st on, c | |
604 | | sub score, 1999 | |
605 | | } | |
606 | = ok | |
607 | ||
608 | `sub`ing a word constant from a word memory location trashes `a`. | |
609 | ||
610 | | word score | |
611 | | routine main | |
612 | | inputs a, score | |
613 | | outputs score, a | |
614 | | trashes c, z, v, n | |
615 | | { | |
616 | | st on, c | |
617 | | sub score, 1999 | |
618 | | } | |
619 | ? UnmeaningfulOutputError: a in main | |
620 | ||
621 | You can `sub` a word memory location from another word memory location. | |
622 | ||
623 | | word score | |
624 | | word delta | |
625 | | routine main | |
626 | | inputs score, delta | |
627 | | outputs score | |
628 | | trashes a, c, z, v, n | |
629 | | { | |
630 | | st off, c | |
631 | | sub score, delta | |
632 | | } | |
633 | = ok | |
634 | ||
635 | `sub`ing a word memory location from a word memory location trashes `a`. | |
636 | ||
637 | | word score | |
638 | | word delta | |
639 | | routine main | |
640 | | inputs score, delta | |
641 | | outputs score | |
642 | | trashes c, z, v, n | |
643 | | { | |
644 | | st off, c | |
645 | | sub score, delta | |
646 | | } | |
647 | ? ForbiddenWriteError: a in main | |
648 | ||
563 | 649 | ### inc ### |
564 | 650 | |
565 | 651 | Location must be initialized and writeable. |
1001 | 1087 | | ld x, a |
1002 | 1088 | | } |
1003 | 1089 | ? UnmeaningfulReadError: a in main |
1090 | ||
1091 | ### trash ### | |
1092 | ||
1093 | Trash does nothing except indicate that we do not care about the value anymore. | |
1094 | ||
1095 | | routine foo | |
1096 | | inputs a | |
1097 | | outputs x | |
1098 | | trashes a, z, n | |
1099 | | { | |
1100 | | st a, x | |
1101 | | ld a, 0 | |
1102 | | trash a | |
1103 | | } | |
1104 | = ok | |
1105 | ||
1106 | | routine foo | |
1107 | | inputs a | |
1108 | | outputs a, x | |
1109 | | trashes z, n | |
1110 | | { | |
1111 | | st a, x | |
1112 | | ld a, 0 | |
1113 | | trash a | |
1114 | | } | |
1115 | ? UnmeaningfulOutputError: a in foo | |
1116 | ||
1117 | | routine foo | |
1118 | | inputs a | |
1119 | | outputs x | |
1120 | | trashes a, z, n | |
1121 | | { | |
1122 | | st a, x | |
1123 | | trash a | |
1124 | | st a, x | |
1125 | | } | |
1126 | ? UnmeaningfulReadError: a in foo | |
1004 | 1127 | |
1005 | 1128 | ### if ### |
1006 | 1129 | |
1063 | 1186 | | } |
1064 | 1187 | | } |
1065 | 1188 | ? InconsistentInitializationError: x |
1189 | ||
1190 | However, this only pertains to initialization. If a value is already | |
1191 | initialized, either because it was set previous to the `if`, or is an | |
1192 | input to the routine, and it is initialized in one branch, it need not | |
1193 | be initialized in the other. | |
1194 | ||
1195 | | routine foo | |
1196 | | inputs x | |
1197 | | outputs x | |
1198 | | trashes a, z, n, c | |
1199 | | { | |
1200 | | ld a, 0 | |
1201 | | cmp a, 42 | |
1202 | | if z { | |
1203 | | ld x, 7 | |
1204 | | } else { | |
1205 | | ld a, 23 | |
1206 | | } | |
1207 | | } | |
1208 | = ok | |
1066 | 1209 | |
1067 | 1210 | An `if` with a single block is analyzed as if it had an empty `else` block. |
1068 | 1211 | |
1174 | 1317 | | } |
1175 | 1318 | ? UnmeaningfulReadError: z in main |
1176 | 1319 | |
1320 | The body of `repeat forever` can be empty. | |
1321 | ||
1322 | | routine main | |
1323 | | { | |
1324 | | repeat { | |
1325 | | } forever | |
1326 | | } | |
1327 | = ok | |
1328 | ||
1177 | 1329 | ### copy ### |
1178 | 1330 | |
1179 | 1331 | Can't `copy` from a memory location that isn't initialized. |
1233 | 1385 | | { |
1234 | 1386 | | copy 0, lives |
1235 | 1387 | | } |
1236 | ? ForbiddenWriteError: a in main | |
1388 | ? ForbiddenWriteError: n in main | |
1237 | 1389 | |
1238 | 1390 | a, z, and n are trashed, and must not be declared as outputs. |
1239 | 1391 | |
1243 | 1395 | | { |
1244 | 1396 | | copy 0, lives |
1245 | 1397 | | } |
1246 | ? UnmeaningfulOutputError: a in main | |
1398 | ? UnmeaningfulOutputError: n in main | |
1247 | 1399 | |
1248 | 1400 | Unless of course you subsequently initialize them. |
1249 | 1401 |
115 | 115 | = $080D LDA $0811 |
116 | 116 | = $0810 RTS |
117 | 117 | = $0811 .byte $03 |
118 | ||
119 | Word memory locations with explicit address, initial value. | |
120 | ||
121 | | word w1 @ 60001 | |
122 | | word w2 : 3003 | |
123 | | | |
124 | | routine main | |
125 | | inputs w1 | |
126 | | outputs w2 | |
127 | | trashes a, z, n | |
128 | | { | |
129 | | copy w1, w2 | |
130 | | } | |
131 | = $080D LDA $EA61 | |
132 | = $0810 STA $081A | |
133 | = $0813 LDA $EA62 | |
134 | = $0816 STA $081B | |
135 | = $0819 RTS | |
136 | = $081A .byte $BB | |
137 | = $081B .byte $0B | |
138 | ||
139 | Initialized byte table. | |
140 | ||
141 | | byte table message : "WHAT?" | |
142 | | | |
143 | | routine main | |
144 | | inputs message | |
145 | | outputs x, a, z, n | |
146 | | { | |
147 | | ld x, 0 | |
148 | | ld a, message + x | |
149 | | } | |
150 | = $080D LDX #$00 | |
151 | = $080F LDA $0813,X | |
152 | = $0812 RTS | |
153 | = $0813 .byte $57 | |
154 | = $0814 PHA | |
155 | = $0815 EOR ($54,X) | |
156 | = $0817 .byte $3F | |
157 | = $0818 BRK | |
158 | = $0819 BRK | |
159 | = $081A BRK | |
160 | = $081B BRK | |
161 | = $081C BRK | |
162 | = $081D BRK | |
163 | = $081E BRK | |
164 | = $081F BRK | |
165 | = $0820 BRK | |
166 | = $0821 BRK | |
167 | = $0822 BRK | |
168 | = $0823 BRK | |
169 | = $0824 BRK | |
170 | = $0825 BRK | |
171 | = $0826 BRK | |
172 | = $0827 BRK | |
173 | = $0828 BRK | |
174 | = $0829 BRK | |
175 | = $082A BRK | |
176 | = $082B BRK | |
177 | = $082C BRK | |
178 | = $082D BRK | |
179 | = $082E BRK | |
180 | = $082F BRK | |
181 | = $0830 BRK | |
182 | = $0831 BRK | |
183 | = $0832 BRK | |
184 | = $0833 BRK | |
185 | = $0834 BRK | |
186 | = $0835 BRK | |
187 | = $0836 BRK | |
188 | = $0837 BRK | |
189 | = $0838 BRK | |
190 | = $0839 BRK | |
191 | = $083A BRK | |
192 | = $083B BRK | |
193 | = $083C BRK | |
194 | = $083D BRK | |
195 | = $083E BRK | |
196 | = $083F BRK | |
197 | = $0840 BRK | |
198 | = $0841 BRK | |
199 | = $0842 BRK | |
200 | = $0843 BRK | |
201 | = $0844 BRK | |
202 | = $0845 BRK | |
203 | = $0846 BRK | |
204 | = $0847 BRK | |
205 | = $0848 BRK | |
206 | = $0849 BRK | |
207 | = $084A BRK | |
208 | = $084B BRK | |
209 | = $084C BRK | |
210 | = $084D BRK | |
211 | = $084E BRK | |
212 | = $084F BRK | |
213 | = $0850 BRK | |
214 | = $0851 BRK | |
215 | = $0852 BRK | |
216 | = $0853 BRK | |
217 | = $0854 BRK | |
218 | = $0855 BRK | |
219 | = $0856 BRK | |
220 | = $0857 BRK | |
221 | = $0858 BRK | |
222 | = $0859 BRK | |
223 | = $085A BRK | |
224 | = $085B BRK | |
225 | = $085C BRK | |
226 | = $085D BRK | |
227 | = $085E BRK | |
228 | = $085F BRK | |
229 | = $0860 BRK | |
230 | = $0861 BRK | |
231 | = $0862 BRK | |
232 | = $0863 BRK | |
233 | = $0864 BRK | |
234 | = $0865 BRK | |
235 | = $0866 BRK | |
236 | = $0867 BRK | |
237 | = $0868 BRK | |
238 | = $0869 BRK | |
239 | = $086A BRK | |
240 | = $086B BRK | |
241 | = $086C BRK | |
242 | = $086D BRK | |
243 | = $086E BRK | |
244 | = $086F BRK | |
245 | = $0870 BRK | |
246 | = $0871 BRK | |
247 | = $0872 BRK | |
248 | = $0873 BRK | |
249 | = $0874 BRK | |
250 | = $0875 BRK | |
251 | = $0876 BRK | |
252 | = $0877 BRK | |
253 | = $0878 BRK | |
254 | = $0879 BRK | |
255 | = $087A BRK | |
256 | = $087B BRK | |
257 | = $087C BRK | |
258 | = $087D BRK | |
259 | = $087E BRK | |
260 | = $087F BRK | |
261 | = $0880 BRK | |
262 | = $0881 BRK | |
263 | = $0882 BRK | |
264 | = $0883 BRK | |
265 | = $0884 BRK | |
266 | = $0885 BRK | |
267 | = $0886 BRK | |
268 | = $0887 BRK | |
269 | = $0888 BRK | |
270 | = $0889 BRK | |
271 | = $088A BRK | |
272 | = $088B BRK | |
273 | = $088C BRK | |
274 | = $088D BRK | |
275 | = $088E BRK | |
276 | = $088F BRK | |
277 | = $0890 BRK | |
278 | = $0891 BRK | |
279 | = $0892 BRK | |
280 | = $0893 BRK | |
281 | = $0894 BRK | |
282 | = $0895 BRK | |
283 | = $0896 BRK | |
284 | = $0897 BRK | |
285 | = $0898 BRK | |
286 | = $0899 BRK | |
287 | = $089A BRK | |
288 | = $089B BRK | |
289 | = $089C BRK | |
290 | = $089D BRK | |
291 | = $089E BRK | |
292 | = $089F BRK | |
293 | = $08A0 BRK | |
294 | = $08A1 BRK | |
295 | = $08A2 BRK | |
296 | = $08A3 BRK | |
297 | = $08A4 BRK | |
298 | = $08A5 BRK | |
299 | = $08A6 BRK | |
300 | = $08A7 BRK | |
301 | = $08A8 BRK | |
302 | = $08A9 BRK | |
303 | = $08AA BRK | |
304 | = $08AB BRK | |
305 | = $08AC BRK | |
306 | = $08AD BRK | |
307 | = $08AE BRK | |
308 | = $08AF BRK | |
309 | = $08B0 BRK | |
310 | = $08B1 BRK | |
311 | = $08B2 BRK | |
312 | = $08B3 BRK | |
313 | = $08B4 BRK | |
314 | = $08B5 BRK | |
315 | = $08B6 BRK | |
316 | = $08B7 BRK | |
317 | = $08B8 BRK | |
318 | = $08B9 BRK | |
319 | = $08BA BRK | |
320 | = $08BB BRK | |
321 | = $08BC BRK | |
322 | = $08BD BRK | |
323 | = $08BE BRK | |
324 | = $08BF BRK | |
325 | = $08C0 BRK | |
326 | = $08C1 BRK | |
327 | = $08C2 BRK | |
328 | = $08C3 BRK | |
329 | = $08C4 BRK | |
330 | = $08C5 BRK | |
331 | = $08C6 BRK | |
332 | = $08C7 BRK | |
333 | = $08C8 BRK | |
334 | = $08C9 BRK | |
335 | = $08CA BRK | |
336 | = $08CB BRK | |
337 | = $08CC BRK | |
338 | = $08CD BRK | |
339 | = $08CE BRK | |
340 | = $08CF BRK | |
341 | = $08D0 BRK | |
342 | = $08D1 BRK | |
343 | = $08D2 BRK | |
344 | = $08D3 BRK | |
345 | = $08D4 BRK | |
346 | = $08D5 BRK | |
347 | = $08D6 BRK | |
348 | = $08D7 BRK | |
349 | = $08D8 BRK | |
350 | = $08D9 BRK | |
351 | = $08DA BRK | |
352 | = $08DB BRK | |
353 | = $08DC BRK | |
354 | = $08DD BRK | |
355 | = $08DE BRK | |
356 | = $08DF BRK | |
357 | = $08E0 BRK | |
358 | = $08E1 BRK | |
359 | = $08E2 BRK | |
360 | = $08E3 BRK | |
361 | = $08E4 BRK | |
362 | = $08E5 BRK | |
363 | = $08E6 BRK | |
364 | = $08E7 BRK | |
365 | = $08E8 BRK | |
366 | = $08E9 BRK | |
367 | = $08EA BRK | |
368 | = $08EB BRK | |
369 | = $08EC BRK | |
370 | = $08ED BRK | |
371 | = $08EE BRK | |
372 | = $08EF BRK | |
373 | = $08F0 BRK | |
374 | = $08F1 BRK | |
375 | = $08F2 BRK | |
376 | = $08F3 BRK | |
377 | = $08F4 BRK | |
378 | = $08F5 BRK | |
379 | = $08F6 BRK | |
380 | = $08F7 BRK | |
381 | = $08F8 BRK | |
382 | = $08F9 BRK | |
383 | = $08FA BRK | |
384 | = $08FB BRK | |
385 | = $08FC BRK | |
386 | = $08FD BRK | |
387 | = $08FE BRK | |
388 | = $08FF BRK | |
389 | = $0900 BRK | |
390 | = $0901 BRK | |
391 | = $0902 BRK | |
392 | = $0903 BRK | |
393 | = $0904 BRK | |
394 | = $0905 BRK | |
395 | = $0906 BRK | |
396 | = $0907 BRK | |
397 | = $0908 BRK | |
398 | = $0909 BRK | |
399 | = $090A BRK | |
400 | = $090B BRK | |
401 | = $090C BRK | |
402 | = $090D BRK | |
403 | = $090E BRK | |
404 | = $090F BRK | |
405 | = $0910 BRK | |
406 | = $0911 BRK | |
407 | = $0912 BRK | |
118 | 408 | |
119 | 409 | Some instructions. |
120 | 410 | |
296 | 586 | = $0810 JMP $080F |
297 | 587 | = $0813 RTS |
298 | 588 | |
589 | The body of `repeat forever` can be empty. | |
590 | ||
591 | | routine main | |
592 | | { | |
593 | | repeat { | |
594 | | } forever | |
595 | | } | |
596 | = $080D JMP $080D | |
597 | = $0810 RTS | |
598 | ||
299 | 599 | Indexed access. |
300 | 600 | |
301 | 601 | | byte one |
402 | 702 | = $0812 LDA #$07 |
403 | 703 | = $0814 STA $0819 |
404 | 704 | = $0817 RTS |
705 | ||
706 | You can also copy a literal word to a word table. | |
707 | ||
708 | | word table many | |
709 | | | |
710 | | routine main | |
711 | | inputs many | |
712 | | outputs many | |
713 | | trashes a, x, n, z | |
714 | | { | |
715 | | ld x, 0 | |
716 | | copy 9999, many + x | |
717 | | } | |
718 | = $080D LDX #$00 | |
719 | = $080F LDA #$0F | |
720 | = $0811 STA $081A,X | |
721 | = $0814 LDA #$27 | |
722 | = $0816 STA $091A,X | |
723 | = $0819 RTS | |
405 | 724 | |
406 | 725 | Copy vector to vector. |
407 | 726 | |
575 | 894 | = $081D STA $0822 |
576 | 895 | = $0820 RTS |
577 | 896 | |
897 | Subtracting a constant word from a word memory location. | |
898 | ||
899 | | word score | |
900 | | routine main | |
901 | | inputs score | |
902 | | outputs score | |
903 | | trashes a, c, z, v, n | |
904 | | { | |
905 | | st on, c | |
906 | | sub score, 1999 | |
907 | | } | |
908 | = $080D SEC | |
909 | = $080E LDA $081F | |
910 | = $0811 SBC #$CF | |
911 | = $0813 STA $081F | |
912 | = $0816 LDA $0820 | |
913 | = $0819 SBC #$07 | |
914 | = $081B STA $0820 | |
915 | = $081E RTS | |
916 | ||
917 | Subtracting a word memory location from another word memory location. | |
918 | ||
919 | | word score | |
920 | | word delta | |
921 | | routine main | |
922 | | inputs score, delta | |
923 | | outputs score | |
924 | | trashes a, c, z, v, n | |
925 | | { | |
926 | | st on, c | |
927 | | sub score, delta | |
928 | | } | |
929 | = $080D SEC | |
930 | = $080E LDA $0821 | |
931 | = $0811 SBC $0823 | |
932 | = $0814 STA $0821 | |
933 | = $0817 LDA $0822 | |
934 | = $081A SBC $0824 | |
935 | = $081D STA $0822 | |
936 | = $0820 RTS | |
937 | ||
578 | 938 | ### Buffers and Pointers |
579 | 939 | |
580 | 940 | Load address into pointer. |
644 | 1004 | = $081A STA ($FE),Y |
645 | 1005 | = $081C RTS |
646 | 1006 | |
647 | Read through a pointer. | |
1007 | Read through a pointer, into a byte storage location, or the `a` register. | |
648 | 1008 | |
649 | 1009 | | buffer[2048] buf |
650 | 1010 | | pointer ptr @ 254 |
658 | 1018 | | ld y, 0 |
659 | 1019 | | copy ^buf, ptr |
660 | 1020 | | copy [ptr] + y, foo |
1021 | | copy [ptr] + y, a | |
661 | 1022 | | } |
662 | 1023 | = $080D LDY #$00 |
663 | = $080F LDA #$1D | |
1024 | = $080F LDA #$1F | |
664 | 1025 | = $0811 STA $FE |
665 | 1026 | = $0813 LDA #$08 |
666 | 1027 | = $0815 STA $FF |
667 | 1028 | = $0817 LDA ($FE),Y |
668 | = $0819 STA $101D | |
669 | = $081C RTS | |
1029 | = $0819 STA $101F | |
1030 | = $081C LDA ($FE),Y | |
1031 | = $081E RTS | |
670 | 1032 | |
671 | 1033 | Add a word memory location, and a literal word, to a pointer, and then read through it. |
672 | 1034 | Note that this is *not* range-checked. (Yet.) |
712 | 1074 | = $083B LDA ($FE),Y |
713 | 1075 | = $083D STA $1041 |
714 | 1076 | = $0840 RTS |
1077 | ||
1078 | ### Trash | |
1079 | ||
1080 | Trash does nothing except indicate that we do not care about the value anymore. | |
1081 | ||
1082 | | routine main | |
1083 | | inputs a | |
1084 | | outputs x | |
1085 | | trashes a, z, n | |
1086 | | { | |
1087 | | ld x, a | |
1088 | | ld a, 0 | |
1089 | | trash a | |
1090 | | } | |
1091 | = $080D TAX | |
1092 | = $080E LDA #$00 | |
1093 | = $0810 RTS |
67 | 67 | | outputs a |
68 | 68 | | trashes x |
69 | 69 | | @ 65487 |
70 | = ok | |
71 | ||
72 | Trash. | |
73 | ||
74 | | routine main { | |
75 | | trash a | |
76 | | trash n | |
77 | | } | |
70 | 78 | = ok |
71 | 79 | |
72 | 80 | If with not |
177 | 185 | | } |
178 | 186 | = ok |
179 | 187 | |
188 | Initialized byte table. | |
189 | ||
190 | | byte table message : "WHAT DO YOU WANT TO DO NEXT?" | |
191 | | | |
192 | | routine main { | |
193 | | } | |
194 | = ok | |
195 | ||
196 | Can't initialize anything but a byte table with a string. | |
197 | ||
198 | | word message : "WHAT DO YOU WANT TO DO NEXT?" | |
199 | | | |
200 | | routine main { | |
201 | | } | |
202 | ? SyntaxError | |
203 | ||
180 | 204 | Can't access an undeclared memory location. |
181 | 205 | |
182 | 206 | | routine main { |
237 | 261 | | call x |
238 | 262 | | } |
239 | 263 | ? SyntaxError |
264 | ||
265 | But you can call a routine that is yet to be defined, further on. | |
266 | ||
267 | | routine main { | |
268 | | ld x, 0 | |
269 | | ld y, 1 | |
270 | | call up | |
271 | | call up | |
272 | | } | |
273 | | routine up { | |
274 | | ld a, 0 | |
275 | | } | |
276 | = ok | |
240 | 277 | |
241 | 278 | Can't define two routines with the same name. |
242 | 279 | |
342 | 379 | | } |
343 | 380 | = ok |
344 | 381 | |
382 | A routine can be copied into a vector before the routine appears in the program, | |
383 | *however*, it must be marked as such with the keyword `forward`. | |
384 | ||
385 | | vector cinv inputs cinv, a outputs cinv, x trashes a, x, z, n @ 788 | |
386 | | routine main { | |
387 | | with interrupts off { | |
388 | | copy foo, cinv | |
389 | | } | |
390 | | call cinv | |
391 | | } | |
392 | | routine foo { | |
393 | | ld a, 0 | |
394 | | } | |
395 | ? SyntaxError: Undefined symbol | |
396 | ||
397 | | vector cinv inputs cinv, a outputs cinv, x trashes a, x, z, n @ 788 | |
398 | | routine main { | |
399 | | with interrupts off { | |
400 | | copy forward foo, cinv | |
401 | | } | |
402 | | call cinv | |
403 | | } | |
404 | | routine foo { | |
405 | | ld a, 0 | |
406 | | } | |
407 | = ok | |
408 | ||
345 | 409 | goto. |
346 | 410 | |
347 | 411 | | routine foo { |
349 | 413 | | } |
350 | 414 | | routine main { |
351 | 415 | | goto foo |
416 | | } | |
417 | = ok | |
418 | ||
419 | | routine main { | |
420 | | goto foo | |
421 | | } | |
422 | | routine foo { | |
423 | | ld a, 0 | |
352 | 424 | | } |
353 | 425 | = ok |
354 | 426 |