Merge pull request #19 from catseye/develop-0.18
Develop 0.18
Chris Pressey authored 4 years ago
GitHub committed 4 years ago
0 | 0 | History of SixtyPical |
1 | 1 | ===================== |
2 | ||
3 | 0.18 | |
4 | ---- | |
5 | ||
6 | * The "consistent initialization" check inside `if` blocks has | |
7 | been dropped. If a location is initialized inside one block | |
8 | but not the other, it is treated as uninitialized afterwards. | |
9 | * Syntactically, `goto` may only appear at the end of a block. | |
10 | It need no longer be the final instruction in a routine, | |
11 | as long as the type context is consistent at every exit. | |
12 | * When the range of a location is known, `inc` and `dec` | |
13 | on it will usually shift the known instead of invalidating it. | |
14 | * `cmp` instruction can now perform a 16-bit unsigned comparison | |
15 | of `word` memory locations and `word` literals (at the cost of | |
16 | trashing the `a` register.) | |
17 | * `add` (resp. `sub`) now support adding (resp. subtracting) a | |
18 | byte location or a byte literal from a byte location. | |
19 | * Fixed pathological memory use in the lexical scanner - should | |
20 | be much less inefficient now when parsing large source files. | |
21 | * Reorganized the examples in `eg/rudiments/` to make them | |
22 | officially platform-agnostic and to state the expected output. | |
2 | 23 | |
3 | 24 | 0.17 |
4 | 25 | ---- |
0 | 0 | SixtyPical |
1 | 1 | ========== |
2 | 2 | |
3 | _Version 0.17. Work-in-progress, everything is subject to change._ | |
3 | _Version 0.18. Work-in-progress, everything is subject to change._ | |
4 | 4 | |
5 | 5 | **SixtyPical** is a low-level programming language with advanced |
6 | 6 | static analysis. Many of its primitive instructions resemble |
0 | 0 | TODO for SixtyPical |
1 | 1 | =================== |
2 | ||
3 | ### 16-bit `cmp` | |
4 | ||
5 | This is because we don't actually want `low` and `high` address operators | |
6 | that turn `word` type into `byte`. | |
7 | ||
8 | This is because this immediately makes things harder (that is, effectively | |
9 | impossible) to analyze. | |
10 | ||
11 | 16-bit `cmp` also benefits from some special differences between `cmp` | |
12 | and `sub` on 6502, so it would be nice to capture them. | |
13 | 2 | |
14 | 3 | ### Save values to other-than-the-stack |
15 | 4 | |
54 | 43 | An alternative would be `static` pointers, which are currently not possible because |
55 | 44 | pointers must be zero-page, thus `@`, thus uninitialized. |
56 | 45 | |
57 | ### Question "consistent initialization" | |
58 | ||
59 | Question the value of the "consistent initialization" principle for `if` statement analysis. | |
60 | ||
61 | Part of this is the trashes at the end; I think what it should be is that the trashes | |
62 | after the `if` is the union of the trashes in each of the branches; this would obviate the | |
63 | need to `trash` values explicitly, but if you tried to access them afterwards, it would still | |
64 | error. | |
65 | ||
66 | 46 | ### Tail-call optimization |
67 | ||
68 | More generally, define a block as having zero or one `goto`s at the end. (and `goto`s cannot | |
69 | appear elsewhere.) | |
70 | 47 | |
71 | 48 | If a block ends in a `call` can that be converted to end in a `goto`? Why not? I think it can, |
72 | 49 | if the block is in tail position. The constraints should iron out the same both ways. |
73 | 50 | |
74 | And - once we have this - why do we need `goto` to be in tail position, strictly? | |
75 | 51 | As long as the routine has consistent type context every place it exits, that should be fine. |
76 | 52 | |
77 | 53 | ### "Include" directives |
287 | 287 | |
288 | 288 | copy <src-memory-location>, <dest-memory-location> |
289 | 289 | |
290 | Reads from src and writes to dest. Differs from `st` in that is able to | |
291 | copy more general types of data (for example, vectors,) and it trashes the | |
292 | `z` and `n` flags and the `a` register. | |
290 | Reads from src and writes to dest. Differs from `ld` and `st` in that | |
291 | it is able to copy more general types of data (for example, vectors,) | |
292 | and it trashes the `z` and `n` flags and the `a` register. | |
293 | 293 | |
294 | 294 | * It is illegal if dest is read-only. |
295 | 295 | * It is illegal if dest does not occur in the WRITES of the current routine. |
332 | 332 | |
333 | 333 | Adds the contents of src to dest and stores the result in dest. |
334 | 334 | |
335 | * It is illegal if src OR dest OR c is uninitialized. | |
335 | * It is illegal if src OR dest OR `c` is uninitialized. | |
336 | 336 | * It is illegal if dest is read-only. |
337 | * It is illegal if dest is `x` or `y`. | |
337 | 338 | * It is illegal if dest does not occur in the WRITES of the current routine. |
338 | 339 | |
339 | 340 | Affects n, z, c, and v flags, requiring that they be in the WRITES, |
344 | 345 | In addition, if dest is of `word` type, then src must also be of `word` |
345 | 346 | type, and in this case this instruction trashes the `a` register. |
346 | 347 | |
348 | In fact, this instruction trashes the `a` register in all cases except | |
349 | when the dest is `a`. | |
350 | ||
347 | 351 | NOTE: If dest is a pointer, the addition does not check if the result of |
348 | 352 | the pointer arithmetic continues to be valid (within a buffer) or not. |
349 | 353 | |
366 | 370 | |
367 | 371 | Subtracts the contents of src from dest and stores the result in dest. |
368 | 372 | |
369 | * It is illegal if src OR dest OR c is uninitialized. | |
373 | * It is illegal if src OR dest OR `c` is uninitialized. | |
370 | 374 | * It is illegal if dest is read-only. |
375 | * It is illegal if dest is `x` or `y`. | |
371 | 376 | * It is illegal if dest does not occur in the WRITES of the current routine. |
372 | 377 | |
373 | 378 | Affects n, z, c, and v flags, requiring that they be in the WRITES, |
375 | 380 | |
376 | 381 | dest and src continue to be initialized afterwards. |
377 | 382 | |
383 | In addition, if dest is of `word` type, then src must also be of `word` | |
384 | type, and in this case this instruction trashes the `a` register. | |
385 | ||
386 | In fact, this instruction trashes the `a` register in all cases except | |
387 | when the dest is `a`. | |
388 | ||
378 | 389 | ### dec ### |
379 | 390 | |
380 | 391 | dec <dest-memory-location> |
394 | 405 | |
395 | 406 | Subtracts the contents of src from dest (without considering carry) but |
396 | 407 | does not store the result anywhere, only sets the resulting flags. |
408 | This means that `z` is set if src and dest are equal, | |
409 | and `c` is set if dest is greater than or equal to src | |
410 | (`c` is unset if dest is less than src.) | |
397 | 411 | |
398 | 412 | * It is illegal if src OR dest is uninitialized. |
399 | 413 | |
400 | 414 | Affects n, z, and c flags, requiring that they be in the WRITES, |
401 | 415 | and initializing them afterwards. |
416 | ||
417 | In addition, if dest is of `word` type, then src must also be of `word` | |
418 | type, and in this case this instruction trashes the `a` register. | |
419 | ||
420 | Note that `cmp` is not suitable for making a | |
421 | signed comparison; this article, which mentions | |
422 | techniques that a SixtyPical compiler could use to | |
423 | implement `cmp`, also explains why that is: | |
424 | [Beyond 8-bit Unsigned Comparisons, by Bruce Clark](http://www.6502.org/tutorials/compare_beyond.html). | |
402 | 425 | |
403 | 426 | ### and, or, xor ### |
404 | 427 |
310 | 310 | ld a, 1 |
311 | 311 | st a, player_died |
312 | 312 | } |
313 | ||
314 | // FIXME these trashes, strictly speaking, probably shouldn't be needed, | |
315 | // but currently the compiler cares a little too much about values that are | |
316 | // initialized in one branch of an `if`, but not the other, but are trashed | |
317 | // at the end of the routine anyway. | |
318 | trash ptr | |
319 | trash y | |
320 | trash v | |
321 | 313 | } |
322 | 314 | } |
323 | 315 | |
354 | 346 | add ptr, pos |
355 | 347 | copy 82, [ptr] + y |
356 | 348 | } |
357 | ||
358 | // FIXME these trashes, strictly speaking, probably shouldn't be needed, | |
359 | // but currently the compiler cares too much about values that are | |
360 | // initialized in one branch of an `if`, but not the other, but trashed | |
361 | // at the end of the routine anyway. | |
362 | trash ptr | |
363 | trash y | |
364 | 349 | } else { |
365 | 350 | copy delta, compare_target |
366 | 351 | st on, c |
0 | 0 | This directory contains example sources which demonstrate |
1 | 1 | the rudiments of SixtyPical. |
2 | 2 | |
3 | Some are meant to fail and produce an error message. | |
3 | Examples that are meant to fail and produce an error message | |
4 | are in the `errorful/` subdirectory. | |
4 | 5 | |
5 | They are not meant to be specific to any architecture, but | |
6 | many do assume the existence of a routine at 65490 which | |
7 | outputs the value of the accumulator as an ASCII character, | |
6 | These files are intended to be architecture-agnostic. | |
7 | For the ones that do produce output, an appropriate source | |
8 | under `platform/`, should be included first, like | |
9 | ||
10 | sixtypical platform/c64.60p vector-table.60p | |
11 | ||
12 | so that system entry points such as `chrout` are defined. | |
13 | ||
14 | There's a `loadngo.sh` script in this directory that does this. | |
15 | ||
16 | ./loadngo.sh c64 vector-table.60p | |
17 | ||
18 | `chrout` is a routine with outputs the value of the accumulator | |
19 | as an ASCII character, disturbing none of the other registers, | |
8 | 20 | simply for the purposes of producing some observable output. |
9 | (This is an address of a KERNAL routine which does this | |
10 | on both the Commodore 64 and the Commodore VIC-20, so these | |
11 | sources should be usable on these architectures.) | |
21 | ||
22 | (There is a KERNAL routine which does this on both the | |
23 | Commodore 64 and the Commodore VIC-20. It should not be hard | |
24 | to find or write such a routine for most other architectures.) |
0 | word score | |
1 | define main routine | |
2 | inputs score | |
3 | outputs score | |
4 | trashes a, c, z, v, n | |
5 | { | |
6 | st off, c | |
7 | add score, 1999 | |
8 | } |
0 | // Include `support/${PLATFORM}.60p` before this source | |
1 | // Should print YY | |
2 | ||
3 | word score | |
4 | ||
5 | define main routine | |
6 | inputs a, score | |
7 | outputs score | |
8 | trashes a, c, z, n, v | |
9 | { | |
10 | ld a, 3 | |
11 | st off, c | |
12 | add a, 4 | |
13 | ||
14 | cmp a, 7 | |
15 | if z { | |
16 | ld a, 89 | |
17 | call chrout | |
18 | } else { | |
19 | ld a, 78 | |
20 | call chrout | |
21 | } | |
22 | ||
23 | copy 999, score | |
24 | st off, c | |
25 | add score, 1999 | |
26 | ||
27 | cmp score, 2998 | |
28 | if z { | |
29 | ld a, 89 | |
30 | call chrout | |
31 | } else { | |
32 | ld a, 78 | |
33 | call chrout | |
34 | } | |
35 | } |
0 | vector vec | |
1 | inputs y | |
2 | outputs y | |
3 | trashes z, n | |
4 | ||
5 | routine foo | |
6 | inputs x | |
7 | outputs x | |
8 | trashes z, n | |
9 | { | |
10 | inc x | |
11 | } | |
12 | ||
13 | routine main | |
14 | inputs foo | |
15 | outputs vec | |
16 | trashes a, z, n | |
17 | { | |
18 | copy foo, vec | |
19 | } |
0 | // Include `support/${PLATFORM}.60p` before this source | |
1 | // Should print Y | |
2 | ||
0 | 3 | buffer[2048] buf |
1 | 4 | pointer ptr @ 254 |
2 | 5 | byte foo |
4 | 7 | define main routine |
5 | 8 | inputs buf |
6 | 9 | outputs buf, y, foo |
7 | trashes a, z, n, ptr | |
10 | trashes a, z, n, c, ptr | |
8 | 11 | { |
9 | 12 | ld y, 0 |
10 | 13 | copy ^buf, ptr |
11 | 14 | copy 123, [ptr] + y |
12 | 15 | copy [ptr] + y, foo |
13 | 16 | copy foo, [ptr] + y |
17 | ||
18 | // TODO: support saying `cmp foo, 123`, maybe | |
19 | ld a, foo | |
20 | cmp a, 123 | |
21 | ||
22 | if z { | |
23 | ld a, 89 | |
24 | call chrout | |
25 | } else { | |
26 | ld a, 78 | |
27 | call chrout | |
28 | } | |
14 | 29 | } |
0 | define chrout routine | |
1 | inputs a | |
2 | trashes a | |
3 | @ 65490 | |
0 | // Include `support/${PLATFORM}.60p` before this source | |
1 | // Should print AA | |
4 | 2 | |
5 | 3 | define print routine |
6 | 4 | trashes a, z, n |
0 | // Include `support/${PLATFORM}.60p` before this source | |
1 | // Should print ENGGL | |
2 | ||
3 | byte b | |
4 | ||
5 | define main routine | |
6 | outputs b | |
7 | trashes a, x, y, z, n, c, v | |
8 | { | |
9 | ld a, 40 | |
10 | st a, b | |
11 | ||
12 | cmp a, b | |
13 | if z { | |
14 | ld a, 69 // E | |
15 | call chrout | |
16 | } else { | |
17 | ld a, 78 // N | |
18 | call chrout | |
19 | } | |
20 | ||
21 | ld a, 41 | |
22 | st a, b | |
23 | ld a, 40 | |
24 | ||
25 | cmp a, b | |
26 | if z { | |
27 | ld a, 69 // E | |
28 | call chrout | |
29 | } else { | |
30 | ld a, 78 // N | |
31 | call chrout | |
32 | } | |
33 | ||
34 | ld a, 20 | |
35 | st a, b | |
36 | ||
37 | ld a, 21 | |
38 | ||
39 | cmp a, b // 21 >= 20 | |
40 | if c { | |
41 | ld a, 71 // G | |
42 | call chrout | |
43 | } else { | |
44 | ld a, 76 // L | |
45 | call chrout | |
46 | } | |
47 | ||
48 | ld a, 20 | |
49 | ||
50 | cmp a, b // 20 >= 20 | |
51 | if c { | |
52 | ld a, 71 // G | |
53 | call chrout | |
54 | } else { | |
55 | ld a, 76 // L | |
56 | call chrout | |
57 | } | |
58 | ||
59 | ld a, 19 | |
60 | ||
61 | cmp a, b // 19 < 20 | |
62 | if c { | |
63 | ld a, 71 // G | |
64 | call chrout | |
65 | } else { | |
66 | ld a, 76 // L | |
67 | call chrout | |
68 | } | |
69 | } |
0 | // Include `support/${PLATFORM}.60p` before this source | |
1 | // Should print ENGGL | |
2 | ||
3 | word w1 | |
4 | ||
5 | define main routine | |
6 | outputs w1 | |
7 | trashes a, x, y, z, n, c, v | |
8 | { | |
9 | copy 4000, w1 | |
10 | ||
11 | cmp w1, 4000 | |
12 | if z { | |
13 | ld a, 69 // E | |
14 | call chrout | |
15 | } else { | |
16 | ld a, 78 // N | |
17 | call chrout | |
18 | } | |
19 | ||
20 | copy 4000, w1 | |
21 | ||
22 | cmp w1, 4001 | |
23 | if z { | |
24 | ld a, 69 // E | |
25 | call chrout | |
26 | } else { | |
27 | ld a, 78 // N | |
28 | call chrout | |
29 | } | |
30 | ||
31 | copy 20002, w1 | |
32 | ||
33 | cmp w1, 20001 // 20002 >= 20001 | |
34 | if c { | |
35 | ld a, 71 // G | |
36 | call chrout | |
37 | } else { | |
38 | ld a, 76 // L | |
39 | call chrout | |
40 | } | |
41 | ||
42 | copy 20001, w1 | |
43 | ||
44 | cmp w1, 20001 // 20001 >= 20001 | |
45 | if c { | |
46 | ld a, 71 // G | |
47 | call chrout | |
48 | } else { | |
49 | ld a, 76 // L | |
50 | call chrout | |
51 | } | |
52 | ||
53 | copy 20000, w1 | |
54 | ||
55 | cmp w1, 20001 // 20000 < 20001 | |
56 | if c { | |
57 | ld a, 71 // G | |
58 | call chrout | |
59 | } else { | |
60 | ld a, 76 // L | |
61 | call chrout | |
62 | } | |
63 | } |
0 | // Include `support/${PLATFORM}.60p` before this source | |
1 | // Should print ENGGL | |
2 | ||
3 | word w1 | |
4 | word w2 | |
5 | ||
6 | define main routine | |
7 | outputs w1, w2 | |
8 | trashes a, x, y, z, n, c, v | |
9 | { | |
10 | copy 4000, w1 | |
11 | copy 4000, w2 | |
12 | ||
13 | cmp w1, w2 | |
14 | if z { | |
15 | ld a, 69 // E | |
16 | call chrout | |
17 | } else { | |
18 | ld a, 78 // N | |
19 | call chrout | |
20 | } | |
21 | ||
22 | copy 4000, w1 | |
23 | copy 4001, w2 | |
24 | ||
25 | cmp w1, w2 | |
26 | if z { | |
27 | ld a, 69 // E | |
28 | call chrout | |
29 | } else { | |
30 | ld a, 78 // N | |
31 | call chrout | |
32 | } | |
33 | ||
34 | copy 20002, w1 | |
35 | copy 20001, w2 | |
36 | ||
37 | cmp w1, w2 // 20002 >= 20001 | |
38 | if c { | |
39 | ld a, 71 // G | |
40 | call chrout | |
41 | } else { | |
42 | ld a, 76 // L | |
43 | call chrout | |
44 | } | |
45 | ||
46 | copy 20001, w1 | |
47 | ||
48 | cmp w1, w2 // 20001 >= 20001 | |
49 | if c { | |
50 | ld a, 71 // G | |
51 | call chrout | |
52 | } else { | |
53 | ld a, 76 // L | |
54 | call chrout | |
55 | } | |
56 | ||
57 | copy 20000, w1 | |
58 | ||
59 | cmp w1, w2 // 20000 < 20001 | |
60 | if c { | |
61 | ld a, 71 // G | |
62 | call chrout | |
63 | } else { | |
64 | ld a, 76 // L | |
65 | call chrout | |
66 | } | |
67 | } |
0 | define chrout routine | |
1 | inputs a | |
2 | trashes a | |
3 | @ 65490 | |
0 | // Demonstrates vector tables. | |
1 | // Include `support/${PLATFORM}.60p` before this source | |
2 | // Should print YN | |
4 | 3 | |
5 | 4 | define main routine |
6 | 5 | trashes a, x, y, z, n, c, v |
0 | define chrout routine | |
1 | inputs a | |
2 | trashes a | |
3 | @ 65490 | |
0 | // Include `support/${PLATFORM}.60p` before this source | |
1 | // Should print YA | |
4 | 2 | |
5 | 3 | define main routine |
6 | 4 | trashes a, x, y, z, n, c, v |
0 | byte bar | |
1 | byte baz | |
2 | ||
3 | define main routine | |
4 | inputs baz | |
5 | outputs bar | |
6 | trashes a, n, z | |
7 | { | |
8 | copy baz, bar | |
9 | } |
0 | This directory contains example SixtyPical sources which | |
1 | are intentionally invalid (for demonstration purposes) | |
2 | and are expected to elicit an error message from a | |
3 | SixtyPical implementation. |
0 | byte table[8] message : "WHAT?" | |
1 | ||
2 | define main routine | |
3 | inputs message | |
4 | outputs x, a, z, n | |
5 | { | |
6 | ld x, 9 | |
7 | ld a, message + x | |
8 | } |
0 | vector vec | |
1 | inputs y | |
2 | outputs y | |
3 | trashes z, n | |
4 | ||
5 | routine foo | |
6 | inputs x | |
7 | outputs x | |
8 | trashes z, n | |
9 | { | |
10 | inc x | |
11 | } | |
12 | ||
13 | routine main | |
14 | inputs foo | |
15 | outputs vec | |
16 | trashes a, z, n | |
17 | { | |
18 | copy foo, vec | |
19 | } |
0 | // Include `support/${PLATFORM}.60p` and `support/stdlib.60p` before this source | |
1 | // Should print 01 | |
2 | ||
0 | 3 | byte lives |
1 | 4 | |
2 | 5 | define main routine |
3 | inputs lives | |
6 | inputs lives, hexchars | |
4 | 7 | outputs lives |
5 | 8 | trashes a, x, z, n, c, v |
6 | { | |
7 | ld a, 0 | |
8 | st a, lives | |
9 | ld x, lives | |
10 | st off, c | |
11 | add x, 1 | |
12 | st x, lives | |
13 | } | |
9 | { | |
10 | ld a, 0 | |
11 | st a, lives | |
12 | ld x, lives | |
13 | st off, c | |
14 | inc x | |
15 | st x, lives | |
16 | ld a, lives | |
17 | call prbyte | |
18 | } |
0 | // This program is expected to loop forever. | |
1 | // Be prepared to forcibly terminate your emulator. | |
2 | ||
0 | 3 | define main routine |
1 | 4 | trashes a, y, z, n, c |
2 | 5 | { |
0 | define chrout routine | |
1 | inputs a | |
2 | trashes a | |
3 | @ 65490 | |
0 | // Include `support/${PLATFORM}.60p` before this source | |
1 | // Should print AB | |
4 | 2 | |
5 | 3 | define bar routine trashes a, z, n { |
6 | 4 | ld a, 66 |
0 | define main routine | |
1 | inputs a | |
2 | outputs a | |
3 | trashes z, n, c | |
4 | { | |
5 | cmp a, 42 | |
6 | if z { | |
7 | ld a, 7 | |
8 | } else { | |
9 | ld a, 23 | |
10 | } | |
11 | } |
0 | #!/bin/sh | |
1 | ||
2 | usage="Usage: loadngo.sh (c64|vic20) <source.60p>" | |
3 | ||
4 | arch="$1" | |
5 | shift 1 | |
6 | if [ "X$arch" = "Xc64" ]; then | |
7 | output_format='c64-basic-prg' | |
8 | if [ -e vicerc ]; then | |
9 | emu="x64 -config vicerc" | |
10 | else | |
11 | emu="x64" | |
12 | fi | |
13 | elif [ "X$arch" = "Xvic20" ]; then | |
14 | output_format='vic20-basic-prg' | |
15 | if [ -e vicerc ]; then | |
16 | emu="xvic -config vicerc" | |
17 | else | |
18 | emu="xvic" | |
19 | fi | |
20 | else | |
21 | echo $usage && exit 1 | |
22 | fi | |
23 | ||
24 | src="$1" | |
25 | if [ "X$src" = "X" ]; then | |
26 | echo $usage && exit 1 | |
27 | fi | |
28 | ||
29 | ### do it ### | |
30 | ||
31 | out=/tmp/a-out.prg | |
32 | ../../bin/sixtypical --traceback --output-format=$output_format support/$arch.60p support/stdlib.60p $src --output $out || exit 1 | |
33 | ls -la $out | |
34 | $emu $out | |
35 | rm -f $out |
0 | define chrout routine | |
1 | inputs a | |
2 | trashes a | |
3 | @ 65490 | |
0 | // Include `support/${PLATFORM}.60p` before this source | |
1 | // Should print ABCDEFGHIJKLMNOPQRSTUVWXYZ | |
4 | 2 | |
5 | 3 | define main routine |
6 | 4 | trashes a, y, z, n, c |
0 | // Include `support/${PLATFORM}.60p` before this source | |
1 | // Should print AB | |
2 | ||
0 | 3 | byte foo |
1 | ||
2 | define chrout routine | |
3 | inputs a | |
4 | trashes a | |
5 | @ 65490 | |
6 | 4 | |
7 | 5 | define print routine |
8 | 6 | inputs foo |
0 | // SixtyPical 0.11 introduced a new syntax for defining routines | |
1 | // (routine was made into a type, and you can now say: define name type.) | |
2 | ||
3 | typedef routine | |
4 | inputs x | |
5 | outputs x | |
6 | trashes z, n | |
7 | routine_type | |
8 | ||
9 | vector routine_type vec | |
10 | ||
11 | define foo routine_type | |
12 | { | |
13 | inc x | |
14 | } | |
15 | ||
16 | define main routine | |
17 | outputs vec | |
18 | trashes a, z, n | |
19 | { | |
20 | copy foo, vec | |
21 | } |
0 | define chrout routine | |
1 | inputs a | |
2 | trashes a | |
3 | @ 65490 | |
0 | // Include `support/${PLATFORM}.60p` before this source | |
1 | // Should print A | |
4 | 2 | |
5 | 3 | define main routine |
6 | 4 | inputs a |
0 | byte table[8] message : "WHAT?" | |
1 | ||
2 | define main routine | |
3 | inputs message | |
4 | outputs x, a, z, n | |
5 | { | |
6 | ld x, 9 | |
7 | ld a, message + x | |
8 | } |
0 | // Implementation of `chrout` for the Commodore 64 platform. | |
1 | ||
2 | define chrout routine | |
3 | inputs a | |
4 | trashes a | |
5 | @ 65490 |
0 | byte table[16] hexchars : "0123456789ABCDEF" | |
1 | ||
2 | define prbyte routine | |
3 | inputs a, hexchars | |
4 | trashes a, z, n, c, v | |
5 | { | |
6 | save x { | |
7 | save a { | |
8 | st off, c | |
9 | shr a | |
10 | shr a | |
11 | shr a | |
12 | shr a | |
13 | and a, 15 | |
14 | ld x, a | |
15 | ld a, hexchars + x | |
16 | call chrout | |
17 | } | |
18 | save a { | |
19 | and a, 15 | |
20 | ld x, a | |
21 | ld a, hexchars + x | |
22 | call chrout | |
23 | } | |
24 | } | |
25 | } |
0 | // Implementation of `chrout` for the Commodore VIC-20 platform. | |
1 | ||
2 | define chrout routine | |
3 | inputs a | |
4 | trashes a | |
5 | @ 65490 |
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 | define chrout routine | |
4 | inputs a | |
5 | trashes a | |
6 | @ 65490 | |
7 | ||
8 | define printa routine | |
9 | trashes a, z, n | |
10 | { | |
11 | ld a, 65 | |
12 | call chrout | |
13 | } | |
14 | ||
15 | define printb routine | |
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 | ||
6 | ||
7 | // routine printb | |
8 | // trashes a, z, n | |
9 | // { | |
10 | // ld a, 66 | |
11 | // call chrout | |
12 | // } | |
13 | ||
14 | define main routine | |
15 | trashes print, a, z, n | |
16 | { | |
17 | copy printa, print | |
18 | call print | |
19 | copy printb, print | |
20 | call print | |
21 | } |
0 | // | |
1 | 0 | // Demonstrates vector tables. |
2 | // Prints "AABAB". | |
3 | // | |
1 | // Include `support/${PLATFORM}.60p` before this source | |
2 | // Should print AABAB | |
4 | 3 | |
5 | 4 | vector routine |
6 | 5 | trashes a, z, n |
9 | 8 | vector (routine |
10 | 9 | trashes a, z, n) |
11 | 10 | table[32] vectors |
12 | ||
13 | define chrout routine | |
14 | inputs a | |
15 | trashes a | |
16 | @ 65490 | |
17 | 11 | |
18 | 12 | define printa routine |
19 | 13 | trashes a, z, n |
48 | 42 | copy printb, vectors + x |
49 | 43 | |
50 | 44 | ld x, 0 |
51 | repeat { | |
45 | for x up to 4 { | |
52 | 46 | copy vectors + x, print |
53 | 47 | call print |
54 | inc x | |
55 | cmp x, 5 | |
56 | } until z | |
48 | } | |
57 | 49 | } |
0 | // Include `support/${PLATFORM}.60p` before this source | |
1 | // Should print AB | |
2 | ||
0 | 3 | vector routine |
1 | 4 | trashes a, z, n |
2 | 5 | |
3 | ||
4 | define chrout routine | |
5 | inputs a | |
6 | trashes a | |
7 | @ 65490 | |
8 | 6 | |
9 | 7 | define printa routine |
10 | 8 | trashes a, z, n |
0 | // Include `support/${PLATFORM}.60p` before this source | |
1 | // Should print YY | |
2 | ||
0 | 3 | word one |
1 | 4 | word table[256] many |
2 | 5 | |
3 | 6 | define main routine |
4 | 7 | inputs one, many |
5 | 8 | outputs one, many |
6 | trashes a, x, y, n, z | |
9 | trashes a, x, y, c, n, z | |
7 | 10 | { |
8 | 11 | ld x, 0 |
9 | ld y, 0 | |
12 | ld y, 1 | |
10 | 13 | copy 777, one |
11 | 14 | copy one, many + x |
15 | copy 888, one | |
12 | 16 | copy one, many + y |
17 | ||
18 | ld x, 1 | |
19 | ld y, 0 | |
20 | ||
13 | 21 | copy many + x, one |
22 | cmp one, 888 | |
23 | if z { | |
24 | ld a, 89 | |
25 | call chrout | |
26 | } else { | |
27 | ld a, 78 | |
28 | call chrout | |
29 | } | |
30 | ||
14 | 31 | copy many + y, one |
32 | cmp one, 777 | |
33 | if z { | |
34 | ld a, 89 | |
35 | call chrout | |
36 | } else { | |
37 | ld a, 78 | |
38 | call chrout | |
39 | } | |
15 | 40 | } |
33 | 33 | pass |
34 | 34 | |
35 | 35 | |
36 | class InconsistentExitError(StaticAnalysisError): | |
37 | """The type context differs at two different exit points of the routine.""" | |
38 | pass | |
39 | ||
40 | ||
36 | 41 | class ForbiddenWriteError(StaticAnalysisError): |
37 | 42 | pass |
38 | 43 | |
42 | 47 | |
43 | 48 | |
44 | 49 | class IllegalJumpError(StaticAnalysisError): |
50 | pass | |
51 | ||
52 | ||
53 | class TerminatedContextError(StaticAnalysisError): | |
54 | """What the program is doing here is not valid, due to preceding `goto`s, | |
55 | which make this dead code.""" | |
45 | 56 | pass |
46 | 57 | |
47 | 58 | |
100 | 111 | self._touched = set() |
101 | 112 | self._range = dict() |
102 | 113 | self._writeable = set() |
114 | self._terminated = False | |
103 | 115 | self._gotos_encountered = set() |
104 | 116 | |
105 | 117 | for ref in inputs: |
131 | 143 | c._writeable = set(self._writeable) |
132 | 144 | return c |
133 | 145 | |
146 | def update_from(self, other): | |
147 | self.routines = other.routines | |
148 | self.routine = other.routine | |
149 | self._touched = set(other._touched) | |
150 | self._range = dict(other._range) | |
151 | self._writeable = set(other._writeable) | |
152 | self._terminated = other._terminated | |
153 | self._gotos_encounters = set(other._gotos_encountered) | |
154 | ||
134 | 155 | def each_meaningful(self): |
135 | 156 | for ref in self._range.keys(): |
136 | 157 | yield ref |
137 | 158 | |
138 | 159 | def each_touched(self): |
139 | 160 | for ref in self._touched: |
161 | yield ref | |
162 | ||
163 | def each_writeable(self): | |
164 | for ref in self._writeable: | |
140 | 165 | yield ref |
141 | 166 | |
142 | 167 | def assert_meaningful(self, *refs, **kwargs): |
278 | 303 | def encountered_gotos(self): |
279 | 304 | return self._gotos_encountered |
280 | 305 | |
306 | def set_terminated(self): | |
307 | # Having a terminated context and having encountered gotos is not the same thing. | |
308 | self._terminated = True | |
309 | ||
310 | def has_terminated(self): | |
311 | return self._terminated | |
312 | ||
281 | 313 | def assert_types_for_read_table(self, instr, src, dest, type_): |
282 | 314 | if (not TableType.is_a_table_type(src.ref.type, type_)) or (not dest.type == type_): |
283 | 315 | raise TypeMismatchError(instr, '{} and {}'.format(src.ref.name, dest.name)) |
363 | 395 | |
364 | 396 | def analyze_routine(self, routine): |
365 | 397 | assert isinstance(routine, Routine) |
366 | self.current_routine = routine | |
367 | 398 | if routine.block is None: |
368 | 399 | # it's an extern, that's fine |
369 | 400 | return |
401 | ||
402 | self.current_routine = routine | |
370 | 403 | type_ = routine.location.type |
371 | 404 | context = Context(self.routines, routine, type_.inputs, type_.outputs, type_.trashes) |
405 | self.exit_contexts = [] | |
372 | 406 | |
373 | 407 | if self.debug: |
374 | 408 | print("at start of routine `{}`:".format(routine.name)) |
375 | 409 | print(context) |
376 | 410 | |
377 | 411 | self.analyze_block(routine.block, context) |
412 | ||
378 | 413 | trashed = set(context.each_touched()) - set(context.each_meaningful()) |
379 | 414 | |
380 | 415 | if self.debug: |
389 | 424 | print('-' * 79) |
390 | 425 | print('') |
391 | 426 | |
392 | # even if we goto another routine, we can't trash an output. | |
427 | if self.exit_contexts: | |
428 | # check that they are all consistent | |
429 | exit_context = self.exit_contexts[0] | |
430 | exit_meaningful = set(exit_context.each_meaningful()) | |
431 | exit_touched = set(exit_context.each_touched()) | |
432 | exit_writeable = set(exit_context.each_writeable()) | |
433 | for ex in self.exit_contexts[1:]: | |
434 | if set(ex.each_meaningful()) != exit_meaningful: | |
435 | raise InconsistentExitError("Exit contexts are not consistent") | |
436 | if set(ex.each_touched()) != exit_touched: | |
437 | raise InconsistentExitError("Exit contexts are not consistent") | |
438 | if set(ex.each_writeable()) != exit_writeable: | |
439 | raise InconsistentExitError("Exit contexts are not consistent") | |
440 | context.update_from(exit_context) | |
441 | ||
442 | # these all apply whether we encountered goto(s) in this routine, or not...: | |
443 | ||
444 | # can't trash an output. | |
393 | 445 | for ref in trashed: |
394 | 446 | if ref in type_.outputs: |
395 | 447 | raise UnmeaningfulOutputError(routine, ref.name) |
396 | 448 | |
397 | if not context.encountered_gotos(): | |
398 | for ref in type_.outputs: | |
399 | context.assert_meaningful(ref, exception_class=UnmeaningfulOutputError) | |
400 | for ref in context.each_touched(): | |
401 | if ref not in type_.outputs and ref not in type_.trashes and not routine_has_static(routine, ref): | |
402 | raise ForbiddenWriteError(routine, ref.name) | |
449 | # all outputs are meaningful. | |
450 | for ref in type_.outputs: | |
451 | context.assert_meaningful(ref, exception_class=UnmeaningfulOutputError) | |
452 | ||
453 | # if something was touched, then it should have been declared to be writable. | |
454 | for ref in context.each_touched(): | |
455 | if ref not in type_.outputs and ref not in type_.trashes and not routine_has_static(routine, ref): | |
456 | raise ForbiddenWriteError(routine, ref.name) | |
457 | ||
458 | self.exit_contexts = None | |
403 | 459 | self.current_routine = None |
404 | 460 | return context |
405 | 461 | |
432 | 488 | dest = instr.dest |
433 | 489 | src = instr.src |
434 | 490 | |
435 | if context.encountered_gotos(): | |
436 | raise IllegalJumpError(instr, instr) | |
491 | if context.has_terminated(): | |
492 | raise TerminatedContextError(instr, instr) | |
437 | 493 | |
438 | 494 | if opcode == 'ld': |
439 | 495 | if isinstance(src, IndexedRef): |
476 | 532 | context.assert_types_for_read_table(instr, src, dest, TYPE_BYTE) |
477 | 533 | elif src.type == TYPE_BYTE: |
478 | 534 | self.assert_type(TYPE_BYTE, src, dest) |
535 | if dest != REG_A: | |
536 | context.set_touched(REG_A) | |
537 | context.set_unmeaningful(REG_A) | |
479 | 538 | else: |
480 | 539 | self.assert_type(TYPE_WORD, src) |
481 | 540 | if dest.type == TYPE_WORD: |
494 | 553 | context.assert_types_for_read_table(instr, src, dest, TYPE_BYTE) |
495 | 554 | elif src.type == TYPE_BYTE: |
496 | 555 | self.assert_type(TYPE_BYTE, src, dest) |
556 | if dest != REG_A: | |
557 | context.set_touched(REG_A) | |
558 | context.set_unmeaningful(REG_A) | |
497 | 559 | else: |
498 | 560 | self.assert_type(TYPE_WORD, src, dest) |
499 | 561 | context.set_touched(REG_A) |
504 | 566 | context.assert_meaningful(src, dest) |
505 | 567 | if isinstance(src, IndexedRef): |
506 | 568 | context.assert_types_for_read_table(instr, src, dest, TYPE_BYTE) |
507 | else: | |
569 | elif src.type == TYPE_BYTE: | |
508 | 570 | self.assert_type(TYPE_BYTE, src, dest) |
571 | else: | |
572 | self.assert_type(TYPE_WORD, src, dest) | |
573 | context.set_touched(REG_A) | |
574 | context.set_unmeaningful(REG_A) | |
509 | 575 | context.set_written(FLAG_Z, FLAG_N, FLAG_C) |
510 | 576 | elif opcode == 'and': |
511 | 577 | if isinstance(src, IndexedRef): |
536 | 602 | else: |
537 | 603 | self.assert_type(TYPE_BYTE, dest) |
538 | 604 | context.set_written(dest, FLAG_Z, FLAG_N) |
539 | context.invalidate_range(dest) | |
605 | bottom = context.get_bottom_of_range(dest) | |
606 | top = context.get_top_of_range(dest) | |
607 | if opcode == 'inc': | |
608 | if bottom == top and top < 255: | |
609 | context.set_range(dest, bottom + 1, top + 1) | |
610 | else: | |
611 | context.invalidate_range(dest) | |
612 | elif opcode == 'dec': | |
613 | if bottom == top and bottom > 0: | |
614 | context.set_range(dest, bottom - 1, top - 1) | |
615 | else: | |
616 | context.invalidate_range(dest) | |
617 | else: | |
618 | raise NotImplementedError | |
540 | 619 | elif opcode in ('shl', 'shr'): |
541 | 620 | context.assert_meaningful(dest, FLAG_C) |
542 | 621 | if isinstance(dest, IndexedRef): |
673 | 752 | self.assert_affected_within('trashes', type_, current_type) |
674 | 753 | |
675 | 754 | context.encounter_gotos(set([instr.location])) |
755 | ||
756 | # Now that we have encountered a goto, we update the | |
757 | # context here to match what someone calling the goto'ed | |
758 | # function directly, would expect. (which makes sense | |
759 | # when you think about it; if this goto's F, then calling | |
760 | # this is like calling F, from the perspective of what is | |
761 | # returned.) | |
762 | # | |
763 | # However, this isn't the current context anymore. This | |
764 | # is an exit context of this routine. | |
765 | ||
766 | exit_context = context.clone() | |
767 | ||
768 | for ref in type_.outputs: | |
769 | exit_context.set_touched(ref) # ? | |
770 | exit_context.set_written(ref) | |
771 | ||
772 | for ref in type_.trashes: | |
773 | exit_context.assert_writeable(ref) | |
774 | exit_context.set_touched(ref) | |
775 | exit_context.set_unmeaningful(ref) | |
776 | ||
777 | self.exit_contexts.append(exit_context) | |
778 | ||
779 | # When we get to the end, we'll check that all the | |
780 | # exit contexts are consistent with each other. | |
781 | ||
782 | # We set the current context as having terminated. | |
783 | # If we are in a branch, the merge will deal with | |
784 | # having terminated. If we are at the end of the | |
785 | # routine, the routine end will deal with that. | |
786 | ||
787 | context.set_terminated() | |
788 | ||
676 | 789 | elif opcode == 'trash': |
677 | 790 | context.set_touched(instr.dest) |
678 | 791 | context.set_unmeaningful(instr.dest) |
693 | 806 | outgoing_meaningful = set(context1.each_meaningful()) & set(context2.each_meaningful()) |
694 | 807 | outgoing_trashes = incoming_meaningful - outgoing_meaningful |
695 | 808 | |
696 | # TODO may we need to deal with touched separately here too? | |
697 | # probably not; if it wasn't meaningful in the first place, it | |
698 | # doesn't really matter if you modified it or not, coming out. | |
699 | for ref in context1.each_meaningful(): | |
700 | if ref in outgoing_trashes: | |
701 | continue | |
702 | context2.assert_meaningful( | |
703 | ref, exception_class=InconsistentInitializationError, | |
704 | message='initialized in block 1 but not in block 2 of `if {}`'.format(instr.src) | |
705 | ) | |
706 | for ref in context2.each_meaningful(): | |
707 | if ref in outgoing_trashes: | |
708 | continue | |
709 | context1.assert_meaningful( | |
710 | ref, exception_class=InconsistentInitializationError, | |
711 | message='initialized in block 2 but not in block 1 of `if {}`'.format(instr.src) | |
712 | ) | |
713 | ||
714 | # merge the contexts. this used to be a method called `set_from` | |
715 | context._touched = set(context1._touched) | set(context2._touched) | |
716 | context.set_meaningful(*list(outgoing_meaningful)) | |
717 | context._writeable = set(context1._writeable) | set(context2._writeable) | |
809 | # merge the contexts. | |
810 | ||
811 | # first, the easy case: if one of the contexts has terminated, just use the other one. | |
812 | # if both have terminated, we return a terminated context, and that's OK. | |
813 | ||
814 | if context1.has_terminated(): | |
815 | context.update_from(context2) | |
816 | elif context2.has_terminated(): | |
817 | context.update_from(context1) | |
818 | else: | |
819 | # the more complicated case: merge the contents of the contexts. | |
820 | context._touched = set(context1._touched) | set(context2._touched) | |
821 | context.set_meaningful(*list(outgoing_meaningful)) | |
822 | context._writeable = set(context1._writeable) | set(context2._writeable) | |
823 | ||
824 | # in both cases, we need to merge the encountered gotos, in order that | |
825 | # fallthru optimization continues to work correctly. | |
718 | 826 | context.encounter_gotos(context1.encountered_gotos() | context2.encountered_gotos()) |
719 | 827 | |
720 | 828 | for ref in outgoing_trashes: |
727 | 835 | self.analyze_block(instr.block, context) |
728 | 836 | if instr.src is not None: # None indicates 'repeat forever' |
729 | 837 | context.assert_meaningful(instr.src) |
838 | ||
839 | if context.encountered_gotos(): | |
840 | raise IllegalJumpError(instr, instr) | |
730 | 841 | |
731 | 842 | # now analyze it having been executed a second time, with the context |
732 | 843 | # of it having already been executed. |
243 | 243 | raise UnsupportedOpcodeError(instr) |
244 | 244 | self.emitter.emit(op_cls(operand)) |
245 | 245 | elif opcode == 'add': |
246 | if dest == REG_X or dest == REG_Y: | |
247 | raise UnsupportedOpcodeError(instr) | |
246 | 248 | if dest == REG_A: |
247 | 249 | if isinstance(src, ConstantRef): |
248 | 250 | self.emitter.emit(ADC(Immediate(Byte(src.value)))) |
250 | 252 | self.emitter.emit(ADC(self.addressing_mode_for_index(src.index)(self.get_label(src.ref.name)))) |
251 | 253 | else: |
252 | 254 | self.emitter.emit(ADC(Absolute(self.get_label(src.name)))) |
255 | elif isinstance(dest, LocationRef) and src.type == TYPE_BYTE and dest.type == TYPE_BYTE: | |
256 | if isinstance(src, ConstantRef): | |
257 | dest_label = self.get_label(dest.name) | |
258 | self.emitter.emit(LDA(Absolute(dest_label))) | |
259 | self.emitter.emit(ADC(Immediate(Byte(src.low_byte())))) | |
260 | self.emitter.emit(STA(Absolute(dest_label))) | |
261 | elif isinstance(src, LocationRef): | |
262 | src_label = self.get_label(src.name) | |
263 | dest_label = self.get_label(dest.name) | |
264 | self.emitter.emit(LDA(Absolute(dest_label))) | |
265 | self.emitter.emit(ADC(Absolute(src_label))) | |
266 | self.emitter.emit(STA(Absolute(dest_label))) | |
267 | else: | |
268 | raise UnsupportedOpcodeError(instr) | |
253 | 269 | elif isinstance(dest, LocationRef) and src.type == TYPE_WORD and dest.type == TYPE_WORD: |
254 | 270 | if isinstance(src, ConstantRef): |
255 | 271 | dest_label = self.get_label(dest.name) |
293 | 309 | else: |
294 | 310 | raise UnsupportedOpcodeError(instr) |
295 | 311 | elif opcode == 'sub': |
312 | if dest == REG_X or dest == REG_Y: | |
313 | raise UnsupportedOpcodeError(instr) | |
296 | 314 | if dest == REG_A: |
297 | 315 | if isinstance(src, ConstantRef): |
298 | 316 | self.emitter.emit(SBC(Immediate(Byte(src.value)))) |
300 | 318 | self.emitter.emit(SBC(self.addressing_mode_for_index(src.index)(self.get_label(src.ref.name)))) |
301 | 319 | else: |
302 | 320 | self.emitter.emit(SBC(Absolute(self.get_label(src.name)))) |
321 | elif isinstance(dest, LocationRef) and src.type == TYPE_BYTE and dest.type == TYPE_BYTE: | |
322 | if isinstance(src, ConstantRef): | |
323 | dest_label = self.get_label(dest.name) | |
324 | self.emitter.emit(LDA(Absolute(dest_label))) | |
325 | self.emitter.emit(SBC(Immediate(Byte(src.low_byte())))) | |
326 | self.emitter.emit(STA(Absolute(dest_label))) | |
327 | elif isinstance(src, LocationRef): | |
328 | src_label = self.get_label(src.name) | |
329 | dest_label = self.get_label(dest.name) | |
330 | self.emitter.emit(LDA(Absolute(dest_label))) | |
331 | self.emitter.emit(SBC(Absolute(src_label))) | |
332 | self.emitter.emit(STA(Absolute(dest_label))) | |
333 | else: | |
334 | raise UnsupportedOpcodeError(instr) | |
303 | 335 | elif isinstance(dest, LocationRef) and src.type == TYPE_WORD and dest.type == TYPE_WORD: |
304 | 336 | if isinstance(src, ConstantRef): |
305 | 337 | dest_label = self.get_label(dest.name) |
390 | 422 | |
391 | 423 | def compile_cmp(self, instr, src, dest): |
392 | 424 | """`instr` is only for reporting purposes""" |
425 | if isinstance(src, LocationRef) and src.type == TYPE_WORD: | |
426 | src_label = self.get_label(src.name) | |
427 | dest_label = self.get_label(dest.name) | |
428 | self.emitter.emit(LDA(Absolute(dest_label))) | |
429 | self.emitter.emit(CMP(Absolute(src_label))) | |
430 | end_label = Label('end_label') | |
431 | self.emitter.emit(BNE(Relative(end_label))) | |
432 | self.emitter.emit(LDA(Absolute(Offset(dest_label, 1)))) | |
433 | self.emitter.emit(CMP(Absolute(Offset(src_label, 1)))) | |
434 | self.emitter.resolve_label(end_label) | |
435 | return | |
436 | if isinstance(src, ConstantRef) and src.type == TYPE_WORD: | |
437 | dest_label = self.get_label(dest.name) | |
438 | self.emitter.emit(LDA(Absolute(dest_label))) | |
439 | self.emitter.emit(CMP(Immediate(Byte(src.low_byte())))) | |
440 | end_label = Label('end_label') | |
441 | self.emitter.emit(BNE(Relative(end_label))) | |
442 | self.emitter.emit(LDA(Absolute(Offset(dest_label, 1)))) | |
443 | self.emitter.emit(CMP(Immediate(Byte(src.high_byte())))) | |
444 | self.emitter.resolve_label(end_label) | |
445 | return | |
393 | 446 | cls = { |
394 | 447 | 'a': CMP, |
395 | 448 | 'x': CPX, |
379 | 379 | self.scanner.expect('{') |
380 | 380 | while not self.scanner.on('}'): |
381 | 381 | instrs.append(self.instr()) |
382 | if isinstance(instrs[-1], SingleOp) and instrs[-1].opcode == 'goto': | |
383 | break | |
382 | 384 | self.scanner.expect('}') |
383 | 385 | return Block(self.scanner.line_number, instrs=instrs) |
384 | 386 |
16 | 16 | self.filename = filename |
17 | 17 | self.token = None |
18 | 18 | self.type = None |
19 | self.pos = 0 | |
19 | 20 | self.line_number = 1 |
20 | 21 | self.scan() |
21 | 22 | |
22 | def scan_pattern(self, pattern, type, token_group=1, rest_group=2): | |
23 | pattern = r'^(' + pattern + r')(.*?)$' | |
24 | match = re.match(pattern, self.text, re.DOTALL) | |
23 | def scan_pattern(self, pattern, type, token_group=1): | |
24 | pattern = r'(' + pattern + r')' | |
25 | regexp = re.compile(pattern, flags=re.DOTALL) | |
26 | match = regexp.match(self.text, pos=self.pos) | |
25 | 27 | if not match: |
26 | 28 | return False |
27 | 29 | else: |
28 | 30 | self.type = type |
29 | 31 | self.token = match.group(token_group) |
30 | self.text = match.group(rest_group) | |
32 | self.pos += len(match.group(0)) | |
31 | 33 | self.line_number += self.token.count('\n') |
32 | 34 | return True |
33 | 35 | |
35 | 37 | self.scan_pattern(r'[ \t\n\r]*', 'whitespace') |
36 | 38 | while self.scan_pattern(r'\/\/.*?[\n\r]', 'comment'): |
37 | 39 | self.scan_pattern(r'[ \t\n\r]*', 'whitespace') |
38 | if not self.text: | |
40 | if self.pos >= len(self.text): | |
39 | 41 | self.token = None |
40 | 42 | self.type = 'EOF' |
41 | 43 | return |
43 | 45 | return |
44 | 46 | if self.scan_pattern(r'\d+', 'integer literal'): |
45 | 47 | return |
46 | if self.scan_pattern(r'\$([0-9a-fA-F]+)', 'integer literal', | |
47 | token_group=2, rest_group=3): | |
48 | if self.scan_pattern(r'\$([0-9a-fA-F]+)', 'integer literal', token_group=2): | |
48 | 49 | # ecch |
49 | 50 | self.token = str(eval('0x' + self.token)) |
50 | 51 | return |
51 | if self.scan_pattern(r'\"(.*?)\"', 'string literal', | |
52 | token_group=2, rest_group=3): | |
52 | if self.scan_pattern(r'\"(.*?)\"', 'string literal', token_group=2): | |
53 | 53 | return |
54 | 54 | if self.scan_pattern(r'\w+', 'identifier'): |
55 | 55 | return |
56 | 56 | if self.scan_pattern(r'.', 'unknown character'): |
57 | 57 | return |
58 | 58 | else: |
59 | raise AssertionError("this should never happen, self.text=({})".format(self.text)) | |
59 | raise AssertionError("this should never happen, self.text=({}), self.pos=({})".format(self.text, self.pos)) | |
60 | 60 | |
61 | 61 | def expect(self, token): |
62 | 62 | if self.token == token: |
761 | 761 | | } |
762 | 762 | ? RangeExceededError |
763 | 763 | |
764 | When the range of a location is known, incrementing or | |
765 | decrementing that location's value will shift the known | |
766 | range. It will not invalidate it unless the known range | |
767 | is at the limits of the possible ranges for the type. | |
768 | ||
769 | | vector routine | |
770 | | trashes a, z, n | |
771 | ||
772 | | | |
773 | | vector (routine | |
774 | | trashes a, z, n) | |
775 | | table[32] vectors | |
776 | | | |
777 | | define main routine | |
778 | | inputs vectors, print | |
779 | | outputs vectors | |
780 | | trashes print, a, x, z, n, c | |
781 | | { | |
782 | | ld x, 0 | |
783 | | inc x | |
784 | | copy print, vectors + x | |
785 | | } | |
786 | = ok | |
787 | ||
788 | | vector routine | |
789 | | trashes a, z, n | |
790 | ||
791 | | | |
792 | | vector (routine | |
793 | | trashes a, z, n) | |
794 | | table[32] vectors | |
795 | | | |
796 | | define main routine | |
797 | | inputs vectors, print | |
798 | | outputs vectors | |
799 | | trashes print, a, x, z, n, c | |
800 | | { | |
801 | | ld x, 32 | |
802 | | dec x | |
803 | | copy print, vectors + x | |
804 | | } | |
805 | = ok | |
806 | ||
764 | 807 | ### add ### |
765 | 808 | |
766 | 809 | Can't `add` from or to a memory location that isn't initialized. |
808 | 851 | | } |
809 | 852 | ? ForbiddenWriteError: a |
810 | 853 | |
854 | You can `add` a byte constant to a byte memory location. | |
855 | ||
856 | | byte lives | |
857 | | define main routine | |
858 | | inputs a, lives | |
859 | | outputs lives | |
860 | | trashes a, c, z, v, n | |
861 | | { | |
862 | | st off, c | |
863 | | add lives, 3 | |
864 | | } | |
865 | = ok | |
866 | ||
867 | `add`ing a byte constant to a byte memory location trashes `a`. | |
868 | ||
869 | | byte lives | |
870 | | define main routine | |
871 | | inputs a, lives | |
872 | | outputs a, lives | |
873 | | trashes c, z, v, n | |
874 | | { | |
875 | | st off, c | |
876 | | add lives, 3 | |
877 | | } | |
878 | ? UnmeaningfulOutputError: a | |
879 | ||
880 | You can `add` a byte memory location to another byte memory location. | |
881 | This trashes `a`. | |
882 | ||
883 | | byte lives | |
884 | | byte extra | |
885 | | define main routine | |
886 | | inputs a, lives, extra | |
887 | | outputs lives | |
888 | | trashes a, c, z, v, n | |
889 | | { | |
890 | | st off, c | |
891 | | add lives, extra | |
892 | | } | |
893 | = ok | |
894 | ||
895 | | byte lives | |
896 | | byte extra | |
897 | | define main routine | |
898 | | inputs a, lives, extra | |
899 | | outputs a, lives | |
900 | | trashes c, z, v, n | |
901 | | { | |
902 | | st off, c | |
903 | | add lives, extra | |
904 | | } | |
905 | ? UnmeaningfulOutputError: a | |
906 | ||
811 | 907 | You can `add` a word constant to a word memory location. |
812 | 908 | |
813 | 909 | | word score |
952 | 1048 | | } |
953 | 1049 | ? ForbiddenWriteError: a |
954 | 1050 | |
1051 | You can `sub` a byte constant from a byte memory location. | |
1052 | ||
1053 | | byte lives | |
1054 | | define main routine | |
1055 | | inputs a, lives | |
1056 | | outputs lives | |
1057 | | trashes a, c, z, v, n | |
1058 | | { | |
1059 | | st on, c | |
1060 | | sub lives, 3 | |
1061 | | } | |
1062 | = ok | |
1063 | ||
1064 | `sub`ing a byte constant from a byte memory location trashes `a`. | |
1065 | ||
1066 | | byte lives | |
1067 | | define main routine | |
1068 | | inputs a, lives | |
1069 | | outputs a, lives | |
1070 | | trashes c, z, v, n | |
1071 | | { | |
1072 | | st on, c | |
1073 | | sub lives, 3 | |
1074 | | } | |
1075 | ? UnmeaningfulOutputError: a | |
1076 | ||
1077 | You can `sub` a byte memory location from another byte memory location. | |
1078 | This trashes `a`. | |
1079 | ||
1080 | | byte lives | |
1081 | | byte extra | |
1082 | | define main routine | |
1083 | | inputs a, lives, extra | |
1084 | | outputs lives | |
1085 | | trashes a, c, z, v, n | |
1086 | | { | |
1087 | | st on, c | |
1088 | | sub lives, extra | |
1089 | | } | |
1090 | = ok | |
1091 | ||
1092 | | byte lives | |
1093 | | byte extra | |
1094 | | define main routine | |
1095 | | inputs a, lives, extra | |
1096 | | outputs a, lives | |
1097 | | trashes c, z, v, n | |
1098 | | { | |
1099 | | st on, c | |
1100 | | sub lives, extra | |
1101 | | } | |
1102 | ? UnmeaningfulOutputError: a | |
1103 | ||
955 | 1104 | You can `sub` a word constant from a word memory location. |
956 | 1105 | |
957 | 1106 | | word score |
1116 | 1265 | | cmp a, 4 |
1117 | 1266 | | } |
1118 | 1267 | ? UnmeaningfulReadError: a |
1268 | ||
1269 | `cmp` can work on words. In this case, it trashes `a`. | |
1270 | ||
1271 | | word za | |
1272 | | word zb | |
1273 | | | |
1274 | | define main routine | |
1275 | | inputs za, zb | |
1276 | | trashes a, z, c, n | |
1277 | | { | |
1278 | | cmp za, zb | |
1279 | | } | |
1280 | = ok | |
1281 | ||
1282 | | word za | |
1283 | | word zb | |
1284 | | | |
1285 | | define main routine | |
1286 | | inputs za, zb | |
1287 | | trashes a, z, n | |
1288 | | { | |
1289 | | cmp za, zb | |
1290 | | } | |
1291 | ? ForbiddenWriteError: c | |
1292 | ||
1293 | | word za | |
1294 | | word zb | |
1295 | | | |
1296 | | define main routine | |
1297 | | inputs za, zb | |
1298 | | trashes z, c, n | |
1299 | | { | |
1300 | | cmp za, zb | |
1301 | | } | |
1302 | ? ForbiddenWriteError: a | |
1303 | ||
1304 | | word za | |
1305 | | word zb | |
1306 | | | |
1307 | | define main routine | |
1308 | | inputs za | |
1309 | | trashes z, c, n | |
1310 | | { | |
1311 | | cmp za, zb | |
1312 | | } | |
1313 | ? UnmeaningfulReadError: zb | |
1314 | ||
1315 | `cmp` can compare against a literal word. | |
1316 | ||
1317 | | word za | |
1318 | | | |
1319 | | define main routine | |
1320 | | inputs za | |
1321 | | trashes a, z, c, n | |
1322 | | { | |
1323 | | cmp za, 4000 | |
1324 | | } | |
1325 | = ok | |
1326 | ||
1327 | | word za | |
1328 | | | |
1329 | | define main routine | |
1330 | | inputs za | |
1331 | | trashes z, c, n | |
1332 | | { | |
1333 | | cmp za, 4000 | |
1334 | | } | |
1335 | ? ForbiddenWriteError: a | |
1119 | 1336 | |
1120 | 1337 | ### and ### |
1121 | 1338 | |
1517 | 1734 | | } |
1518 | 1735 | = ok |
1519 | 1736 | |
1520 | If a location is initialized in one block, is must be initialized in the other as well. | |
1737 | If a location is initialized in one block, it must be initialized in the other as well | |
1738 | in order to be considered to be initialized after the block. If it is not consistent, | |
1739 | it will be considered uninitialized. | |
1521 | 1740 | |
1522 | 1741 | | define foo routine |
1523 | 1742 | | inputs a |
1531 | 1750 | | ld a, 23 |
1532 | 1751 | | } |
1533 | 1752 | | } |
1534 | ? InconsistentInitializationError: x | |
1753 | ? UnmeaningfulOutputError: x | |
1535 | 1754 | |
1536 | 1755 | | define foo routine |
1537 | 1756 | | inputs a |
1545 | 1764 | | ld x, 7 |
1546 | 1765 | | } |
1547 | 1766 | | } |
1548 | ? InconsistentInitializationError: x | |
1767 | ? UnmeaningfulOutputError: x | |
1549 | 1768 | |
1550 | 1769 | | define foo routine |
1551 | 1770 | | inputs a |
1559 | 1778 | | ld x, 7 |
1560 | 1779 | | } |
1561 | 1780 | | } |
1562 | ? InconsistentInitializationError: x | |
1781 | ? UnmeaningfulOutputError: x | |
1782 | ||
1783 | | define foo routine | |
1784 | | inputs a | |
1785 | | trashes a, x, z, n, c | |
1786 | | { | |
1787 | | cmp a, 42 | |
1788 | | if not z { | |
1789 | | ld a, 6 | |
1790 | | } else { | |
1791 | | ld x, 7 | |
1792 | | } | |
1793 | | ld a, x | |
1794 | | } | |
1795 | ? UnmeaningfulReadError: x | |
1796 | ||
1797 | If we don't care if it's uninitialized after the `if`, that's okay then. | |
1798 | ||
1799 | | define foo routine | |
1800 | | inputs a | |
1801 | | trashes a, x, z, n, c | |
1802 | | { | |
1803 | | cmp a, 42 | |
1804 | | if not z { | |
1805 | | ld a, 6 | |
1806 | | } else { | |
1807 | | ld x, 7 | |
1808 | | } | |
1809 | | } | |
1810 | = ok | |
1811 | ||
1812 | Or, if it does get initialized on both branches, that's okay then. | |
1813 | ||
1814 | | define foo routine | |
1815 | | inputs a | |
1816 | | outputs x | |
1817 | | trashes a, z, n, c | |
1818 | | { | |
1819 | | cmp a, 42 | |
1820 | | if not z { | |
1821 | | ld x, 0 | |
1822 | | ld a, 6 | |
1823 | | } else { | |
1824 | | ld x, 7 | |
1825 | | } | |
1826 | | } | |
1827 | = ok | |
1563 | 1828 | |
1564 | 1829 | However, this only pertains to initialization. If a value is already |
1565 | 1830 | initialized, either because it was set previous to the `if`, or is an |
1608 | 1873 | | ld x, 7 |
1609 | 1874 | | } |
1610 | 1875 | | } |
1611 | ? InconsistentInitializationError: x | |
1876 | ? UnmeaningfulOutputError: x | |
1612 | 1877 | |
1613 | 1878 | | define foo routine |
1614 | 1879 | | inputs a |
2279 | 2544 | | } |
2280 | 2545 | ? TypeMismatchError |
2281 | 2546 | |
2282 | A `goto` cannot appear within a `save` block, even if it is otherwise in tail position. | |
2547 | A `goto` cannot appear within a `save` block. | |
2283 | 2548 | |
2284 | 2549 | | define other routine |
2285 | 2550 | | trashes a, z, n |
2324 | 2589 | | } |
2325 | 2590 | = ok |
2326 | 2591 | |
2327 | A `goto` cannot appear within a `with interrupts` block, even if it is | |
2328 | otherwise in tail position. | |
2592 | A `goto` cannot appear within a `with interrupts` block. | |
2329 | 2593 | |
2330 | 2594 | | vector routine |
2331 | 2595 | | inputs x |
2972 | 3236 | | } |
2973 | 3237 | ? UnmeaningfulOutputError: x |
2974 | 3238 | |
2975 | `goto`, if present, must be in tail position (the final instruction in a routine.) | |
3239 | For now at least, you cannot have a `goto` inside a `repeat` loop. | |
2976 | 3240 | |
2977 | 3241 | | define bar routine trashes x, z, n { |
2978 | 3242 | | ld x, 200 |
2980 | 3244 | | |
2981 | 3245 | | define main routine trashes x, z, n { |
2982 | 3246 | | ld x, 0 |
2983 | | goto bar | |
2984 | | } | |
2985 | = ok | |
3247 | | repeat { | |
3248 | | inc x | |
3249 | | goto bar | |
3250 | | } until z | |
3251 | | } | |
3252 | ? IllegalJumpError | |
3253 | ||
3254 | `goto`, as a matter of syntax, can only appear at the end | |
3255 | of a block; but it need not be the final instruction in a | |
3256 | routine. | |
2986 | 3257 | |
2987 | 3258 | | define bar routine trashes x, z, n { |
2988 | 3259 | | ld x, 200 |
2989 | 3260 | | } |
2990 | 3261 | | |
2991 | 3262 | | define main routine trashes x, z, n { |
3263 | | ld x, 0 | |
2992 | 3264 | | goto bar |
2993 | | ld x, 0 | |
2994 | | } | |
2995 | ? IllegalJumpError | |
3265 | | } | |
3266 | = ok | |
2996 | 3267 | |
2997 | 3268 | | define bar routine trashes x, z, n { |
2998 | 3269 | | ld x, 200 |
3017 | 3288 | | ld x, 1 |
3018 | 3289 | | goto bar |
3019 | 3290 | | } |
3020 | | ld x, 0 | |
3021 | | } | |
3022 | ? IllegalJumpError | |
3291 | | goto bar | |
3292 | | } | |
3293 | = ok | |
3294 | ||
3295 | | define bar routine trashes x, z, n { | |
3296 | | ld x, 200 | |
3297 | | } | |
3298 | | | |
3299 | | define main routine trashes x, z, n { | |
3300 | | ld x, 0 | |
3301 | | if z { | |
3302 | | ld x, 1 | |
3303 | | goto bar | |
3304 | | } | |
3305 | | ld x, 0 | |
3306 | | } | |
3307 | = ok | |
3023 | 3308 | |
3024 | 3309 | | define bar routine trashes x, z, n { |
3025 | 3310 | | ld x, 200 |
3052 | 3337 | | } |
3053 | 3338 | = ok |
3054 | 3339 | |
3055 | For the purposes of `goto`, the end of a loop is never tail position. | |
3056 | ||
3057 | 3340 | | define bar routine trashes x, z, n { |
3058 | 3341 | | ld x, 200 |
3059 | 3342 | | } |
3060 | 3343 | | |
3061 | 3344 | | define main routine trashes x, z, n { |
3062 | 3345 | | ld x, 0 |
3063 | | repeat { | |
3064 | | inc x | |
3346 | | if z { | |
3347 | | ld x, 1 | |
3065 | 3348 | | goto bar |
3066 | | } until z | |
3067 | | } | |
3068 | ? IllegalJumpError | |
3349 | | } else { | |
3350 | | ld x, 0 | |
3351 | | } | |
3352 | | ld x, 0 | |
3353 | | } | |
3354 | = ok | |
3355 | ||
3356 | | define bar routine trashes x, z, n { | |
3357 | | ld x, 200 | |
3358 | | } | |
3359 | | | |
3360 | | define main routine trashes x, z, n { | |
3361 | | ld x, 0 | |
3362 | | if z { | |
3363 | | ld x, 1 | |
3364 | | goto bar | |
3365 | | } else { | |
3366 | | ld x, 0 | |
3367 | | } | |
3368 | | goto bar | |
3369 | | } | |
3370 | = ok | |
3371 | ||
3372 | Even though `goto` can only appear at the end of a block, | |
3373 | you can still wind up with dead code; the analysis detects | |
3374 | this. | |
3375 | ||
3376 | | define bar routine trashes x, z, n { | |
3377 | | ld x, 200 | |
3378 | | } | |
3379 | | | |
3380 | | define main routine trashes x, z, n { | |
3381 | | ld x, 0 | |
3382 | | if z { | |
3383 | | ld x, 1 | |
3384 | | goto bar | |
3385 | | } else { | |
3386 | | ld x, 0 | |
3387 | | goto bar | |
3388 | | } | |
3389 | | ld x, 100 | |
3390 | | } | |
3391 | ? TerminatedContextError | |
3392 | ||
3393 | It is important that the type context at every | |
3394 | `goto` is compatible with the type context at the end of | |
3395 | the routine. | |
3396 | ||
3397 | | define bar routine | |
3398 | | inputs x | |
3399 | | trashes x, z, n | |
3400 | | { | |
3401 | | ld x, 200 | |
3402 | | } | |
3403 | | | |
3404 | | define main routine trashes x, z, n { | |
3405 | | ld x, 0 | |
3406 | | if z { | |
3407 | | ld x, 1 | |
3408 | | goto bar | |
3409 | | } else { | |
3410 | | ld x, 0 | |
3411 | | } | |
3412 | | ld x, 1 | |
3413 | | } | |
3414 | = ok | |
3415 | ||
3416 | Here, we try to trash `x` before `goto`ing a routine that inputs `x`. | |
3417 | ||
3418 | | define bar routine | |
3419 | | inputs x | |
3420 | | trashes x, z, n | |
3421 | | { | |
3422 | | ld x, 200 | |
3423 | | } | |
3424 | | | |
3425 | | define main routine | |
3426 | | outputs a | |
3427 | | trashes x, z, n | |
3428 | | { | |
3429 | | ld x, 0 | |
3430 | | if z { | |
3431 | | trash x | |
3432 | | goto bar | |
3433 | | } else { | |
3434 | | trash x | |
3435 | | } | |
3436 | | ld a, 1 | |
3437 | | } | |
3438 | ? UnmeaningfulReadError: x | |
3439 | ||
3440 | Here, we declare that main outputs `a`, but we `goto` a routine that does not output `a`. | |
3441 | ||
3442 | | define bar routine | |
3443 | | inputs x | |
3444 | | trashes x, z, n | |
3445 | | { | |
3446 | | ld x, 200 | |
3447 | | } | |
3448 | | | |
3449 | | define main routine | |
3450 | | outputs a | |
3451 | | trashes x, z, n | |
3452 | | { | |
3453 | | ld x, 0 | |
3454 | | if z { | |
3455 | | ld x, 1 | |
3456 | | goto bar | |
3457 | | } else { | |
3458 | | ld x, 2 | |
3459 | | } | |
3460 | | ld a, 1 | |
3461 | | } | |
3462 | ? UnmeaningfulOutputError: a | |
3463 | ||
3464 | Here, we declare that main outputs a, and we goto a routine that outputs a so that's OK. | |
3465 | ||
3466 | | define bar routine | |
3467 | | inputs x | |
3468 | | outputs a | |
3469 | | trashes x, z, n | |
3470 | | { | |
3471 | | ld x, 200 | |
3472 | | ld a, 1 | |
3473 | | } | |
3474 | | | |
3475 | | define main routine | |
3476 | | outputs a | |
3477 | | trashes x, z, n | |
3478 | | { | |
3479 | | ld x, 0 | |
3480 | | if z { | |
3481 | | ld x, 1 | |
3482 | | goto bar | |
3483 | | } else { | |
3484 | | ld x, 2 | |
3485 | | } | |
3486 | | ld a, 1 | |
3487 | | } | |
3488 | = ok | |
3489 | ||
3490 | Here, we declare that main outputs `a`, and we `goto` two routines, and they both output `a`. | |
3491 | ||
3492 | | define bar0 routine | |
3493 | | inputs x | |
3494 | | outputs a | |
3495 | | trashes x, z, n | |
3496 | | { | |
3497 | | ld a, x | |
3498 | | } | |
3499 | | | |
3500 | | define bar1 routine | |
3501 | | inputs x | |
3502 | | outputs a | |
3503 | | trashes x, z, n | |
3504 | | { | |
3505 | | ld a, 200 | |
3506 | | } | |
3507 | | | |
3508 | | define main routine | |
3509 | | outputs a | |
3510 | | trashes x, z, n | |
3511 | | { | |
3512 | | ld x, 0 | |
3513 | | if z { | |
3514 | | ld x, 1 | |
3515 | | goto bar0 | |
3516 | | } else { | |
3517 | | ld x, 2 | |
3518 | | goto bar1 | |
3519 | | } | |
3520 | | } | |
3521 | = ok | |
3522 | ||
3523 | Here is like just above, but one routine doesn't output `a`. | |
3524 | ||
3525 | | define bar0 routine | |
3526 | | inputs x | |
3527 | | outputs a | |
3528 | | trashes x, z, n | |
3529 | | { | |
3530 | | ld a, x | |
3531 | | } | |
3532 | | | |
3533 | | define bar1 routine | |
3534 | | inputs x | |
3535 | | trashes x, z, n | |
3536 | | { | |
3537 | | ld x, 200 | |
3538 | | } | |
3539 | | | |
3540 | | define main routine | |
3541 | | outputs a | |
3542 | | trashes x, z, n | |
3543 | | { | |
3544 | | ld x, 0 | |
3545 | | if z { | |
3546 | | ld x, 1 | |
3547 | | goto bar0 | |
3548 | | } else { | |
3549 | | ld x, 2 | |
3550 | | goto bar1 | |
3551 | | } | |
3552 | | } | |
3553 | ? InconsistentExitError | |
3554 | ||
3555 | Here is like the above, but the two routines have different inputs, and that's OK. | |
3556 | ||
3557 | | define bar0 routine | |
3558 | | inputs x | |
3559 | | outputs a | |
3560 | | trashes x, z, n | |
3561 | | { | |
3562 | | ld a, x | |
3563 | | } | |
3564 | | | |
3565 | | define bar1 routine | |
3566 | | outputs a | |
3567 | | trashes x, z, n | |
3568 | | { | |
3569 | | ld a, 200 | |
3570 | | } | |
3571 | | | |
3572 | | define main routine | |
3573 | | outputs a | |
3574 | | trashes x, z, n | |
3575 | | { | |
3576 | | ld x, 0 | |
3577 | | if z { | |
3578 | | ld x, 1 | |
3579 | | goto bar0 | |
3580 | | } else { | |
3581 | | ld x, 2 | |
3582 | | goto bar1 | |
3583 | | } | |
3584 | | } | |
3585 | = ok | |
3586 | ||
3587 | TODO: we should have a lot more test cases for the above, here. | |
3069 | 3588 | |
3070 | 3589 | Can't `goto` a routine that outputs or trashes more than the current routine. |
3071 | 3590 |
384 | 384 | = $081B DEC $081F,X |
385 | 385 | = $081E RTS |
386 | 386 | |
387 | Compiling 16-bit `cmp`. | |
388 | ||
389 | | word za @ 60001 | |
390 | | word zb : 3003 | |
391 | | | |
392 | | define main routine | |
393 | | inputs za, zb | |
394 | | trashes a, z, c, n | |
395 | | { | |
396 | | cmp za, zb | |
397 | | cmp za, 4000 | |
398 | | } | |
399 | = $080D LDA $EA61 | |
400 | = $0810 CMP $0828 | |
401 | = $0813 BNE $081B | |
402 | = $0815 LDA $EA62 | |
403 | = $0818 CMP $0829 | |
404 | = $081B LDA $EA61 | |
405 | = $081E CMP #$A0 | |
406 | = $0820 BNE $0827 | |
407 | = $0822 LDA $EA62 | |
408 | = $0825 CMP #$0F | |
409 | = $0827 RTS | |
410 | = $0828 .byte $BB | |
411 | = $0829 .byte $0B | |
412 | ||
387 | 413 | Compiling `if`. |
388 | 414 | |
389 | 415 | | define main routine |
997 | 1023 | = $0841 RTS |
998 | 1024 | = $0842 JMP ($0846) |
999 | 1025 | = $0845 RTS |
1026 | ||
1027 | ### add, sub | |
1028 | ||
1029 | Various modes of `add`. | |
1030 | ||
1031 | | byte lives | |
1032 | | byte extra | |
1033 | | word score | |
1034 | | word bonus | |
1035 | | define main routine | |
1036 | | inputs lives, score, extra, bonus | |
1037 | | outputs lives, score | |
1038 | | trashes a, x, y, c, z, v, n | |
1039 | | { | |
1040 | | ld a, 0 | |
1041 | | ld x, 0 | |
1042 | | ld y, 0 | |
1043 | | st off, c | |
1044 | | add a, 7 | |
1045 | | add a, lives | |
1046 | | add lives, 2 | |
1047 | | add lives, extra | |
1048 | | add score, 1999 | |
1049 | | add score, bonus | |
1050 | | } | |
1051 | = $080D LDA #$00 | |
1052 | = $080F LDX #$00 | |
1053 | = $0811 LDY #$00 | |
1054 | = $0813 CLC | |
1055 | = $0814 ADC #$07 | |
1056 | = $0816 ADC $084D | |
1057 | = $0819 LDA $084D | |
1058 | = $081C ADC #$02 | |
1059 | = $081E STA $084D | |
1060 | = $0821 LDA $084D | |
1061 | = $0824 ADC $084E | |
1062 | = $0827 STA $084D | |
1063 | = $082A LDA $084F | |
1064 | = $082D ADC #$CF | |
1065 | = $082F STA $084F | |
1066 | = $0832 LDA $0850 | |
1067 | = $0835 ADC #$07 | |
1068 | = $0837 STA $0850 | |
1069 | = $083A LDA $084F | |
1070 | = $083D ADC $0851 | |
1071 | = $0840 STA $084F | |
1072 | = $0843 LDA $0850 | |
1073 | = $0846 ADC $0852 | |
1074 | = $0849 STA $0850 | |
1075 | = $084C RTS | |
1076 | ||
1077 | Various modes of `sub`. | |
1078 | ||
1079 | | byte lives | |
1080 | | byte extra | |
1081 | | word score | |
1082 | | word bonus | |
1083 | | define main routine | |
1084 | | inputs lives, score, extra, bonus | |
1085 | | outputs lives, score | |
1086 | | trashes a, x, y, c, z, v, n | |
1087 | | { | |
1088 | | ld a, 0 | |
1089 | | ld x, 0 | |
1090 | | ld y, 0 | |
1091 | | st on, c | |
1092 | | sub a, 7 | |
1093 | | sub a, lives | |
1094 | | sub lives, 2 | |
1095 | | sub lives, extra | |
1096 | | sub score, 1999 | |
1097 | | sub score, bonus | |
1098 | | } | |
1099 | = $080D LDA #$00 | |
1100 | = $080F LDX #$00 | |
1101 | = $0811 LDY #$00 | |
1102 | = $0813 SEC | |
1103 | = $0814 SBC #$07 | |
1104 | = $0816 SBC $084D | |
1105 | = $0819 LDA $084D | |
1106 | = $081C SBC #$02 | |
1107 | = $081E STA $084D | |
1108 | = $0821 LDA $084D | |
1109 | = $0824 SBC $084E | |
1110 | = $0827 STA $084D | |
1111 | = $082A LDA $084F | |
1112 | = $082D SBC #$CF | |
1113 | = $082F STA $084F | |
1114 | = $0832 LDA $0850 | |
1115 | = $0835 SBC #$07 | |
1116 | = $0837 STA $0850 | |
1117 | = $083A LDA $084F | |
1118 | = $083D SBC $0851 | |
1119 | = $0840 STA $084F | |
1120 | = $0843 LDA $0850 | |
1121 | = $0846 SBC $0852 | |
1122 | = $0849 STA $0850 | |
1123 | = $084C RTS | |
1000 | 1124 | |
1001 | 1125 | ### word operations |
1002 | 1126 |
550 | 550 | | } |
551 | 551 | = ok |
552 | 552 | |
553 | The label doesn't have to be defined yet at the point | |
554 | in the program text where it is `goto`d. | |
555 | ||
553 | 556 | | define main routine { |
554 | 557 | | goto foo |
555 | 558 | | } |
558 | 561 | | } |
559 | 562 | = ok |
560 | 563 | |
564 | Syntactically, you can `goto` a vector. | |
565 | ||
561 | 566 | | vector routine foo |
562 | 567 | | |
563 | 568 | | define main routine { |
565 | 570 | | } |
566 | 571 | = ok |
567 | 572 | |
573 | But you can't `goto` a label that never gets defined. | |
574 | ||
568 | 575 | | define main routine { |
569 | 576 | | goto foo |
570 | 577 | | } |
571 | 578 | ? SyntaxError |
579 | ||
580 | `goto` may only be the final instruction in a block. | |
581 | ||
582 | | define bar routine trashes x, z, n { | |
583 | | ld x, 200 | |
584 | | } | |
585 | | | |
586 | | define main routine trashes x, z, n { | |
587 | | goto bar | |
588 | | ld x, 0 | |
589 | | } | |
590 | ? Expected '}', but found 'ld' | |
572 | 591 | |
573 | 592 | Buffers and pointers. |
574 | 593 |