Forbid nested `with interrupts` blocks, and more refactoring.
Chris Pressey
3 years ago
11 | 11 | * Local locations need no longer be static. If they are not |
12 | 12 | static, they are considered uninitialized until assigned, |
13 | 13 | and they can be declared with an explicit fixed address. |
14 | * Along with `goto`, `call` and `with interrupts off` are | |
15 | now forbidden inside a `with interrupts off` block. | |
16 | * More tests to assure that using `call` inside a `point into` | |
17 | block or inside a `for` block does not cause trouble, | |
18 | particularly when the routine being called also uses the | |
19 | variable named in that block. | |
14 | 20 | * Fixed a bug where two local statics could be declared with |
15 | 21 | the same name. |
16 | * Split context off from analyzer and put it in its own module. | |
17 | * Split the SixtyPical Analysis tests across three files. | |
22 | * Split analysis context support off from analyzer, and | |
23 | symbol table support from parse, and it their own modules. | |
24 | * Split the SixtyPical Analysis tests across three files, | |
25 | and placed test appliances for `sixtypical` in own file. | |
18 | 26 | |
19 | 27 | 0.19 |
20 | 28 | ---- |
6 | 6 | |
7 | 7 | ----------------------------------------------------------------------------- |
8 | 8 | |
9 | Copyright (c)2014-2018 Chris Pressey, Cat's Eye Technologies. | |
9 | Copyright (c)2014-2019 Chris Pressey, Cat's Eye Technologies. | |
10 | 10 | |
11 | 11 | The authors intend this Report to belong to the entire SixtyPical |
12 | 12 | community, and so we grant permission to copy and distribute it for |
23 | 23 | |
24 | 24 | ----------------------------------------------------------------------------- |
25 | 25 | |
26 | Copyright (c)2014-2018, Chris Pressey, Cat's Eye Technologies. | |
26 | Copyright (c)2014-2019, Chris Pressey, Cat's Eye Technologies. | |
27 | 27 | All rights reserved. |
28 | 28 | |
29 | 29 | Redistribution and use in source and binary forms, with or without |
0 | 0 | SixtyPical |
1 | 1 | ========== |
2 | 2 | |
3 | _Version 0.19. Work-in-progress, everything is subject to change._ | |
3 | _Version 0.20. Work-in-progress, everything is subject to change._ | |
4 | 4 | |
5 | 5 | **SixtyPical** is a [low-level](#low-level) programming language |
6 | 6 | supporting a sophisticated [static analysis](#static-analysis). |
108 | 108 | |
109 | 109 | * [SixtyPical specification](doc/SixtyPical.md) |
110 | 110 | * [Literate test suite for SixtyPical syntax](tests/SixtyPical%20Syntax.md) |
111 | * [Literate test suite for SixtyPical analysis](tests/SixtyPical%20Analysis.md) | |
111 | * [Literate test suite for SixtyPical analysis (operations)](tests/SixtyPical%20Analysis.md) | |
112 | * [Literate test suite for SixtyPical analysis (storage)](tests/SixtyPical%20Storage.md) | |
113 | * [Literate test suite for SixtyPical analysis (control flow)](tests/SixtyPical%20Control%20Flow.md) | |
112 | 114 | * [Literate test suite for SixtyPical compilation](tests/SixtyPical%20Compilation.md) |
113 | 115 | * [Literate test suite for SixtyPical fallthru optimization](tests/SixtyPical%20Fallthru.md) |
114 | 116 |
0 | 0 | TODO for SixtyPical |
1 | 1 | =================== |
2 | ||
3 | Language | |
4 | -------- | |
2 | 5 | |
3 | 6 | ### Save values to other-than-the-stack |
4 | 7 | |
8 | 11 | ... |
9 | 12 | } |
10 | 13 | |
11 | Which uses some other storage location instead of the stack. A local static | |
12 | would be a good candidate for such. | |
14 | Which uses some other storage location instead of the stack. A local non-static | |
15 | would be a good candidate for such. At any rate, the location must not | |
16 | be writeable by anything that is called from within the block. So, probably | |
17 | just restrict this to local non-statics. | |
13 | 18 | |
14 | ### Analyze `call` within blocks? | |
19 | ### Copy byte to/from table | |
15 | 20 | |
16 | What happens if you call another routine from inside a `with interrupts off` block? | |
21 | Do we want a `copy bytevar, table + x` instruction? We don't currently have one. | |
22 | You have to `ld a`, `st a`. I think maybe we should have one. | |
17 | 23 | |
18 | What happens if you call another routine from inside a `save` block? | |
24 | ### Character literals | |
19 | 25 | |
20 | What happens if you call another routine from inside a `point into` block? | |
26 | For goodness sake, let the programmer say `'A'` instead of `65`. | |
21 | 27 | |
22 | What happens if you call another routine from inside a `for` block? | |
28 | ### Character set mapping | |
23 | 29 | |
24 | Remember that any of these may have a `goto` ... and they may have a second | |
25 | instance of the same block (e.g. `with interrupts off` nested within | |
26 | `with interrupts off` shouldn't be allowed to turn them back on after the | |
27 | inner block has finished -- even if there is no `call`.) | |
30 | Not all computers think `'A'` should be `65`. Allow the character set to be | |
31 | mapped. Probably copy what Ophis does. | |
28 | 32 | |
29 | These holes need to be plugged. | |
33 | ### "Include" directives | |
34 | ||
35 | Search a searchlist of include paths. And use them to make libraries of routines. | |
36 | ||
37 | One such library routine might be an `interrupt routine` type for various architectures. | |
38 | Since "the supervisor" has stored values on the stack, we should be able to trash them | |
39 | with impunity, in such a routine. | |
40 | ||
41 | ### Pointers into non-byte tables | |
42 | ||
43 | Right now you cannot get a pointer into a non-byte (for instance, word or vector) table. | |
44 | ||
45 | Word and vector tables are stored as two byte tables in memory. This is useful for | |
46 | indexed access, but makes pointer access more difficult. | |
47 | ||
48 | Laying them out for pointer access would make indexed access more difficult. | |
49 | ||
50 | ### Saving non-byte values | |
51 | ||
52 | Right now you cannot save a word value. | |
53 | ||
54 | There doesn't seem to be a hugely pressing reason why not. | |
55 | ||
56 | Analysis | |
57 | -------- | |
58 | ||
59 | ### Forbid recursion | |
60 | ||
61 | What happens if a routine calls itself, directly or indirectly? Many | |
62 | constraints might be violated in this case. We should probably disallow | |
63 | recursion by default. (Which means assembling the callgraph in all cases.) | |
64 | ||
65 | ### Analyze memory usage | |
66 | ||
67 | If you define two variables that occupy the same address, an analysis error ought | |
68 | to be raised. (But there should also be a way to annotate this as intentional. | |
69 | Intentionally making two tables overlap could be valuable. However, the analysis | |
70 | will probably completely miss this fact.) | |
71 | ||
72 | Optimization | |
73 | ------------ | |
74 | ||
75 | ### Space optimization of local non-statics | |
76 | ||
77 | If there are two routines A and B, and A never calls B (even indirectly), and | |
78 | B never calls A (even indirectly), then their non-static locals can | |
79 | be allocated at the same space. | |
80 | ||
81 | This is not just an impressive trick -- in the presence of local pointers, which | |
82 | use up a word in zero-page, which we consider a precious resource, it allow those | |
83 | zero-page locations to be re-used. | |
84 | ||
85 | ### Tail-call optimization | |
86 | ||
87 | If a block ends in a `call` can that be converted to end in a `goto`? Why not? I think it can, | |
88 | if the block is in tail position. The constraints should iron out the same both ways. | |
89 | ||
90 | As long as the routine has consistent type context every place it exits, that should be fine. | |
91 | ||
92 | ### Branch optimization in `if` | |
93 | ||
94 | Currently the `if` generator is not smart enough to avoid generating silly | |
95 | jump instructions. (See the Fallthru tests.) Improve it. | |
96 | ||
97 | ### Dead code removal | |
98 | ||
99 | Once we have a call graph we can omit routines that we're sure aren't called. | |
100 | ||
101 | This would let us use include-files and standard-libraries nicely: any | |
102 | routines they define, but that you don't use, don't get included. | |
103 | ||
104 | Analyzing the set of possible routines that a vector can take on would help | |
105 | this immensely. | |
106 | ||
107 | Implementation | |
108 | -------------- | |
109 | ||
110 | ### Line numbers in analysis error messages | |
111 | ||
112 | For analysis errors, there is a line number, but it's the line of the routine | |
113 | after the routine in which the analysis error occurred. Fix this. | |
114 | ||
115 | Blue-skying | |
116 | ----------- | |
30 | 117 | |
31 | 118 | ### Pointers associated globally with a table(?) |
32 | 119 | |
44 | 131 | |
45 | 132 | These can co-exist with general, non-specific-table-linked `pointer` variables. |
46 | 133 | |
47 | ### Space optimization of local non-statics | |
48 | ||
49 | If there are two routines A and B, and A never calls B (even indirectly), and | |
50 | B never calls A (even indirectly), then their non-static locals can | |
51 | be allocated at the same space. | |
52 | ||
53 | This is more an impressive trick than a really useful feature, but still. | |
54 | Impressive tricks are impressive. | |
55 | ||
56 | ### Copy byte to/from table | |
57 | ||
58 | Do we want a `copy bytevar, table + x` instruction? We don't currently have one. | |
59 | You have to `ld a`, `st a`. I think maybe we should have one. | |
60 | ||
61 | ### Analyze memory usage | |
62 | ||
63 | If you define two variables that occupy the same address, an analysis error ought | |
64 | to be raised. (But there should also be a way to annotate this as intentional. | |
65 | Intentionally making two tables overlap could be valuable. However, the analysis | |
66 | will probably completely miss this fact.) | |
67 | ||
68 | ### Character literals | |
69 | ||
70 | For goodness sake, let the programmer say `'A'` instead of `65`. | |
71 | ||
72 | ### Character set mapping | |
73 | ||
74 | Not all computers think `'A'` should be `65`. Allow the character set to be | |
75 | mapped. Probably copy what Ophis does. | |
76 | ||
77 | ### Tail-call optimization | |
78 | ||
79 | If a block ends in a `call` can that be converted to end in a `goto`? Why not? I think it can, | |
80 | if the block is in tail position. The constraints should iron out the same both ways. | |
81 | ||
82 | As long as the routine has consistent type context every place it exits, that should be fine. | |
83 | ||
84 | ### "Include" directives | |
85 | ||
86 | Search a searchlist of include paths. And use them to make libraries of routines. | |
87 | ||
88 | One such library routine might be an `interrupt routine` type for various architectures. | |
89 | Since "the supervisor" has stored values on the stack, we should be able to trash them | |
90 | with impunity, in such a routine. | |
91 | ||
92 | ### Line numbers in analysis error messages | |
93 | ||
94 | For analysis errors, there is a line number, but it's the line of the routine | |
95 | after the routine in which the analysis error occurred. Fix this. | |
134 | If we have local pointers and space optimization for local non-statics, though, | |
135 | these don't add as much. |
15 | 15 | from tempfile import NamedTemporaryFile |
16 | 16 | import traceback |
17 | 17 | |
18 | from sixtypical.parser import Parser, SymbolTable, merge_programs | |
18 | from sixtypical.symtab import SymbolTable | |
19 | from sixtypical.parser import Parser, merge_programs | |
19 | 20 | from sixtypical.analyzer import Analyzer |
20 | 21 | from sixtypical.outputter import outputter_class_for |
21 | 22 | from sixtypical.compiler import Compiler |
183 | 184 | argparser.add_argument( |
184 | 185 | "--version", |
185 | 186 | action="version", |
186 | version="%(prog)s 0.19" | |
187 | version="%(prog)s 0.20" | |
187 | 188 | ) |
188 | 189 | |
189 | 190 | options, unknown = argparser.parse_known_args(sys.argv[1:]) |
0 | 0 | SixtyPical |
1 | 1 | ========== |
2 | 2 | |
3 | This document describes the SixtyPical programming language version 0.19, | |
3 | This document describes the SixtyPical programming language version 0.20, | |
4 | 4 | both its static semantics (the capabilities and limits of the static |
5 | 5 | analyses it defines) and its runtime semantics (with reference to the |
6 | 6 | semantics of 6502 machine code.) |
195 | 195 | There are extended instruction modes for using these types of memory location. |
196 | 196 | See `copy` below, but here is some illustrative example code: |
197 | 197 | |
198 | point ptr into buf { // this is the only way to initialize a pointer | |
198 | point ptr into buf { // this associates this pointer with this table | |
199 | reset ptr 0 // this is the only way to initialize a pointer | |
199 | 200 | add ptr, 4 // note, this is unchecked against table's size! |
200 | 201 | ld y, 0 // you must set this to something yourself |
201 | 202 | copy [ptr] + y, byt // read memory through pointer, into byte |
657 | 658 | | "repeat" Block ("until" ["not"] LocExpr | "forever") |
658 | 659 | | "for" LocExpr ("up"|"down") "to" Const Block |
659 | 660 | | "with" "interrupts" LitBit Block |
661 | | "point" LocExpr "into" LocExpr Block | |
662 | | "reset" LocExpr Const | |
660 | 663 | . |
268 | 268 | st off, c |
269 | 269 | add ptr, new_pos |
270 | 270 | ld y, 0 |
271 | ||
271 | 272 | // check collision. |
272 | 273 | ld a, [ptr] + y |
273 | } | |
274 | ||
275 | // if "collision" is with your own self, treat it as if it's blank space! | |
276 | cmp a, 81 | |
277 | if z { | |
278 | ld a, 32 | |
279 | } | |
280 | cmp a, 32 | |
281 | if z { | |
282 | point ptr into screen { | |
274 | ||
275 | // if "collision" is with your own self, treat it as if it's blank space! | |
276 | cmp a, 81 | |
277 | if z { | |
278 | ld a, 32 | |
279 | } | |
280 | cmp a, 32 | |
281 | if z { | |
283 | 282 | reset ptr 0 |
284 | 283 | st off, c |
285 | 284 | add ptr, pos |
286 | 285 | copy 32, [ptr] + y |
287 | } | |
288 | ||
289 | copy new_pos, pos | |
290 | ||
291 | point ptr into screen { | |
286 | ||
287 | copy new_pos, pos | |
288 | ||
292 | 289 | reset ptr 0 |
293 | 290 | st off, c |
294 | 291 | add ptr, pos |
295 | 292 | copy 81, [ptr] + y |
296 | 293 | } |
297 | } else { | |
298 | ld a, 1 | |
299 | st a, player_died | |
300 | } | |
294 | } | |
295 | } else { | |
296 | ld a, 1 | |
297 | st a, player_died | |
301 | 298 | } |
302 | 299 | } |
303 | 300 | |
313 | 310 | st off, c |
314 | 311 | add ptr, new_pos |
315 | 312 | ld y, 0 |
313 | ||
316 | 314 | // check collision. |
317 | 315 | ld a, [ptr] + y |
318 | } | |
319 | // if "collision" is with your own self, treat it as if it's blank space! | |
320 | cmp a, 82 | |
321 | if z { | |
322 | ld a, 32 | |
323 | } | |
324 | cmp a, 32 | |
325 | if z { | |
326 | point ptr into screen { | |
316 | ||
317 | // if "collision" is with your own self, treat it as if it's blank space! | |
318 | cmp a, 82 | |
319 | if z { | |
320 | ld a, 32 | |
321 | } | |
322 | cmp a, 32 | |
323 | if z { | |
327 | 324 | reset ptr 0 |
328 | 325 | st off, c |
329 | 326 | add ptr, pos |
330 | 327 | copy 32, [ptr] + y |
331 | } | |
332 | ||
333 | copy new_pos, pos | |
334 | ||
335 | point ptr into screen { | |
328 | ||
329 | copy new_pos, pos | |
330 | ||
336 | 331 | reset ptr 0 |
337 | 332 | st off, c |
338 | 333 | add ptr, pos |
0 | // Include `support/${PLATFORM}.60p` before this source | |
1 | // Should print H (being ASCII 72 = 8 * 9) | |
2 | ||
3 | // Increase y by 7, circuitously | |
4 | // | |
5 | define foo routine | |
6 | inputs y | |
7 | outputs y, n, z | |
8 | trashes a, c | |
9 | { | |
10 | save x { | |
11 | ld x, 0 | |
12 | for x up to 6 { | |
13 | inc y | |
14 | } | |
15 | } | |
16 | } | |
17 | ||
18 | // Each iteration increases y by 8; there are 9 iterations | |
19 | // | |
20 | define main routine | |
21 | outputs x, y, n, z | |
22 | trashes a, c | |
23 | { | |
24 | ld x, 0 | |
25 | ld y, 0 | |
26 | for x up to 8 { | |
27 | inc y | |
28 | call foo | |
29 | } | |
30 | ld a, y | |
31 | call chrout | |
32 | } |
229 | 229 | elif isinstance(instr, For): |
230 | 230 | self.analyze_for(instr, context) |
231 | 231 | elif isinstance(instr, WithInterruptsOff): |
232 | self.analyze_block(instr.block, context) | |
233 | if context.encountered_gotos(): | |
234 | raise IllegalJumpError(instr, instr) | |
232 | self.analyze_with_interrupts_off(instr, context) | |
235 | 233 | elif isinstance(instr, Save): |
236 | 234 | self.analyze_save(instr, context) |
237 | 235 | elif isinstance(instr, PointInto): |
289 | 287 | target = context.get_assoc(dest.ref) |
290 | 288 | if not target: |
291 | 289 | raise ForbiddenWriteError(instr, dest.ref) |
292 | context.set_touched(target) | |
293 | 290 | context.set_written(target) |
294 | 291 | |
295 | 292 | elif self.get_type(src) != self.get_type(dest): |
464 | 461 | target = context.get_assoc(dest.ref) |
465 | 462 | if not target: |
466 | 463 | raise ForbiddenWriteError(instr, dest.ref) |
467 | context.assert_writeable(target) | |
468 | context.set_touched(target) | |
469 | 464 | context.set_written(target) |
470 | 465 | |
471 | 466 | elif isinstance(src, IndirectRef) and isinstance(dest, LocationRef): |
476 | 471 | raise UnmeaningfulReadError(instr, src.ref) |
477 | 472 | context.assert_meaningful(origin) |
478 | 473 | |
479 | context.assert_writeable(dest) | |
480 | context.set_touched(dest) | |
481 | 474 | context.set_written(dest) |
475 | ||
482 | 476 | elif isinstance(src, IndirectRef) and isinstance(dest, IndirectRef): |
483 | 477 | context.assert_meaningful(src.ref, dest.ref, REG_Y) |
484 | 478 | |
490 | 484 | target = context.get_assoc(dest.ref) |
491 | 485 | if not target: |
492 | 486 | raise ForbiddenWriteError(instr, dest.ref) |
493 | context.assert_writeable(target) | |
494 | context.set_touched(target) | |
495 | 487 | context.set_written(target) |
496 | 488 | |
497 | 489 | elif isinstance(src, LocationRef) and isinstance(dest, IndexedRef): |
502 | 494 | context.set_written(dest.ref) |
503 | 495 | elif isinstance(src, IndexedRef) and isinstance(dest, LocationRef): |
504 | 496 | context.assert_meaningful(src.ref, src.index) |
505 | context.set_touched(dest) | |
506 | 497 | context.set_written(dest) |
507 | 498 | else: |
508 | 499 | context.assert_meaningful(src) |
568 | 559 | exit_context = context.clone() |
569 | 560 | |
570 | 561 | for ref in type_.outputs: |
571 | exit_context.set_touched(ref) # ? | |
572 | 562 | exit_context.set_written(ref) |
573 | 563 | |
574 | 564 | for ref in type_.trashes: |
676 | 666 | context.set_range(instr.dest, instr.final, instr.final) |
677 | 667 | context.set_writeable(instr.dest) |
678 | 668 | |
669 | def analyze_with_interrupts_off(self, instr, context): | |
670 | block = instr.block | |
671 | for instr in block.instrs: | |
672 | if isinstance(instr, (Call, GoTo, WithInterruptsOff)): | |
673 | raise IllegalJumpError(instr, instr) | |
674 | self.analyze_instr(instr, context) | |
675 | ||
679 | 676 | def analyze_save(self, instr, context): |
680 | 677 | batons = [] |
681 | 678 | for location in instr.locations: |
5 | 5 | from sixtypical.model import ( |
6 | 6 | TYPE_BIT, TYPE_BYTE, TYPE_WORD, |
7 | 7 | RoutineType, VectorType, TableType, PointerType, |
8 | LocationRef, ConstantRef, IndirectRef, IndexedRef, | |
8 | ConstantRef, IndirectRef, IndexedRef, | |
9 | 9 | ) |
10 | 10 | from sixtypical.scanner import Scanner |
11 | ||
12 | ||
13 | class SymEntry(object): | |
14 | def __init__(self, ast_node, type_): | |
15 | self.ast_node = ast_node | |
16 | self.type_ = type_ | |
17 | ||
18 | def __repr__(self): | |
19 | return "%s(%r, %r)" % (self.__class__.__name__, self.ast_node, self.type_) | |
11 | from sixtypical.symtab import SymEntry | |
20 | 12 | |
21 | 13 | |
22 | 14 | class ForwardReference(object): |
25 | 17 | |
26 | 18 | def __repr__(self): |
27 | 19 | return "%s(%r)" % (self.__class__.__name__, self.name) |
28 | ||
29 | ||
30 | class SymbolTable(object): | |
31 | def __init__(self): | |
32 | self.symbols = {} # symbol name -> SymEntry | |
33 | self.locals = {} # routine name -> (symbol name -> SymEntry) | |
34 | self.typedefs = {} # type name -> Type AST | |
35 | self.consts = {} # const name -> ConstantRef | |
36 | ||
37 | for name in ('a', 'x', 'y'): | |
38 | self.symbols[name] = SymEntry(None, TYPE_BYTE) | |
39 | for name in ('c', 'z', 'n', 'v'): | |
40 | self.symbols[name] = SymEntry(None, TYPE_BIT) | |
41 | ||
42 | def __str__(self): | |
43 | return "Symbols: {}\nLocals: {}\nTypedefs: {}\nConsts: {}".format(self.symbols, self.locals, self.typedefs, self.consts) | |
44 | ||
45 | def has_local(self, routine_name, name): | |
46 | return name in self.locals.get(routine_name, {}) | |
47 | ||
48 | def fetch_global_type(self, name): | |
49 | return self.symbols[name].type_ | |
50 | ||
51 | def fetch_local_type(self, routine_name, name): | |
52 | return self.locals[routine_name][name].type_ | |
53 | ||
54 | def fetch_global_ref(self, name): | |
55 | if name in self.symbols: | |
56 | return LocationRef(name) | |
57 | return None | |
58 | ||
59 | def fetch_local_ref(self, routine_name, name): | |
60 | routine_locals = self.locals.get(routine_name, {}) | |
61 | if name in routine_locals: | |
62 | return LocationRef(name) | |
63 | return None | |
64 | 20 | |
65 | 21 | |
66 | 22 | class Parser(object): |
0 | # encoding: UTF-8 | |
1 | ||
2 | from sixtypical.model import ( | |
3 | TYPE_BIT, TYPE_BYTE, LocationRef, | |
4 | ) | |
5 | ||
6 | ||
7 | class SymEntry(object): | |
8 | def __init__(self, ast_node, type_): | |
9 | self.ast_node = ast_node | |
10 | self.type_ = type_ | |
11 | ||
12 | def __repr__(self): | |
13 | return "%s(%r, %r)" % (self.__class__.__name__, self.ast_node, self.type_) | |
14 | ||
15 | ||
16 | class SymbolTable(object): | |
17 | def __init__(self): | |
18 | self.symbols = {} # symbol name -> SymEntry | |
19 | self.locals = {} # routine name -> (symbol name -> SymEntry) | |
20 | self.typedefs = {} # type name -> Type AST | |
21 | self.consts = {} # const name -> ConstantRef | |
22 | ||
23 | for name in ('a', 'x', 'y'): | |
24 | self.symbols[name] = SymEntry(None, TYPE_BYTE) | |
25 | for name in ('c', 'z', 'n', 'v'): | |
26 | self.symbols[name] = SymEntry(None, TYPE_BIT) | |
27 | ||
28 | def __str__(self): | |
29 | return "Symbols: {}\nLocals: {}\nTypedefs: {}\nConsts: {}".format(self.symbols, self.locals, self.typedefs, self.consts) | |
30 | ||
31 | def has_local(self, routine_name, name): | |
32 | return name in self.locals.get(routine_name, {}) | |
33 | ||
34 | def fetch_global_type(self, name): | |
35 | return self.symbols[name].type_ | |
36 | ||
37 | def fetch_local_type(self, routine_name, name): | |
38 | return self.locals[routine_name][name].type_ | |
39 | ||
40 | def fetch_global_ref(self, name): | |
41 | if name in self.symbols: | |
42 | return LocationRef(name) | |
43 | return None | |
44 | ||
45 | def fetch_local_ref(self, routine_name, name): | |
46 | routine_locals = self.locals.get(routine_name, {}) | |
47 | if name in routine_locals: | |
48 | return LocationRef(name) | |
49 | return None |
0 | 0 | #!/bin/sh |
1 | 1 | |
2 | # This currently represents a lot of tests! If you only want to run a subset, | |
3 | # it's probably best to run `falderal` manually on the file(s) you want to test. | |
4 | ||
2 | 5 | falderal --substring-error \ |
6 | "tests/appliances/sixtypical.md" \ | |
3 | 7 | "tests/SixtyPical Syntax.md" \ |
4 | 8 | "tests/SixtyPical Analysis.md" \ |
5 | 9 | "tests/SixtyPical Storage.md" \ |
8 | 8 | For control flow, see [SixtyPical Control Flow](SixtyPical%20Control%20Flow.md). |
9 | 9 | |
10 | 10 | [Falderal]: http://catseye.tc/node/Falderal |
11 | ||
12 | -> Functionality "Analyze SixtyPical program" is implemented by | |
13 | -> shell command "bin/sixtypical --analyze-only --traceback %(test-body-file) && echo ok" | |
14 | 11 | |
15 | 12 | -> Tests for functionality "Analyze SixtyPical program" |
16 | 13 | |
753 | 750 | | } |
754 | 751 | ? IllegalJumpError |
755 | 752 | |
753 | A `call` cannot appear within a `with interrupts` block. | |
754 | ||
755 | | vector routine | |
756 | | inputs x | |
757 | | outputs x | |
758 | | trashes z, n | |
759 | | bar | |
760 | | | |
761 | | define foo routine | |
762 | | inputs x | |
763 | | outputs x | |
764 | | trashes z, n | |
765 | | { | |
766 | | inc x | |
767 | | } | |
768 | | | |
769 | | define other routine | |
770 | | trashes bar, a, n, z | |
771 | | { | |
772 | | ld a, 0 | |
773 | | } | |
774 | | | |
775 | | define main routine | |
776 | | trashes bar, a, n, z | |
777 | | { | |
778 | | with interrupts off { | |
779 | | copy foo, bar | |
780 | | call other | |
781 | | } | |
782 | | } | |
783 | ? IllegalJumpError | |
784 | ||
785 | A `with interrupts` block cannot appear within a `with interrupts` block. | |
786 | ||
787 | | vector routine | |
788 | | inputs x | |
789 | | outputs x | |
790 | | trashes z, n | |
791 | | bar | |
792 | | | |
793 | | define foo routine | |
794 | | inputs x | |
795 | | outputs x | |
796 | | trashes z, n | |
797 | | { | |
798 | | inc x | |
799 | | } | |
800 | | | |
801 | | define main routine | |
802 | | trashes bar, a, n, z | |
803 | | { | |
804 | | with interrupts off { | |
805 | | copy foo, bar | |
806 | | with interrupts off { | |
807 | | copy foo, bar | |
808 | | } | |
809 | | } | |
810 | | } | |
811 | ? IllegalJumpError | |
812 | ||
756 | 813 | ### typedef ### |
757 | 814 | |
758 | 815 | A typedef is a more-readable alias for a type. "Alias" means |
4 | 4 | SixtyPical to 6502 machine code. |
5 | 5 | |
6 | 6 | [Falderal]: http://catseye.tc/node/Falderal |
7 | ||
8 | -> Functionality "Compile SixtyPical program" is implemented by | |
9 | -> shell command "bin/sixtypical --output-format=c64-basic-prg --traceback %(test-body-file) --output /tmp/foo && tests/appliances/bin/dcc6502-adapter </tmp/foo" | |
10 | 7 | |
11 | 8 | -> Tests for functionality "Compile SixtyPical program" |
12 | 9 |
703 | 703 | | } |
704 | 704 | ? UnmeaningfulReadError: y |
705 | 705 | |
706 | And in particular, you can't uninitialize the loop variable, in the loop. | |
707 | ||
708 | | define foo routine | |
709 | | trashes x | |
710 | | { | |
711 | | } | |
712 | | | |
713 | | define main routine | |
714 | | outputs x, y, n, z | |
715 | | trashes c | |
716 | | { | |
717 | | ld x, 0 | |
718 | | ld y, 15 | |
719 | | for x up to 15 { | |
720 | | inc y | |
721 | | call foo | |
722 | | } | |
723 | | } | |
724 | ? ForbiddenWriteError: x | |
725 | ||
726 | So, if you call a routine from inside the loop, it better not also | |
727 | loop on the same variable. | |
728 | ||
729 | | define foo routine | |
730 | | inputs y | |
731 | | outputs x, y, n, z | |
732 | | trashes c | |
733 | | { | |
734 | | ld x, 0 | |
735 | | for x up to 15 { | |
736 | | inc y | |
737 | | } | |
738 | | } | |
739 | | | |
740 | | define main routine | |
741 | | outputs x, y, n, z | |
742 | | trashes c | |
743 | | { | |
744 | | ld x, 0 | |
745 | | ld y, 15 | |
746 | | for x up to 15 { | |
747 | | inc y | |
748 | | call foo | |
749 | | } | |
750 | | } | |
751 | ? ForbiddenWriteError: x | |
752 | ||
753 | But if you take care to save and restore the loop variable in the | |
754 | called routine, it will be okay. | |
755 | ||
756 | | define foo routine | |
757 | | inputs y | |
758 | | outputs y, n, z | |
759 | | trashes a, c | |
760 | | { | |
761 | | save x { | |
762 | | ld x, 0 | |
763 | | for x up to 15 { | |
764 | | inc y | |
765 | | } | |
766 | | } | |
767 | | } | |
768 | | | |
769 | | define main routine | |
770 | | outputs x, y, n, z | |
771 | | trashes a, c | |
772 | | { | |
773 | | ld x, 0 | |
774 | | ld y, 15 | |
775 | | for x up to 15 { | |
776 | | inc y | |
777 | | call foo | |
778 | | } | |
779 | | } | |
780 | = ok | |
781 | ||
706 | 782 | The "for" loop does not preserve the `z` or `n` registers. |
707 | 783 | |
708 | 784 | | define foo routine trashes x { |
59 | 59 | to pass these tests to be considered an implementation of SixtyPical. |
60 | 60 | |
61 | 61 | [Falderal]: http://catseye.tc/node/Falderal |
62 | ||
63 | -> Functionality "Dump fallthru info for SixtyPical program" is implemented by | |
64 | -> shell command "bin/sixtypical --optimize-fallthru --dump-fallthru-info --analyze-only --traceback %(test-body-file)" | |
65 | ||
66 | -> Functionality "Compile SixtyPical program with fallthru optimization" is implemented by | |
67 | -> shell command "bin/sixtypical --output-format=c64-basic-prg --optimize-fallthru --traceback %(test-body-file) --output /tmp/foo && tests/appliances/bin/dcc6502-adapter </tmp/foo" | |
68 | 62 | |
69 | 63 | -> Tests for functionality "Dump fallthru info for SixtyPical program" |
70 | 64 |
1588 | 1588 | | } |
1589 | 1589 | ? RangeExceededError |
1590 | 1590 | |
1591 | ### dynamic recurrence of `point ... into` blocks | |
1592 | ||
1593 | You cannot call a routine which trashes the pointer from inside a | |
1594 | `point ... into` block. Remember that `point ... into` by | |
1595 | itself doesn't change anything, so doesn't trash anything. | |
1596 | ||
1597 | | byte table[256] tab | |
1598 | | byte table[256] other | |
1599 | | pointer ptr | |
1600 | | | |
1601 | | define sub routine | |
1602 | | inputs other | |
1603 | | outputs other | |
1604 | | { | |
1605 | | point ptr into other { | |
1606 | | } | |
1607 | | } | |
1608 | | | |
1609 | | define main routine | |
1610 | | inputs tab, other | |
1611 | | outputs tab, other | |
1612 | | trashes a, y, c, z, n, v, ptr | |
1613 | | { | |
1614 | | ld y, 0 | |
1615 | | point ptr into tab { | |
1616 | | reset ptr 0 | |
1617 | | copy 123, [ptr] + y | |
1618 | | call sub | |
1619 | | copy 123, [ptr] + y | |
1620 | | } | |
1621 | | } | |
1622 | = ok | |
1623 | ||
1624 | | byte table[256] tab | |
1625 | | byte table[256] other | |
1626 | | pointer ptr | |
1627 | | | |
1628 | | define sub routine | |
1629 | | inputs other | |
1630 | | outputs other | |
1631 | | trashes ptr | |
1632 | | { | |
1633 | | point ptr into other { | |
1634 | | reset ptr 0 | |
1635 | | } | |
1636 | | } | |
1637 | | | |
1638 | | define main routine | |
1639 | | inputs tab, other | |
1640 | | outputs tab, other | |
1641 | | trashes a, y, c, z, n, v, ptr | |
1642 | | { | |
1643 | | ld y, 0 | |
1644 | | point ptr into tab { | |
1645 | | reset ptr 0 | |
1646 | | copy 123, [ptr] + y | |
1647 | | call sub | |
1648 | | copy 123, [ptr] + y | |
1649 | | } | |
1650 | | } | |
1651 | ? UnmeaningfulReadError | |
1652 | ||
1591 | 1653 | ### locals ### |
1592 | 1654 | |
1593 | 1655 | When memory locations are defined static to a routine, they cannot be |
7 | 7 | but not necessarily sensible programs. |
8 | 8 | |
9 | 9 | [Falderal]: http://catseye.tc/node/Falderal |
10 | ||
11 | -> Functionality "Check syntax of SixtyPical program" is implemented by | |
12 | -> shell command "bin/sixtypical --parse-only --traceback %(test-body-file) && echo ok" | |
13 | 10 | |
14 | 11 | -> Tests for functionality "Check syntax of SixtyPical program" |
15 | 12 |
0 | This file contains only the [Falderal][] directives that define the different | |
1 | functionalities tested by the test suite, assuming that it's the reference | |
2 | implementation, `sixtypical`, that is going to implement these functionalities. | |
3 | ||
4 | [Falderal]: http://catseye.tc/node/Falderal | |
5 | ||
6 | -> Functionality "Check syntax of SixtyPical program" is implemented by | |
7 | -> shell command "bin/sixtypical --parse-only --traceback %(test-body-file) && echo ok" | |
8 | ||
9 | -> Functionality "Analyze SixtyPical program" is implemented by | |
10 | -> shell command "bin/sixtypical --analyze-only --traceback %(test-body-file) && echo ok" | |
11 | ||
12 | -> Functionality "Compile SixtyPical program" is implemented by | |
13 | -> shell command "bin/sixtypical --output-format=c64-basic-prg --traceback %(test-body-file) --output /tmp/foo && tests/appliances/bin/dcc6502-adapter </tmp/foo" | |
14 | ||
15 | -> Functionality "Dump fallthru info for SixtyPical program" is implemented by | |
16 | -> shell command "bin/sixtypical --optimize-fallthru --dump-fallthru-info --analyze-only --traceback %(test-body-file)" | |
17 | ||
18 | -> Functionality "Compile SixtyPical program with fallthru optimization" is implemented by | |
19 | -> shell command "bin/sixtypical --output-format=c64-basic-prg --optimize-fallthru --traceback %(test-body-file) --output /tmp/foo && tests/appliances/bin/dcc6502-adapter </tmp/foo" |