Merge pull request #20 from catseye/develop-0.19
Develop 0.19
Chris Pressey authored 5 years ago
GitHub committed 5 years ago
0 | 0 | History of SixtyPical |
1 | 1 | ===================== |
2 | ||
3 | 0.19 | |
4 | ---- | |
5 | ||
6 | * A `table` may be defined with more than 256 entries, even | |
7 | though the conventional index syntax can only refer to the | |
8 | first 256 entries. | |
9 | * A `pointer` may point inside values of type `byte table`, | |
10 | allowing access to entries beyond the 256th. | |
11 | * `buffer` types have been eliminated from the language, | |
12 | as the above two improvements allow `byte table`s to | |
13 | do everything `buffer`s previously did. | |
14 | * When accessing a table with an index, a constant offset | |
15 | can also be given. | |
16 | * Accessing a `table` through a `pointer` must be done in | |
17 | the context of a `point ... into` block. This allows the | |
18 | analyzer to check *which* table is being accessed. | |
19 | * Refactored compiler internals so that type information | |
20 | is stored in a single symbol table shared by all phases. | |
21 | * Refactored internal data structures that represent | |
22 | references and types to be immutable `namedtuple`s. | |
23 | * Added `--dump-exit-contexts` option to `sixtypical`. | |
24 | * Added a new `--run-on=<emu>` option to `sixtypical`, which | |
25 | replaces the old `loadngo.sh` script. | |
2 | 26 | |
3 | 27 | 0.18 |
4 | 28 | ---- |
0 | 0 | SixtyPical |
1 | 1 | ========== |
2 | 2 | |
3 | _Version 0.18. Work-in-progress, everything is subject to change._ | |
3 | _Version 0.19. Work-in-progress, everything is subject to change._ | |
4 | 4 | |
5 | **SixtyPical** is a low-level programming language with advanced | |
6 | static analysis. Many of its primitive instructions resemble | |
7 | those of the 6502 CPU — in fact it is intended to be compiled to | |
8 | 6502 machine code — but along with these instructions are | |
9 | constructs which ease structuring and analyzing the code. The | |
10 | language aims to fill this niche: | |
5 | **SixtyPical** is a [low-level](#low-level) programming language | |
6 | supporting a sophisticated [static analysis](#static-analysis). | |
7 | Its reference compiler can generate [efficient code](#efficient-code) for | |
8 | several 6502-based [target platforms](#target-platforms) while catching many | |
9 | common mistakes at compile-time, reducing the time spent in debugging. | |
10 | ||
11 | Quick Start | |
12 | ----------- | |
13 | ||
14 | Make sure you have Python (2.7 or 3.5+) installed. Then | |
15 | clone this repository and put its `bin` directory on your | |
16 | executable search path. Then you can run: | |
17 | ||
18 | sixtypical | |
19 | ||
20 | If you have the [VICE][] emulator suite installed, you can run | |
21 | ||
22 | sixtypical --run-on=x64 eg/c64/hearts.60p | |
23 | ||
24 | and it will compile the [hearts.60p source code](eg/c64/hearts.60p) and | |
25 | automatically start it in the `x64` emulator, and you should see: | |
26 | ||
27 |  | |
28 | ||
29 | You can try `sixtypical --run-on` on other sources in the `eg` directory | |
30 | tree, which contains more extensive examples, including an entire | |
31 | game(-like program); see [eg/README.md](eg/README.md) for a listing. | |
32 | ||
33 | Features | |
34 | -------- | |
35 | ||
36 | SixtyPical aims to fill this niche: | |
11 | 37 | |
12 | 38 | * You'd use assembly, but you don't want to spend hours |
13 | 39 | debugging (say) a memory overrun that happened because of a |
18 | 44 | |
19 | 45 | SixtyPical gives the programmer a coding regimen on par with assembly |
20 | 46 | language in terms of size and hands-on-ness, but also able to catch |
21 | many ridiculous silly errors at compile time, such as | |
47 | many ridiculous silly errors at compile time. | |
48 | ||
49 | ### Low level | |
50 | ||
51 | Many of SixtyPical's primitive instructions resemble those of the | |
52 | [MOS Technology 6502][] — it is in fact intended to be compiled to 6502 | |
53 | machine code. However, it also provides some "higher-level" operations | |
54 | based on common 8-bit machine-language programming idioms, including | |
55 | ||
56 | * copying values from one register to another (via a third register when | |
57 | there are no underlying instructions that directly support it) | |
58 | * copying, adding, and comparing 16-bit values (done in two steps) | |
59 | * explicit tail calls | |
60 | * indirect subroutine calls | |
61 | ||
62 | While a programmer will find these constructs convenient, their | |
63 | inclusion in the language is primarily to make programs easier to analyze. | |
64 | ||
65 | ### Static analysis | |
66 | ||
67 | The SixtyPical language defines an [effect system][], and the reference | |
68 | compiler [abstractly interprets][] the input program to check that | |
69 | it conforms to it. It can detect common mistakes such as | |
22 | 70 | |
23 | 71 | * you forgot to clear carry before adding something to the accumulator |
24 | 72 | * a subroutine that you called trashes a register you thought it preserved |
26 | 74 | * you tried to write the address of something that was not a routine, to |
27 | 75 | a jump vector |
28 | 76 | |
29 | Many of these checks are done with _abstract interpretation_, where we | |
30 | go through the program step by step, tracking not just the changes that | |
31 | happen during a _specific_ execution of the program, but _sets_ of changes | |
32 | that could _possibly_ happen in any run of the program. | |
77 | ### Efficient code | |
33 | 78 | |
34 | SixtyPical also provides some convenient operations based on | |
35 | machine-language programming idioms, such as | |
79 | Unlike most conventional languages, in SixtyPical the programmer must manage | |
80 | memory very explicitly, selecting the registers and memory locations to store | |
81 | each piece of data in. So, unlike a C compiler such as [cc65][], a SixtyPical | |
82 | compiler doesn't need to generate code to handle [calling conventions][] or | |
83 | [register allocation][]. This results in smaller (and thus faster) programs. | |
36 | 84 | |
37 | * copying values from one register to another (via a third register when | |
38 | there are no underlying instructions that directly support it); this | |
39 | includes 16-bit values, which are copied in two steps | |
40 | * explicit tail calls | |
41 | * indirect subroutine calls | |
85 | The flagship demo, a minigame for the Commodore 64, compiles to | |
86 | a **930**-byte `.PRG` file. | |
87 | ||
88 | ### Target platforms | |
89 | ||
90 | The reference implementation can analyze and compile SixtyPical programs to | |
91 | 6502 machine code formats which can run on several 6502-based 8-bit architectures: | |
92 | ||
93 | * [Commodore 64][] | |
94 | * [Commodore VIC-20][] | |
95 | * [Atari 2600][] | |
96 | * [Apple II series][] | |
97 | ||
98 | For example programs for each of these, see [eg/README.md](eg/README.md). | |
99 | ||
100 | Specification | |
101 | ------------- | |
42 | 102 | |
43 | 103 | SixtyPical is defined by a specification document, a set of test cases, |
44 | and a reference implementation written in Python 2. The reference | |
45 | implementation can analyze and compile SixtyPical programs to 6502 machine | |
46 | code, which can be run on several 6502-based 8-bit architectures: | |
104 | and a reference implementation written in Python. | |
47 | 105 | |
48 | * Commodore 64 | |
49 | * Commodore VIC-20 | |
50 | * Atari 2600 VCS | |
51 | * Apple II | |
106 | There are over 400 test cases, written in [Falderal][] format for readability. | |
107 | In order to run the tests for compilation, [dcc6502][] needs to be installed. | |
52 | 108 | |
53 | Quick Start | |
54 | ----------- | |
55 | ||
56 | If you have the [VICE][] emulator installed, from this directory, you can run | |
57 | ||
58 | ./loadngo.sh c64 eg/c64/hearts.60p | |
59 | ||
60 | and it will compile the [hearts.60p source code](eg/c64/hearts.60p) and | |
61 | automatically start it in the `x64` emulator, and you should see: | |
62 | ||
63 |  | |
64 | ||
65 | You can try the `loadngo.sh` script on other sources in the `eg` directory | |
66 | tree, which contains more extensive examples, including an entire | |
67 | game(-like program); see [eg/README.md](eg/README.md) for a listing. | |
68 | ||
69 | [VICE]: http://vice-emu.sourceforge.net/ | |
109 | * [SixtyPical specification](doc/SixtyPical.md) | |
110 | * [Literate test suite for SixtyPical syntax](tests/SixtyPical%20Syntax.md) | |
111 | * [Literate test suite for SixtyPical analysis](tests/SixtyPical%20Analysis.md) | |
112 | * [Literate test suite for SixtyPical compilation](tests/SixtyPical%20Compilation.md) | |
113 | * [Literate test suite for SixtyPical fallthru optimization](tests/SixtyPical%20Fallthru.md) | |
70 | 114 | |
71 | 115 | Documentation |
72 | 116 | ------------- |
73 | 117 | |
74 | 118 | * [Design Goals](doc/Design%20Goals.md) |
75 | * [SixtyPical specification](doc/SixtyPical.md) | |
76 | 119 | * [SixtyPical revision history](HISTORY.md) |
77 | * [Literate test suite for SixtyPical syntax](tests/SixtyPical%20Syntax.md) | |
78 | * [Literate test suite for SixtyPical analysis](tests/SixtyPical%20Analysis.md) | |
79 | * [Literate test suite for SixtyPical compilation](tests/SixtyPical%20Compilation.md) | |
80 | * [Literate test suite for SixtyPical fallthru optimization](tests/SixtyPical%20Fallthru.md) | |
81 | 120 | * [6502 Opcodes used/not used in SixtyPical](doc/6502%20Opcodes.md) |
82 | 121 | * [Output formats supported by `sixtypical`](doc/Output%20Formats.md) |
83 | 122 | * [TODO](TODO.md) |
123 | ||
124 | [MOS Technology 6520]: https://en.wikipedia.org/wiki/MOS_Technology_6502 | |
125 | [effect system]: https://en.wikipedia.org/wiki/Effect_system | |
126 | [abstractly interprets]: https://en.wikipedia.org/wiki/Abstract_interpretation | |
127 | [calling conventions]: https://en.wikipedia.org/wiki/Calling_convention | |
128 | [register allocation]: https://en.wikipedia.org/wiki/Register_allocation | |
129 | [VICE]: http://vice-emu.sourceforge.net/ | |
130 | [cc65]: https://cc65.github.io/ | |
131 | [Commodore 64]: https://en.wikipedia.org/wiki/Commodore_64 | |
132 | [Commodore VIC-20]: https://en.wikipedia.org/wiki/Commodore_VIC-20 | |
133 | [Atari 2600]: https://en.wikipedia.org/wiki/Atari_2600 | |
134 | [Apple II series]: https://en.wikipedia.org/wiki/Apple_II_series | |
135 | [Falderal]: https://catseye.tc/node/Falderal | |
136 | [dcc6502]: https://github.com/tcarmelveilleux/dcc6502 |
11 | 11 | Which uses some other storage location instead of the stack. A local static |
12 | 12 | would be a good candidate for such. |
13 | 13 | |
14 | ### Associate each pointer with the buffer it points into | |
14 | ### Analyze `call` within blocks? | |
15 | 15 | |
16 | Check that the buffer being read or written to through pointer, appears in appropriate | |
17 | inputs or outputs set. | |
16 | What happens if you call another routine from inside a `with interrupts off` block? | |
18 | 17 | |
19 | In the analysis, when we obtain a pointer, we need to record, in context, what buffer | |
20 | that pointer came from. | |
18 | What happens if you call another routine from inside a `save` block? | |
21 | 19 | |
22 | When we write through that pointer, we need to set that buffer as written. | |
20 | What happens if you call another routine from inside a `point into` block? | |
23 | 21 | |
24 | When we read through the pointer, we need to check that the buffer is readable. | |
22 | What happens if you call another routine from inside a `for` block? | |
25 | 23 | |
26 | ### Table overlays | |
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`.) | |
27 | 28 | |
28 | They are uninitialized, but the twist is, the address is a buffer that is | |
29 | an input to and/or output of the routine. So, they are defined (insofar | |
30 | as the buffer is defined.) | |
29 | These holes need to be plugged. | |
31 | 30 | |
32 | They are therefore a "view" of a section of a buffer. | |
31 | ### Reset pointer in `point into` blocks | |
33 | 32 | |
34 | This is slightly dangerous since it does permit aliases: the buffer and the | |
35 | table refer to the same memory. | |
33 | We have `point into` blocks, but maybe the action when entering such a | |
34 | block shouldn't always be to set the given pointer to the start of the given table. | |
36 | 35 | |
37 | Although, if they are `static`, you could say, in the routine in which they | |
38 | are `static`, as soon as you've established one, you can no longer use the | |
39 | buffer; and the ones you establish must be disjoint. | |
36 | That is, sometimes we would like to start at some fixed offset. And | |
37 | sometimes we want to (re)set the pointer, without closing and starting a new block. | |
40 | 38 | |
41 | (That seems to be the most compelling case for restricting them to `static`.) | |
39 | ### Pointers associated globally with a table | |
42 | 40 | |
43 | An alternative would be `static` pointers, which are currently not possible because | |
44 | pointers must be zero-page, thus `@`, thus uninitialized. | |
41 | We have `point into` blocks, but we would also like to sometimes pass a pointer | |
42 | around to different routines, and have them all "know" what table it operates on. | |
43 | ||
44 | We could associate every pointer variable with a specific table variable, in its | |
45 | declaration. This makes some things simple, and would allow us to know what table a | |
46 | pointer is supposed to point into, even if that pointer was passed into our routine. | |
47 | ||
48 | One drawback is that it would limit each pointer to be used only on one table. Since a | |
49 | pointer basically represents a zero-page location, and since those are a relatively scarce | |
50 | resource, we would prefer if a single pointer could be used to point into different tables | |
51 | at different times. | |
52 | ||
53 | These can co-exist with general, non-specific-table-linked `pointer` variables. | |
54 | ||
55 | ### Local non-statics | |
56 | ||
57 | Somewhat related to the above, it should be possible to declare a local storage | |
58 | location which is not static. | |
59 | ||
60 | In this case, it would be considered uninitialized each time the routine was | |
61 | entered. | |
62 | ||
63 | So, you do not have a guarantee that it has a valid value. But you are guaranteed | |
64 | that no other routine can read or modify it. | |
65 | ||
66 | It also enables a trick: if there are two routines A and B, and A never calls B | |
67 | (even indirectly), and B never calls A (even indirectly), then their locals can | |
68 | be allocated at the same space. | |
69 | ||
70 | A local could also be given an explicit address. In this case, two locals in | |
71 | different routines could be given the same address, and as long as the condition | |
72 | in the above paragraph holds, that's okay. (If it doesn't, the analyzer should | |
73 | detect it.) | |
74 | ||
75 | This would permit local pointers, which would be one way of addressing the | |
76 | "same pointer to different tables" problem. | |
77 | ||
78 | ### Copy byte to/from table | |
79 | ||
80 | Do we want a `copy bytevar, table + x` instruction? We don't currently have one. | |
81 | You have to `ld a`, `st a`. I think maybe we should have one. | |
45 | 82 | |
46 | 83 | ### Tail-call optimization |
47 | 84 |
0 | 0 | #!/usr/bin/env python |
1 | ||
2 | """Usage: sixtypical [OPTIONS] FILES | |
3 | ||
4 | Analyzes and compiles a Sixtypical program. | |
5 | """ | |
6 | 1 | |
7 | 2 | from os.path import realpath, dirname, join |
8 | 3 | import sys |
11 | 6 | |
12 | 7 | # ----------------------------------------------------------------- # |
13 | 8 | |
9 | from argparse import ArgumentParser | |
14 | 10 | import codecs |
15 | from argparse import ArgumentParser | |
11 | import json | |
16 | 12 | from pprint import pprint |
13 | from subprocess import check_call | |
17 | 14 | import sys |
15 | from tempfile import NamedTemporaryFile | |
18 | 16 | import traceback |
19 | 17 | |
20 | from sixtypical.parser import Parser, ParsingContext, merge_programs | |
18 | from sixtypical.parser import Parser, SymbolTable, merge_programs | |
21 | 19 | from sixtypical.analyzer import Analyzer |
22 | 20 | from sixtypical.outputter import outputter_class_for |
23 | 21 | from sixtypical.compiler import Compiler |
24 | 22 | |
25 | 23 | |
26 | 24 | def process_input_files(filenames, options): |
27 | context = ParsingContext() | |
25 | symtab = SymbolTable() | |
28 | 26 | |
29 | 27 | programs = [] |
30 | 28 | |
31 | 29 | for filename in options.filenames: |
32 | 30 | text = open(filename).read() |
33 | parser = Parser(context, text, filename) | |
31 | parser = Parser(symtab, text, filename) | |
34 | 32 | if options.debug: |
35 | print(context) | |
33 | print(symtab) | |
36 | 34 | program = parser.program() |
37 | 35 | programs.append(program) |
38 | 36 | |
41 | 39 | |
42 | 40 | program = merge_programs(programs) |
43 | 41 | |
44 | analyzer = Analyzer(debug=options.debug) | |
45 | analyzer.analyze_program(program) | |
42 | analyzer = Analyzer(symtab, debug=options.debug) | |
43 | ||
44 | try: | |
45 | analyzer.analyze_program(program) | |
46 | finally: | |
47 | if options.dump_exit_contexts: | |
48 | sys.stdout.write(json.dumps(analyzer.exit_contexts_map, indent=4, sort_keys=True, separators=(',', ':'))) | |
49 | sys.stdout.write("\n") | |
46 | 50 | |
47 | 51 | compilation_roster = None |
48 | 52 | if options.optimize_fallthru: |
49 | 53 | from sixtypical.fallthru import FallthruAnalyzer |
50 | 54 | |
51 | 55 | def dump(data, label=None): |
52 | import json | |
53 | 56 | if not options.dump_fallthru_info: |
54 | 57 | return |
55 | 58 | if label: |
57 | 60 | sys.stdout.write(json.dumps(data, indent=4, sort_keys=True, separators=(',', ':'))) |
58 | 61 | sys.stdout.write("\n") |
59 | 62 | |
60 | fa = FallthruAnalyzer(debug=options.debug) | |
63 | fa = FallthruAnalyzer(symtab, debug=options.debug) | |
61 | 64 | fa.analyze_program(program) |
62 | 65 | compilation_roster = fa.serialize() |
63 | 66 | dump(compilation_roster) |
64 | 67 | |
65 | if options.analyze_only or options.output is None: | |
68 | if options.analyze_only or (options.output is None and not options.run_on): | |
66 | 69 | return |
67 | 70 | |
68 | 71 | start_addr = None |
72 | 75 | else: |
73 | 76 | start_addr = int(options.origin, 10) |
74 | 77 | |
75 | with open(options.output, 'wb') as fh: | |
76 | outputter = outputter_class_for(options.output_format)(fh, start_addr=start_addr) | |
77 | outputter.write_prelude() | |
78 | compiler = Compiler(outputter.emitter) | |
79 | compiler.compile_program(program, compilation_roster=compilation_roster) | |
80 | outputter.write_postlude() | |
81 | if options.debug: | |
82 | pprint(outputter.emitter) | |
83 | else: | |
84 | outputter.emitter.serialize_to(fh) | |
78 | if options.run_on: | |
79 | fh = NamedTemporaryFile(delete=False) | |
80 | output_filename = fh.name | |
81 | Outputter = outputter_class_for({ | |
82 | 'x64': 'c64-basic-prg', | |
83 | 'xvic': 'vic20-basic-prg', | |
84 | 'stella': 'atari2600-cart', | |
85 | }.get(options.run_on)) | |
86 | else: | |
87 | fh = open(options.output, 'wb') | |
88 | output_filename = options.output | |
89 | Outputter = outputter_class_for(options.output_format) | |
90 | ||
91 | outputter = Outputter(fh, start_addr=start_addr) | |
92 | outputter.write_prelude() | |
93 | compiler = Compiler(symtab, outputter.emitter) | |
94 | compiler.compile_program(program, compilation_roster=compilation_roster) | |
95 | outputter.write_postlude() | |
96 | if options.debug: | |
97 | pprint(outputter.emitter) | |
98 | else: | |
99 | outputter.emitter.serialize_to(fh) | |
100 | ||
101 | fh.close() | |
102 | ||
103 | if options.run_on: | |
104 | emu = { | |
105 | 'x64': "x64 -config vicerc", | |
106 | 'xvic': "xvic -config vicerc", | |
107 | 'stella': "stella" | |
108 | }.get(options.run_on) | |
109 | if not emu: | |
110 | raise ValueError("No emulator configured for selected --run-on '{}'".format(options.output_format)) | |
111 | ||
112 | command = "{} {}".format(emu, output_filename) | |
113 | check_call(command, shell=True) | |
85 | 114 | |
86 | 115 | |
87 | 116 | if __name__ == '__main__': |
88 | argparser = ArgumentParser(__doc__.strip()) | |
117 | argparser = ArgumentParser() | |
89 | 118 | |
90 | 119 | argparser.add_argument( |
91 | 120 | 'filenames', metavar='FILENAME', type=str, nargs='+', |
114 | 143 | help="Only parse and analyze the program; do not compile it." |
115 | 144 | ) |
116 | 145 | argparser.add_argument( |
146 | "--dump-exit-contexts", | |
147 | action="store_true", | |
148 | help="Dump a map, in JSON, of the analysis context at each exit of each routine " | |
149 | "after analyzing the program." | |
150 | ) | |
151 | argparser.add_argument( | |
117 | 152 | "--optimize-fallthru", |
118 | 153 | action="store_true", |
119 | 154 | help="Reorder the routines in the program to maximize the number of tail calls " |
122 | 157 | argparser.add_argument( |
123 | 158 | "--dump-fallthru-info", |
124 | 159 | action="store_true", |
125 | help="Dump the fallthru map and ordering to stdout after analyzing the program." | |
160 | help="Dump the ordered fallthru map, in JSON, to stdout after analyzing the program." | |
126 | 161 | ) |
127 | 162 | argparser.add_argument( |
128 | 163 | "--parse-only", |
135 | 170 | help="Display debugging information when analyzing and compiling." |
136 | 171 | ) |
137 | 172 | argparser.add_argument( |
173 | "--run-on", type=str, default=None, | |
174 | help="If given, engage 'load-and-go' operation with the given emulator: write " | |
175 | "the output to a temporary filename using an appropriate --output-format, " | |
176 | "and boot the emulator with it. Options are: x64, xvic, stella." | |
177 | ) | |
178 | argparser.add_argument( | |
138 | 179 | "--traceback", |
139 | 180 | action="store_true", |
140 | 181 | help="When an error occurs, display a full Python traceback." |
182 | ) | |
183 | argparser.add_argument( | |
184 | "--version", | |
185 | action="version", | |
186 | version="%(prog)s 0.19" | |
141 | 187 | ) |
142 | 188 | |
143 | 189 | 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.15, | |
3 | This document describes the SixtyPical programming language version 0.19, | |
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.) |
11 | 11 | Refer to the bottom of this document for an EBNF grammar of the syntax of |
12 | 12 | the language. |
13 | 13 | |
14 | Types | |
15 | ----- | |
16 | ||
17 | There are five *primitive types* in SixtyPical: | |
14 | Data Model | |
15 | ---------- | |
16 | ||
17 | SixtyPical defines a data model where every value has some type | |
18 | information associated with it. The values include those that are | |
19 | directly manipulable by a SixtyPical program, but are not limited to them. | |
20 | Type information includes not only what kind of structure the data has, | |
21 | but other properties as well (sometimes called "type annotations".) | |
22 | ||
23 | ### Basic types ### | |
24 | ||
25 | SixtyPical defines a handful of basic types. There are three types that | |
26 | are "primitive" in that they are not parameterized in any way: | |
18 | 27 | |
19 | 28 | * bit (2 possible values) |
20 | 29 | * byte (256 possible values) |
21 | 30 | * word (65536 possible values) |
31 | ||
32 | Types can also be parameterized and constructed from other types | |
33 | (which is a kind of parameterization). One such type constructor is | |
34 | ||
35 | * pointer (16-bit address of a byte inside a byte table) | |
36 | * vector T (address of a value of type T; T must be a routine type) | |
37 | ||
38 | Values of the above-listed types are directly manipulable by a SixtyPical | |
39 | program. Other types describe values which can only be indirectly | |
40 | manipulated by a program: | |
41 | ||
22 | 42 | * routine (code stored somewhere in memory, read-only) |
23 | * pointer (address of a byte in a buffer) | |
24 | ||
25 | There are also three *type constructors*: | |
26 | ||
27 | * T table[N] (N entries, 1 ≤ N ≤ 256; each entry holds a value | |
28 | of type T, where T is `byte`, `word`, or `vector`) | |
29 | * buffer[N] (N entries; each entry is a byte; 1 ≤ N ≤ 65536) | |
30 | * vector T (address of a value of type T; T must be a routine type) | |
31 | ||
32 | ### User-defined ### | |
43 | * T table[N] (series of 1 ≤ N ≤ 65536 values of type T) | |
44 | ||
45 | There are some restrictions here; for example, a table may only | |
46 | consist of `byte`, `word`, or `vector` types. A pointer may only | |
47 | point to a byte inside a `table` of `byte` type. | |
48 | ||
49 | Each routine is associated with a rich set of type information, | |
50 | which is basically the types and statuses of memory locations that | |
51 | have been declared as being relevant to that routine. | |
52 | ||
53 | #### User-defined #### | |
33 | 54 | |
34 | 55 | A program may define its own types using the `typedef` feature. Typedefs |
35 | 56 | must occur before everything else in the program. A typedef takes a |
36 | 57 | type expression and an identifier which has not previously been used in |
37 | 58 | the program. It associates that identifer with that type. This is merely |
38 | a type alias; two types with different names will compare as equal. | |
39 | ||
40 | Memory locations | |
41 | ---------------- | |
59 | a type alias; if two types have identical structure but different names, | |
60 | they will compare as equal. | |
61 | ||
62 | ### Memory locations ### | |
42 | 63 | |
43 | 64 | A primary concept in SixtyPical is the *memory location*. At any given point |
44 | 65 | in time during execution, each memory location is either *uninitialized* or |
50 | 71 | There are four general kinds of memory location. The first three are |
51 | 72 | pre-defined and built-in. |
52 | 73 | |
53 | ### Registers ### | |
74 | #### Registers #### | |
54 | 75 | |
55 | 76 | Each of these hold a byte. They are initially uninitialized. |
56 | 77 | |
58 | 79 | x |
59 | 80 | y |
60 | 81 | |
61 | ### Flags ### | |
82 | #### Flags #### | |
62 | 83 | |
63 | 84 | Each of these hold a bit. They are initially uninitialized. |
64 | 85 | |
67 | 88 | v (overflow) |
68 | 89 | n (negative) |
69 | 90 | |
70 | ### Constants ### | |
91 | #### Constants #### | |
71 | 92 | |
72 | 93 | It may be strange to think of constants as memory locations, but keep in mind |
73 | 94 | that a memory location in SixtyPical need not map to a memory location in the |
96 | 117 | Note that if a word constant is between 256 and 65535, the leading `word` |
97 | 118 | token can be omitted. |
98 | 119 | |
99 | ### User-defined ### | |
120 | #### User-defined #### | |
100 | 121 | |
101 | 122 | There may be any number of user-defined memory locations. They are defined |
102 | 123 | by giving the type (which may be any type except `bit` and `routine`) and the |
136 | 157 | that literal integers in the code are always immediate values. (But this |
137 | 158 | may change at some point.) |
138 | 159 | |
139 | ### Buffers and Pointers ### | |
140 | ||
141 | Roughly speaking, a `buffer` is a table that can be longer than 256 bytes, | |
142 | and a `pointer` is an address within a buffer. | |
160 | ### Tables and Pointers ### | |
161 | ||
162 | A table is a collection of memory locations that can be indexed in a number | |
163 | of ways. | |
164 | ||
165 | The simplest way is to use another memory location as an index. There | |
166 | are restrictions on which memory locations can be used as indexes; | |
167 | only the `x` and `y` locations can be used this way. Since those can | |
168 | only hold a byte, this method, by itself, only allows access to the first | |
169 | 256 entries of the table. | |
170 | ||
171 | byte table[1024] tab | |
172 | ... | |
173 | ld a, tab + x | |
174 | st a, tab + y | |
175 | ||
176 | However, by combining indexing with a constant _offset_, entries beyond the | |
177 | 256th entry can be accessed. | |
178 | ||
179 | byte table[1024] tab | |
180 | ... | |
181 | ld a, tab + 512 + x | |
182 | st a, tab + 512 + y | |
183 | ||
184 | Even with an offset, the range of indexing still cannot exceed 256 entries. | |
185 | Accessing entries at an arbitrary address inside a table can be done with | |
186 | a `pointer`. Pointers can only be point inside `byte` tables. When a | |
187 | pointer is used, indexing with `x` or `y` will also take place. | |
143 | 188 | |
144 | 189 | A `pointer` is implemented as a zero-page memory location, and accessing the |
145 | buffer pointed to is implemented with "indirect indexed" addressing, as in | |
190 | table pointed to is implemented with "indirect indexed" addressing, as in | |
146 | 191 | |
147 | 192 | LDA ($02), Y |
148 | 193 | STA ($02), Y |
150 | 195 | There are extended instruction modes for using these types of memory location. |
151 | 196 | See `copy` below, but here is some illustrative example code: |
152 | 197 | |
153 | copy ^buf, ptr // this is the only way to initialize a pointer | |
154 | add ptr, 4 // ok, but only if it does not exceed buffer's size | |
155 | ld y, 0 // you must set this to something yourself | |
156 | copy [ptr] + y, byt // read memory through pointer, into byte | |
157 | copy 100, [ptr] + y // write memory through pointer (still trashes a) | |
158 | ||
159 | where `ptr` is a user-defined storage location of `pointer` type, and the | |
160 | `+ y` part is mandatory. | |
198 | point ptr into buf { // this is the only way to initialize a pointer | |
199 | add ptr, 4 // note, this is unchecked against table's size! | |
200 | ld y, 0 // you must set this to something yourself | |
201 | copy [ptr] + y, byt // read memory through pointer, into byte | |
202 | copy 100, [ptr] + y // write memory through pointer (still trashes a) | |
203 | } // after this block, ptr can no longer be used | |
204 | ||
205 | where `ptr` is a user-defined storage location of `pointer` type, `buf` | |
206 | is a `table` of `byte` type, and the `+ y` part is mandatory. | |
161 | 207 | |
162 | 208 | Routines |
163 | 209 | -------- |
299 | 345 | After execution, dest is considered initialized, and `z` and `n`, and |
300 | 346 | `a` are considered uninitialized. |
301 | 347 | |
302 | There are two extra modes that this instruction can be used in. The first is | |
303 | to load an address into a pointer: | |
304 | ||
305 | copy ^<src-memory-location>, <dest-memory-location> | |
306 | ||
307 | This copies the address of src into dest. In this case, src must be | |
308 | of type buffer, and dest must be of type pointer. src will not be | |
309 | considered a memory location that is read, since it is only its address | |
310 | that is being retrieved. | |
311 | ||
312 | The second is to read or write indirectly through a pointer. | |
348 | There is an extra mode that this instruction can be used in: | |
313 | 349 | |
314 | 350 | copy [<src-memory-location>] + y, <dest-memory-location> |
315 | 351 | copy <src-memory-location>, [<dest-memory-location>] + y |
349 | 385 | when the dest is `a`. |
350 | 386 | |
351 | 387 | NOTE: If dest is a pointer, the addition does not check if the result of |
352 | the pointer arithmetic continues to be valid (within a buffer) or not. | |
388 | the pointer arithmetic continues to be valid (within a table) or not. | |
353 | 389 | |
354 | 390 | ### inc ### |
355 | 391 | |
580 | 616 | Program ::= {ConstDefn | TypeDefn} {Defn} {Routine}. |
581 | 617 | ConstDefn::= "const" Ident<new> Const. |
582 | 618 | TypeDefn::= "typedef" Type Ident<new>. |
583 | Defn ::= Type Ident<new> [Constraints] (":" Const | "@" LitWord). | |
619 | Defn ::= Type Ident<new> (":" Const | "@" LitWord). | |
584 | 620 | Type ::= TypeTerm ["table" TypeSize]. |
585 | 621 | TypeExpr::= "byte" |
586 | 622 | | "word" |
587 | | "buffer" TypeSize | |
588 | 623 | | "pointer" |
589 | 624 | | "vector" TypeTerm |
590 | 625 | | "routine" Constraints |
593 | 628 | TypeSize::= "[" LitWord "]". |
594 | 629 | Constrnt::= ["inputs" LocExprs] ["outputs" LocExprs] ["trashes" LocExprs]. |
595 | 630 | Routine ::= "define" Ident<new> Type (Block | "@" LitWord). |
596 | | "routine" Ident<new> Constraints (Block | "@" LitWord) | |
597 | . | |
598 | 631 | LocExprs::= LocExpr {"," LocExpr}. |
599 | LocExpr ::= Register | Flag | Const | Ident. | |
632 | LocExpr ::= Register | Flag | Const | Ident [["+" Const] "+" Register]. | |
600 | 633 | Register::= "a" | "x" | "y". |
601 | 634 | Flag ::= "c" | "z" | "n" | "v". |
602 | 635 | Const ::= Literal | Ident<const>. |
45 | 45 | demo, [smiley.60p](atari2600/smiley.60p) which was converted from an |
46 | 46 | older Atari 2600 skeleton program written in [Ophis][]. |
47 | 47 | |
48 | ### apple2 | |
49 | ||
50 | In the [apple2](apple2/) directory are programs that run on | |
51 | Apple II series computers (Apple II+, Apple //e). `sixtypical`'s | |
52 | support for this architecture could be called embryonic. | |
53 | ||
48 | 54 | [Ophis]: http://michaelcmartin.github.io/Ophis/ |
0 | This directory contains SixtyPical example programs | |
1 | specifically for the Apple II series of computers. | |
2 | ||
3 | See the [README in the parent directory](../README.md) for | |
4 | more information on these example programs. | |
5 | ||
6 | Note that `sixtypical` does not currently support "load | |
7 | and go" execution of these programs, because constructing | |
8 | an Apple II disk image file on the fly is not something | |
9 | it can currently do. If you have the linapple sources | |
10 | checked out, and the a2tools available, you could do | |
11 | something like this: | |
12 | ||
13 | bin/sixtypical --traceback --origin=0x2000 --output-format=raw eg/apple2/prog.60p --output prog.bin | |
14 | cp /path/to/linapple/res/Master.dsk sixtypical.dsk | |
15 | a2rm sixtypical.dsk PROG | |
16 | a2in B sixtypical.dsk PROG prog.bin | |
17 | linapple -d1 sixtypical.dsk -autoboot | |
18 | ||
19 | and then enter | |
20 | ||
21 | BLOAD PROG | |
22 | CALL 8192 | |
23 | ||
24 | Ideally you could | |
25 | ||
26 | BRUN PROG | |
27 | ||
28 | But that does not always return to BASIC and I'm not sure why. |
20 | 20 | // and the end of their own routines, so the type needs to be compatible. |
21 | 21 | // (In a good sense, it is a continuation.) |
22 | 22 | // |
23 | // Further, | |
24 | // | |
25 | // It's very arguable that screen1/2/3/4 and colormap1/2/3/4 are not REALLY inputs. | |
26 | // They're only there to support the fact that game states sometimes clear the | |
27 | // screen, and sometimes don't. When they don't, they preserve the screen, and | |
28 | // currently the way to say "we preserve the screen" is to have it as both input | |
29 | // and output. There is probably a better way to do this, but it needs thought. | |
30 | // | |
31 | 23 | |
32 | 24 | typedef routine |
33 | 25 | inputs joy2, press_fire_msg, dispatch_game_state, |
34 | 26 | actor_pos, actor_delta, actor_logic, |
35 | 27 | player_died, |
36 | screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 | |
28 | screen, colormap | |
37 | 29 | outputs dispatch_game_state, |
38 | 30 | actor_pos, actor_delta, actor_logic, |
39 | 31 | player_died, |
40 | screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 | |
32 | screen, colormap | |
41 | 33 | trashes a, x, y, c, z, n, v, pos, new_pos, delta, ptr, dispatch_logic |
42 | 34 | game_state_routine |
43 | 35 | |
61 | 53 | |
62 | 54 | byte vic_border @ 53280 |
63 | 55 | byte vic_bg @ 53281 |
64 | ||
65 | byte table[256] screen1 @ 1024 | |
66 | byte table[256] screen2 @ 1274 | |
67 | byte table[256] screen3 @ 1524 | |
68 | byte table[256] screen4 @ 1774 | |
69 | ||
70 | byte table[256] colormap1 @ 55296 | |
71 | byte table[256] colormap2 @ 55546 | |
72 | byte table[256] colormap3 @ 55796 | |
73 | byte table[256] colormap4 @ 56046 | |
74 | ||
75 | buffer[2048] screen @ 1024 | |
56 | byte table[2048] screen @ 1024 | |
57 | byte table[2048] colormap @ 55296 | |
76 | 58 | byte joy2 @ $dc00 |
77 | 59 | |
78 | 60 | // ---------------------------------------------------------------- |
186 | 168 | } |
187 | 169 | |
188 | 170 | define clear_screen routine |
189 | outputs screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 | |
171 | outputs screen, colormap | |
190 | 172 | trashes a, y, c, n, z |
191 | 173 | { |
192 | 174 | ld y, 0 |
193 | 175 | repeat { |
194 | 176 | ld a, 1 |
195 | st a, colormap1 + y | |
196 | st a, colormap2 + y | |
197 | st a, colormap3 + y | |
198 | st a, colormap4 + y | |
177 | st a, colormap + y | |
178 | st a, colormap + 250 + y | |
179 | st a, colormap + 500 + y | |
180 | st a, colormap + 750 + y | |
199 | 181 | |
200 | 182 | ld a, 32 |
201 | st a, screen1 + y | |
202 | st a, screen2 + y | |
203 | st a, screen3 + y | |
204 | st a, screen4 + y | |
183 | st a, screen + y | |
184 | st a, screen + 250 + y | |
185 | st a, screen + 500 + y | |
186 | st a, screen + 750 + y | |
205 | 187 | |
206 | 188 | inc y |
207 | 189 | cmp y, 250 |
281 | 263 | call check_new_position_in_bounds |
282 | 264 | |
283 | 265 | if c { |
284 | copy ^screen, ptr | |
285 | st off, c | |
286 | add ptr, new_pos | |
287 | ld y, 0 | |
288 | ||
289 | // check collision. | |
290 | ld a, [ptr] + y | |
266 | point ptr into screen { | |
267 | st off, c | |
268 | add ptr, new_pos | |
269 | ld y, 0 | |
270 | // check collision. | |
271 | ld a, [ptr] + y | |
272 | } | |
273 | ||
291 | 274 | // if "collision" is with your own self, treat it as if it's blank space! |
292 | 275 | cmp a, 81 |
293 | 276 | if z { |
295 | 278 | } |
296 | 279 | cmp a, 32 |
297 | 280 | if z { |
298 | copy ^screen, ptr | |
299 | st off, c | |
300 | add ptr, pos | |
301 | copy 32, [ptr] + y | |
281 | point ptr into screen { | |
282 | st off, c | |
283 | add ptr, pos | |
284 | copy 32, [ptr] + y | |
285 | } | |
302 | 286 | |
303 | 287 | copy new_pos, pos |
304 | 288 | |
305 | copy ^screen, ptr | |
306 | st off, c | |
307 | add ptr, pos | |
308 | copy 81, [ptr] + y | |
289 | point ptr into screen { | |
290 | st off, c | |
291 | add ptr, pos | |
292 | copy 81, [ptr] + y | |
293 | } | |
309 | 294 | } else { |
310 | 295 | ld a, 1 |
311 | 296 | st a, player_died |
320 | 305 | call check_new_position_in_bounds |
321 | 306 | |
322 | 307 | if c { |
323 | copy ^screen, ptr | |
324 | st off, c | |
325 | add ptr, new_pos | |
326 | ld y, 0 | |
327 | ||
328 | // check collision. | |
329 | ld a, [ptr] + y | |
308 | point ptr into screen { | |
309 | st off, c | |
310 | add ptr, new_pos | |
311 | ld y, 0 | |
312 | // check collision. | |
313 | ld a, [ptr] + y | |
314 | } | |
330 | 315 | // if "collision" is with your own self, treat it as if it's blank space! |
331 | 316 | cmp a, 82 |
332 | 317 | if z { |
334 | 319 | } |
335 | 320 | cmp a, 32 |
336 | 321 | if z { |
337 | copy ^screen, ptr | |
338 | st off, c | |
339 | add ptr, pos | |
340 | copy 32, [ptr] + y | |
322 | point ptr into screen { | |
323 | st off, c | |
324 | add ptr, pos | |
325 | copy 32, [ptr] + y | |
326 | } | |
341 | 327 | |
342 | 328 | copy new_pos, pos |
343 | 329 | |
344 | copy ^screen, ptr | |
345 | st off, c | |
346 | add ptr, pos | |
347 | copy 82, [ptr] + y | |
330 | point ptr into screen { | |
331 | st off, c | |
332 | add ptr, pos | |
333 | copy 82, [ptr] + y | |
334 | } | |
348 | 335 | } |
349 | 336 | } else { |
350 | 337 | copy delta, compare_target |
371 | 358 | st on, c |
372 | 359 | sub a, 64 // yuck. oh well |
373 | 360 | |
374 | st a, screen1 + y | |
361 | st a, screen + y | |
375 | 362 | } |
376 | 363 | |
377 | 364 | st off, c |
443 | 430 | |
444 | 431 | define main routine |
445 | 432 | inputs cinv |
446 | outputs cinv, save_cinv, pos, dispatch_game_state, | |
447 | screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 | |
433 | outputs cinv, save_cinv, pos, dispatch_game_state, screen, colormap | |
448 | 434 | trashes a, y, n, c, z, vic_border, vic_bg |
449 | 435 | { |
450 | 436 | ld a, 5 |
49 | 49 | // be practical. So we just jump to this location instead. |
50 | 50 | |
51 | 51 | define pla_tay_pla_tax_pla_rti routine |
52 | inputs a | |
53 | trashes a | |
52 | inputs border_color, vic_intr | |
53 | outputs border_color, vic_intr | |
54 | trashes a, z, n, c | |
54 | 55 | @ $EA81 |
55 | 56 | |
56 | 57 | // ----- Interrupt Handler ----- |
5 | 5 | |
6 | 6 | These files are intended to be architecture-agnostic. |
7 | 7 | For the ones that do produce output, an appropriate source |
8 | under `platform/`, should be included first, like | |
8 | under `support/` should be included first, so that system entry | |
9 | points such as `chrout` are defined. In addition, some of these | |
10 | programs use "standard" support modules, so those should be included | |
11 | first too. For example: | |
9 | 12 | |
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 | |
13 | sixtypical --run-on=x64 support/c64.60p support/stdlib.60p vector-table.60p | |
17 | 14 | |
18 | 15 | `chrout` is a routine with outputs the value of the accumulator |
19 | 16 | as an ASCII character, disturbing none of the other registers, |
0 | 0 | // Include `support/${PLATFORM}.60p` before this source |
1 | 1 | // Should print Y |
2 | 2 | |
3 | buffer[2048] buf | |
3 | byte table[2048] buf | |
4 | 4 | pointer ptr @ 254 |
5 | 5 | byte foo |
6 | 6 |
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 | #!/bin/sh | |
1 | ||
2 | usage="Usage: loadngo.sh (c64|vic20|atari2600|apple2) [--dry-run] <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 | elif [ "X$arch" = "Xatari2600" ]; then | |
21 | output_format='atari2600-cart' | |
22 | emu='stella' | |
23 | elif [ "X$arch" = "Xapple2" ]; then | |
24 | src="$1" | |
25 | out=/tmp/a-out.bin | |
26 | bin/sixtypical --traceback --origin=0x2000 --output-format=raw $src --output $out || exit 1 | |
27 | ls -la $out | |
28 | cp ~/scratchpad/linapple/res/Master.dsk sixtypical.dsk | |
29 | # TODO: replace HELLO with something that does like | |
30 | # BLOAD "PROG" | |
31 | # CALL 8192 | |
32 | # (not BRUN because it does not always return to BASIC afterwards not sure why) | |
33 | a2rm sixtypical.dsk PROG | |
34 | a2in B sixtypical.dsk PROG $out | |
35 | linapple -d1 sixtypical.dsk -autoboot | |
36 | rm -f $out sixtypical.dsk | |
37 | exit 0 | |
38 | else | |
39 | echo $usage && exit 1 | |
40 | fi | |
41 | ||
42 | if [ "X$1" = "X--dry-run" ]; then | |
43 | shift 1 | |
44 | emu='echo' | |
45 | fi | |
46 | ||
47 | src="$1" | |
48 | if [ "X$src" = "X" ]; then | |
49 | echo $usage && exit 1 | |
50 | fi | |
51 | ||
52 | ### do it ### | |
53 | ||
54 | out=/tmp/a-out.prg | |
55 | bin/sixtypical --traceback --output-format=$output_format $src --output $out || exit 1 | |
56 | ls -la $out | |
57 | $emu $out | |
58 | rm -f $out |
0 | 0 | # encoding: UTF-8 |
1 | 1 | |
2 | from sixtypical.ast import Program, Routine, Block, SingleOp, If, Repeat, For, WithInterruptsOff, Save | |
2 | from sixtypical.ast import ( | |
3 | Program, Routine, Block, SingleOp, Call, GoTo, If, Repeat, For, WithInterruptsOff, Save, PointInto | |
4 | ) | |
3 | 5 | from sixtypical.model import ( |
4 | 6 | TYPE_BYTE, TYPE_WORD, |
5 | TableType, BufferType, PointerType, VectorType, RoutineType, | |
6 | ConstantRef, LocationRef, IndirectRef, IndexedRef, AddressRef, | |
7 | TableType, PointerType, VectorType, RoutineType, | |
8 | ConstantRef, LocationRef, IndirectRef, IndexedRef, | |
7 | 9 | REG_A, REG_Y, FLAG_Z, FLAG_N, FLAG_V, FLAG_C |
8 | 10 | ) |
9 | 11 | |
77 | 79 | pass |
78 | 80 | |
79 | 81 | |
80 | def routine_has_static(routine, ref): | |
81 | if not hasattr(routine, 'statics'): | |
82 | return False | |
83 | for static in routine.statics: | |
84 | if static.location == ref: | |
85 | return True | |
86 | return False | |
87 | ||
88 | ||
89 | class Context(object): | |
82 | class AnalysisContext(object): | |
90 | 83 | """ |
91 | 84 | A location is touched if it was changed (or even potentially |
92 | 85 | changed) during this routine, or some routine called by this routine. |
105 | 98 | lists of this routine. A location can also be temporarily marked |
106 | 99 | unwriteable in certain contexts, such as `for` loops. |
107 | 100 | """ |
108 | def __init__(self, routines, routine, inputs, outputs, trashes): | |
109 | self.routines = routines # Location -> AST node | |
110 | self.routine = routine | |
111 | self._touched = set() | |
112 | self._range = dict() | |
113 | self._writeable = set() | |
101 | def __init__(self, symtab, routine, inputs, outputs, trashes): | |
102 | self.symtab = symtab | |
103 | self.routine = routine # Routine (AST node) | |
104 | self._touched = set() # {LocationRef} | |
105 | self._range = dict() # LocationRef -> (Int, Int) | |
106 | self._writeable = set() # {LocationRef} | |
114 | 107 | self._terminated = False |
115 | 108 | self._gotos_encountered = set() |
109 | self._pointer_assoc = dict() | |
116 | 110 | |
117 | 111 | for ref in inputs: |
118 | if ref.is_constant(): | |
112 | if self.is_constant(ref): | |
119 | 113 | raise ConstantConstraintError(self.routine, ref.name) |
120 | self._range[ref] = ref.max_range() | |
114 | self._range[ref] = self.max_range(ref) | |
121 | 115 | output_names = set() |
122 | 116 | for ref in outputs: |
123 | if ref.is_constant(): | |
117 | if self.is_constant(ref): | |
124 | 118 | raise ConstantConstraintError(self.routine, ref.name) |
125 | 119 | output_names.add(ref.name) |
126 | 120 | self._writeable.add(ref) |
127 | 121 | for ref in trashes: |
128 | if ref.is_constant(): | |
122 | if self.is_constant(ref): | |
129 | 123 | raise ConstantConstraintError(self.routine, ref.name) |
130 | 124 | if ref.name in output_names: |
131 | 125 | raise InconsistentConstraintsError(self.routine, ref.name) |
132 | 126 | self._writeable.add(ref) |
133 | 127 | |
134 | 128 | def __str__(self): |
135 | return "Context(\n _touched={},\n _range={},\n _writeable={}\n)".format( | |
129 | return "{}(\n _touched={},\n _range={},\n _writeable={}\n)".format( | |
130 | self.__class__.__name__, | |
136 | 131 | LocationRef.format_set(self._touched), LocationRef.format_set(self._range), LocationRef.format_set(self._writeable) |
137 | 132 | ) |
138 | 133 | |
134 | def to_json_data(self): | |
135 | type_ = self.symtab.fetch_global_type(self.routine.name) | |
136 | return { | |
137 | 'routine_inputs': ','.join(sorted(loc.name for loc in type_.inputs)), | |
138 | 'routine_outputs': ','.join(sorted(loc.name for loc in type_.outputs)), | |
139 | 'routine_trashes': ','.join(sorted(loc.name for loc in type_.trashes)), | |
140 | 'touched': ','.join(sorted(loc.name for loc in self._touched)), | |
141 | 'range': dict((loc.name, '{}-{}'.format(rng[0], rng[1])) for (loc, rng) in self._range.items()), | |
142 | 'writeable': ','.join(sorted(loc.name for loc in self._writeable)), | |
143 | 'terminated': self._terminated, | |
144 | 'gotos_encountered': ','.join(sorted(loc.name for loc in self._gotos_encountered)), | |
145 | } | |
146 | ||
139 | 147 | def clone(self): |
140 | c = Context(self.routines, self.routine, [], [], []) | |
148 | c = AnalysisContext(self.symtab, self.routine, [], [], []) | |
141 | 149 | c._touched = set(self._touched) |
142 | 150 | c._range = dict(self._range) |
143 | 151 | c._writeable = set(self._writeable) |
152 | c._pointer_assoc = dict(self._pointer_assoc) | |
153 | c._gotos_encountered = set(self._gotos_encountered) | |
144 | 154 | return c |
145 | 155 | |
146 | 156 | def update_from(self, other): |
147 | self.routines = other.routines | |
157 | """Replaces the information in this context, with the information from the other context. | |
158 | This is an overwriting action - it does not attempt to merge the contexts. | |
159 | ||
160 | We do not replace the gotos_encountered for technical reasons. (In `analyze_if`, | |
161 | we merge those sets afterwards; at the end of `analyze_routine`, they are not distinct in the | |
162 | set of contexts we are updating from, and we want to retain our own.)""" | |
148 | 163 | self.routine = other.routine |
149 | 164 | self._touched = set(other._touched) |
150 | 165 | self._range = dict(other._range) |
151 | 166 | self._writeable = set(other._writeable) |
152 | 167 | self._terminated = other._terminated |
153 | self._gotos_encounters = set(other._gotos_encountered) | |
168 | self._pointer_assoc = dict(other._pointer_assoc) | |
154 | 169 | |
155 | 170 | def each_meaningful(self): |
156 | 171 | for ref in self._range.keys(): |
168 | 183 | exception_class = kwargs.get('exception_class', UnmeaningfulReadError) |
169 | 184 | for ref in refs: |
170 | 185 | # statics are always meaningful |
171 | if routine_has_static(self.routine, ref): | |
186 | if self.symtab.has_static(self.routine.name, ref.name): | |
172 | 187 | continue |
173 | if ref.is_constant() or ref in self.routines: | |
188 | if self.is_constant(ref): | |
174 | 189 | pass |
175 | 190 | elif isinstance(ref, LocationRef): |
176 | 191 | if ref not in self._range: |
188 | 203 | exception_class = kwargs.get('exception_class', ForbiddenWriteError) |
189 | 204 | for ref in refs: |
190 | 205 | # statics are always writeable |
191 | if routine_has_static(self.routine, ref): | |
206 | if self.symtab.has_static(self.routine.name, ref.name): | |
192 | 207 | continue |
193 | 208 | if ref not in self._writeable: |
194 | 209 | message = ref.name |
196 | 211 | message += ' (%s)' % kwargs['message'] |
197 | 212 | raise exception_class(self.routine, message) |
198 | 213 | |
199 | def assert_in_range(self, inside, outside): | |
200 | # FIXME there's a bit of I'm-not-sure-the-best-way-to-do-this-ness, here... | |
214 | def assert_in_range(self, inside, outside, offset): | |
215 | """Given two locations, assert that the first location, offset by the given offset, | |
216 | is contained 'inside' the second location.""" | |
217 | assert isinstance(inside, LocationRef) | |
218 | assert isinstance(outside, LocationRef) | |
201 | 219 | |
202 | 220 | # inside should always be meaningful |
203 | 221 | inside_range = self._range[inside] |
206 | 224 | if outside in self._range: |
207 | 225 | outside_range = self._range[outside] |
208 | 226 | else: |
209 | outside_range = outside.max_range() | |
210 | if isinstance(outside.type, TableType): | |
211 | outside_range = (0, outside.type.size-1) | |
212 | ||
213 | if inside_range[0] < outside_range[0] or inside_range[1] > outside_range[1]: | |
227 | outside_range = self.max_range(outside) | |
228 | ||
229 | if (inside_range[0] + offset.value) < outside_range[0] or (inside_range[1] + offset.value) > outside_range[1]: | |
214 | 230 | raise RangeExceededError(self.routine, |
215 | "Possible range of {} {} exceeds acceptable range of {} {}".format( | |
216 | inside, inside_range, outside, outside_range | |
231 | "Possible range of {} {} (+{}) exceeds acceptable range of {} {}".format( | |
232 | inside, inside_range, offset, outside, outside_range | |
217 | 233 | ) |
218 | 234 | ) |
219 | 235 | |
225 | 241 | def set_meaningful(self, *refs): |
226 | 242 | for ref in refs: |
227 | 243 | if ref not in self._range: |
228 | self._range[ref] = ref.max_range() | |
244 | self._range[ref] = self.max_range(ref) | |
229 | 245 | |
230 | 246 | def set_top_of_range(self, ref, top): |
231 | 247 | self.assert_meaningful(ref) |
267 | 283 | if src in self._range: |
268 | 284 | src_range = self._range[src] |
269 | 285 | else: |
270 | src_range = src.max_range() | |
286 | src_range = self.max_range(src) | |
271 | 287 | self._range[dest] = src_range |
272 | 288 | |
273 | 289 | def invalidate_range(self, ref): |
274 | 290 | self.assert_meaningful(ref) |
275 | self._range[ref] = ref.max_range() | |
291 | self._range[ref] = self.max_range(ref) | |
276 | 292 | |
277 | 293 | def set_unmeaningful(self, *refs): |
278 | 294 | for ref in refs: |
309 | 325 | |
310 | 326 | def has_terminated(self): |
311 | 327 | return self._terminated |
312 | ||
313 | def assert_types_for_read_table(self, instr, src, dest, type_): | |
314 | if (not TableType.is_a_table_type(src.ref.type, type_)) or (not dest.type == type_): | |
315 | raise TypeMismatchError(instr, '{} and {}'.format(src.ref.name, dest.name)) | |
316 | self.assert_meaningful(src, src.index) | |
317 | self.assert_in_range(src.index, src.ref) | |
318 | ||
319 | def assert_types_for_update_table(self, instr, dest, type_): | |
320 | if not TableType.is_a_table_type(dest.ref.type, type_): | |
321 | raise TypeMismatchError(instr, '{}'.format(dest.ref.name)) | |
322 | self.assert_meaningful(dest.index) | |
323 | self.assert_in_range(dest.index, dest.ref) | |
324 | self.set_written(dest.ref) | |
325 | 328 | |
326 | 329 | def extract(self, location): |
327 | 330 | """Sets the given location as writeable in the context, and returns a 'baton' representing |
358 | 361 | elif location in self._writeable: |
359 | 362 | self._writeable.remove(location) |
360 | 363 | |
364 | def get_assoc(self, pointer): | |
365 | return self._pointer_assoc.get(pointer) | |
366 | ||
367 | def set_assoc(self, pointer, table): | |
368 | self._pointer_assoc[pointer] = table | |
369 | ||
370 | def is_constant(self, ref): | |
371 | """read-only means that the program cannot change the value | |
372 | of a location. constant means that the value of the location | |
373 | will not change during the lifetime of the program.""" | |
374 | if isinstance(ref, ConstantRef): | |
375 | return True | |
376 | if isinstance(ref, (IndirectRef, IndexedRef)): | |
377 | return False | |
378 | if isinstance(ref, LocationRef): | |
379 | type_ = self.symtab.fetch_global_type(ref.name) | |
380 | return isinstance(type_, RoutineType) | |
381 | raise NotImplementedError | |
382 | ||
383 | def max_range(self, ref): | |
384 | if isinstance(ref, ConstantRef): | |
385 | return (ref.value, ref.value) | |
386 | elif self.symtab.has_static(self.routine.name, ref.name): | |
387 | return self.symtab.fetch_static_type(self.routine.name, ref.name).max_range | |
388 | else: | |
389 | return self.symtab.fetch_global_type(ref.name).max_range | |
390 | ||
361 | 391 | |
362 | 392 | class Analyzer(object): |
363 | 393 | |
364 | def __init__(self, debug=False): | |
394 | def __init__(self, symtab, debug=False): | |
395 | self.symtab = symtab | |
365 | 396 | self.current_routine = None |
366 | self.routines = {} | |
367 | 397 | self.debug = debug |
398 | self.exit_contexts_map = {} | |
399 | ||
400 | # - - - - helper methods - - - - | |
401 | ||
402 | def get_type_for_name(self, name): | |
403 | if self.current_routine and self.symtab.has_static(self.current_routine.name, name): | |
404 | return self.symtab.fetch_static_type(self.current_routine.name, name) | |
405 | return self.symtab.fetch_global_type(name) | |
406 | ||
407 | def get_type(self, ref): | |
408 | if isinstance(ref, ConstantRef): | |
409 | return ref.type | |
410 | if not isinstance(ref, LocationRef): | |
411 | raise NotImplementedError | |
412 | return self.get_type_for_name(ref.name) | |
368 | 413 | |
369 | 414 | def assert_type(self, type_, *locations): |
370 | 415 | for location in locations: |
371 | if location.type != type_: | |
416 | if self.get_type(location) != type_: | |
372 | 417 | raise TypeMismatchError(self.current_routine, location.name) |
373 | 418 | |
374 | 419 | def assert_affected_within(self, name, affecting_type, limiting_type): |
386 | 431 | ) |
387 | 432 | raise IncompatibleConstraintsError(self.current_routine, message) |
388 | 433 | |
434 | def assert_types_for_read_table(self, context, instr, src, dest, type_, offset): | |
435 | if (not TableType.is_a_table_type(self.get_type(src.ref), type_)) or (not self.get_type(dest) == type_): | |
436 | raise TypeMismatchError(instr, '{} and {}'.format(src.ref.name, dest.name)) | |
437 | context.assert_meaningful(src, src.index) | |
438 | context.assert_in_range(src.index, src.ref, offset) | |
439 | ||
440 | def assert_types_for_update_table(self, context, instr, dest, type_, offset): | |
441 | if not TableType.is_a_table_type(self.get_type(dest.ref), type_): | |
442 | raise TypeMismatchError(instr, '{}'.format(dest.ref.name)) | |
443 | context.assert_meaningful(dest.index) | |
444 | context.assert_in_range(dest.index, dest.ref, offset) | |
445 | context.set_written(dest.ref) | |
446 | ||
447 | # - - - - visitor methods - - - - | |
448 | ||
389 | 449 | def analyze_program(self, program): |
390 | 450 | assert isinstance(program, Program) |
391 | self.routines = {r.location: r for r in program.routines} | |
392 | 451 | for routine in program.routines: |
393 | 452 | context = self.analyze_routine(routine) |
394 | 453 | routine.encountered_gotos = list(context.encountered_gotos()) if context else [] |
397 | 456 | assert isinstance(routine, Routine) |
398 | 457 | if routine.block is None: |
399 | 458 | # it's an extern, that's fine |
400 | return | |
459 | return None | |
401 | 460 | |
402 | 461 | self.current_routine = routine |
403 | type_ = routine.location.type | |
404 | context = Context(self.routines, routine, type_.inputs, type_.outputs, type_.trashes) | |
462 | type_ = self.get_type_for_name(routine.name) | |
463 | context = AnalysisContext(self.symtab, routine, type_.inputs, type_.outputs, type_.trashes) | |
405 | 464 | self.exit_contexts = [] |
406 | 465 | |
407 | if self.debug: | |
408 | print("at start of routine `{}`:".format(routine.name)) | |
409 | print(context) | |
410 | ||
411 | 466 | self.analyze_block(routine.block, context) |
412 | 467 | |
413 | 468 | trashed = set(context.each_touched()) - set(context.each_meaningful()) |
414 | 469 | |
415 | if self.debug: | |
416 | print("at end of routine `{}`:".format(routine.name)) | |
417 | print(context) | |
418 | print("trashed: ", LocationRef.format_set(trashed)) | |
419 | print("outputs: ", LocationRef.format_set(type_.outputs)) | |
420 | trashed_outputs = type_.outputs & trashed | |
421 | if trashed_outputs: | |
422 | print("TRASHED OUTPUTS: ", LocationRef.format_set(trashed_outputs)) | |
423 | print('') | |
424 | print('-' * 79) | |
425 | print('') | |
470 | self.exit_contexts_map[routine.name] = { | |
471 | 'end_context': context.to_json_data(), | |
472 | 'exit_contexts': [e.to_json_data() for e in self.exit_contexts] | |
473 | } | |
426 | 474 | |
427 | 475 | if self.exit_contexts: |
428 | 476 | # check that they are all consistent |
432 | 480 | exit_writeable = set(exit_context.each_writeable()) |
433 | 481 | for ex in self.exit_contexts[1:]: |
434 | 482 | if set(ex.each_meaningful()) != exit_meaningful: |
435 | raise InconsistentExitError("Exit contexts are not consistent") | |
483 | raise InconsistentExitError(routine, "Exit contexts are not consistent") | |
436 | 484 | if set(ex.each_touched()) != exit_touched: |
437 | raise InconsistentExitError("Exit contexts are not consistent") | |
485 | raise InconsistentExitError(routine, "Exit contexts are not consistent") | |
438 | 486 | if set(ex.each_writeable()) != exit_writeable: |
439 | raise InconsistentExitError("Exit contexts are not consistent") | |
487 | raise InconsistentExitError(routine, "Exit contexts are not consistent") | |
488 | ||
489 | # We now set the main context to the (consistent) exit context | |
490 | # so that this routine is perceived as having the same effect | |
491 | # that any of the goto'ed routines have. | |
440 | 492 | context.update_from(exit_context) |
441 | 493 | |
442 | 494 | # these all apply whether we encountered goto(s) in this routine, or not...: |
452 | 504 | |
453 | 505 | # if something was touched, then it should have been declared to be writable. |
454 | 506 | 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): | |
507 | if ref not in type_.outputs and ref not in type_.trashes and not self.symtab.has_static(routine.name, ref.name): | |
456 | 508 | raise ForbiddenWriteError(routine, ref.name) |
457 | 509 | |
458 | 510 | self.exit_contexts = None |
467 | 519 | def analyze_instr(self, instr, context): |
468 | 520 | if isinstance(instr, SingleOp): |
469 | 521 | self.analyze_single_op(instr, context) |
522 | elif isinstance(instr, Call): | |
523 | self.analyze_call(instr, context) | |
524 | elif isinstance(instr, GoTo): | |
525 | self.analyze_goto(instr, context) | |
470 | 526 | elif isinstance(instr, If): |
471 | 527 | self.analyze_if(instr, context) |
472 | 528 | elif isinstance(instr, Repeat): |
479 | 535 | raise IllegalJumpError(instr, instr) |
480 | 536 | elif isinstance(instr, Save): |
481 | 537 | self.analyze_save(instr, context) |
538 | elif isinstance(instr, PointInto): | |
539 | self.analyze_point_into(instr, context) | |
482 | 540 | else: |
483 | 541 | raise NotImplementedError |
484 | 542 | |
493 | 551 | |
494 | 552 | if opcode == 'ld': |
495 | 553 | if isinstance(src, IndexedRef): |
496 | context.assert_types_for_read_table(instr, src, dest, TYPE_BYTE) | |
554 | self.assert_types_for_read_table(context, instr, src, dest, TYPE_BYTE, src.offset) | |
497 | 555 | elif isinstance(src, IndirectRef): |
498 | 556 | # copying this analysis from the matching branch in `copy`, below |
499 | if isinstance(src.ref.type, PointerType) and dest.type == TYPE_BYTE: | |
557 | if isinstance(self.get_type(src.ref), PointerType) and self.get_type(dest) == TYPE_BYTE: | |
500 | 558 | pass |
501 | 559 | else: |
502 | 560 | raise TypeMismatchError(instr, (src, dest)) |
561 | ||
562 | origin = context.get_assoc(src.ref) | |
563 | if not origin: | |
564 | raise UnmeaningfulReadError(instr, src.ref) | |
565 | context.assert_meaningful(origin) | |
566 | ||
503 | 567 | context.assert_meaningful(src.ref, REG_Y) |
504 | elif src.type != dest.type: | |
568 | elif self.get_type(src) != self.get_type(dest): | |
505 | 569 | raise TypeMismatchError(instr, '{} and {}'.format(src.name, dest.name)) |
506 | 570 | else: |
507 | 571 | context.assert_meaningful(src) |
509 | 573 | context.set_written(dest, FLAG_Z, FLAG_N) |
510 | 574 | elif opcode == 'st': |
511 | 575 | if isinstance(dest, IndexedRef): |
512 | if src.type != TYPE_BYTE: | |
576 | if self.get_type(src) != TYPE_BYTE: | |
513 | 577 | raise TypeMismatchError(instr, (src, dest)) |
514 | context.assert_types_for_update_table(instr, dest, TYPE_BYTE) | |
578 | self.assert_types_for_update_table(context, instr, dest, TYPE_BYTE, dest.offset) | |
515 | 579 | elif isinstance(dest, IndirectRef): |
516 | 580 | # copying this analysis from the matching branch in `copy`, below |
517 | if isinstance(dest.ref.type, PointerType) and src.type == TYPE_BYTE: | |
581 | if isinstance(self.get_type(dest.ref), PointerType) and self.get_type(src) == TYPE_BYTE: | |
518 | 582 | pass |
519 | 583 | else: |
520 | 584 | raise TypeMismatchError(instr, (src, dest)) |
585 | ||
521 | 586 | context.assert_meaningful(dest.ref, REG_Y) |
522 | context.set_written(dest.ref) | |
523 | elif src.type != dest.type: | |
587 | ||
588 | target = context.get_assoc(dest.ref) | |
589 | if not target: | |
590 | raise ForbiddenWriteError(instr, dest.ref) | |
591 | context.set_touched(target) | |
592 | context.set_written(target) | |
593 | ||
594 | elif self.get_type(src) != self.get_type(dest): | |
524 | 595 | raise TypeMismatchError(instr, '{} and {}'.format(src, dest)) |
525 | 596 | else: |
526 | 597 | context.set_written(dest) |
529 | 600 | elif opcode == 'add': |
530 | 601 | context.assert_meaningful(src, dest, FLAG_C) |
531 | 602 | if isinstance(src, IndexedRef): |
532 | context.assert_types_for_read_table(instr, src, dest, TYPE_BYTE) | |
533 | elif src.type == TYPE_BYTE: | |
603 | self.assert_types_for_read_table(context, instr, src, dest, TYPE_BYTE, src.offset) | |
604 | elif self.get_type(src) == TYPE_BYTE: | |
534 | 605 | self.assert_type(TYPE_BYTE, src, dest) |
535 | 606 | if dest != REG_A: |
536 | 607 | context.set_touched(REG_A) |
537 | 608 | context.set_unmeaningful(REG_A) |
538 | 609 | else: |
539 | 610 | self.assert_type(TYPE_WORD, src) |
540 | if dest.type == TYPE_WORD: | |
611 | dest_type = self.get_type(dest) | |
612 | if dest_type == TYPE_WORD: | |
541 | 613 | context.set_touched(REG_A) |
542 | 614 | context.set_unmeaningful(REG_A) |
543 | elif isinstance(dest.type, PointerType): | |
615 | elif isinstance(dest_type, PointerType): | |
544 | 616 | context.set_touched(REG_A) |
545 | 617 | context.set_unmeaningful(REG_A) |
546 | 618 | else: |
550 | 622 | elif opcode == 'sub': |
551 | 623 | context.assert_meaningful(src, dest, FLAG_C) |
552 | 624 | if isinstance(src, IndexedRef): |
553 | context.assert_types_for_read_table(instr, src, dest, TYPE_BYTE) | |
554 | elif src.type == TYPE_BYTE: | |
625 | self.assert_types_for_read_table(context, instr, src, dest, TYPE_BYTE, src.offset) | |
626 | elif self.get_type(src) == TYPE_BYTE: | |
555 | 627 | self.assert_type(TYPE_BYTE, src, dest) |
556 | 628 | if dest != REG_A: |
557 | 629 | context.set_touched(REG_A) |
565 | 637 | elif opcode == 'cmp': |
566 | 638 | context.assert_meaningful(src, dest) |
567 | 639 | if isinstance(src, IndexedRef): |
568 | context.assert_types_for_read_table(instr, src, dest, TYPE_BYTE) | |
569 | elif src.type == TYPE_BYTE: | |
640 | self.assert_types_for_read_table(context, instr, src, dest, TYPE_BYTE, src.offset) | |
641 | elif self.get_type(src) == TYPE_BYTE: | |
570 | 642 | self.assert_type(TYPE_BYTE, src, dest) |
571 | 643 | else: |
572 | 644 | self.assert_type(TYPE_WORD, src, dest) |
575 | 647 | context.set_written(FLAG_Z, FLAG_N, FLAG_C) |
576 | 648 | elif opcode == 'and': |
577 | 649 | if isinstance(src, IndexedRef): |
578 | context.assert_types_for_read_table(instr, src, dest, TYPE_BYTE) | |
650 | self.assert_types_for_read_table(context, instr, src, dest, TYPE_BYTE, src.offset) | |
579 | 651 | else: |
580 | 652 | self.assert_type(TYPE_BYTE, src, dest) |
581 | 653 | context.assert_meaningful(src, dest) |
587 | 659 | context.set_top_of_range(dest, context.get_top_of_range(src)) |
588 | 660 | elif opcode in ('or', 'xor'): |
589 | 661 | if isinstance(src, IndexedRef): |
590 | context.assert_types_for_read_table(instr, src, dest, TYPE_BYTE) | |
662 | self.assert_types_for_read_table(context, instr, src, dest, TYPE_BYTE, src.offset) | |
591 | 663 | else: |
592 | 664 | self.assert_type(TYPE_BYTE, src, dest) |
593 | 665 | context.assert_meaningful(src, dest) |
596 | 668 | elif opcode in ('inc', 'dec'): |
597 | 669 | context.assert_meaningful(dest) |
598 | 670 | if isinstance(dest, IndexedRef): |
599 | context.assert_types_for_update_table(instr, dest, TYPE_BYTE) | |
671 | self.assert_types_for_update_table(context, instr, dest, TYPE_BYTE, dest.offset) | |
600 | 672 | context.set_written(dest.ref, FLAG_Z, FLAG_N) |
601 | 673 | #context.invalidate_range(dest) |
602 | 674 | else: |
619 | 691 | elif opcode in ('shl', 'shr'): |
620 | 692 | context.assert_meaningful(dest, FLAG_C) |
621 | 693 | if isinstance(dest, IndexedRef): |
622 | context.assert_types_for_update_table(instr, dest, TYPE_BYTE) | |
694 | self.assert_types_for_update_table(context, instr, dest, TYPE_BYTE, dest.offset) | |
623 | 695 | context.set_written(dest.ref, FLAG_Z, FLAG_N, FLAG_C) |
624 | 696 | #context.invalidate_range(dest) |
625 | 697 | else: |
626 | 698 | self.assert_type(TYPE_BYTE, dest) |
627 | 699 | context.set_written(dest, FLAG_Z, FLAG_N, FLAG_C) |
628 | 700 | context.invalidate_range(dest) |
629 | elif opcode == 'call': | |
630 | type = instr.location.type | |
631 | if not isinstance(type, (RoutineType, VectorType)): | |
632 | raise TypeMismatchError(instr, instr.location) | |
633 | if isinstance(type, VectorType): | |
634 | type = type.of_type | |
635 | for ref in type.inputs: | |
636 | context.assert_meaningful(ref) | |
637 | for ref in type.outputs: | |
638 | context.set_written(ref) | |
639 | for ref in type.trashes: | |
640 | context.assert_writeable(ref) | |
641 | context.set_touched(ref) | |
642 | context.set_unmeaningful(ref) | |
643 | 701 | elif opcode == 'copy': |
644 | 702 | if dest == REG_A: |
645 | 703 | raise ForbiddenWriteError(instr, "{} cannot be used as destination for copy".format(dest)) |
646 | 704 | |
647 | 705 | # 1. check that their types are compatible |
648 | 706 | |
649 | if isinstance(src, AddressRef) and isinstance(dest, LocationRef): | |
650 | if isinstance(src.ref.type, BufferType) and isinstance(dest.type, PointerType): | |
651 | pass | |
652 | else: | |
653 | raise TypeMismatchError(instr, (src, dest)) | |
654 | elif isinstance(src, (LocationRef, ConstantRef)) and isinstance(dest, IndirectRef): | |
655 | if src.type == TYPE_BYTE and isinstance(dest.ref.type, PointerType): | |
707 | if isinstance(src, (LocationRef, ConstantRef)) and isinstance(dest, IndirectRef): | |
708 | if self.get_type(src) == TYPE_BYTE and isinstance(self.get_type(dest.ref), PointerType): | |
656 | 709 | pass |
657 | 710 | else: |
658 | 711 | raise TypeMismatchError(instr, (src, dest)) |
659 | 712 | elif isinstance(src, IndirectRef) and isinstance(dest, LocationRef): |
660 | if isinstance(src.ref.type, PointerType) and dest.type == TYPE_BYTE: | |
713 | if isinstance(self.get_type(src.ref), PointerType) and self.get_type(dest) == TYPE_BYTE: | |
661 | 714 | pass |
662 | 715 | else: |
663 | 716 | raise TypeMismatchError(instr, (src, dest)) |
664 | 717 | elif isinstance(src, IndirectRef) and isinstance(dest, IndirectRef): |
665 | if isinstance(src.ref.type, PointerType) and isinstance(dest.ref.type, PointerType): | |
718 | if isinstance(self.get_type(src.ref), PointerType) and isinstance(self.get_type(dest.ref), PointerType): | |
666 | 719 | pass |
667 | 720 | else: |
668 | 721 | raise TypeMismatchError(instr, (src, dest)) |
669 | 722 | |
670 | 723 | elif isinstance(src, (LocationRef, ConstantRef)) and isinstance(dest, IndexedRef): |
671 | if src.type == TYPE_WORD and TableType.is_a_table_type(dest.ref.type, TYPE_WORD): | |
724 | if self.get_type(src) == TYPE_WORD and TableType.is_a_table_type(self.get_type(dest.ref), TYPE_WORD): | |
672 | 725 | pass |
673 | elif (isinstance(src.type, VectorType) and isinstance(dest.ref.type, TableType) and | |
674 | RoutineType.executable_types_compatible(src.type.of_type, dest.ref.type.of_type)): | |
726 | elif (isinstance(self.get_type(src), VectorType) and isinstance(self.get_type(dest.ref), TableType) and | |
727 | RoutineType.executable_types_compatible(self.get_type(src).of_type, self.get_type(dest.ref).of_type)): | |
675 | 728 | pass |
676 | elif (isinstance(src.type, RoutineType) and isinstance(dest.ref.type, TableType) and | |
677 | RoutineType.executable_types_compatible(src.type, dest.ref.type.of_type)): | |
729 | elif (isinstance(self.get_type(src), RoutineType) and isinstance(self.get_type(dest.ref), TableType) and | |
730 | RoutineType.executable_types_compatible(self.get_type(src), self.get_type(dest.ref).of_type)): | |
678 | 731 | pass |
679 | 732 | else: |
680 | 733 | raise TypeMismatchError(instr, (src, dest)) |
681 | context.assert_in_range(dest.index, dest.ref) | |
734 | context.assert_in_range(dest.index, dest.ref, dest.offset) | |
682 | 735 | |
683 | 736 | elif isinstance(src, IndexedRef) and isinstance(dest, LocationRef): |
684 | if TableType.is_a_table_type(src.ref.type, TYPE_WORD) and dest.type == TYPE_WORD: | |
737 | if TableType.is_a_table_type(self.get_type(src.ref), TYPE_WORD) and self.get_type(dest) == TYPE_WORD: | |
685 | 738 | pass |
686 | elif (isinstance(src.ref.type, TableType) and isinstance(dest.type, VectorType) and | |
687 | RoutineType.executable_types_compatible(src.ref.type.of_type, dest.type.of_type)): | |
739 | elif (isinstance(self.get_type(src.ref), TableType) and isinstance(self.get_type(dest), VectorType) and | |
740 | RoutineType.executable_types_compatible(self.get_type(src.ref).of_type, self.get_type(dest).of_type)): | |
688 | 741 | pass |
689 | 742 | else: |
690 | 743 | raise TypeMismatchError(instr, (src, dest)) |
691 | context.assert_in_range(src.index, src.ref) | |
744 | context.assert_in_range(src.index, src.ref, src.offset) | |
692 | 745 | |
693 | 746 | elif isinstance(src, (LocationRef, ConstantRef)) and isinstance(dest, LocationRef): |
694 | if src.type == dest.type: | |
747 | if self.get_type(src) == self.get_type(dest): | |
695 | 748 | pass |
696 | elif isinstance(src.type, RoutineType) and isinstance(dest.type, VectorType): | |
697 | self.assert_affected_within('inputs', src.type, dest.type.of_type) | |
698 | self.assert_affected_within('outputs', src.type, dest.type.of_type) | |
699 | self.assert_affected_within('trashes', src.type, dest.type.of_type) | |
749 | elif isinstance(self.get_type(src), RoutineType) and isinstance(self.get_type(dest), VectorType): | |
750 | self.assert_affected_within('inputs', self.get_type(src), self.get_type(dest).of_type) | |
751 | self.assert_affected_within('outputs', self.get_type(src), self.get_type(dest).of_type) | |
752 | self.assert_affected_within('trashes', self.get_type(src), self.get_type(dest).of_type) | |
700 | 753 | else: |
701 | 754 | raise TypeMismatchError(instr, (src, dest)) |
702 | 755 | else: |
706 | 759 | |
707 | 760 | if isinstance(src, (LocationRef, ConstantRef)) and isinstance(dest, IndirectRef): |
708 | 761 | context.assert_meaningful(src, REG_Y) |
709 | # TODO this will need to be more sophisticated. it's the thing ref points to that is written, not ref itself. | |
710 | context.set_written(dest.ref) | |
762 | ||
763 | target = context.get_assoc(dest.ref) | |
764 | if not target: | |
765 | raise ForbiddenWriteError(instr, dest.ref) | |
766 | context.set_touched(target) | |
767 | context.set_written(target) | |
768 | ||
711 | 769 | elif isinstance(src, IndirectRef) and isinstance(dest, LocationRef): |
712 | 770 | context.assert_meaningful(src.ref, REG_Y) |
713 | # TODO more sophisticated? | |
771 | ||
772 | origin = context.get_assoc(src.ref) | |
773 | if not origin: | |
774 | raise UnmeaningfulReadError(instr, src.ref) | |
775 | context.assert_meaningful(origin) | |
776 | ||
777 | context.set_touched(dest) | |
714 | 778 | context.set_written(dest) |
715 | 779 | elif isinstance(src, IndirectRef) and isinstance(dest, IndirectRef): |
716 | 780 | context.assert_meaningful(src.ref, REG_Y) |
717 | # TODO more sophisticated? | |
718 | context.set_written(dest.ref) | |
781 | ||
782 | origin = context.get_assoc(src.ref) | |
783 | if not origin: | |
784 | raise UnmeaningfulReadError(instr, src.ref) | |
785 | context.assert_meaningful(origin) | |
786 | ||
787 | target = context.get_assoc(dest.ref) | |
788 | if not target: | |
789 | raise ForbiddenWriteError(instr, dest.ref) | |
790 | context.set_touched(target) | |
791 | context.set_written(target) | |
792 | ||
719 | 793 | elif isinstance(src, LocationRef) and isinstance(dest, IndexedRef): |
720 | 794 | context.assert_meaningful(src, dest.ref, dest.index) |
721 | 795 | context.set_written(dest.ref) |
732 | 806 | |
733 | 807 | context.set_touched(REG_A, FLAG_Z, FLAG_N) |
734 | 808 | context.set_unmeaningful(REG_A, FLAG_Z, FLAG_N) |
735 | elif opcode == 'goto': | |
736 | location = instr.location | |
737 | type_ = location.type | |
738 | ||
739 | if not isinstance(type_, (RoutineType, VectorType)): | |
740 | raise TypeMismatchError(instr, location) | |
741 | ||
742 | # assert that the dest routine's inputs are all initialized | |
743 | if isinstance(type_, VectorType): | |
744 | type_ = type_.of_type | |
745 | for ref in type_.inputs: | |
746 | context.assert_meaningful(ref) | |
747 | ||
748 | # and that this routine's trashes and output constraints are a | |
749 | # superset of the called routine's | |
750 | current_type = self.current_routine.location.type | |
751 | self.assert_affected_within('outputs', type_, current_type) | |
752 | self.assert_affected_within('trashes', type_, current_type) | |
753 | ||
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 | 809 | |
789 | 810 | elif opcode == 'trash': |
790 | 811 | context.set_touched(instr.dest) |
793 | 814 | pass |
794 | 815 | else: |
795 | 816 | raise NotImplementedError(opcode) |
817 | ||
818 | def analyze_call(self, instr, context): | |
819 | type = self.get_type(instr.location) | |
820 | if not isinstance(type, (RoutineType, VectorType)): | |
821 | raise TypeMismatchError(instr, instr.location.name) | |
822 | if isinstance(type, VectorType): | |
823 | type = type.of_type | |
824 | for ref in type.inputs: | |
825 | context.assert_meaningful(ref) | |
826 | for ref in type.outputs: | |
827 | context.set_written(ref) | |
828 | for ref in type.trashes: | |
829 | context.assert_writeable(ref) | |
830 | context.set_touched(ref) | |
831 | context.set_unmeaningful(ref) | |
832 | ||
833 | def analyze_goto(self, instr, context): | |
834 | location = instr.location | |
835 | type_ = self.get_type(instr.location) | |
836 | ||
837 | if not isinstance(type_, (RoutineType, VectorType)): | |
838 | raise TypeMismatchError(instr, location.name) | |
839 | ||
840 | # assert that the dest routine's inputs are all initialized | |
841 | if isinstance(type_, VectorType): | |
842 | type_ = type_.of_type | |
843 | for ref in type_.inputs: | |
844 | context.assert_meaningful(ref) | |
845 | ||
846 | # and that this routine's trashes and output constraints are a | |
847 | # superset of the called routine's | |
848 | current_type = self.get_type_for_name(self.current_routine.name) | |
849 | self.assert_affected_within('outputs', type_, current_type) | |
850 | self.assert_affected_within('trashes', type_, current_type) | |
851 | ||
852 | context.encounter_gotos(set([instr.location])) | |
853 | ||
854 | # Now that we have encountered a goto, we update the | |
855 | # context here to match what someone calling the goto'ed | |
856 | # function directly, would expect. (which makes sense | |
857 | # when you think about it; if this goto's F, then calling | |
858 | # this is like calling F, from the perspective of what is | |
859 | # returned.) | |
860 | # | |
861 | # However, this isn't the current context anymore. This | |
862 | # is an exit context of this routine. | |
863 | ||
864 | exit_context = context.clone() | |
865 | ||
866 | for ref in type_.outputs: | |
867 | exit_context.set_touched(ref) # ? | |
868 | exit_context.set_written(ref) | |
869 | ||
870 | for ref in type_.trashes: | |
871 | exit_context.assert_writeable(ref) | |
872 | exit_context.set_touched(ref) | |
873 | exit_context.set_unmeaningful(ref) | |
874 | ||
875 | self.exit_contexts.append(exit_context) | |
876 | ||
877 | # When we get to the end, we'll check that all the | |
878 | # exit contexts are consistent with each other. | |
879 | ||
880 | # We set the current context as having terminated. | |
881 | # If we are in a branch, the merge will deal with | |
882 | # having terminated. If we are at the end of the | |
883 | # routine, the routine end will deal with that. | |
884 | ||
885 | context.set_terminated() | |
796 | 886 | |
797 | 887 | def analyze_if(self, instr, context): |
798 | 888 | incoming_meaningful = set(context.each_meaningful()) |
904 | 994 | else: |
905 | 995 | context.set_touched(REG_A) |
906 | 996 | context.set_unmeaningful(REG_A) |
997 | ||
998 | def analyze_point_into(self, instr, context): | |
999 | if not isinstance(self.get_type(instr.pointer), PointerType): | |
1000 | raise TypeMismatchError(instr, instr.pointer) | |
1001 | if not TableType.is_a_table_type(self.get_type(instr.table), TYPE_BYTE): | |
1002 | raise TypeMismatchError(instr, instr.table) | |
1003 | ||
1004 | # check that pointer is not yet associated with any table. | |
1005 | ||
1006 | if context.get_assoc(instr.pointer): | |
1007 | raise ForbiddenWriteError(instr, instr.pointer) | |
1008 | ||
1009 | # associate pointer with table, mark it as meaningful. | |
1010 | ||
1011 | context.set_assoc(instr.pointer, instr.table) | |
1012 | context.set_meaningful(instr.pointer) | |
1013 | context.set_touched(instr.pointer) | |
1014 | ||
1015 | self.analyze_block(instr.block, context) | |
1016 | if context.encountered_gotos(): | |
1017 | raise IllegalJumpError(instr, instr) | |
1018 | ||
1019 | # unassociate pointer with table, mark as unmeaningful. | |
1020 | ||
1021 | context.set_assoc(instr.pointer, None) | |
1022 | context.set_unmeaningful(instr.pointer) |
53 | 53 | |
54 | 54 | |
55 | 55 | class Defn(AST): |
56 | value_attrs = ('name', 'addr', 'initial', 'location',) | |
56 | value_attrs = ('name', 'addr', 'initial',) | |
57 | 57 | |
58 | 58 | |
59 | 59 | class Routine(AST): |
60 | value_attrs = ('name', 'addr', 'initial', 'location',) | |
60 | value_attrs = ('name', 'addr', 'initial',) | |
61 | 61 | children_attrs = ('statics',) |
62 | 62 | child_attrs = ('block',) |
63 | 63 | |
71 | 71 | |
72 | 72 | |
73 | 73 | class SingleOp(Instr): |
74 | value_attrs = ('opcode', 'dest', 'src', 'location',) | |
74 | value_attrs = ('opcode', 'dest', 'src',) | |
75 | ||
76 | ||
77 | class Call(Instr): | |
78 | value_attrs = ('location',) | |
79 | ||
80 | ||
81 | class GoTo(Instr): | |
82 | value_attrs = ('location',) | |
75 | 83 | |
76 | 84 | |
77 | 85 | class If(Instr): |
96 | 104 | class Save(Instr): |
97 | 105 | value_attrs = ('locations',) |
98 | 106 | child_attrs = ('block',) |
107 | ||
108 | ||
109 | class PointInto(Instr): | |
110 | value_attrs = ('pointer', 'table',) | |
111 | child_attrs = ('block',) |
0 | 0 | # encoding: UTF-8 |
1 | 1 | |
2 | from sixtypical.ast import Program, Routine, Block, SingleOp, If, Repeat, For, WithInterruptsOff, Save | |
2 | from sixtypical.ast import ( | |
3 | Program, Routine, Block, SingleOp, Call, GoTo, If, Repeat, For, WithInterruptsOff, Save, PointInto | |
4 | ) | |
3 | 5 | from sixtypical.model import ( |
4 | ConstantRef, LocationRef, IndexedRef, IndirectRef, AddressRef, | |
6 | ConstantRef, LocationRef, IndexedRef, IndirectRef, | |
5 | 7 | TYPE_BIT, TYPE_BYTE, TYPE_WORD, |
6 | TableType, BufferType, PointerType, RoutineType, VectorType, | |
8 | TableType, PointerType, RoutineType, VectorType, | |
7 | 9 | REG_A, REG_X, REG_Y, FLAG_C |
8 | 10 | ) |
9 | 11 | from sixtypical.emitter import Byte, Word, Table, Label, Offset, LowAddressByte, HighAddressByte |
27 | 29 | |
28 | 30 | |
29 | 31 | class Compiler(object): |
30 | def __init__(self, emitter): | |
32 | def __init__(self, symtab, emitter): | |
33 | self.symtab = symtab | |
31 | 34 | self.emitter = emitter |
32 | 35 | self.routines = {} # routine.name -> Routine |
33 | 36 | self.routine_statics = {} # routine.name -> { static.name -> Label } |
35 | 38 | self.trampolines = {} # Location -> Label |
36 | 39 | self.current_routine = None |
37 | 40 | |
38 | # helper methods | |
41 | # - - - - helper methods - - - - | |
42 | ||
43 | def get_type_for_name(self, name): | |
44 | if self.current_routine and self.symtab.has_static(self.current_routine.name, name): | |
45 | return self.symtab.fetch_static_type(self.current_routine.name, name) | |
46 | return self.symtab.fetch_global_type(name) | |
47 | ||
48 | def get_type(self, ref): | |
49 | if isinstance(ref, ConstantRef): | |
50 | return ref.type | |
51 | if not isinstance(ref, LocationRef): | |
52 | raise NotImplementedError | |
53 | return self.get_type_for_name(ref.name) | |
39 | 54 | |
40 | 55 | def addressing_mode_for_index(self, index): |
41 | 56 | if index == REG_X: |
47 | 62 | |
48 | 63 | def compute_length_of_defn(self, defn): |
49 | 64 | length = None |
50 | type_ = defn.location.type | |
65 | type_ = self.get_type_for_name(defn.name) | |
51 | 66 | if type_ == TYPE_BYTE: |
52 | 67 | length = 1 |
53 | 68 | elif type_ == TYPE_WORD or isinstance(type_, (PointerType, VectorType)): |
54 | 69 | length = 2 |
55 | 70 | elif isinstance(type_, TableType): |
56 | 71 | length = type_.size * (1 if type_.of_type == TYPE_BYTE else 2) |
57 | elif isinstance(type_, BufferType): | |
58 | length = type_.size | |
59 | 72 | if length is None: |
60 | 73 | raise NotImplementedError("Need size for type {}".format(type_)) |
61 | 74 | return length |
73 | 86 | else: |
74 | 87 | return Absolute(label) |
75 | 88 | |
76 | # visitor methods | |
89 | # - - - - visitor methods - - - - | |
77 | 90 | |
78 | 91 | def compile_program(self, program, compilation_roster=None): |
79 | 92 | assert isinstance(program, Program) |
80 | 93 | |
81 | defn_labels = [] | |
94 | declarations = [] | |
82 | 95 | |
83 | 96 | for defn in program.defns: |
84 | 97 | length = self.compute_length_of_defn(defn) |
85 | 98 | label = Label(defn.name, addr=defn.addr, length=length) |
86 | 99 | self.labels[defn.name] = label |
87 | defn_labels.append((defn, label)) | |
100 | declarations.append((defn, self.symtab.fetch_global_type(defn.name), label)) | |
88 | 101 | |
89 | 102 | for routine in program.routines: |
90 | 103 | self.routines[routine.name] = routine |
94 | 107 | self.labels[routine.name] = label |
95 | 108 | |
96 | 109 | if hasattr(routine, 'statics'): |
110 | self.current_routine = routine | |
97 | 111 | static_labels = {} |
98 | 112 | for defn in routine.statics: |
99 | 113 | length = self.compute_length_of_defn(defn) |
100 | 114 | label = Label(defn.name, addr=defn.addr, length=length) |
101 | 115 | static_labels[defn.name] = label |
102 | defn_labels.append((defn, label)) | |
116 | declarations.append((defn, self.symtab.fetch_static_type(routine.name, defn.name), label)) | |
103 | 117 | self.routine_statics[routine.name] = static_labels |
118 | self.current_routine = None | |
104 | 119 | |
105 | 120 | if compilation_roster is None: |
106 | 121 | compilation_roster = [['main']] + [[routine.name] for routine in program.routines if routine.name != 'main'] |
117 | 132 | self.emitter.emit(RTS()) |
118 | 133 | |
119 | 134 | # initialized data |
120 | for defn, label in defn_labels: | |
135 | for defn, type_, label in declarations: | |
121 | 136 | if defn.initial is not None: |
122 | 137 | initial_data = None |
123 | type_ = defn.location.type | |
124 | 138 | if type_ == TYPE_BYTE: |
125 | 139 | initial_data = Byte(defn.initial) |
126 | 140 | elif type_ == TYPE_WORD: |
136 | 150 | self.emitter.emit(initial_data) |
137 | 151 | |
138 | 152 | # uninitialized, "BSS" data |
139 | for defn, label in defn_labels: | |
153 | for defn, type_, label in declarations: | |
140 | 154 | if defn.initial is None and defn.addr is None: |
141 | 155 | self.emitter.resolve_bss_label(label) |
142 | 156 | |
161 | 175 | def compile_instr(self, instr): |
162 | 176 | if isinstance(instr, SingleOp): |
163 | 177 | return self.compile_single_op(instr) |
178 | elif isinstance(instr, Call): | |
179 | return self.compile_call(instr) | |
180 | elif isinstance(instr, GoTo): | |
181 | return self.compile_goto(instr) | |
164 | 182 | elif isinstance(instr, If): |
165 | 183 | return self.compile_if(instr) |
166 | 184 | elif isinstance(instr, Repeat): |
171 | 189 | return self.compile_with_interrupts_off(instr) |
172 | 190 | elif isinstance(instr, Save): |
173 | 191 | return self.compile_save(instr) |
192 | elif isinstance(instr, PointInto): | |
193 | return self.compile_point_into(instr) | |
174 | 194 | else: |
175 | 195 | raise NotImplementedError |
176 | 196 | |
189 | 209 | elif isinstance(src, ConstantRef): |
190 | 210 | self.emitter.emit(LDA(Immediate(Byte(src.value)))) |
191 | 211 | elif isinstance(src, IndexedRef) and src.index == REG_X: |
192 | self.emitter.emit(LDA(AbsoluteX(self.get_label(src.ref.name)))) | |
212 | self.emitter.emit(LDA(AbsoluteX(Offset(self.get_label(src.ref.name), src.offset.value)))) | |
193 | 213 | elif isinstance(src, IndexedRef) and src.index == REG_Y: |
194 | self.emitter.emit(LDA(AbsoluteY(self.get_label(src.ref.name)))) | |
195 | elif isinstance(src, IndirectRef) and isinstance(src.ref.type, PointerType): | |
214 | self.emitter.emit(LDA(AbsoluteY(Offset(self.get_label(src.ref.name), src.offset.value)))) | |
215 | elif isinstance(src, IndirectRef) and isinstance(self.get_type(src.ref), PointerType): | |
196 | 216 | self.emitter.emit(LDA(IndirectY(self.get_label(src.ref.name)))) |
197 | 217 | else: |
198 | 218 | self.emitter.emit(LDA(self.absolute_or_zero_page(self.get_label(src.name)))) |
202 | 222 | elif isinstance(src, ConstantRef): |
203 | 223 | self.emitter.emit(LDX(Immediate(Byte(src.value)))) |
204 | 224 | elif isinstance(src, IndexedRef) and src.index == REG_Y: |
205 | self.emitter.emit(LDX(AbsoluteY(self.get_label(src.ref.name)))) | |
225 | self.emitter.emit(LDX(AbsoluteY(Offset(self.get_label(src.ref.name), src.offset.value)))) | |
206 | 226 | else: |
207 | 227 | self.emitter.emit(LDX(self.absolute_or_zero_page(self.get_label(src.name)))) |
208 | 228 | elif dest == REG_Y: |
211 | 231 | elif isinstance(src, ConstantRef): |
212 | 232 | self.emitter.emit(LDY(Immediate(Byte(src.value)))) |
213 | 233 | elif isinstance(src, IndexedRef) and src.index == REG_X: |
214 | self.emitter.emit(LDY(AbsoluteX(self.get_label(src.ref.name)))) | |
234 | self.emitter.emit(LDY(AbsoluteX(Offset(self.get_label(src.ref.name), src.offset.value)))) | |
215 | 235 | else: |
216 | 236 | self.emitter.emit(LDY(self.absolute_or_zero_page(self.get_label(src.name)))) |
217 | 237 | else: |
233 | 253 | REG_X: AbsoluteX, |
234 | 254 | REG_Y: AbsoluteY, |
235 | 255 | }[dest.index] |
236 | operand = mode_cls(self.get_label(dest.ref.name)) | |
237 | elif isinstance(dest, IndirectRef) and isinstance(dest.ref.type, PointerType): | |
256 | operand = mode_cls(Offset(self.get_label(dest.ref.name), dest.offset.value)) | |
257 | elif isinstance(dest, IndirectRef) and isinstance(self.get_type(dest.ref), PointerType): | |
238 | 258 | operand = IndirectY(self.get_label(dest.ref.name)) |
239 | 259 | else: |
240 | 260 | operand = self.absolute_or_zero_page(self.get_label(dest.name)) |
249 | 269 | if isinstance(src, ConstantRef): |
250 | 270 | self.emitter.emit(ADC(Immediate(Byte(src.value)))) |
251 | 271 | elif isinstance(src, IndexedRef): |
252 | self.emitter.emit(ADC(self.addressing_mode_for_index(src.index)(self.get_label(src.ref.name)))) | |
272 | mode = self.addressing_mode_for_index(src.index) | |
273 | self.emitter.emit(ADC(mode(Offset(self.get_label(src.ref.name), src.offset.value)))) | |
253 | 274 | else: |
254 | 275 | 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: | |
276 | elif isinstance(dest, LocationRef) and self.get_type(src) == TYPE_BYTE and self.get_type(dest) == TYPE_BYTE: | |
256 | 277 | if isinstance(src, ConstantRef): |
257 | 278 | dest_label = self.get_label(dest.name) |
258 | 279 | self.emitter.emit(LDA(Absolute(dest_label))) |
266 | 287 | self.emitter.emit(STA(Absolute(dest_label))) |
267 | 288 | else: |
268 | 289 | raise UnsupportedOpcodeError(instr) |
269 | elif isinstance(dest, LocationRef) and src.type == TYPE_WORD and dest.type == TYPE_WORD: | |
290 | elif isinstance(dest, LocationRef) and self.get_type(src) == TYPE_WORD and self.get_type(dest) == TYPE_WORD: | |
270 | 291 | if isinstance(src, ConstantRef): |
271 | 292 | dest_label = self.get_label(dest.name) |
272 | 293 | self.emitter.emit(LDA(Absolute(dest_label))) |
286 | 307 | self.emitter.emit(STA(Absolute(Offset(dest_label, 1)))) |
287 | 308 | else: |
288 | 309 | raise UnsupportedOpcodeError(instr) |
289 | elif isinstance(dest, LocationRef) and src.type == TYPE_WORD and isinstance(dest.type, PointerType): | |
310 | elif isinstance(dest, LocationRef) and self.get_type(src) == TYPE_WORD and isinstance(self.get_type(dest), PointerType): | |
290 | 311 | if isinstance(src, ConstantRef): |
291 | 312 | dest_label = self.get_label(dest.name) |
292 | 313 | self.emitter.emit(LDA(ZeroPage(dest_label))) |
315 | 336 | if isinstance(src, ConstantRef): |
316 | 337 | self.emitter.emit(SBC(Immediate(Byte(src.value)))) |
317 | 338 | elif isinstance(src, IndexedRef): |
318 | self.emitter.emit(SBC(self.addressing_mode_for_index(src.index)(self.get_label(src.ref.name)))) | |
339 | mode = self.addressing_mode_for_index(src.index) | |
340 | self.emitter.emit(SBC(mode(Offset(self.get_label(src.ref.name), src.offset.value)))) | |
319 | 341 | else: |
320 | 342 | 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: | |
343 | elif isinstance(dest, LocationRef) and self.get_type(src) == TYPE_BYTE and self.get_type(dest) == TYPE_BYTE: | |
322 | 344 | if isinstance(src, ConstantRef): |
323 | 345 | dest_label = self.get_label(dest.name) |
324 | 346 | self.emitter.emit(LDA(Absolute(dest_label))) |
332 | 354 | self.emitter.emit(STA(Absolute(dest_label))) |
333 | 355 | else: |
334 | 356 | raise UnsupportedOpcodeError(instr) |
335 | elif isinstance(dest, LocationRef) and src.type == TYPE_WORD and dest.type == TYPE_WORD: | |
357 | elif isinstance(dest, LocationRef) and self.get_type(src) == TYPE_WORD and self.get_type(dest) == TYPE_WORD: | |
336 | 358 | if isinstance(src, ConstantRef): |
337 | 359 | dest_label = self.get_label(dest.name) |
338 | 360 | self.emitter.emit(LDA(Absolute(dest_label))) |
366 | 388 | if isinstance(src, ConstantRef): |
367 | 389 | self.emitter.emit(cls(Immediate(Byte(src.value)))) |
368 | 390 | elif isinstance(src, IndexedRef): |
369 | self.emitter.emit(cls(self.addressing_mode_for_index(src.index)(self.get_label(src.ref.name)))) | |
391 | mode = self.addressing_mode_for_index(src.index) | |
392 | self.emitter.emit(cls(mode(Offset(self.get_label(src.ref.name), src.offset.value)))) | |
370 | 393 | else: |
371 | 394 | self.emitter.emit(cls(self.absolute_or_zero_page(self.get_label(src.name)))) |
372 | 395 | else: |
383 | 406 | if dest == REG_A: |
384 | 407 | self.emitter.emit(cls()) |
385 | 408 | elif isinstance(dest, IndexedRef): |
386 | self.emitter.emit(cls(self.addressing_mode_for_index(dest.index)(self.get_label(dest.ref.name)))) | |
409 | mode = self.addressing_mode_for_index(dest.index) | |
410 | self.emitter.emit(cls(mode(Offset(self.get_label(dest.ref.name), dest.offset.value)))) | |
387 | 411 | else: |
388 | 412 | self.emitter.emit(cls(self.absolute_or_zero_page(self.get_label(dest.name)))) |
389 | elif opcode == 'call': | |
390 | location = instr.location | |
391 | label = self.get_label(instr.location.name) | |
392 | if isinstance(location.type, RoutineType): | |
393 | self.emitter.emit(JSR(Absolute(label))) | |
394 | elif isinstance(location.type, VectorType): | |
395 | trampoline = self.trampolines.setdefault( | |
396 | location, Label(location.name + '_trampoline') | |
397 | ) | |
398 | self.emitter.emit(JSR(Absolute(trampoline))) | |
399 | else: | |
400 | raise NotImplementedError | |
401 | elif opcode == 'goto': | |
402 | self.final_goto_seen = True | |
403 | if self.skip_final_goto: | |
404 | pass | |
405 | else: | |
406 | location = instr.location | |
407 | label = self.get_label(instr.location.name) | |
408 | if isinstance(location.type, RoutineType): | |
409 | self.emitter.emit(JMP(Absolute(label))) | |
410 | elif isinstance(location.type, VectorType): | |
411 | self.emitter.emit(JMP(Indirect(label))) | |
412 | else: | |
413 | raise NotImplementedError | |
414 | 413 | elif opcode == 'copy': |
415 | 414 | self.compile_copy(instr, instr.src, instr.dest) |
416 | 415 | elif opcode == 'trash': |
420 | 419 | else: |
421 | 420 | raise NotImplementedError(opcode) |
422 | 421 | |
422 | def compile_call(self, instr): | |
423 | location = instr.location | |
424 | label = self.get_label(instr.location.name) | |
425 | location_type = self.get_type(location) | |
426 | if isinstance(location_type, RoutineType): | |
427 | self.emitter.emit(JSR(Absolute(label))) | |
428 | elif isinstance(location_type, VectorType): | |
429 | trampoline = self.trampolines.setdefault( | |
430 | location, Label(location.name + '_trampoline') | |
431 | ) | |
432 | self.emitter.emit(JSR(Absolute(trampoline))) | |
433 | else: | |
434 | raise NotImplementedError(location_type) | |
435 | ||
436 | def compile_goto(self, instr): | |
437 | self.final_goto_seen = True | |
438 | if self.skip_final_goto: | |
439 | pass | |
440 | else: | |
441 | location = instr.location | |
442 | label = self.get_label(instr.location.name) | |
443 | location_type = self.get_type(location) | |
444 | if isinstance(location_type, RoutineType): | |
445 | self.emitter.emit(JMP(Absolute(label))) | |
446 | elif isinstance(location_type, VectorType): | |
447 | self.emitter.emit(JMP(Indirect(label))) | |
448 | else: | |
449 | raise NotImplementedError(location_type) | |
450 | ||
423 | 451 | def compile_cmp(self, instr, src, dest): |
424 | 452 | """`instr` is only for reporting purposes""" |
425 | if isinstance(src, LocationRef) and src.type == TYPE_WORD: | |
453 | if isinstance(src, LocationRef) and self.get_type(src) == TYPE_WORD: | |
426 | 454 | src_label = self.get_label(src.name) |
427 | 455 | dest_label = self.get_label(dest.name) |
428 | 456 | self.emitter.emit(LDA(Absolute(dest_label))) |
433 | 461 | self.emitter.emit(CMP(Absolute(Offset(src_label, 1)))) |
434 | 462 | self.emitter.resolve_label(end_label) |
435 | 463 | return |
436 | if isinstance(src, ConstantRef) and src.type == TYPE_WORD: | |
464 | if isinstance(src, ConstantRef) and self.get_type(src) == TYPE_WORD: | |
437 | 465 | dest_label = self.get_label(dest.name) |
438 | 466 | self.emitter.emit(LDA(Absolute(dest_label))) |
439 | 467 | self.emitter.emit(CMP(Immediate(Byte(src.low_byte())))) |
454 | 482 | self.emitter.emit(cls(Immediate(Byte(src.value)))) |
455 | 483 | elif isinstance(src, IndexedRef): |
456 | 484 | # FIXME might not work for some dest's (that is, cls's) |
457 | self.emitter.emit(cls(self.addressing_mode_for_index(src.index)(self.get_label(src.ref.name)))) | |
485 | mode = self.addressing_mode_for_index(src.index) | |
486 | self.emitter.emit(cls(mode(Offset(self.get_label(src.ref.name), src.offset.value)))) | |
458 | 487 | else: |
459 | 488 | self.emitter.emit(cls(Absolute(self.get_label(src.name)))) |
460 | 489 | |
465 | 494 | elif dest == REG_Y: |
466 | 495 | self.emitter.emit(INY()) |
467 | 496 | elif isinstance(dest, IndexedRef): |
468 | self.emitter.emit(INC(self.addressing_mode_for_index(dest.index)(self.get_label(dest.ref.name)))) | |
497 | mode = self.addressing_mode_for_index(dest.index) | |
498 | self.emitter.emit(INC(mode(Offset(self.get_label(dest.ref.name), dest.offset.value)))) | |
469 | 499 | else: |
470 | 500 | self.emitter.emit(INC(Absolute(self.get_label(dest.name)))) |
471 | 501 | |
476 | 506 | elif dest == REG_Y: |
477 | 507 | self.emitter.emit(DEY()) |
478 | 508 | elif isinstance(dest, IndexedRef): |
479 | self.emitter.emit(DEC(self.addressing_mode_for_index(dest.index)(self.get_label(dest.ref.name)))) | |
509 | mode = self.addressing_mode_for_index(dest.index) | |
510 | self.emitter.emit(DEC(mode(Offset(self.get_label(dest.ref.name), dest.offset.value)))) | |
480 | 511 | else: |
481 | 512 | self.emitter.emit(DEC(Absolute(self.get_label(dest.name)))) |
482 | 513 | |
483 | 514 | def compile_copy(self, instr, src, dest): |
484 | if isinstance(src, ConstantRef) and isinstance(dest, IndirectRef) and src.type == TYPE_BYTE and isinstance(dest.ref.type, PointerType): | |
515 | ||
516 | if isinstance(src, (IndirectRef, IndexedRef)): | |
517 | src_ref_type = self.get_type(src.ref) | |
518 | else: | |
519 | src_type = self.get_type(src) | |
520 | ||
521 | if isinstance(dest, (IndirectRef, IndexedRef)): | |
522 | dest_ref_type = self.get_type(dest.ref) | |
523 | else: | |
524 | dest_type = self.get_type(dest) | |
525 | ||
526 | if isinstance(src, ConstantRef) and isinstance(dest, IndirectRef) and src_type == TYPE_BYTE and isinstance(dest_ref_type, PointerType): | |
485 | 527 | ### copy 123, [ptr] + y |
486 | 528 | dest_label = self.get_label(dest.ref.name) |
487 | 529 | self.emitter.emit(LDA(Immediate(Byte(src.value)))) |
488 | 530 | self.emitter.emit(STA(IndirectY(dest_label))) |
489 | elif isinstance(src, LocationRef) and isinstance(dest, IndirectRef) and src.type == TYPE_BYTE and isinstance(dest.ref.type, PointerType): | |
531 | elif isinstance(src, LocationRef) and isinstance(dest, IndirectRef) and src_type == TYPE_BYTE and isinstance(dest_ref_type, PointerType): | |
490 | 532 | ### copy b, [ptr] + y |
491 | 533 | src_label = self.get_label(src.name) |
492 | 534 | dest_label = self.get_label(dest.ref.name) |
493 | 535 | self.emitter.emit(LDA(Absolute(src_label))) |
494 | 536 | self.emitter.emit(STA(IndirectY(dest_label))) |
495 | elif isinstance(src, IndirectRef) and isinstance(dest, LocationRef) and dest.type == TYPE_BYTE and isinstance(src.ref.type, PointerType): | |
537 | elif isinstance(src, IndirectRef) and isinstance(dest, LocationRef) and dest_type == TYPE_BYTE and isinstance(src_ref_type, PointerType): | |
496 | 538 | ### copy [ptr] + y, b |
497 | 539 | src_label = self.get_label(src.ref.name) |
498 | 540 | dest_label = self.get_label(dest.name) |
499 | 541 | self.emitter.emit(LDA(IndirectY(src_label))) |
500 | 542 | self.emitter.emit(STA(Absolute(dest_label))) |
501 | elif isinstance(src, IndirectRef) and isinstance(dest, IndirectRef) and isinstance(src.ref.type, PointerType) and isinstance(dest.ref.type, PointerType): | |
543 | elif isinstance(src, IndirectRef) and isinstance(dest, IndirectRef) and isinstance(src_ref_type, PointerType) and isinstance(dest_ref_type, PointerType): | |
502 | 544 | ### copy [ptra] + y, [ptrb] + y |
503 | 545 | src_label = self.get_label(src.ref.name) |
504 | 546 | dest_label = self.get_label(dest.ref.name) |
505 | 547 | self.emitter.emit(LDA(IndirectY(src_label))) |
506 | 548 | self.emitter.emit(STA(IndirectY(dest_label))) |
507 | elif isinstance(src, AddressRef) and isinstance(dest, LocationRef) and isinstance(src.ref.type, BufferType) and isinstance(dest.type, PointerType): | |
508 | ### copy ^buf, ptr | |
509 | src_label = self.get_label(src.ref.name) | |
510 | dest_label = self.get_label(dest.name) | |
511 | self.emitter.emit(LDA(Immediate(HighAddressByte(src_label)))) | |
512 | self.emitter.emit(STA(ZeroPage(dest_label))) | |
513 | self.emitter.emit(LDA(Immediate(LowAddressByte(src_label)))) | |
514 | self.emitter.emit(STA(ZeroPage(Offset(dest_label, 1)))) | |
515 | elif isinstance(src, LocationRef) and isinstance(dest, IndexedRef) and src.type == TYPE_WORD and TableType.is_a_table_type(dest.ref.type, TYPE_WORD): | |
549 | elif isinstance(src, LocationRef) and isinstance(dest, IndexedRef) and src_type == TYPE_WORD and TableType.is_a_table_type(dest_ref_type, TYPE_WORD): | |
516 | 550 | ### copy w, wtab + y |
517 | 551 | src_label = self.get_label(src.name) |
518 | 552 | dest_label = self.get_label(dest.ref.name) |
553 | mode = self.addressing_mode_for_index(dest.index) | |
519 | 554 | self.emitter.emit(LDA(Absolute(src_label))) |
520 | self.emitter.emit(STA(self.addressing_mode_for_index(dest.index)(dest_label))) | |
555 | self.emitter.emit(STA(mode(Offset(dest_label, dest.offset.value)))) | |
521 | 556 | self.emitter.emit(LDA(Absolute(Offset(src_label, 1)))) |
522 | self.emitter.emit(STA(self.addressing_mode_for_index(dest.index)(Offset(dest_label, 256)))) | |
523 | elif isinstance(src, LocationRef) and isinstance(dest, IndexedRef) and isinstance(src.type, VectorType) and isinstance(dest.ref.type, TableType) and isinstance(dest.ref.type.of_type, VectorType): | |
557 | self.emitter.emit(STA(mode(Offset(dest_label, dest.offset.value + 256)))) | |
558 | elif isinstance(src, LocationRef) and isinstance(dest, IndexedRef) and isinstance(src_type, VectorType) and isinstance(dest_ref_type, TableType) and isinstance(dest_ref_type.of_type, VectorType): | |
524 | 559 | ### copy vec, vtab + y |
525 | 560 | # FIXME this is the exact same as above - can this be simplified? |
526 | 561 | src_label = self.get_label(src.name) |
527 | 562 | dest_label = self.get_label(dest.ref.name) |
563 | mode = self.addressing_mode_for_index(dest.index) | |
528 | 564 | self.emitter.emit(LDA(Absolute(src_label))) |
529 | self.emitter.emit(STA(self.addressing_mode_for_index(dest.index)(dest_label))) | |
565 | self.emitter.emit(STA(mode(Offset(dest_label, dest.offset.value)))) | |
530 | 566 | self.emitter.emit(LDA(Absolute(Offset(src_label, 1)))) |
531 | self.emitter.emit(STA(self.addressing_mode_for_index(dest.index)(Offset(dest_label, 256)))) | |
532 | elif isinstance(src, LocationRef) and isinstance(dest, IndexedRef) and isinstance(src.type, RoutineType) and isinstance(dest.ref.type, TableType) and isinstance(dest.ref.type.of_type, VectorType): | |
567 | self.emitter.emit(STA(mode(Offset(dest_label, dest.offset.value + 256)))) | |
568 | elif isinstance(src, LocationRef) and isinstance(dest, IndexedRef) and isinstance(src_type, RoutineType) and isinstance(dest_ref_type, TableType) and isinstance(dest_ref_type.of_type, VectorType): | |
533 | 569 | ### copy routine, vtab + y |
534 | 570 | src_label = self.get_label(src.name) |
535 | 571 | dest_label = self.get_label(dest.ref.name) |
572 | mode = self.addressing_mode_for_index(dest.index) | |
536 | 573 | self.emitter.emit(LDA(Immediate(HighAddressByte(src_label)))) |
537 | self.emitter.emit(STA(self.addressing_mode_for_index(dest.index)(dest_label))) | |
574 | self.emitter.emit(STA(mode(Offset(dest_label, dest.offset.value)))) | |
538 | 575 | self.emitter.emit(LDA(Immediate(LowAddressByte(src_label)))) |
539 | self.emitter.emit(STA(self.addressing_mode_for_index(dest.index)(Offset(dest_label, 256)))) | |
540 | elif isinstance(src, ConstantRef) and isinstance(dest, IndexedRef) and src.type == TYPE_WORD and TableType.is_a_table_type(dest.ref.type, TYPE_WORD): | |
576 | self.emitter.emit(STA(mode(Offset(dest_label, dest.offset.value + 256)))) | |
577 | elif isinstance(src, ConstantRef) and isinstance(dest, IndexedRef) and src_type == TYPE_WORD and TableType.is_a_table_type(dest_ref_type, TYPE_WORD): | |
541 | 578 | ### copy 9999, wtab + y |
542 | 579 | dest_label = self.get_label(dest.ref.name) |
580 | mode = self.addressing_mode_for_index(dest.index) | |
543 | 581 | self.emitter.emit(LDA(Immediate(Byte(src.low_byte())))) |
544 | self.emitter.emit(STA(self.addressing_mode_for_index(dest.index)(dest_label))) | |
582 | self.emitter.emit(STA(mode(Offset(dest_label, dest.offset.value)))) | |
545 | 583 | self.emitter.emit(LDA(Immediate(Byte(src.high_byte())))) |
546 | self.emitter.emit(STA(self.addressing_mode_for_index(dest.index)(Offset(dest_label, 256)))) | |
547 | elif isinstance(src, IndexedRef) and isinstance(dest, LocationRef) and TableType.is_a_table_type(src.ref.type, TYPE_WORD) and dest.type == TYPE_WORD: | |
584 | self.emitter.emit(STA(mode(Offset(dest_label, dest.offset.value + 256)))) | |
585 | elif isinstance(src, IndexedRef) and isinstance(dest, LocationRef) and TableType.is_a_table_type(src_ref_type, TYPE_WORD) and dest_type == TYPE_WORD: | |
548 | 586 | ### copy wtab + y, w |
549 | 587 | src_label = self.get_label(src.ref.name) |
550 | 588 | dest_label = self.get_label(dest.name) |
551 | self.emitter.emit(LDA(self.addressing_mode_for_index(src.index)(src_label))) | |
589 | mode = self.addressing_mode_for_index(src.index) | |
590 | self.emitter.emit(LDA(mode(Offset(src_label, src.offset.value)))) | |
552 | 591 | self.emitter.emit(STA(Absolute(dest_label))) |
553 | self.emitter.emit(LDA(self.addressing_mode_for_index(src.index)(Offset(src_label, 256)))) | |
592 | self.emitter.emit(LDA(mode(Offset(src_label, src.offset.value + 256)))) | |
554 | 593 | self.emitter.emit(STA(Absolute(Offset(dest_label, 1)))) |
555 | elif isinstance(src, IndexedRef) and isinstance(dest, LocationRef) and isinstance(dest.type, VectorType) and isinstance(src.ref.type, TableType) and isinstance(src.ref.type.of_type, VectorType): | |
594 | elif isinstance(src, IndexedRef) and isinstance(dest, LocationRef) and isinstance(dest_type, VectorType) and isinstance(src_ref_type, TableType) and isinstance(src_ref_type.of_type, VectorType): | |
556 | 595 | ### copy vtab + y, vec |
557 | 596 | # FIXME this is the exact same as above - can this be simplified? |
558 | 597 | src_label = self.get_label(src.ref.name) |
559 | 598 | dest_label = self.get_label(dest.name) |
560 | self.emitter.emit(LDA(self.addressing_mode_for_index(src.index)(src_label))) | |
599 | mode = self.addressing_mode_for_index(src.index) | |
600 | self.emitter.emit(LDA(mode(Offset(src_label, src.offset.value)))) | |
561 | 601 | self.emitter.emit(STA(Absolute(dest_label))) |
562 | self.emitter.emit(LDA(self.addressing_mode_for_index(src.index)(Offset(src_label, 256)))) | |
602 | self.emitter.emit(LDA(mode(Offset(src_label, src.offset.value + 256)))) | |
563 | 603 | self.emitter.emit(STA(Absolute(Offset(dest_label, 1)))) |
564 | elif src.type == TYPE_BYTE and dest.type == TYPE_BYTE and not isinstance(src, ConstantRef): | |
604 | elif src_type == TYPE_BYTE and dest_type == TYPE_BYTE and not isinstance(src, ConstantRef): | |
565 | 605 | ### copy b1, b2 |
566 | 606 | src_label = self.get_label(src.name) |
567 | 607 | dest_label = self.get_label(dest.name) |
568 | 608 | self.emitter.emit(LDA(Absolute(src_label))) |
569 | 609 | self.emitter.emit(STA(Absolute(dest_label))) |
570 | elif src.type == TYPE_WORD and dest.type == TYPE_WORD and isinstance(src, ConstantRef): | |
610 | elif src_type == TYPE_WORD and dest_type == TYPE_WORD and isinstance(src, ConstantRef): | |
571 | 611 | ### copy 9999, w |
572 | 612 | dest_label = self.get_label(dest.name) |
573 | 613 | self.emitter.emit(LDA(Immediate(Byte(src.low_byte())))) |
574 | 614 | self.emitter.emit(STA(Absolute(dest_label))) |
575 | 615 | self.emitter.emit(LDA(Immediate(Byte(src.high_byte())))) |
576 | 616 | self.emitter.emit(STA(Absolute(Offset(dest_label, 1)))) |
577 | elif src.type == TYPE_WORD and dest.type == TYPE_WORD and not isinstance(src, ConstantRef): | |
617 | elif src_type == TYPE_WORD and dest_type == TYPE_WORD and not isinstance(src, ConstantRef): | |
578 | 618 | ### copy w1, w2 |
579 | 619 | src_label = self.get_label(src.name) |
580 | 620 | dest_label = self.get_label(dest.name) |
582 | 622 | self.emitter.emit(STA(Absolute(dest_label))) |
583 | 623 | self.emitter.emit(LDA(Absolute(Offset(src_label, 1)))) |
584 | 624 | self.emitter.emit(STA(Absolute(Offset(dest_label, 1)))) |
585 | elif isinstance(src.type, VectorType) and isinstance(dest.type, VectorType): | |
625 | elif isinstance(src_type, VectorType) and isinstance(dest_type, VectorType): | |
586 | 626 | ### copy v1, v2 |
587 | 627 | src_label = self.get_label(src.name) |
588 | 628 | dest_label = self.get_label(dest.name) |
590 | 630 | self.emitter.emit(STA(Absolute(dest_label))) |
591 | 631 | self.emitter.emit(LDA(Absolute(Offset(src_label, 1)))) |
592 | 632 | self.emitter.emit(STA(Absolute(Offset(dest_label, 1)))) |
593 | elif isinstance(src.type, RoutineType) and isinstance(dest.type, VectorType): | |
633 | elif isinstance(src_type, RoutineType) and isinstance(dest_type, VectorType): | |
594 | 634 | ### copy routine, vec |
595 | 635 | src_label = self.get_label(src.name) |
596 | 636 | dest_label = self.get_label(dest.name) |
599 | 639 | self.emitter.emit(LDA(Immediate(LowAddressByte(src_label)))) |
600 | 640 | self.emitter.emit(STA(Absolute(Offset(dest_label, 1)))) |
601 | 641 | else: |
602 | raise NotImplementedError(src.type) | |
642 | raise NotImplementedError(src_type) | |
603 | 643 | |
604 | 644 | def compile_if(self, instr): |
605 | 645 | cls = { |
699 | 739 | src_label = self.get_label(location.name) |
700 | 740 | self.emitter.emit(PLA()) |
701 | 741 | self.emitter.emit(STA(Absolute(src_label))) |
742 | ||
743 | def compile_point_into(self, instr): | |
744 | src_label = self.get_label(instr.table.name) | |
745 | dest_label = self.get_label(instr.pointer.name) | |
746 | ||
747 | self.emitter.emit(LDA(Immediate(HighAddressByte(src_label)))) | |
748 | self.emitter.emit(STA(ZeroPage(dest_label))) | |
749 | self.emitter.emit(LDA(Immediate(LowAddressByte(src_label)))) | |
750 | self.emitter.emit(STA(ZeroPage(Offset(dest_label, 1)))) | |
751 | ||
752 | self.compile_block(instr.block) |
6 | 6 | |
7 | 7 | class FallthruAnalyzer(object): |
8 | 8 | |
9 | def __init__(self, debug=False): | |
9 | def __init__(self, symtab, debug=False): | |
10 | self.symtab = symtab | |
10 | 11 | self.debug = debug |
11 | 12 | |
12 | 13 | def analyze_program(self, program): |
15 | 16 | self.fallthru_map = {} |
16 | 17 | for routine in program.routines: |
17 | 18 | encountered_gotos = list(routine.encountered_gotos) |
18 | if len(encountered_gotos) == 1 and isinstance(encountered_gotos[0].type, RoutineType): | |
19 | if len(encountered_gotos) == 1 and isinstance(self.symtab.fetch_global_type(encountered_gotos[0].name), RoutineType): | |
19 | 20 | self.fallthru_map[routine.name] = encountered_gotos[0].name |
20 | 21 | else: |
21 | 22 | self.fallthru_map[routine.name] = None |
0 | 0 | """Data/storage model for SixtyPical.""" |
1 | 1 | |
2 | ||
3 | class Type(object): | |
4 | def __init__(self, name, max_range=None): | |
5 | self.name = name | |
6 | self.max_range = max_range | |
7 | ||
8 | def __repr__(self): | |
9 | return 'Type(%r)' % self.name | |
10 | ||
11 | def __str__(self): | |
12 | return self.name | |
13 | ||
14 | def __eq__(self, other): | |
15 | return isinstance(other, Type) and other.name == self.name | |
16 | ||
17 | def __hash__(self): | |
18 | return hash(self.name) | |
2 | from collections import namedtuple | |
19 | 3 | |
20 | 4 | |
21 | TYPE_BIT = Type('bit', max_range=(0, 1)) | |
22 | TYPE_BYTE = Type('byte', max_range=(0, 255)) | |
23 | TYPE_WORD = Type('word', max_range=(0, 65535)) | |
5 | class BitType(namedtuple('BitType', ['typename'])): | |
6 | max_range = (0, 1) | |
7 | def __new__(cls): | |
8 | return super(BitType, cls).__new__(cls, 'bit') | |
9 | TYPE_BIT = BitType() | |
24 | 10 | |
25 | 11 | |
12 | class ByteType(namedtuple('ByteType', ['typename'])): | |
13 | max_range = (0, 255) | |
14 | def __new__(cls): | |
15 | return super(ByteType, cls).__new__(cls, 'byte') | |
16 | TYPE_BYTE = ByteType() | |
26 | 17 | |
27 | class RoutineType(Type): | |
18 | ||
19 | class WordType(namedtuple('WordType', ['typename'])): | |
20 | max_range = (0, 65535) | |
21 | def __new__(cls): | |
22 | return super(WordType, cls).__new__(cls, 'word') | |
23 | TYPE_WORD = WordType() | |
24 | ||
25 | ||
26 | class RoutineType(namedtuple('RoutineType', ['typename', 'inputs', 'outputs', 'trashes'])): | |
28 | 27 | """This memory location contains the code for a routine.""" |
29 | def __init__(self, inputs, outputs, trashes): | |
30 | self.name = 'routine' | |
31 | self.inputs = inputs | |
32 | self.outputs = outputs | |
33 | self.trashes = trashes | |
28 | max_range = (0, 0) | |
34 | 29 | |
35 | def __repr__(self): | |
36 | return '%s(%r, inputs=%r, outputs=%r, trashes=%r)' % ( | |
37 | self.__class__.__name__, self.name, self.inputs, self.outputs, self.trashes | |
38 | ) | |
39 | ||
40 | def __eq__(self, other): | |
41 | return isinstance(other, RoutineType) and ( | |
42 | other.name == self.name and | |
43 | other.inputs == self.inputs and | |
44 | other.outputs == self.outputs and | |
45 | other.trashes == self.trashes | |
46 | ) | |
47 | ||
48 | def __hash__(self): | |
49 | return hash(self.name) ^ hash(self.inputs) ^ hash(self.outputs) ^ hash(self.trashes) | |
30 | def __new__(cls, *args): | |
31 | return super(RoutineType, cls).__new__(cls, 'routine', *args) | |
50 | 32 | |
51 | 33 | @classmethod |
52 | 34 | def executable_types_compatible(cls_, src, dest): |
66 | 48 | return False |
67 | 49 | |
68 | 50 | |
69 | class VectorType(Type): | |
51 | class VectorType(namedtuple('VectorType', ['typename', 'of_type'])): | |
70 | 52 | """This memory location contains the address of some other type (currently, only RoutineType).""" |
71 | def __init__(self, of_type): | |
72 | self.name = 'vector' | |
73 | self.of_type = of_type | |
53 | max_range = (0, 65535) | |
74 | 54 | |
75 | def __repr__(self): | |
76 | return '%s(%r)' % ( | |
77 | self.__class__.__name__, self.of_type | |
78 | ) | |
79 | ||
80 | def __eq__(self, other): | |
81 | return self.name == other.name and self.of_type == other.of_type | |
82 | ||
83 | def __hash__(self): | |
84 | return hash(self.name) ^ hash(self.of_type) | |
55 | def __new__(cls, *args): | |
56 | return super(VectorType, cls).__new__(cls, 'vector', *args) | |
85 | 57 | |
86 | 58 | |
87 | class TableType(Type): | |
88 | def __init__(self, of_type, size): | |
89 | self.of_type = of_type | |
90 | self.size = size | |
91 | self.name = '{} table[{}]'.format(self.of_type.name, self.size) | |
59 | class TableType(namedtuple('TableType', ['typename', 'of_type', 'size'])): | |
92 | 60 | |
93 | def __repr__(self): | |
94 | return '%s(%r, %r)' % ( | |
95 | self.__class__.__name__, self.of_type, self.size | |
96 | ) | |
61 | def __new__(cls, *args): | |
62 | return super(TableType, cls).__new__(cls, 'table', *args) | |
63 | ||
64 | @property | |
65 | def max_range(self): | |
66 | return (0, self.size - 1) | |
97 | 67 | |
98 | 68 | @classmethod |
99 | 69 | def is_a_table_type(cls_, x, of_type): |
100 | 70 | return isinstance(x, TableType) and x.of_type == of_type |
101 | 71 | |
102 | 72 | |
103 | class BufferType(Type): | |
104 | def __init__(self, size): | |
105 | self.size = size | |
106 | self.name = 'buffer[%s]' % self.size | |
73 | class PointerType(namedtuple('PointerType', ['typename'])): | |
74 | max_range = (0, 65535) | |
75 | ||
76 | def __new__(cls): | |
77 | return super(PointerType, cls).__new__(cls, 'pointer') | |
107 | 78 | |
108 | 79 | |
109 | class PointerType(Type): | |
110 | def __init__(self): | |
111 | self.name = 'pointer' | |
80 | # -------------------------------------------------------- | |
112 | 81 | |
113 | 82 | |
114 | class Ref(object): | |
115 | def is_constant(self): | |
116 | """read-only means that the program cannot change the value | |
117 | of a location. constant means that the value of the location | |
118 | will not change during the lifetime of the program.""" | |
119 | raise NotImplementedError("class {} must implement is_constant()".format(self.__class__.__name__)) | |
120 | ||
121 | def max_range(self): | |
122 | raise NotImplementedError("class {} must implement max_range()".format(self.__class__.__name__)) | |
123 | ||
124 | ||
125 | class LocationRef(Ref): | |
126 | def __init__(self, type, name): | |
127 | self.type = type | |
128 | self.name = name | |
129 | ||
130 | def __eq__(self, other): | |
131 | # Ordinarily there will only be one ref with a given name, | |
132 | # but because we store the type in here and we want to treat | |
133 | # these objects as immutable, we compare the types, too, | |
134 | # just to be sure. | |
135 | equal = isinstance(other, self.__class__) and other.name == self.name | |
136 | if equal: | |
137 | assert other.type == self.type, repr((self, other)) | |
138 | return equal | |
139 | ||
140 | def __hash__(self): | |
141 | return hash(self.name + str(self.type)) | |
142 | ||
143 | def __repr__(self): | |
144 | return '%s(%r, %r)' % (self.__class__.__name__, self.type, self.name) | |
145 | ||
146 | def __str__(self): | |
147 | return "{}:{}".format(self.name, self.type) | |
148 | ||
149 | def is_constant(self): | |
150 | return isinstance(self.type, RoutineType) | |
151 | ||
152 | def max_range(self): | |
153 | try: | |
154 | return self.type.max_range | |
155 | except: | |
156 | return (0, 0) | |
83 | class LocationRef(namedtuple('LocationRef', ['reftype', 'name'])): | |
84 | def __new__(cls, *args): | |
85 | return super(LocationRef, cls).__new__(cls, 'location', *args) | |
157 | 86 | |
158 | 87 | @classmethod |
159 | 88 | def format_set(cls, location_refs): |
160 | 89 | return '{%s}' % ', '.join([str(loc) for loc in sorted(location_refs, key=lambda x: x.name)]) |
161 | 90 | |
162 | 91 | |
163 | class IndirectRef(Ref): | |
164 | def __init__(self, ref): | |
165 | self.ref = ref | |
166 | ||
167 | def __eq__(self, other): | |
168 | return isinstance(other, self.__class__) and self.ref == other.ref | |
169 | ||
170 | def __hash__(self): | |
171 | return hash(self.__class__.name) ^ hash(self.ref) | |
172 | ||
173 | def __repr__(self): | |
174 | return '%s(%r)' % (self.__class__.__name__, self.ref) | |
92 | class IndirectRef(namedtuple('IndirectRef', ['reftype', 'ref'])): | |
93 | def __new__(cls, *args): | |
94 | return super(IndirectRef, cls).__new__(cls, 'indirect', *args) | |
175 | 95 | |
176 | 96 | @property |
177 | 97 | def name(self): |
178 | 98 | return '[{}]+y'.format(self.ref.name) |
179 | 99 | |
180 | def is_constant(self): | |
181 | return False | |
182 | 100 | |
183 | ||
184 | class IndexedRef(Ref): | |
185 | def __init__(self, ref, index): | |
186 | self.ref = ref | |
187 | self.index = index | |
188 | ||
189 | def __eq__(self, other): | |
190 | return isinstance(other, self.__class__) and self.ref == other.ref and self.index == other.index | |
191 | ||
192 | def __hash__(self): | |
193 | return hash(self.__class__.name) ^ hash(self.ref) ^ hash(self.index) | |
194 | ||
195 | def __repr__(self): | |
196 | return '%s(%r, %r)' % (self.__class__.__name__, self.ref, self.index) | |
101 | class IndexedRef(namedtuple('IndexedRef', ['reftype', 'ref', 'offset', 'index'])): | |
102 | def __new__(cls, *args): | |
103 | return super(IndexedRef, cls).__new__(cls, 'indexed', *args) | |
197 | 104 | |
198 | 105 | @property |
199 | 106 | def name(self): |
200 | return '{}+{}'.format(self.ref.name, self.index.name) | |
201 | ||
202 | def is_constant(self): | |
203 | return False | |
107 | return '{}+{}+{}'.format(self.ref.name, self.offset, self.index.name) | |
204 | 108 | |
205 | 109 | |
206 | class AddressRef(Ref): | |
207 | def __init__(self, ref): | |
208 | self.ref = ref | |
209 | ||
210 | def __eq__(self, other): | |
211 | return self.ref == other.ref | |
212 | ||
213 | def __hash__(self): | |
214 | return hash(self.__class__.name) ^ hash(self.ref) | |
215 | ||
216 | def __repr__(self): | |
217 | return '%s(%r)' % (self.__class__.__name__, self.ref) | |
218 | ||
219 | @property | |
220 | def name(self): | |
221 | return '^{}'.format(self.ref.name) | |
222 | ||
223 | def is_constant(self): | |
224 | return True | |
225 | ||
226 | ||
227 | class PartRef(Ref): | |
228 | """For 'low byte of' location and 'high byte of' location modifiers. | |
229 | ||
230 | height=0 = low byte, height=1 = high byte. | |
231 | ||
232 | NOTE: Not actually used yet. Might require more thought before it's usable. | |
233 | """ | |
234 | def __init__(self, ref, height): | |
235 | assert isinstance(ref, Ref) | |
236 | assert ref.type == TYPE_WORD | |
237 | self.ref = ref | |
238 | self.height = height | |
239 | self.type = TYPE_BYTE | |
240 | ||
241 | def __eq__(self, other): | |
242 | return isinstance(other, PartRef) and ( | |
243 | other.height == self.height and other.ref == self.ref | |
244 | ) | |
245 | ||
246 | def __hash__(self): | |
247 | return hash(self.ref) ^ hash(self.height) ^ hash(self.type) | |
248 | ||
249 | def __repr__(self): | |
250 | return '%s(%r, %r)' % (self.__class__.__name__, self.ref, self.height) | |
251 | ||
252 | def is_constant(self): | |
253 | return self.ref.is_constant() | |
254 | ||
255 | ||
256 | class ConstantRef(Ref): | |
257 | def __init__(self, type, value): | |
258 | self.type = type | |
259 | self.value = value | |
260 | ||
261 | def __eq__(self, other): | |
262 | return isinstance(other, ConstantRef) and ( | |
263 | other.type == self.type and other.value == self.value | |
264 | ) | |
265 | ||
266 | def __hash__(self): | |
267 | return hash(str(self.value) + str(self.type)) | |
268 | ||
269 | def __repr__(self): | |
270 | return '%s(%r, %r)' % (self.__class__.__name__, self.type, self.value) | |
271 | ||
272 | def is_constant(self): | |
273 | return True | |
274 | ||
275 | def max_range(self): | |
276 | return (self.value, self.value) | |
110 | class ConstantRef(namedtuple('ConstantRef', ['reftype', 'type', 'value'])): | |
111 | def __new__(cls, *args): | |
112 | return super(ConstantRef, cls).__new__(cls, 'constant', *args) | |
277 | 113 | |
278 | 114 | def high_byte(self): |
279 | 115 | return (self.value >> 8) & 255 |
295 | 131 | value -= 256 |
296 | 132 | return ConstantRef(self.type, value) |
297 | 133 | |
134 | @property | |
135 | def name(self): | |
136 | return 'constant({})'.format(self.value) | |
298 | 137 | |
299 | REG_A = LocationRef(TYPE_BYTE, 'a') | |
300 | REG_X = LocationRef(TYPE_BYTE, 'x') | |
301 | REG_Y = LocationRef(TYPE_BYTE, 'y') | |
302 | 138 | |
303 | FLAG_Z = LocationRef(TYPE_BIT, 'z') | |
304 | FLAG_C = LocationRef(TYPE_BIT, 'c') | |
305 | FLAG_N = LocationRef(TYPE_BIT, 'n') | |
306 | FLAG_V = LocationRef(TYPE_BIT, 'v') | |
139 | REG_A = LocationRef('a') | |
140 | REG_X = LocationRef('x') | |
141 | REG_Y = LocationRef('y') | |
142 | ||
143 | FLAG_Z = LocationRef('z') | |
144 | FLAG_C = LocationRef('c') | |
145 | FLAG_N = LocationRef('n') | |
146 | FLAG_V = LocationRef('v') |
0 | 0 | # encoding: UTF-8 |
1 | 1 | |
2 | from sixtypical.ast import Program, Defn, Routine, Block, SingleOp, If, Repeat, For, WithInterruptsOff, Save | |
2 | from sixtypical.ast import ( | |
3 | Program, Defn, Routine, Block, SingleOp, Call, GoTo, If, Repeat, For, WithInterruptsOff, Save, PointInto | |
4 | ) | |
3 | 5 | from sixtypical.model import ( |
4 | 6 | TYPE_BIT, TYPE_BYTE, TYPE_WORD, |
5 | RoutineType, VectorType, TableType, BufferType, PointerType, | |
6 | LocationRef, ConstantRef, IndirectRef, IndexedRef, AddressRef, | |
7 | RoutineType, VectorType, TableType, PointerType, | |
8 | LocationRef, ConstantRef, IndirectRef, IndexedRef, | |
7 | 9 | ) |
8 | 10 | from sixtypical.scanner import Scanner |
9 | 11 | |
10 | 12 | |
11 | 13 | class SymEntry(object): |
12 | def __init__(self, ast_node, model): | |
14 | def __init__(self, ast_node, type_): | |
13 | 15 | self.ast_node = ast_node |
14 | self.model = model | |
16 | self.type_ = type_ | |
15 | 17 | |
16 | 18 | def __repr__(self): |
17 | return "%s(%r, %r)" % (self.__class__.__name__, self.ast_node, self.model) | |
19 | return "%s(%r, %r)" % (self.__class__.__name__, self.ast_node, self.type_) | |
18 | 20 | |
19 | 21 | |
20 | 22 | class ForwardReference(object): |
25 | 27 | return "%s(%r)" % (self.__class__.__name__, self.name) |
26 | 28 | |
27 | 29 | |
28 | class ParsingContext(object): | |
30 | class SymbolTable(object): | |
29 | 31 | def __init__(self): |
30 | self.symbols = {} # token -> SymEntry | |
31 | self.statics = {} # token -> SymEntry | |
32 | self.typedefs = {} # token -> Type AST | |
33 | self.consts = {} # token -> Loc | |
34 | ||
35 | for token in ('a', 'x', 'y'): | |
36 | self.symbols[token] = SymEntry(None, LocationRef(TYPE_BYTE, token)) | |
37 | for token in ('c', 'z', 'n', 'v'): | |
38 | self.symbols[token] = SymEntry(None, LocationRef(TYPE_BIT, token)) | |
32 | self.symbols = {} # symbol name -> SymEntry | |
33 | self.statics = {} # 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) | |
39 | 41 | |
40 | 42 | def __str__(self): |
41 | 43 | return "Symbols: {}\nStatics: {}\nTypedefs: {}\nConsts: {}".format(self.symbols, self.statics, self.typedefs, self.consts) |
42 | 44 | |
43 | def fetch(self, name): | |
44 | if name in self.statics: | |
45 | return self.statics[name].model | |
45 | def has_static(self, routine_name, name): | |
46 | return name in self.statics.get(routine_name, {}) | |
47 | ||
48 | def fetch_global_type(self, name): | |
49 | return self.symbols[name].type_ | |
50 | ||
51 | def fetch_static_type(self, routine_name, name): | |
52 | return self.statics[routine_name][name].type_ | |
53 | ||
54 | def fetch_global_ref(self, name): | |
46 | 55 | if name in self.symbols: |
47 | return self.symbols[name].model | |
56 | return LocationRef(name) | |
48 | 57 | return None |
49 | 58 | |
59 | def fetch_static_ref(self, routine_name, name): | |
60 | routine_statics = self.statics.get(routine_name, {}) | |
61 | if name in routine_statics: | |
62 | return LocationRef(name) | |
63 | return None | |
64 | ||
50 | 65 | |
51 | 66 | class Parser(object): |
52 | def __init__(self, context, text, filename): | |
53 | self.context = context | |
67 | def __init__(self, symtab, text, filename): | |
68 | self.symtab = symtab | |
54 | 69 | self.scanner = Scanner(text, filename) |
70 | self.current_routine_name = None | |
55 | 71 | |
56 | 72 | def syntax_error(self, msg): |
57 | 73 | self.scanner.syntax_error(msg) |
58 | 74 | |
59 | def lookup(self, name): | |
60 | model = self.context.fetch(name) | |
75 | def lookup(self, name, allow_forward=False, routine_name=None): | |
76 | model = self.symtab.fetch_global_ref(name) | |
77 | if model is None and routine_name: | |
78 | model = self.symtab.fetch_static_ref(routine_name, name) | |
79 | if model is None and allow_forward: | |
80 | return ForwardReference(name) | |
61 | 81 | if model is None: |
62 | 82 | self.syntax_error('Undefined symbol "{}"'.format(name)) |
63 | 83 | return model |
64 | 84 | |
65 | def declare(self, name, symentry, static=False): | |
66 | if self.context.fetch(name): | |
85 | def declare(self, name, ast_node, type_): | |
86 | if self.symtab.fetch_global_ref(name): | |
67 | 87 | self.syntax_error('Symbol "%s" already declared' % name) |
68 | if static: | |
69 | self.context.statics[name] = symentry | |
70 | else: | |
71 | self.context.symbols[name] = symentry | |
72 | ||
73 | def clear_statics(self): | |
74 | self.context.statics = {} | |
88 | self.symtab.symbols[name] = SymEntry(ast_node, type_) | |
89 | ||
90 | def declare_static(self, routine_name, name, ast_node, type_): | |
91 | if self.symtab.fetch_global_ref(name): | |
92 | self.syntax_error('Symbol "%s" already declared' % name) | |
93 | self.symtab.statics.setdefault(routine_name, {})[name] = SymEntry(ast_node, type_) | |
75 | 94 | |
76 | 95 | # ---- symbol resolution |
77 | 96 | |
78 | 97 | def resolve_symbols(self, program): |
79 | 98 | # This could stand to be better unified. |
80 | 99 | |
81 | def backpatch_constraint_labels(type_): | |
82 | def resolve(w): | |
83 | if not isinstance(w, ForwardReference): | |
84 | return w | |
85 | return self.lookup(w.name) | |
100 | def resolve(w): | |
101 | return self.lookup(w.name) if isinstance(w, ForwardReference) else w | |
102 | ||
103 | def backpatched_type(type_): | |
86 | 104 | if isinstance(type_, TableType): |
87 | backpatch_constraint_labels(type_.of_type) | |
105 | return TableType(backpatched_type(type_.of_type), type_.size) | |
88 | 106 | elif isinstance(type_, VectorType): |
89 | backpatch_constraint_labels(type_.of_type) | |
107 | return VectorType(backpatched_type(type_.of_type)) | |
90 | 108 | elif isinstance(type_, RoutineType): |
91 | type_.inputs = set([resolve(w) for w in type_.inputs]) | |
92 | type_.outputs = set([resolve(w) for w in type_.outputs]) | |
93 | type_.trashes = set([resolve(w) for w in type_.trashes]) | |
94 | ||
95 | for defn in program.defns: | |
96 | backpatch_constraint_labels(defn.location.type) | |
97 | for routine in program.routines: | |
98 | backpatch_constraint_labels(routine.location.type) | |
109 | return RoutineType( | |
110 | frozenset([resolve(w) for w in type_.inputs]), | |
111 | frozenset([resolve(w) for w in type_.outputs]), | |
112 | frozenset([resolve(w) for w in type_.trashes]), | |
113 | ) | |
114 | else: | |
115 | return type_ | |
116 | ||
117 | for name, symentry in self.symtab.symbols.items(): | |
118 | symentry.type_ = backpatched_type(symentry.type_) | |
99 | 119 | |
100 | 120 | def resolve_fwd_reference(obj, field): |
101 | 121 | field_value = getattr(obj, field, None) |
107 | 127 | |
108 | 128 | for node in program.all_children(): |
109 | 129 | if isinstance(node, SingleOp): |
110 | resolve_fwd_reference(node, 'location') | |
111 | 130 | resolve_fwd_reference(node, 'src') |
112 | 131 | resolve_fwd_reference(node, 'dest') |
132 | if isinstance(node, (Call, GoTo)): | |
133 | resolve_fwd_reference(node, 'location') | |
113 | 134 | |
114 | 135 | # --- grammar productions |
115 | 136 | |
121 | 142 | self.typedef() |
122 | 143 | if self.scanner.on('const'): |
123 | 144 | self.defn_const() |
124 | typenames = ['byte', 'word', 'table', 'vector', 'buffer', 'pointer'] # 'routine', | |
125 | typenames.extend(self.context.typedefs.keys()) | |
145 | typenames = ['byte', 'word', 'table', 'vector', 'pointer'] # 'routine', | |
146 | typenames.extend(self.symtab.typedefs.keys()) | |
126 | 147 | while self.scanner.on(*typenames): |
127 | defn = self.defn() | |
128 | self.declare(defn.name, SymEntry(defn, defn.location)) | |
148 | type_, defn = self.defn() | |
149 | self.declare(defn.name, defn, type_) | |
129 | 150 | defns.append(defn) |
130 | 151 | while self.scanner.consume('define'): |
131 | 152 | name = self.scanner.token |
132 | 153 | self.scanner.scan() |
133 | routine = self.routine(name) | |
134 | self.declare(name, SymEntry(routine, routine.location)) | |
154 | self.current_routine_name = name | |
155 | type_, routine = self.routine(name) | |
156 | self.declare(name, routine, type_) | |
135 | 157 | routines.append(routine) |
158 | self.current_routine_name = None | |
136 | 159 | self.scanner.check_type('EOF') |
137 | 160 | |
138 | 161 | program = Program(self.scanner.line_number, defns=defns, routines=routines) |
143 | 166 | self.scanner.expect('typedef') |
144 | 167 | type_ = self.defn_type() |
145 | 168 | name = self.defn_name() |
146 | if name in self.context.typedefs: | |
169 | if name in self.symtab.typedefs: | |
147 | 170 | self.syntax_error('Type "%s" already declared' % name) |
148 | self.context.typedefs[name] = type_ | |
171 | self.symtab.typedefs[name] = type_ | |
149 | 172 | return type_ |
150 | 173 | |
151 | 174 | def defn_const(self): |
152 | 175 | self.scanner.expect('const') |
153 | 176 | name = self.defn_name() |
154 | if name in self.context.consts: | |
177 | if name in self.symtab.consts: | |
155 | 178 | self.syntax_error('Const "%s" already declared' % name) |
156 | 179 | loc = self.const() |
157 | self.context.consts[name] = loc | |
180 | self.symtab.consts[name] = loc | |
158 | 181 | return loc |
159 | 182 | |
160 | 183 | def defn(self): |
184 | 207 | if initial is not None and addr is not None: |
185 | 208 | self.syntax_error("Definition cannot have both initial value and explicit address") |
186 | 209 | |
187 | location = LocationRef(type_, name) | |
188 | ||
189 | return Defn(self.scanner.line_number, name=name, addr=addr, initial=initial, location=location) | |
210 | return type_, Defn(self.scanner.line_number, name=name, addr=addr, initial=initial) | |
190 | 211 | |
191 | 212 | def const(self): |
192 | 213 | if self.scanner.token in ('on', 'off'): |
203 | 224 | loc = ConstantRef(TYPE_WORD, int(self.scanner.token)) |
204 | 225 | self.scanner.scan() |
205 | 226 | return loc |
206 | elif self.scanner.token in self.context.consts: | |
207 | loc = self.context.consts[self.scanner.token] | |
227 | elif self.scanner.token in self.symtab.consts: | |
228 | loc = self.symtab.consts[self.scanner.token] | |
208 | 229 | self.scanner.scan() |
209 | 230 | return loc |
210 | 231 | else: |
221 | 242 | |
222 | 243 | if self.scanner.consume('table'): |
223 | 244 | size = self.defn_size() |
224 | if size <= 0 or size > 256: | |
225 | self.syntax_error("Table size must be > 0 and <= 256") | |
245 | if size <= 0 or size > 65536: | |
246 | self.syntax_error("Table size must be > 0 and <= 65536") | |
226 | 247 | type_ = TableType(type_, size) |
227 | 248 | |
228 | 249 | return type_ |
246 | 267 | type_ = VectorType(type_) |
247 | 268 | elif self.scanner.consume('routine'): |
248 | 269 | (inputs, outputs, trashes) = self.constraints() |
249 | type_ = RoutineType(inputs=inputs, outputs=outputs, trashes=trashes) | |
250 | elif self.scanner.consume('buffer'): | |
251 | size = self.defn_size() | |
252 | type_ = BufferType(size) | |
270 | type_ = RoutineType(frozenset(inputs), frozenset(outputs), frozenset(trashes)) | |
253 | 271 | elif self.scanner.consume('pointer'): |
254 | 272 | type_ = PointerType() |
255 | 273 | else: |
256 | 274 | type_name = self.scanner.token |
257 | 275 | self.scanner.scan() |
258 | if type_name not in self.context.typedefs: | |
276 | if type_name not in self.symtab.typedefs: | |
259 | 277 | self.syntax_error("Undefined type '%s'" % type_name) |
260 | type_ = self.context.typedefs[type_name] | |
278 | type_ = self.symtab.typedefs[type_name] | |
261 | 279 | |
262 | 280 | return type_ |
263 | 281 | |
286 | 304 | def routine(self, name): |
287 | 305 | type_ = self.defn_type() |
288 | 306 | if not isinstance(type_, RoutineType): |
289 | self.syntax_error("Can only define a routine, not %r" % type_) | |
307 | self.syntax_error("Can only define a routine, not {}".format(repr(type_))) | |
290 | 308 | statics = [] |
291 | 309 | if self.scanner.consume('@'): |
292 | 310 | self.scanner.check_type('integer literal') |
295 | 313 | self.scanner.scan() |
296 | 314 | else: |
297 | 315 | statics = self.statics() |
298 | ||
299 | self.clear_statics() | |
300 | for defn in statics: | |
301 | self.declare(defn.name, SymEntry(defn, defn.location), static=True) | |
302 | 316 | block = self.block() |
303 | self.clear_statics() | |
304 | ||
305 | 317 | addr = None |
306 | location = LocationRef(type_, name) | |
307 | return Routine( | |
308 | self.scanner.line_number, | |
309 | name=name, block=block, addr=addr, | |
310 | location=location, statics=statics | |
311 | ) | |
318 | return type_, Routine(self.scanner.line_number, name=name, block=block, addr=addr, statics=statics) | |
312 | 319 | |
313 | 320 | def labels(self): |
314 | 321 | accum = [] |
332 | 339 | return accum |
333 | 340 | |
334 | 341 | def locexpr(self): |
335 | if self.scanner.token in ('on', 'off', 'word') or self.scanner.token in self.context.consts or self.scanner.on_type('integer literal'): | |
342 | if self.scanner.token in ('on', 'off', 'word') or self.scanner.token in self.symtab.consts or self.scanner.on_type('integer literal'): | |
336 | 343 | return self.const() |
337 | 344 | else: |
338 | 345 | name = self.scanner.token |
339 | 346 | self.scanner.scan() |
340 | loc = self.context.fetch(name) | |
341 | if loc: | |
342 | return loc | |
343 | else: | |
344 | return ForwardReference(name) | |
347 | return self.lookup(name, allow_forward=True, routine_name=self.current_routine_name) | |
345 | 348 | |
346 | 349 | def indlocexpr(self): |
347 | 350 | if self.scanner.consume('['): |
350 | 353 | self.scanner.expect('+') |
351 | 354 | self.scanner.expect('y') |
352 | 355 | return IndirectRef(loc) |
353 | elif self.scanner.consume('^'): | |
354 | loc = self.locexpr() | |
355 | return AddressRef(loc) | |
356 | 356 | else: |
357 | 357 | return self.indexed_locexpr() |
358 | 358 | |
360 | 360 | loc = self.locexpr() |
361 | 361 | if not isinstance(loc, str): |
362 | 362 | index = None |
363 | offset = ConstantRef(TYPE_BYTE, 0) | |
363 | 364 | if self.scanner.consume('+'): |
365 | if self.scanner.token in self.symtab.consts or self.scanner.on_type('integer literal'): | |
366 | offset = self.const() | |
367 | self.scanner.expect('+') | |
364 | 368 | index = self.locexpr() |
365 | loc = IndexedRef(loc, index) | |
369 | loc = IndexedRef(loc, offset, index) | |
366 | 370 | return loc |
367 | 371 | |
368 | 372 | def statics(self): |
369 | 373 | defns = [] |
370 | 374 | while self.scanner.consume('static'): |
371 | defn = self.defn() | |
375 | type_, defn = self.defn() | |
372 | 376 | if defn.initial is None: |
373 | 377 | self.syntax_error("Static definition {} must have initial value".format(defn)) |
378 | self.declare_static(self.current_routine_name, defn.name, defn, type_) | |
374 | 379 | defns.append(defn) |
375 | 380 | return defns |
376 | 381 | |
379 | 384 | self.scanner.expect('{') |
380 | 385 | while not self.scanner.on('}'): |
381 | 386 | instrs.append(self.instr()) |
382 | if isinstance(instrs[-1], SingleOp) and instrs[-1].opcode == 'goto': | |
387 | if isinstance(instrs[-1], GoTo): | |
383 | 388 | break |
384 | 389 | self.scanner.expect('}') |
385 | 390 | return Block(self.scanner.line_number, instrs=instrs) |
449 | 454 | opcode = self.scanner.token |
450 | 455 | self.scanner.scan() |
451 | 456 | return SingleOp(self.scanner.line_number, opcode=opcode, dest=None, src=None) |
452 | elif self.scanner.token in ("call", "goto"): | |
453 | opcode = self.scanner.token | |
454 | self.scanner.scan() | |
457 | elif self.scanner.consume("call"): | |
455 | 458 | name = self.scanner.token |
456 | 459 | self.scanner.scan() |
457 | instr = SingleOp(self.scanner.line_number, opcode=opcode, location=ForwardReference(name), dest=None, src=None) | |
460 | instr = Call(self.scanner.line_number, location=ForwardReference(name)) | |
461 | return instr | |
462 | elif self.scanner.consume("goto"): | |
463 | name = self.scanner.token | |
464 | self.scanner.scan() | |
465 | instr = GoTo(self.scanner.line_number, location=ForwardReference(name)) | |
458 | 466 | return instr |
459 | 467 | elif self.scanner.token in ("copy",): |
460 | 468 | opcode = self.scanner.token |
473 | 481 | locations = self.locexprs() |
474 | 482 | block = self.block() |
475 | 483 | return Save(self.scanner.line_number, locations=locations, block=block) |
484 | elif self.scanner.consume("point"): | |
485 | pointer = self.locexpr() | |
486 | self.scanner.expect("into") | |
487 | table = self.locexpr() | |
488 | block = self.block() | |
489 | return PointInto(self.scanner.line_number, pointer=pointer, table=table, block=block) | |
476 | 490 | elif self.scanner.consume("trash"): |
477 | 491 | dest = self.locexpr() |
478 | 492 | return SingleOp(self.scanner.line_number, opcode='trash', src=None, dest=dest) |
501 | 501 | | ld a, many + x |
502 | 502 | | } |
503 | 503 | ? UnmeaningfulReadError: x |
504 | ||
505 | Storing to a table, you may also include a constant offset. | |
506 | ||
507 | | byte one | |
508 | | byte table[256] many | |
509 | | | |
510 | | define main routine | |
511 | | outputs many | |
512 | | trashes a, x, n, z | |
513 | | { | |
514 | | ld x, 0 | |
515 | | ld a, 0 | |
516 | | st a, many + 100 + x | |
517 | | } | |
518 | = ok | |
519 | ||
520 | Reading from a table, you may also include a constant offset. | |
521 | ||
522 | | byte table[256] many | |
523 | | | |
524 | | define main routine | |
525 | | inputs many | |
526 | | outputs many | |
527 | | trashes a, x, n, z | |
528 | | { | |
529 | | ld x, 0 | |
530 | | ld a, many + 100 + x | |
531 | | } | |
532 | = ok | |
533 | ||
534 | Using a constant offset, you can read and write entries in | |
535 | the table beyond the 256th. | |
536 | ||
537 | | byte one | |
538 | | byte table[1024] many | |
539 | | | |
540 | | define main routine | |
541 | | inputs many | |
542 | | outputs many | |
543 | | trashes a, x, n, z | |
544 | | { | |
545 | | ld x, 0 | |
546 | | ld a, many + 999 + x | |
547 | | st a, many + 1000 + x | |
548 | | } | |
549 | = ok | |
504 | 550 | |
505 | 551 | There are other operations you can do on tables. (1/3) |
506 | 552 | |
613 | 659 | | } |
614 | 660 | = ok |
615 | 661 | |
662 | Copying to and from a word table with a constant offset. | |
663 | ||
664 | | word one | |
665 | | word table[256] many | |
666 | | | |
667 | | define main routine | |
668 | | inputs one, many | |
669 | | outputs one, many | |
670 | | trashes a, x, n, z | |
671 | | { | |
672 | | ld x, 0 | |
673 | | copy one, many + 100 + x | |
674 | | copy many + 100 + x, one | |
675 | | copy 9999, many + 1 + x | |
676 | | } | |
677 | = ok | |
678 | ||
616 | 679 | #### tables: range checking #### |
617 | 680 | |
618 | 681 | It is a static analysis error if it cannot be proven that a read or write |
619 | 682 | to a table falls within the defined size of that table. |
620 | 683 | |
621 | (If a table has 256 entries, then there is never a problem, because a byte | |
622 | cannot index any entry outside of 0..255.) | |
684 | If a table has 256 entries, then there is never a problem (so long as | |
685 | no constant offset is supplied), because a byte cannot index any entry | |
686 | outside of 0..255. | |
687 | ||
688 | But if the table has fewer than 256 entries, or if a constant offset is | |
689 | supplied, there is the possibility that the index will refer to an | |
690 | entry in the table which does not exist. | |
623 | 691 | |
624 | 692 | A SixtyPical implementation must be able to prove that the index is inside |
625 | 693 | the range of the table in various ways. The simplest is to show that a |
663 | 731 | | } |
664 | 732 | ? RangeExceededError |
665 | 733 | |
734 | Any constant offset is taken into account in this check. | |
735 | ||
736 | | byte table[32] many | |
737 | | | |
738 | | define main routine | |
739 | | inputs many | |
740 | | outputs many | |
741 | | trashes a, x, n, z | |
742 | | { | |
743 | | ld x, 31 | |
744 | | ld a, many + 1 + x | |
745 | | } | |
746 | ? RangeExceededError | |
747 | ||
748 | | byte table[32] many | |
749 | | | |
750 | | define main routine | |
751 | | inputs many | |
752 | | outputs many | |
753 | | trashes a, x, n, z | |
754 | | { | |
755 | | ld x, 31 | |
756 | | ld a, 0 | |
757 | | st a, many + 1 + x | |
758 | | } | |
759 | ? RangeExceededError | |
760 | ||
666 | 761 | This applies to `copy` as well. |
667 | 762 | |
668 | 763 | | word one: 77 |
702 | 797 | | { |
703 | 798 | | ld x, 32 |
704 | 799 | | copy one, many + x |
800 | | } | |
801 | ? RangeExceededError | |
802 | ||
803 | Any constant offset is taken into account in this check. | |
804 | ||
805 | | word one: 77 | |
806 | | word table[32] many | |
807 | | | |
808 | | define main routine | |
809 | | inputs many, one | |
810 | | outputs many, one | |
811 | | trashes a, x, n, z | |
812 | | { | |
813 | | ld x, 31 | |
814 | | copy many + 1 + x, one | |
815 | | } | |
816 | ? RangeExceededError | |
817 | ||
818 | | word one: 77 | |
819 | | word table[32] many | |
820 | | | |
821 | | define main routine | |
822 | | inputs many, one | |
823 | | outputs many, one | |
824 | | trashes a, x, n, z | |
825 | | { | |
826 | | ld x, 31 | |
827 | | copy one, many + 1 + x | |
705 | 828 | | } |
706 | 829 | ? RangeExceededError |
707 | 830 | |
725 | 848 | | } |
726 | 849 | = ok |
727 | 850 | |
728 | Test for "clipping", but not enough. | |
851 | Tests for "clipping", but not enough. | |
729 | 852 | |
730 | 853 | | word one: 77 |
731 | 854 | | word table[32] many |
739 | 862 | | ld x, a |
740 | 863 | | copy one, many + x |
741 | 864 | | copy many + x, one |
865 | | } | |
866 | ? RangeExceededError | |
867 | ||
868 | | word one: 77 | |
869 | | word table[32] many | |
870 | | | |
871 | | define main routine | |
872 | | inputs a, many, one | |
873 | | outputs many, one | |
874 | | trashes a, x, n, z | |
875 | | { | |
876 | | and a, 31 | |
877 | | ld x, a | |
878 | | copy one, many + 1 + x | |
879 | | copy many + 1 + x, one | |
742 | 880 | | } |
743 | 881 | ? RangeExceededError |
744 | 882 | |
2778 | 2916 | | } |
2779 | 2917 | ? TypeMismatchError |
2780 | 2918 | |
2781 | ### Buffers and pointers ### | |
2782 | ||
2783 | Note that `^buf` is a constant value, so it by itself does not require `buf` to be | |
2784 | listed in any input/output sets. | |
2785 | ||
2786 | However, if the code reads from it through a pointer, it *should* be in `inputs`. | |
2787 | ||
2788 | Likewise, if the code writes to it through a pointer, it *should* be in `outputs`. | |
2789 | ||
2790 | Of course, unless you write to *all* the bytes in a buffer, some of those bytes | |
2791 | might not be meaningful. So how meaningful is this check? | |
2792 | ||
2793 | This is an open problem. | |
2794 | ||
2795 | For now, convention says: if it is being read, list it in `inputs`, and if it is | |
2796 | being modified, list it in both `inputs` and `outputs`. | |
2797 | ||
2798 | Write literal through a pointer. | |
2799 | ||
2800 | | buffer[2048] buf | |
2919 | ### point ... into blocks ### | |
2920 | ||
2921 | Pointer must be a pointer type. | |
2922 | ||
2923 | | byte table[256] tab | |
2924 | | word ptr | |
2925 | | | |
2926 | | define main routine | |
2927 | | inputs tab | |
2928 | | outputs y, tab | |
2929 | | trashes a, z, n, ptr | |
2930 | | { | |
2931 | | ld y, 0 | |
2932 | | point ptr into tab { | |
2933 | | copy 123, [ptr] + y | |
2934 | | } | |
2935 | | } | |
2936 | ? TypeMismatchError | |
2937 | ||
2938 | Cannot write through pointer outside a `point ... into` block. | |
2939 | ||
2940 | | byte table[256] tab | |
2801 | 2941 | | pointer ptr |
2802 | 2942 | | |
2803 | 2943 | | define main routine |
2804 | | inputs buf | |
2805 | | outputs y, buf | |
2944 | | inputs tab, ptr | |
2945 | | outputs y, tab | |
2806 | 2946 | | trashes a, z, n, ptr |
2807 | 2947 | | { |
2808 | 2948 | | ld y, 0 |
2809 | | copy ^buf, ptr | |
2810 | 2949 | | copy 123, [ptr] + y |
2811 | 2950 | | } |
2812 | = ok | |
2813 | ||
2814 | It does use `y`. | |
2815 | ||
2816 | | buffer[2048] buf | |
2951 | ? ForbiddenWriteError | |
2952 | ||
2953 | | byte table[256] tab | |
2817 | 2954 | | pointer ptr |
2818 | 2955 | | |
2819 | 2956 | | define main routine |
2820 | | inputs buf | |
2821 | | outputs buf | |
2957 | | inputs tab | |
2958 | | outputs y, tab | |
2822 | 2959 | | trashes a, z, n, ptr |
2823 | 2960 | | { |
2824 | | copy ^buf, ptr | |
2961 | | ld y, 0 | |
2962 | | point ptr into tab { | |
2963 | | copy 123, [ptr] + y | |
2964 | | } | |
2825 | 2965 | | copy 123, [ptr] + y |
2826 | 2966 | | } |
2967 | ? ForbiddenWriteError | |
2968 | ||
2969 | Write literal through a pointer into a table. | |
2970 | ||
2971 | | byte table[256] tab | |
2972 | | pointer ptr | |
2973 | | | |
2974 | | define main routine | |
2975 | | inputs tab | |
2976 | | outputs y, tab | |
2977 | | trashes a, z, n, ptr | |
2978 | | { | |
2979 | | ld y, 0 | |
2980 | | point ptr into tab { | |
2981 | | copy 123, [ptr] + y | |
2982 | | } | |
2983 | | } | |
2984 | = ok | |
2985 | ||
2986 | Writing into a table via a pointer does use `y`. | |
2987 | ||
2988 | | byte table[256] tab | |
2989 | | pointer ptr | |
2990 | | | |
2991 | | define main routine | |
2992 | | inputs tab | |
2993 | | outputs tab | |
2994 | | trashes a, z, n, ptr | |
2995 | | { | |
2996 | | point ptr into tab { | |
2997 | | copy 123, [ptr] + y | |
2998 | | } | |
2999 | | } | |
2827 | 3000 | ? UnmeaningfulReadError |
2828 | 3001 | |
2829 | Write stored value through a pointer. | |
2830 | ||
2831 | | buffer[2048] buf | |
3002 | Write stored value through a pointer into a table. | |
3003 | ||
3004 | | byte table[256] tab | |
2832 | 3005 | | pointer ptr |
2833 | 3006 | | byte foo |
2834 | 3007 | | |
2835 | 3008 | | define main routine |
2836 | | inputs foo, buf | |
2837 | | outputs y, buf | |
3009 | | inputs foo, tab | |
3010 | | outputs y, tab | |
2838 | 3011 | | trashes a, z, n, ptr |
2839 | 3012 | | { |
2840 | 3013 | | ld y, 0 |
2841 | | copy ^buf, ptr | |
2842 | | copy foo, [ptr] + y | |
2843 | | } | |
2844 | = ok | |
2845 | ||
2846 | Read through a pointer. | |
2847 | ||
2848 | | buffer[2048] buf | |
3014 | | point ptr into tab { | |
3015 | | copy foo, [ptr] + y | |
3016 | | } | |
3017 | | } | |
3018 | = ok | |
3019 | ||
3020 | Read a table entry via a pointer. | |
3021 | ||
3022 | | byte table[256] tab | |
2849 | 3023 | | pointer ptr |
2850 | 3024 | | byte foo |
2851 | 3025 | | |
2852 | 3026 | | define main routine |
2853 | | inputs buf | |
3027 | | inputs tab | |
2854 | 3028 | | outputs foo |
2855 | 3029 | | trashes a, y, z, n, ptr |
2856 | 3030 | | { |
2857 | 3031 | | ld y, 0 |
2858 | | copy ^buf, ptr | |
3032 | | point ptr into tab { | |
2859 | 3033 | | copy [ptr] + y, foo |
2860 | | } | |
2861 | = ok | |
2862 | ||
2863 | Read and write through two pointers. | |
2864 | ||
2865 | | buffer[2048] buf | |
3034 | | } | |
3035 | | } | |
3036 | = ok | |
3037 | ||
3038 | Read and write through two pointers into a table. | |
3039 | ||
3040 | | byte table[256] tab | |
2866 | 3041 | | pointer ptra |
2867 | 3042 | | pointer ptrb |
2868 | 3043 | | |
2869 | 3044 | | define main routine |
2870 | | inputs buf | |
2871 | | outputs buf | |
3045 | | inputs tab | |
3046 | | outputs tab | |
2872 | 3047 | | trashes a, y, z, n, ptra, ptrb |
2873 | 3048 | | { |
2874 | 3049 | | ld y, 0 |
2875 | | copy ^buf, ptra | |
2876 | | copy ^buf, ptrb | |
2877 | | copy [ptra] + y, [ptrb] + y | |
2878 | | } | |
2879 | = ok | |
2880 | ||
2881 | Read through a pointer to the `a` register. Note that this is done with `ld`, | |
3050 | | point ptra into tab { | |
3051 | | point ptrb into tab { | |
3052 | | copy [ptra] + y, [ptrb] + y | |
3053 | | } | |
3054 | | } | |
3055 | | } | |
3056 | = ok | |
3057 | ||
3058 | Read through a pointer into a table, to the `a` register. Note that this is done with `ld`, | |
2882 | 3059 | not `copy`. |
2883 | 3060 | |
2884 | | buffer[2048] buf | |
3061 | | byte table[256] tab | |
2885 | 3062 | | pointer ptr |
2886 | 3063 | | byte foo |
2887 | 3064 | | |
2888 | 3065 | | define main routine |
2889 | | inputs buf | |
3066 | | inputs tab | |
2890 | 3067 | | outputs a |
2891 | 3068 | | trashes y, z, n, ptr |
2892 | 3069 | | { |
2893 | 3070 | | ld y, 0 |
2894 | | copy ^buf, ptr | |
2895 | | ld a, [ptr] + y | |
2896 | | } | |
2897 | = ok | |
2898 | ||
2899 | Write the `a` register through a pointer. Note that this is done with `st`, | |
3071 | | point ptr into tab { | |
3072 | | ld a, [ptr] + y | |
3073 | | } | |
3074 | | } | |
3075 | = ok | |
3076 | ||
3077 | Write the `a` register through a pointer into a table. Note that this is done with `st`, | |
2900 | 3078 | not `copy`. |
2901 | 3079 | |
2902 | | buffer[2048] buf | |
3080 | | byte table[256] tab | |
2903 | 3081 | | pointer ptr |
2904 | 3082 | | byte foo |
2905 | 3083 | | |
2906 | 3084 | | define main routine |
2907 | | inputs buf | |
2908 | | outputs buf | |
3085 | | inputs tab | |
3086 | | outputs tab | |
2909 | 3087 | | trashes a, y, z, n, ptr |
2910 | 3088 | | { |
2911 | 3089 | | ld y, 0 |
2912 | | copy ^buf, ptr | |
2913 | | ld a, 255 | |
2914 | | st a, [ptr] + y | |
3090 | | point ptr into tab { | |
3091 | | ld a, 255 | |
3092 | | st a, [ptr] + y | |
3093 | | } | |
3094 | | } | |
3095 | = ok | |
3096 | ||
3097 | Cannot get a pointer into a non-byte (for instance, word) table. | |
3098 | ||
3099 | | word table[256] tab | |
3100 | | pointer ptr | |
3101 | | byte foo | |
3102 | | | |
3103 | | define main routine | |
3104 | | inputs tab | |
3105 | | outputs foo | |
3106 | | trashes a, y, z, n, ptr | |
3107 | | { | |
3108 | | ld y, 0 | |
3109 | | point ptr into tab { | |
3110 | | copy [ptr] + y, foo | |
3111 | | } | |
3112 | | } | |
3113 | ? TypeMismatchError | |
3114 | ||
3115 | Cannot get a pointer into a non-byte (for instance, vector) table. | |
3116 | ||
3117 | | vector (routine trashes a, z, n) table[256] tab | |
3118 | | pointer ptr | |
3119 | | vector (routine trashes a, z, n) foo | |
3120 | | | |
3121 | | define main routine | |
3122 | | inputs tab | |
3123 | | outputs foo | |
3124 | | trashes a, y, z, n, ptr | |
3125 | | { | |
3126 | | ld y, 0 | |
3127 | | point ptr into tab { | |
3128 | | copy [ptr] + y, foo | |
3129 | | } | |
3130 | | } | |
3131 | ? TypeMismatchError | |
3132 | ||
3133 | `point into` by itself only requires `ptr` to be writeable. By itself, | |
3134 | it does not require `tab` to be readable or writeable. | |
3135 | ||
3136 | | byte table[256] tab | |
3137 | | pointer ptr | |
3138 | | | |
3139 | | define main routine | |
3140 | | trashes a, z, n, ptr | |
3141 | | { | |
3142 | | point ptr into tab { | |
3143 | | ld a, 0 | |
3144 | | } | |
3145 | | } | |
3146 | = ok | |
3147 | ||
3148 | | byte table[256] tab | |
3149 | | pointer ptr | |
3150 | | | |
3151 | | define main routine | |
3152 | | trashes a, z, n | |
3153 | | { | |
3154 | | point ptr into tab { | |
3155 | | ld a, 0 | |
3156 | | } | |
3157 | | } | |
3158 | ? ForbiddenWriteError | |
3159 | ||
3160 | After a `point into` block, the pointer is no longer meaningful and cannot | |
3161 | be considered an output of the routine. | |
3162 | ||
3163 | | byte table[256] tab | |
3164 | | pointer ptr | |
3165 | | | |
3166 | | define main routine | |
3167 | | inputs tab | |
3168 | | outputs y, tab, ptr | |
3169 | | trashes a, z, n | |
3170 | | { | |
3171 | | ld y, 0 | |
3172 | | point ptr into tab { | |
3173 | | copy 123, [ptr] + y | |
3174 | | } | |
3175 | | } | |
3176 | ? UnmeaningfulOutputError | |
3177 | ||
3178 | If code in a routine reads from a table through a pointer, the table must be in | |
3179 | the `inputs` of that routine. | |
3180 | ||
3181 | | byte table[256] tab | |
3182 | | pointer ptr | |
3183 | | byte foo | |
3184 | | | |
3185 | | define main routine | |
3186 | | outputs foo | |
3187 | | trashes a, y, z, n, ptr | |
3188 | | { | |
3189 | | ld y, 0 | |
3190 | | point ptr into tab { | |
3191 | | copy [ptr] + y, foo | |
3192 | | } | |
3193 | | } | |
3194 | ? UnmeaningfulReadError | |
3195 | ||
3196 | Likewise, if code in a routine writes into a table via a pointer, the table must | |
3197 | be in the `outputs` of that routine. | |
3198 | ||
3199 | | byte table[256] tab | |
3200 | | pointer ptr | |
3201 | | | |
3202 | | define main routine | |
3203 | | inputs tab | |
3204 | | trashes a, y, z, n, ptr | |
3205 | | { | |
3206 | | ld y, 0 | |
3207 | | point ptr into tab { | |
3208 | | copy 123, [ptr] + y | |
3209 | | } | |
3210 | | } | |
3211 | ? ForbiddenWriteError | |
3212 | ||
3213 | If code in a routine reads from a table through a pointer, the pointer *should* | |
3214 | remain inside the range of the table. This is currently not checked. | |
3215 | ||
3216 | | byte table[32] tab | |
3217 | | pointer ptr | |
3218 | | byte foo | |
3219 | | | |
3220 | | define main routine | |
3221 | | inputs tab | |
3222 | | outputs foo | |
3223 | | trashes a, y, c, z, n, v, ptr | |
3224 | | { | |
3225 | | ld y, 0 | |
3226 | | point ptr into tab { | |
3227 | | st off, c | |
3228 | | add ptr, word 100 | |
3229 | | copy [ptr] + y, foo | |
3230 | | } | |
3231 | | } | |
3232 | = ok | |
3233 | ||
3234 | Likewise, if code in a routine writes into a table through a pointer, the pointer | |
3235 | *should* remain inside the range of the table. This is currently not checked. | |
3236 | ||
3237 | | byte table[32] tab | |
3238 | | pointer ptr | |
3239 | | | |
3240 | | define main routine | |
3241 | | inputs tab | |
3242 | | outputs tab | |
3243 | | trashes a, y, c, z, n, v, ptr | |
3244 | | { | |
3245 | | ld y, 0 | |
3246 | | point ptr into tab { | |
3247 | | st off, c | |
3248 | | add ptr, word 100 | |
3249 | | copy 123, [ptr] + y | |
3250 | | } | |
2915 | 3251 | | } |
2916 | 3252 | = ok |
2917 | 3253 | |
3584 | 3920 | | } |
3585 | 3921 | = ok |
3586 | 3922 | |
3587 | TODO: we should have a lot more test cases for the above, here. | |
3923 | Another inconsistent exit test, this one based on "real" code | |
3924 | (the `ribos2` demo). | |
3925 | ||
3926 | | typedef routine | |
3927 | | inputs border_color, vic_intr | |
3928 | | outputs border_color, vic_intr | |
3929 | | trashes a, z, n, c | |
3930 | | irq_handler | |
3931 | | | |
3932 | | vector irq_handler cinv @ $314 | |
3933 | | vector irq_handler saved_irq_vec | |
3934 | | byte vic_intr @ $d019 | |
3935 | | byte border_color @ $d020 | |
3936 | | | |
3937 | | define pla_tay_pla_tax_pla_rti routine | |
3938 | | inputs a | |
3939 | | trashes a | |
3940 | | @ $EA81 | |
3941 | | | |
3942 | | define our_service_routine irq_handler | |
3943 | | { | |
3944 | | ld a, vic_intr | |
3945 | | st a, vic_intr | |
3946 | | and a, 1 | |
3947 | | cmp a, 1 | |
3948 | | if not z { | |
3949 | | goto saved_irq_vec | |
3950 | | } else { | |
3951 | | ld a, border_color | |
3952 | | xor a, $ff | |
3953 | | st a, border_color | |
3954 | | goto pla_tay_pla_tax_pla_rti | |
3955 | | } | |
3956 | | } | |
3957 | | | |
3958 | | define main routine | |
3959 | | { | |
3960 | | } | |
3961 | ? InconsistentExitError | |
3962 | ||
3963 | | typedef routine | |
3964 | | inputs border_color, vic_intr | |
3965 | | outputs border_color, vic_intr | |
3966 | | trashes a, z, n, c | |
3967 | | irq_handler | |
3968 | | | |
3969 | | vector irq_handler cinv @ $314 | |
3970 | | vector irq_handler saved_irq_vec | |
3971 | | byte vic_intr @ $d019 | |
3972 | | byte border_color @ $d020 | |
3973 | | | |
3974 | | define pla_tay_pla_tax_pla_rti routine | |
3975 | | inputs border_color, vic_intr | |
3976 | | outputs border_color, vic_intr | |
3977 | | trashes a, z, n, c | |
3978 | | @ $EA81 | |
3979 | | | |
3980 | | define our_service_routine irq_handler | |
3981 | | { | |
3982 | | ld a, vic_intr | |
3983 | | st a, vic_intr | |
3984 | | and a, 1 | |
3985 | | cmp a, 1 | |
3986 | | if not z { | |
3987 | | goto saved_irq_vec | |
3988 | | } else { | |
3989 | | ld a, border_color | |
3990 | | xor a, $ff | |
3991 | | st a, border_color | |
3992 | | goto pla_tay_pla_tax_pla_rti | |
3993 | | } | |
3994 | | } | |
3995 | | | |
3996 | | define main routine | |
3997 | | { | |
3998 | | } | |
3999 | = ok | |
3588 | 4000 | |
3589 | 4001 | Can't `goto` a routine that outputs or trashes more than the current routine. |
3590 | 4002 |
384 | 384 | = $081B DEC $081F,X |
385 | 385 | = $081E RTS |
386 | 386 | |
387 | Using a constant offset, you can read and write entries in | |
388 | the table beyond the 256th. | |
389 | ||
390 | | byte one | |
391 | | byte table[1024] many | |
392 | | | |
393 | | define main routine | |
394 | | inputs many | |
395 | | outputs many | |
396 | | trashes a, x, n, z | |
397 | | { | |
398 | | ld x, 0 | |
399 | | ld a, many + x | |
400 | | st a, many + x | |
401 | | ld a, many + 999 + x | |
402 | | st a, many + 1000 + x | |
403 | | } | |
404 | = $080D LDX #$00 | |
405 | = $080F LDA $081D,X | |
406 | = $0812 STA $081D,X | |
407 | = $0815 LDA $0C04,X | |
408 | = $0818 STA $0C05,X | |
409 | = $081B RTS | |
410 | ||
411 | Instructions on tables, with constant offsets. | |
412 | ||
413 | | byte table[256] many | |
414 | | | |
415 | | define main routine | |
416 | | inputs many | |
417 | | outputs many | |
418 | | trashes a, x, c, n, z, v | |
419 | | { | |
420 | | ld x, 0 | |
421 | | ld a, 0 | |
422 | | st off, c | |
423 | | add a, many + 1 + x | |
424 | | sub a, many + 2 + x | |
425 | | cmp a, many + 3 + x | |
426 | | and a, many + 4 + x | |
427 | | or a, many + 5 + x | |
428 | | xor a, many + 6 + x | |
429 | | shl many + 7 + x | |
430 | | shr many + 8 + x | |
431 | | inc many + 9 + x | |
432 | | dec many + 10 + x | |
433 | | } | |
434 | = $080D LDX #$00 | |
435 | = $080F LDA #$00 | |
436 | = $0811 CLC | |
437 | = $0812 ADC $0832,X | |
438 | = $0815 SBC $0833,X | |
439 | = $0818 CMP $0834,X | |
440 | = $081B AND $0835,X | |
441 | = $081E ORA $0836,X | |
442 | = $0821 EOR $0837,X | |
443 | = $0824 ROL $0838,X | |
444 | = $0827 ROR $0839,X | |
445 | = $082A INC $083A,X | |
446 | = $082D DEC $083B,X | |
447 | = $0830 RTS | |
448 | ||
387 | 449 | Compiling 16-bit `cmp`. |
388 | 450 | |
389 | 451 | | word za @ 60001 |
874 | 936 | = $0817 RTS |
875 | 937 | = $0818 INX |
876 | 938 | = $0819 RTS |
939 | ||
940 | Copy byte to byte table and back, with both `x` and `y` as indexes, | |
941 | plus constant offsets. | |
942 | ||
943 | | byte one | |
944 | | byte table[256] many | |
945 | | | |
946 | | define main routine | |
947 | | inputs one, many | |
948 | | outputs one, many | |
949 | | trashes a, x, y, n, z | |
950 | | { | |
951 | | ld x, 0 | |
952 | | ld y, 0 | |
953 | | ld a, 77 | |
954 | | st a, many + x | |
955 | | st a, many + y | |
956 | | st a, many + 1 + x | |
957 | | st a, many + 1 + y | |
958 | | ld a, many + x | |
959 | | ld a, many + y | |
960 | | ld a, many + 8 + x | |
961 | | ld a, many + 8 + y | |
962 | | } | |
963 | = $080D LDX #$00 | |
964 | = $080F LDY #$00 | |
965 | = $0811 LDA #$4D | |
966 | = $0813 STA $082D,X | |
967 | = $0816 STA $082D,Y | |
968 | = $0819 STA $082E,X | |
969 | = $081C STA $082E,Y | |
970 | = $081F LDA $082D,X | |
971 | = $0822 LDA $082D,Y | |
972 | = $0825 LDA $0835,X | |
973 | = $0828 LDA $0835,Y | |
974 | = $082B RTS | |
877 | 975 | |
878 | 976 | Copy word to word table and back, with both `x` and `y` as indexes. |
879 | 977 | |
917 | 1015 | = $0848 STA $084D |
918 | 1016 | = $084B RTS |
919 | 1017 | |
1018 | Copy word to word table and back, with constant offsets. | |
1019 | ||
1020 | | word one | |
1021 | | word table[256] many | |
1022 | | | |
1023 | | define main routine | |
1024 | | inputs one, many | |
1025 | | outputs one, many | |
1026 | | trashes a, x, y, n, z | |
1027 | | { | |
1028 | | ld x, 0 | |
1029 | | ld y, 0 | |
1030 | | copy 777, one | |
1031 | | copy one, many + 1 + x | |
1032 | | copy one, many + 2 + y | |
1033 | | copy many + 3 + x, one | |
1034 | | copy many + 4 + y, one | |
1035 | | } | |
1036 | = $080D LDX #$00 | |
1037 | = $080F LDY #$00 | |
1038 | = $0811 LDA #$09 | |
1039 | = $0813 STA $084C | |
1040 | = $0816 LDA #$03 | |
1041 | = $0818 STA $084D | |
1042 | = $081B LDA $084C | |
1043 | = $081E STA $084F,X | |
1044 | = $0821 LDA $084D | |
1045 | = $0824 STA $094F,X | |
1046 | = $0827 LDA $084C | |
1047 | = $082A STA $0850,Y | |
1048 | = $082D LDA $084D | |
1049 | = $0830 STA $0950,Y | |
1050 | = $0833 LDA $0851,X | |
1051 | = $0836 STA $084C | |
1052 | = $0839 LDA $0951,X | |
1053 | = $083C STA $084D | |
1054 | = $083F LDA $0852,Y | |
1055 | = $0842 STA $084C | |
1056 | = $0845 LDA $0952,Y | |
1057 | = $0848 STA $084D | |
1058 | = $084B RTS | |
1059 | ||
920 | 1060 | Indirect call. |
921 | 1061 | |
922 | 1062 | | vector routine |
1016 | 1156 | = $082F LDA $0848,X |
1017 | 1157 | = $0832 STA $0846 |
1018 | 1158 | = $0835 LDA $0948,X |
1159 | = $0838 STA $0847 | |
1160 | = $083B JSR $0842 | |
1161 | = $083E RTS | |
1162 | = $083F LDX #$C8 | |
1163 | = $0841 RTS | |
1164 | = $0842 JMP ($0846) | |
1165 | = $0845 RTS | |
1166 | ||
1167 | Copying to and from a vector table, with constant offsets. | |
1168 | ||
1169 | | vector routine | |
1170 | | outputs x | |
1171 | | trashes a, z, n | |
1172 | | one | |
1173 | | vector routine | |
1174 | | outputs x | |
1175 | | trashes a, z, n | |
1176 | | table[256] many | |
1177 | | | |
1178 | | define bar routine outputs x trashes a, z, n { | |
1179 | | ld x, 200 | |
1180 | | } | |
1181 | | | |
1182 | | define main routine | |
1183 | | inputs one, many | |
1184 | | outputs one, many | |
1185 | | trashes a, x, n, z | |
1186 | | { | |
1187 | | ld x, 0 | |
1188 | | copy bar, one | |
1189 | | copy bar, many + 1 + x | |
1190 | | copy one, many + 2 + x | |
1191 | | copy many + 3 + x, one | |
1192 | | call one | |
1193 | | } | |
1194 | = $080D LDX #$00 | |
1195 | = $080F LDA #$3F | |
1196 | = $0811 STA $0846 | |
1197 | = $0814 LDA #$08 | |
1198 | = $0816 STA $0847 | |
1199 | = $0819 LDA #$3F | |
1200 | = $081B STA $0849,X | |
1201 | = $081E LDA #$08 | |
1202 | = $0820 STA $0949,X | |
1203 | = $0823 LDA $0846 | |
1204 | = $0826 STA $084A,X | |
1205 | = $0829 LDA $0847 | |
1206 | = $082C STA $094A,X | |
1207 | = $082F LDA $084B,X | |
1208 | = $0832 STA $0846 | |
1209 | = $0835 LDA $094B,X | |
1019 | 1210 | = $0838 STA $0847 |
1020 | 1211 | = $083B JSR $0842 |
1021 | 1212 | = $083E RTS |
1206 | 1397 | = $081D STA $0822 |
1207 | 1398 | = $0820 RTS |
1208 | 1399 | |
1209 | ### Buffers and Pointers | |
1210 | ||
1211 | Load address into pointer. | |
1212 | ||
1213 | | buffer[2048] buf | |
1400 | ### Tables and Pointers | |
1401 | ||
1402 | Load address of table into pointer. | |
1403 | ||
1404 | | byte table[256] tab | |
1214 | 1405 | | pointer ptr @ 254 |
1215 | 1406 | | |
1216 | 1407 | | define main routine |
1217 | | inputs buf | |
1218 | | outputs buf, y | |
1408 | | inputs tab | |
1409 | | outputs tab, y | |
1219 | 1410 | | trashes a, z, n, ptr |
1220 | 1411 | | { |
1221 | 1412 | | ld y, 0 |
1222 | | copy ^buf, ptr | |
1413 | | point ptr into tab { | |
1414 | | } | |
1223 | 1415 | | } |
1224 | 1416 | = $080D LDY #$00 |
1225 | 1417 | = $080F LDA #$18 |
1230 | 1422 | |
1231 | 1423 | Write literal through a pointer. |
1232 | 1424 | |
1233 | | buffer[2048] buf | |
1425 | | byte table[256] tab | |
1234 | 1426 | | pointer ptr @ 254 |
1235 | 1427 | | |
1236 | 1428 | | define main routine |
1237 | | inputs buf | |
1238 | | outputs buf, y | |
1429 | | inputs tab | |
1430 | | outputs tab, y | |
1239 | 1431 | | trashes a, z, n, ptr |
1240 | 1432 | | { |
1241 | 1433 | | ld y, 0 |
1242 | | copy ^buf, ptr | |
1243 | | copy 123, [ptr] + y | |
1434 | | point ptr into tab { | |
1435 | | copy 123, [ptr] + y | |
1436 | | } | |
1244 | 1437 | | } |
1245 | 1438 | = $080D LDY #$00 |
1246 | 1439 | = $080F LDA #$1C |
1253 | 1446 | |
1254 | 1447 | Write stored value through a pointer. |
1255 | 1448 | |
1256 | | buffer[2048] buf | |
1449 | | byte table[256] tab | |
1257 | 1450 | | pointer ptr @ 254 |
1258 | 1451 | | byte foo |
1259 | 1452 | | |
1260 | 1453 | | define main routine |
1261 | | inputs foo, buf | |
1262 | | outputs y, buf | |
1454 | | inputs foo, tab | |
1455 | | outputs y, tab | |
1263 | 1456 | | trashes a, z, n, ptr |
1264 | 1457 | | { |
1265 | 1458 | | ld y, 0 |
1266 | | copy ^buf, ptr | |
1267 | | copy foo, [ptr] + y | |
1459 | | point ptr into tab { | |
1460 | | copy foo, [ptr] + y | |
1461 | | } | |
1268 | 1462 | | } |
1269 | 1463 | = $080D LDY #$00 |
1270 | 1464 | = $080F LDA #$1D |
1271 | 1465 | = $0811 STA $FE |
1272 | 1466 | = $0813 LDA #$08 |
1273 | 1467 | = $0815 STA $FF |
1274 | = $0817 LDA $101D | |
1468 | = $0817 LDA $091D | |
1275 | 1469 | = $081A STA ($FE),Y |
1276 | 1470 | = $081C RTS |
1277 | 1471 | |
1278 | 1472 | Read through a pointer, into a byte storage location, or the `a` register. |
1279 | 1473 | |
1280 | | buffer[2048] buf | |
1474 | | byte table[256] tab | |
1281 | 1475 | | pointer ptr @ 254 |
1282 | 1476 | | byte foo |
1283 | 1477 | | |
1284 | 1478 | | define main routine |
1285 | | inputs buf | |
1479 | | inputs tab | |
1286 | 1480 | | outputs y, foo |
1287 | 1481 | | trashes a, z, n, ptr |
1288 | 1482 | | { |
1289 | 1483 | | ld y, 0 |
1290 | | copy ^buf, ptr | |
1291 | | copy [ptr] + y, foo | |
1292 | | ld a, [ptr] + y | |
1484 | | point ptr into tab { | |
1485 | | copy [ptr] + y, foo | |
1486 | | ld a, [ptr] + y | |
1487 | | } | |
1293 | 1488 | | } |
1294 | 1489 | = $080D LDY #$00 |
1295 | 1490 | = $080F LDA #$1F |
1297 | 1492 | = $0813 LDA #$08 |
1298 | 1493 | = $0815 STA $FF |
1299 | 1494 | = $0817 LDA ($FE),Y |
1300 | = $0819 STA $101F | |
1495 | = $0819 STA $091F | |
1301 | 1496 | = $081C LDA ($FE),Y |
1302 | 1497 | = $081E RTS |
1303 | 1498 | |
1304 | 1499 | Read and write through two pointers. |
1305 | 1500 | |
1306 | | buffer[2048] buf | |
1501 | | byte table[256] tab | |
1307 | 1502 | | pointer ptra @ 252 |
1308 | 1503 | | pointer ptrb @ 254 |
1309 | 1504 | | |
1310 | 1505 | | define main routine |
1311 | | inputs buf | |
1312 | | outputs buf | |
1506 | | inputs tab | |
1507 | | outputs tab | |
1313 | 1508 | | trashes a, y, z, n, ptra, ptrb |
1314 | 1509 | | { |
1315 | 1510 | | ld y, 0 |
1316 | | copy ^buf, ptra | |
1317 | | copy ^buf, ptrb | |
1318 | | copy [ptra] + y, [ptrb] + y | |
1511 | | point ptra into tab { | |
1512 | | point ptrb into tab { | |
1513 | | copy [ptra] + y, [ptrb] + y | |
1514 | | } | |
1515 | | } | |
1319 | 1516 | | } |
1320 | 1517 | = $080D LDY #$00 |
1321 | 1518 | = $080F LDA #$24 |
1332 | 1529 | |
1333 | 1530 | Write the `a` register through a pointer. |
1334 | 1531 | |
1335 | | buffer[2048] buf | |
1532 | | byte table[256] tab | |
1336 | 1533 | | pointer ptr @ 254 |
1337 | 1534 | | byte foo |
1338 | 1535 | | |
1339 | 1536 | | define main routine |
1340 | | inputs buf | |
1341 | | outputs buf | |
1537 | | inputs tab | |
1538 | | outputs tab | |
1342 | 1539 | | trashes a, y, z, n, ptr |
1343 | 1540 | | { |
1344 | 1541 | | ld y, 0 |
1345 | | copy ^buf, ptr | |
1346 | | ld a, 255 | |
1347 | | st a, [ptr] + y | |
1542 | | point ptr into tab { | |
1543 | | ld a, 255 | |
1544 | | st a, [ptr] + y | |
1545 | | } | |
1348 | 1546 | | } |
1349 | 1547 | = $080D LDY #$00 |
1350 | 1548 | = $080F LDA #$1C |
1358 | 1556 | Add a word memory location, and a literal word, to a pointer, and then read through it. |
1359 | 1557 | Note that this is *not* range-checked. (Yet.) |
1360 | 1558 | |
1361 | | buffer[2048] buf | |
1559 | | byte table[256] tab | |
1362 | 1560 | | pointer ptr @ 254 |
1363 | 1561 | | byte foo |
1364 | 1562 | | word delta |
1365 | 1563 | | |
1366 | 1564 | | define main routine |
1367 | | inputs buf | |
1565 | | inputs tab | |
1368 | 1566 | | outputs y, foo, delta |
1369 | 1567 | | trashes a, c, v, z, n, ptr |
1370 | 1568 | | { |
1371 | 1569 | | copy 619, delta |
1372 | 1570 | | ld y, 0 |
1373 | 1571 | | st off, c |
1374 | | copy ^buf, ptr | |
1375 | | add ptr, delta | |
1376 | | add ptr, word 1 | |
1377 | | copy [ptr] + y, foo | |
1572 | | point ptr into tab { | |
1573 | | add ptr, delta | |
1574 | | add ptr, word 1 | |
1575 | | copy [ptr] + y, foo | |
1576 | | } | |
1378 | 1577 | | } |
1379 | 1578 | = $080D LDA #$6B |
1380 | = $080F STA $1043 | |
1579 | = $080F STA $0943 | |
1381 | 1580 | = $0812 LDA #$02 |
1382 | = $0814 STA $1044 | |
1581 | = $0814 STA $0944 | |
1383 | 1582 | = $0817 LDY #$00 |
1384 | 1583 | = $0819 CLC |
1385 | 1584 | = $081A LDA #$42 |
1387 | 1586 | = $081E LDA #$08 |
1388 | 1587 | = $0820 STA $FF |
1389 | 1588 | = $0822 LDA $FE |
1390 | = $0824 ADC $1043 | |
1589 | = $0824 ADC $0943 | |
1391 | 1590 | = $0827 STA $FE |
1392 | 1591 | = $0829 LDA $FF |
1393 | = $082B ADC $1044 | |
1592 | = $082B ADC $0944 | |
1394 | 1593 | = $082E STA $FF |
1395 | 1594 | = $0830 LDA $FE |
1396 | 1595 | = $0832 ADC #$01 |
1399 | 1598 | = $0838 ADC #$00 |
1400 | 1599 | = $083A STA $FF |
1401 | 1600 | = $083C LDA ($FE),Y |
1402 | = $083E STA $1042 | |
1601 | = $083E STA $0942 | |
1403 | 1602 | = $0841 RTS |
1404 | 1603 | |
1405 | 1604 | ### Trash |
162 | 162 | |
163 | 163 | Other blocks. |
164 | 164 | |
165 | | byte table[256] tab | |
166 | | pointer ptr | |
167 | | | |
165 | 168 | | define main routine trashes a, x, c, z, v { |
166 | 169 | | with interrupts off { |
167 | 170 | | save a, x, c { |
171 | 174 | | save a, x, c { |
172 | 175 | | ld a, 0 |
173 | 176 | | } |
177 | | point ptr into tab { | |
178 | | ld a, [ptr] + y | |
179 | | } | |
174 | 180 | | } |
175 | 181 | = ok |
176 | 182 | |
179 | 185 | | byte byt |
180 | 186 | | word wor |
181 | 187 | | vector routine trashes a vec |
182 | | buffer[2048] buf | |
188 | | byte table[2048] buf | |
183 | 189 | | pointer ptr |
184 | 190 | | |
185 | 191 | | define main routine { |
191 | 197 | | byte table[256] many |
192 | 198 | | word table[256] wmany |
193 | 199 | | vector (routine trashes a) table[256] vmany |
200 | | byte bval | |
201 | | word wval | |
194 | 202 | | |
195 | 203 | | define main routine { |
196 | 204 | | ld x, 0 |
206 | 214 | | shr many + x |
207 | 215 | | inc many + x |
208 | 216 | | dec many + x |
217 | | ld a, many + x | |
218 | | st a, many + x | |
219 | | copy wval, wmany + x | |
220 | | copy wmany + x, wval | |
221 | | } | |
222 | = ok | |
223 | ||
224 | Indexing with an offset in some tables. | |
225 | ||
226 | | byte table[256] many | |
227 | | word table[256] wmany | |
228 | | byte bval | |
229 | | word wval | |
230 | | | |
231 | | define main routine { | |
232 | | ld x, 0 | |
233 | | ld a, 0 | |
234 | | st off, c | |
235 | | add a, many + 100 + x | |
236 | | sub a, many + 100 + x | |
237 | | cmp a, many + 100 + x | |
238 | | and a, many + 100 + x | |
239 | | or a, many + 100 + x | |
240 | | xor a, many + 100 + x | |
241 | | shl many + 100 + x | |
242 | | shr many + 100 + x | |
243 | | inc many + 100 + x | |
244 | | dec many + 100 + x | |
245 | | ld a, many + 100 + x | |
246 | | st a, many + 100 + x | |
247 | | copy wval, wmany + 100 + x | |
248 | | copy wmany + 100 + x, wval | |
209 | 249 | | } |
210 | 250 | = ok |
211 | 251 | |
212 | 252 | The number of entries in a table must be |
213 | greater than 0 and less than or equal to 256. | |
253 | greater than 0 and less than or equal to 65536. | |
254 | ||
255 | (In previous versions, a table could have at | |
256 | most 256 entries. They can now have more, however | |
257 | the offset-access syntax can only access the | |
258 | first 256. To access more, a pointer is required.) | |
214 | 259 | |
215 | 260 | | word table[512] many |
216 | 261 | | |
221 | 266 | | { |
222 | 267 | | ld x, 0 |
223 | 268 | | copy 9999, many + x |
269 | | } | |
270 | = ok | |
271 | ||
272 | | byte table[65536] many | |
273 | | | |
274 | | define main routine | |
275 | | inputs many | |
276 | | outputs many | |
277 | | trashes a, x, n, z | |
278 | | { | |
279 | | ld x, 0 | |
280 | | copy 99, many + x | |
281 | | } | |
282 | = ok | |
283 | ||
284 | | byte table[65537] many | |
285 | | | |
286 | | define main routine | |
287 | | inputs many | |
288 | | outputs many | |
289 | | trashes a, x, n, z | |
290 | | { | |
291 | | ld x, 0 | |
292 | | copy 99, many + x | |
224 | 293 | | } |
225 | 294 | ? SyntaxError |
226 | 295 | |
281 | 350 | | |
282 | 351 | | define main routine { |
283 | 352 | | ld a, lives |
353 | | } | |
354 | = ok | |
355 | ||
356 | Named constants can be used as offsets. | |
357 | ||
358 | | const lives 3 | |
359 | | const w1 1000 | |
360 | | | |
361 | | byte table[w1] those | |
362 | | | |
363 | | define main routine { | |
364 | | ld y, 0 | |
365 | | ld a, those + lives + y | |
284 | 366 | | } |
285 | 367 | = ok |
286 | 368 | |
589 | 671 | | } |
590 | 672 | ? Expected '}', but found 'ld' |
591 | 673 | |
592 | Buffers and pointers. | |
593 | ||
594 | | buffer[2048] buf | |
674 | Tables and pointers. | |
675 | ||
676 | | byte table[2048] buf | |
595 | 677 | | pointer ptr |
596 | 678 | | pointer ptrb |
597 | 679 | | byte foo |
598 | 680 | | |
599 | 681 | | define main routine { |
600 | | copy ^buf, ptr | |
601 | | copy 123, [ptr] + y | |
602 | | copy [ptr] + y, foo | |
603 | | copy [ptr] + y, [ptrb] + y | |
682 | | point ptr into buf { | |
683 | | copy 123, [ptr] + y | |
684 | | copy [ptr] + y, foo | |
685 | | copy [ptr] + y, [ptrb] + y | |
686 | | } | |
604 | 687 | | } |
605 | 688 | = ok |
606 | 689 |