git @ Cat's Eye Technologies SixtyPical / 1c7efb0
Merge pull request #20 from catseye/develop-0.19 Develop 0.19 Chris Pressey authored 5 years ago GitHub committed 5 years ago
22 changed file(s) with 1924 addition(s) and 1078 deletion(s). Raw diff Collapse all Expand all
00 History of SixtyPical
11 =====================
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.
226
327 0.18
428 ----
00 SixtyPical
11 ==========
22
3 _Version 0.18. Work-in-progress, everything is subject to change._
3 _Version 0.19. Work-in-progress, everything is subject to change._
44
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 ![Screenshot of result of running hearts.60p](images/hearts.png?raw=true)
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:
1137
1238 * You'd use assembly, but you don't want to spend hours
1339 debugging (say) a memory overrun that happened because of a
1844
1945 SixtyPical gives the programmer a coding regimen on par with assembly
2046 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
2270
2371 * you forgot to clear carry before adding something to the accumulator
2472 * a subroutine that you called trashes a register you thought it preserved
2674 * you tried to write the address of something that was not a routine, to
2775 a jump vector
2876
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
3378
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.
3684
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 -------------
42102
43103 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.
47105
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.
52108
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 ![Screenshot of result of running hearts.60p](images/hearts.png?raw=true)
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)
70114
71115 Documentation
72116 -------------
73117
74118 * [Design Goals](doc/Design%20Goals.md)
75 * [SixtyPical specification](doc/SixtyPical.md)
76119 * [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)
81120 * [6502 Opcodes used/not used in SixtyPical](doc/6502%20Opcodes.md)
82121 * [Output formats supported by `sixtypical`](doc/Output%20Formats.md)
83122 * [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
1111 Which uses some other storage location instead of the stack. A local static
1212 would be a good candidate for such.
1313
14 ### Associate each pointer with the buffer it points into
14 ### Analyze `call` within blocks?
1515
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?
1817
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?
2119
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?
2321
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?
2523
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`.)
2728
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.
3130
32 They are therefore a "view" of a section of a buffer.
31 ### Reset pointer in `point into` blocks
3332
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.
3635
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.
4038
41 (That seems to be the most compelling case for restricting them to `static`.)
39 ### Pointers associated globally with a table
4240
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.
4582
4683 ### Tail-call optimization
4784
00 #!/usr/bin/env python
1
2 """Usage: sixtypical [OPTIONS] FILES
3
4 Analyzes and compiles a Sixtypical program.
5 """
61
72 from os.path import realpath, dirname, join
83 import sys
116
127 # ----------------------------------------------------------------- #
138
9 from argparse import ArgumentParser
1410 import codecs
15 from argparse import ArgumentParser
11 import json
1612 from pprint import pprint
13 from subprocess import check_call
1714 import sys
15 from tempfile import NamedTemporaryFile
1816 import traceback
1917
20 from sixtypical.parser import Parser, ParsingContext, merge_programs
18 from sixtypical.parser import Parser, SymbolTable, merge_programs
2119 from sixtypical.analyzer import Analyzer
2220 from sixtypical.outputter import outputter_class_for
2321 from sixtypical.compiler import Compiler
2422
2523
2624 def process_input_files(filenames, options):
27 context = ParsingContext()
25 symtab = SymbolTable()
2826
2927 programs = []
3028
3129 for filename in options.filenames:
3230 text = open(filename).read()
33 parser = Parser(context, text, filename)
31 parser = Parser(symtab, text, filename)
3432 if options.debug:
35 print(context)
33 print(symtab)
3634 program = parser.program()
3735 programs.append(program)
3836
4139
4240 program = merge_programs(programs)
4341
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")
4650
4751 compilation_roster = None
4852 if options.optimize_fallthru:
4953 from sixtypical.fallthru import FallthruAnalyzer
5054
5155 def dump(data, label=None):
52 import json
5356 if not options.dump_fallthru_info:
5457 return
5558 if label:
5760 sys.stdout.write(json.dumps(data, indent=4, sort_keys=True, separators=(',', ':')))
5861 sys.stdout.write("\n")
5962
60 fa = FallthruAnalyzer(debug=options.debug)
63 fa = FallthruAnalyzer(symtab, debug=options.debug)
6164 fa.analyze_program(program)
6265 compilation_roster = fa.serialize()
6366 dump(compilation_roster)
6467
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):
6669 return
6770
6871 start_addr = None
7275 else:
7376 start_addr = int(options.origin, 10)
7477
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)
85114
86115
87116 if __name__ == '__main__':
88 argparser = ArgumentParser(__doc__.strip())
117 argparser = ArgumentParser()
89118
90119 argparser.add_argument(
91120 'filenames', metavar='FILENAME', type=str, nargs='+',
114143 help="Only parse and analyze the program; do not compile it."
115144 )
116145 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(
117152 "--optimize-fallthru",
118153 action="store_true",
119154 help="Reorder the routines in the program to maximize the number of tail calls "
122157 argparser.add_argument(
123158 "--dump-fallthru-info",
124159 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."
126161 )
127162 argparser.add_argument(
128163 "--parse-only",
135170 help="Display debugging information when analyzing and compiling."
136171 )
137172 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(
138179 "--traceback",
139180 action="store_true",
140181 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"
141187 )
142188
143189 options, unknown = argparser.parse_known_args(sys.argv[1:])
00 SixtyPical
11 ==========
22
3 This document describes the SixtyPical programming language version 0.15,
3 This document describes the SixtyPical programming language version 0.19,
44 both its static semantics (the capabilities and limits of the static
55 analyses it defines) and its runtime semantics (with reference to the
66 semantics of 6502 machine code.)
1111 Refer to the bottom of this document for an EBNF grammar of the syntax of
1212 the language.
1313
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:
1827
1928 * bit (2 possible values)
2029 * byte (256 possible values)
2130 * 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
2242 * 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 ####
3354
3455 A program may define its own types using the `typedef` feature. Typedefs
3556 must occur before everything else in the program. A typedef takes a
3657 type expression and an identifier which has not previously been used in
3758 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 ###
4263
4364 A primary concept in SixtyPical is the *memory location*. At any given point
4465 in time during execution, each memory location is either *uninitialized* or
5071 There are four general kinds of memory location. The first three are
5172 pre-defined and built-in.
5273
53 ### Registers ###
74 #### Registers ####
5475
5576 Each of these hold a byte. They are initially uninitialized.
5677
5879 x
5980 y
6081
61 ### Flags ###
82 #### Flags ####
6283
6384 Each of these hold a bit. They are initially uninitialized.
6485
6788 v (overflow)
6889 n (negative)
6990
70 ### Constants ###
91 #### Constants ####
7192
7293 It may be strange to think of constants as memory locations, but keep in mind
7394 that a memory location in SixtyPical need not map to a memory location in the
96117 Note that if a word constant is between 256 and 65535, the leading `word`
97118 token can be omitted.
98119
99 ### User-defined ###
120 #### User-defined ####
100121
101122 There may be any number of user-defined memory locations. They are defined
102123 by giving the type (which may be any type except `bit` and `routine`) and the
136157 that literal integers in the code are always immediate values. (But this
137158 may change at some point.)
138159
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.
143188
144189 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
146191
147192 LDA ($02), Y
148193 STA ($02), Y
150195 There are extended instruction modes for using these types of memory location.
151196 See `copy` below, but here is some illustrative example code:
152197
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.
161207
162208 Routines
163209 --------
299345 After execution, dest is considered initialized, and `z` and `n`, and
300346 `a` are considered uninitialized.
301347
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:
313349
314350 copy [<src-memory-location>] + y, <dest-memory-location>
315351 copy <src-memory-location>, [<dest-memory-location>] + y
349385 when the dest is `a`.
350386
351387 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.
353389
354390 ### inc ###
355391
580616 Program ::= {ConstDefn | TypeDefn} {Defn} {Routine}.
581617 ConstDefn::= "const" Ident<new> Const.
582618 TypeDefn::= "typedef" Type Ident<new>.
583 Defn ::= Type Ident<new> [Constraints] (":" Const | "@" LitWord).
619 Defn ::= Type Ident<new> (":" Const | "@" LitWord).
584620 Type ::= TypeTerm ["table" TypeSize].
585621 TypeExpr::= "byte"
586622 | "word"
587 | "buffer" TypeSize
588623 | "pointer"
589624 | "vector" TypeTerm
590625 | "routine" Constraints
593628 TypeSize::= "[" LitWord "]".
594629 Constrnt::= ["inputs" LocExprs] ["outputs" LocExprs] ["trashes" LocExprs].
595630 Routine ::= "define" Ident<new> Type (Block | "@" LitWord).
596 | "routine" Ident<new> Constraints (Block | "@" LitWord)
597 .
598631 LocExprs::= LocExpr {"," LocExpr}.
599 LocExpr ::= Register | Flag | Const | Ident.
632 LocExpr ::= Register | Flag | Const | Ident [["+" Const] "+" Register].
600633 Register::= "a" | "x" | "y".
601634 Flag ::= "c" | "z" | "n" | "v".
602635 Const ::= Literal | Ident<const>.
4545 demo, [smiley.60p](atari2600/smiley.60p) which was converted from an
4646 older Atari 2600 skeleton program written in [Ophis][].
4747
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
4854 [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.
2020 // and the end of their own routines, so the type needs to be compatible.
2121 // (In a good sense, it is a continuation.)
2222 //
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 //
3123
3224 typedef routine
3325 inputs joy2, press_fire_msg, dispatch_game_state,
3426 actor_pos, actor_delta, actor_logic,
3527 player_died,
36 screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4
28 screen, colormap
3729 outputs dispatch_game_state,
3830 actor_pos, actor_delta, actor_logic,
3931 player_died,
40 screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4
32 screen, colormap
4133 trashes a, x, y, c, z, n, v, pos, new_pos, delta, ptr, dispatch_logic
4234 game_state_routine
4335
6153
6254 byte vic_border @ 53280
6355 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
7658 byte joy2 @ $dc00
7759
7860 // ----------------------------------------------------------------
186168 }
187169
188170 define clear_screen routine
189 outputs screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4
171 outputs screen, colormap
190172 trashes a, y, c, n, z
191173 {
192174 ld y, 0
193175 repeat {
194176 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
199181
200182 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
205187
206188 inc y
207189 cmp y, 250
281263 call check_new_position_in_bounds
282264
283265 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
291274 // if "collision" is with your own self, treat it as if it's blank space!
292275 cmp a, 81
293276 if z {
295278 }
296279 cmp a, 32
297280 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 }
302286
303287 copy new_pos, pos
304288
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 }
309294 } else {
310295 ld a, 1
311296 st a, player_died
320305 call check_new_position_in_bounds
321306
322307 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 }
330315 // if "collision" is with your own self, treat it as if it's blank space!
331316 cmp a, 82
332317 if z {
334319 }
335320 cmp a, 32
336321 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 }
341327
342328 copy new_pos, pos
343329
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 }
348335 }
349336 } else {
350337 copy delta, compare_target
371358 st on, c
372359 sub a, 64 // yuck. oh well
373360
374 st a, screen1 + y
361 st a, screen + y
375362 }
376363
377364 st off, c
443430
444431 define main routine
445432 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
448434 trashes a, y, n, c, z, vic_border, vic_bg
449435 {
450436 ld a, 5
4949 // be practical. So we just jump to this location instead.
5050
5151 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
5455 @ $EA81
5556
5657 // ----- Interrupt Handler -----
55
66 These files are intended to be architecture-agnostic.
77 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:
912
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
1714
1815 `chrout` is a routine with outputs the value of the accumulator
1916 as an ASCII character, disturbing none of the other registers,
00 // Include `support/${PLATFORM}.60p` before this source
11 // Should print Y
22
3 buffer[2048] buf
3 byte table[2048] buf
44 pointer ptr @ 254
55 byte foo
66
+0
-36
eg/rudiments/loadngo.sh less more
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
-59
loadngo.sh less more
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
00 # encoding: UTF-8
11
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 )
35 from sixtypical.model import (
46 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,
79 REG_A, REG_Y, FLAG_Z, FLAG_N, FLAG_V, FLAG_C
810 )
911
7779 pass
7880
7981
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):
9083 """
9184 A location is touched if it was changed (or even potentially
9285 changed) during this routine, or some routine called by this routine.
10598 lists of this routine. A location can also be temporarily marked
10699 unwriteable in certain contexts, such as `for` loops.
107100 """
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}
114107 self._terminated = False
115108 self._gotos_encountered = set()
109 self._pointer_assoc = dict()
116110
117111 for ref in inputs:
118 if ref.is_constant():
112 if self.is_constant(ref):
119113 raise ConstantConstraintError(self.routine, ref.name)
120 self._range[ref] = ref.max_range()
114 self._range[ref] = self.max_range(ref)
121115 output_names = set()
122116 for ref in outputs:
123 if ref.is_constant():
117 if self.is_constant(ref):
124118 raise ConstantConstraintError(self.routine, ref.name)
125119 output_names.add(ref.name)
126120 self._writeable.add(ref)
127121 for ref in trashes:
128 if ref.is_constant():
122 if self.is_constant(ref):
129123 raise ConstantConstraintError(self.routine, ref.name)
130124 if ref.name in output_names:
131125 raise InconsistentConstraintsError(self.routine, ref.name)
132126 self._writeable.add(ref)
133127
134128 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__,
136131 LocationRef.format_set(self._touched), LocationRef.format_set(self._range), LocationRef.format_set(self._writeable)
137132 )
138133
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
139147 def clone(self):
140 c = Context(self.routines, self.routine, [], [], [])
148 c = AnalysisContext(self.symtab, self.routine, [], [], [])
141149 c._touched = set(self._touched)
142150 c._range = dict(self._range)
143151 c._writeable = set(self._writeable)
152 c._pointer_assoc = dict(self._pointer_assoc)
153 c._gotos_encountered = set(self._gotos_encountered)
144154 return c
145155
146156 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.)"""
148163 self.routine = other.routine
149164 self._touched = set(other._touched)
150165 self._range = dict(other._range)
151166 self._writeable = set(other._writeable)
152167 self._terminated = other._terminated
153 self._gotos_encounters = set(other._gotos_encountered)
168 self._pointer_assoc = dict(other._pointer_assoc)
154169
155170 def each_meaningful(self):
156171 for ref in self._range.keys():
168183 exception_class = kwargs.get('exception_class', UnmeaningfulReadError)
169184 for ref in refs:
170185 # statics are always meaningful
171 if routine_has_static(self.routine, ref):
186 if self.symtab.has_static(self.routine.name, ref.name):
172187 continue
173 if ref.is_constant() or ref in self.routines:
188 if self.is_constant(ref):
174189 pass
175190 elif isinstance(ref, LocationRef):
176191 if ref not in self._range:
188203 exception_class = kwargs.get('exception_class', ForbiddenWriteError)
189204 for ref in refs:
190205 # statics are always writeable
191 if routine_has_static(self.routine, ref):
206 if self.symtab.has_static(self.routine.name, ref.name):
192207 continue
193208 if ref not in self._writeable:
194209 message = ref.name
196211 message += ' (%s)' % kwargs['message']
197212 raise exception_class(self.routine, message)
198213
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)
201219
202220 # inside should always be meaningful
203221 inside_range = self._range[inside]
206224 if outside in self._range:
207225 outside_range = self._range[outside]
208226 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]:
214230 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
217233 )
218234 )
219235
225241 def set_meaningful(self, *refs):
226242 for ref in refs:
227243 if ref not in self._range:
228 self._range[ref] = ref.max_range()
244 self._range[ref] = self.max_range(ref)
229245
230246 def set_top_of_range(self, ref, top):
231247 self.assert_meaningful(ref)
267283 if src in self._range:
268284 src_range = self._range[src]
269285 else:
270 src_range = src.max_range()
286 src_range = self.max_range(src)
271287 self._range[dest] = src_range
272288
273289 def invalidate_range(self, ref):
274290 self.assert_meaningful(ref)
275 self._range[ref] = ref.max_range()
291 self._range[ref] = self.max_range(ref)
276292
277293 def set_unmeaningful(self, *refs):
278294 for ref in refs:
309325
310326 def has_terminated(self):
311327 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)
325328
326329 def extract(self, location):
327330 """Sets the given location as writeable in the context, and returns a 'baton' representing
358361 elif location in self._writeable:
359362 self._writeable.remove(location)
360363
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
361391
362392 class Analyzer(object):
363393
364 def __init__(self, debug=False):
394 def __init__(self, symtab, debug=False):
395 self.symtab = symtab
365396 self.current_routine = None
366 self.routines = {}
367397 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)
368413
369414 def assert_type(self, type_, *locations):
370415 for location in locations:
371 if location.type != type_:
416 if self.get_type(location) != type_:
372417 raise TypeMismatchError(self.current_routine, location.name)
373418
374419 def assert_affected_within(self, name, affecting_type, limiting_type):
386431 )
387432 raise IncompatibleConstraintsError(self.current_routine, message)
388433
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
389449 def analyze_program(self, program):
390450 assert isinstance(program, Program)
391 self.routines = {r.location: r for r in program.routines}
392451 for routine in program.routines:
393452 context = self.analyze_routine(routine)
394453 routine.encountered_gotos = list(context.encountered_gotos()) if context else []
397456 assert isinstance(routine, Routine)
398457 if routine.block is None:
399458 # it's an extern, that's fine
400 return
459 return None
401460
402461 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)
405464 self.exit_contexts = []
406465
407 if self.debug:
408 print("at start of routine `{}`:".format(routine.name))
409 print(context)
410
411466 self.analyze_block(routine.block, context)
412467
413468 trashed = set(context.each_touched()) - set(context.each_meaningful())
414469
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 }
426474
427475 if self.exit_contexts:
428476 # check that they are all consistent
432480 exit_writeable = set(exit_context.each_writeable())
433481 for ex in self.exit_contexts[1:]:
434482 if set(ex.each_meaningful()) != exit_meaningful:
435 raise InconsistentExitError("Exit contexts are not consistent")
483 raise InconsistentExitError(routine, "Exit contexts are not consistent")
436484 if set(ex.each_touched()) != exit_touched:
437 raise InconsistentExitError("Exit contexts are not consistent")
485 raise InconsistentExitError(routine, "Exit contexts are not consistent")
438486 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.
440492 context.update_from(exit_context)
441493
442494 # these all apply whether we encountered goto(s) in this routine, or not...:
452504
453505 # if something was touched, then it should have been declared to be writable.
454506 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):
456508 raise ForbiddenWriteError(routine, ref.name)
457509
458510 self.exit_contexts = None
467519 def analyze_instr(self, instr, context):
468520 if isinstance(instr, SingleOp):
469521 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)
470526 elif isinstance(instr, If):
471527 self.analyze_if(instr, context)
472528 elif isinstance(instr, Repeat):
479535 raise IllegalJumpError(instr, instr)
480536 elif isinstance(instr, Save):
481537 self.analyze_save(instr, context)
538 elif isinstance(instr, PointInto):
539 self.analyze_point_into(instr, context)
482540 else:
483541 raise NotImplementedError
484542
493551
494552 if opcode == 'ld':
495553 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)
497555 elif isinstance(src, IndirectRef):
498556 # 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:
500558 pass
501559 else:
502560 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
503567 context.assert_meaningful(src.ref, REG_Y)
504 elif src.type != dest.type:
568 elif self.get_type(src) != self.get_type(dest):
505569 raise TypeMismatchError(instr, '{} and {}'.format(src.name, dest.name))
506570 else:
507571 context.assert_meaningful(src)
509573 context.set_written(dest, FLAG_Z, FLAG_N)
510574 elif opcode == 'st':
511575 if isinstance(dest, IndexedRef):
512 if src.type != TYPE_BYTE:
576 if self.get_type(src) != TYPE_BYTE:
513577 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)
515579 elif isinstance(dest, IndirectRef):
516580 # 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:
518582 pass
519583 else:
520584 raise TypeMismatchError(instr, (src, dest))
585
521586 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):
524595 raise TypeMismatchError(instr, '{} and {}'.format(src, dest))
525596 else:
526597 context.set_written(dest)
529600 elif opcode == 'add':
530601 context.assert_meaningful(src, dest, FLAG_C)
531602 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:
534605 self.assert_type(TYPE_BYTE, src, dest)
535606 if dest != REG_A:
536607 context.set_touched(REG_A)
537608 context.set_unmeaningful(REG_A)
538609 else:
539610 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:
541613 context.set_touched(REG_A)
542614 context.set_unmeaningful(REG_A)
543 elif isinstance(dest.type, PointerType):
615 elif isinstance(dest_type, PointerType):
544616 context.set_touched(REG_A)
545617 context.set_unmeaningful(REG_A)
546618 else:
550622 elif opcode == 'sub':
551623 context.assert_meaningful(src, dest, FLAG_C)
552624 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:
555627 self.assert_type(TYPE_BYTE, src, dest)
556628 if dest != REG_A:
557629 context.set_touched(REG_A)
565637 elif opcode == 'cmp':
566638 context.assert_meaningful(src, dest)
567639 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:
570642 self.assert_type(TYPE_BYTE, src, dest)
571643 else:
572644 self.assert_type(TYPE_WORD, src, dest)
575647 context.set_written(FLAG_Z, FLAG_N, FLAG_C)
576648 elif opcode == 'and':
577649 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)
579651 else:
580652 self.assert_type(TYPE_BYTE, src, dest)
581653 context.assert_meaningful(src, dest)
587659 context.set_top_of_range(dest, context.get_top_of_range(src))
588660 elif opcode in ('or', 'xor'):
589661 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)
591663 else:
592664 self.assert_type(TYPE_BYTE, src, dest)
593665 context.assert_meaningful(src, dest)
596668 elif opcode in ('inc', 'dec'):
597669 context.assert_meaningful(dest)
598670 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)
600672 context.set_written(dest.ref, FLAG_Z, FLAG_N)
601673 #context.invalidate_range(dest)
602674 else:
619691 elif opcode in ('shl', 'shr'):
620692 context.assert_meaningful(dest, FLAG_C)
621693 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)
623695 context.set_written(dest.ref, FLAG_Z, FLAG_N, FLAG_C)
624696 #context.invalidate_range(dest)
625697 else:
626698 self.assert_type(TYPE_BYTE, dest)
627699 context.set_written(dest, FLAG_Z, FLAG_N, FLAG_C)
628700 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)
643701 elif opcode == 'copy':
644702 if dest == REG_A:
645703 raise ForbiddenWriteError(instr, "{} cannot be used as destination for copy".format(dest))
646704
647705 # 1. check that their types are compatible
648706
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):
656709 pass
657710 else:
658711 raise TypeMismatchError(instr, (src, dest))
659712 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:
661714 pass
662715 else:
663716 raise TypeMismatchError(instr, (src, dest))
664717 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):
666719 pass
667720 else:
668721 raise TypeMismatchError(instr, (src, dest))
669722
670723 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):
672725 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)):
675728 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)):
678731 pass
679732 else:
680733 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)
682735
683736 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:
685738 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)):
688741 pass
689742 else:
690743 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)
692745
693746 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):
695748 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)
700753 else:
701754 raise TypeMismatchError(instr, (src, dest))
702755 else:
706759
707760 if isinstance(src, (LocationRef, ConstantRef)) and isinstance(dest, IndirectRef):
708761 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
711769 elif isinstance(src, IndirectRef) and isinstance(dest, LocationRef):
712770 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)
714778 context.set_written(dest)
715779 elif isinstance(src, IndirectRef) and isinstance(dest, IndirectRef):
716780 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
719793 elif isinstance(src, LocationRef) and isinstance(dest, IndexedRef):
720794 context.assert_meaningful(src, dest.ref, dest.index)
721795 context.set_written(dest.ref)
732806
733807 context.set_touched(REG_A, FLAG_Z, FLAG_N)
734808 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()
788809
789810 elif opcode == 'trash':
790811 context.set_touched(instr.dest)
793814 pass
794815 else:
795816 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()
796886
797887 def analyze_if(self, instr, context):
798888 incoming_meaningful = set(context.each_meaningful())
904994 else:
905995 context.set_touched(REG_A)
906996 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)
5353
5454
5555 class Defn(AST):
56 value_attrs = ('name', 'addr', 'initial', 'location',)
56 value_attrs = ('name', 'addr', 'initial',)
5757
5858
5959 class Routine(AST):
60 value_attrs = ('name', 'addr', 'initial', 'location',)
60 value_attrs = ('name', 'addr', 'initial',)
6161 children_attrs = ('statics',)
6262 child_attrs = ('block',)
6363
7171
7272
7373 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',)
7583
7684
7785 class If(Instr):
96104 class Save(Instr):
97105 value_attrs = ('locations',)
98106 child_attrs = ('block',)
107
108
109 class PointInto(Instr):
110 value_attrs = ('pointer', 'table',)
111 child_attrs = ('block',)
00 # encoding: UTF-8
11
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 )
35 from sixtypical.model import (
4 ConstantRef, LocationRef, IndexedRef, IndirectRef, AddressRef,
6 ConstantRef, LocationRef, IndexedRef, IndirectRef,
57 TYPE_BIT, TYPE_BYTE, TYPE_WORD,
6 TableType, BufferType, PointerType, RoutineType, VectorType,
8 TableType, PointerType, RoutineType, VectorType,
79 REG_A, REG_X, REG_Y, FLAG_C
810 )
911 from sixtypical.emitter import Byte, Word, Table, Label, Offset, LowAddressByte, HighAddressByte
2729
2830
2931 class Compiler(object):
30 def __init__(self, emitter):
32 def __init__(self, symtab, emitter):
33 self.symtab = symtab
3134 self.emitter = emitter
3235 self.routines = {} # routine.name -> Routine
3336 self.routine_statics = {} # routine.name -> { static.name -> Label }
3538 self.trampolines = {} # Location -> Label
3639 self.current_routine = None
3740
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)
3954
4055 def addressing_mode_for_index(self, index):
4156 if index == REG_X:
4762
4863 def compute_length_of_defn(self, defn):
4964 length = None
50 type_ = defn.location.type
65 type_ = self.get_type_for_name(defn.name)
5166 if type_ == TYPE_BYTE:
5267 length = 1
5368 elif type_ == TYPE_WORD or isinstance(type_, (PointerType, VectorType)):
5469 length = 2
5570 elif isinstance(type_, TableType):
5671 length = type_.size * (1 if type_.of_type == TYPE_BYTE else 2)
57 elif isinstance(type_, BufferType):
58 length = type_.size
5972 if length is None:
6073 raise NotImplementedError("Need size for type {}".format(type_))
6174 return length
7386 else:
7487 return Absolute(label)
7588
76 # visitor methods
89 # - - - - visitor methods - - - -
7790
7891 def compile_program(self, program, compilation_roster=None):
7992 assert isinstance(program, Program)
8093
81 defn_labels = []
94 declarations = []
8295
8396 for defn in program.defns:
8497 length = self.compute_length_of_defn(defn)
8598 label = Label(defn.name, addr=defn.addr, length=length)
8699 self.labels[defn.name] = label
87 defn_labels.append((defn, label))
100 declarations.append((defn, self.symtab.fetch_global_type(defn.name), label))
88101
89102 for routine in program.routines:
90103 self.routines[routine.name] = routine
94107 self.labels[routine.name] = label
95108
96109 if hasattr(routine, 'statics'):
110 self.current_routine = routine
97111 static_labels = {}
98112 for defn in routine.statics:
99113 length = self.compute_length_of_defn(defn)
100114 label = Label(defn.name, addr=defn.addr, length=length)
101115 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))
103117 self.routine_statics[routine.name] = static_labels
118 self.current_routine = None
104119
105120 if compilation_roster is None:
106121 compilation_roster = [['main']] + [[routine.name] for routine in program.routines if routine.name != 'main']
117132 self.emitter.emit(RTS())
118133
119134 # initialized data
120 for defn, label in defn_labels:
135 for defn, type_, label in declarations:
121136 if defn.initial is not None:
122137 initial_data = None
123 type_ = defn.location.type
124138 if type_ == TYPE_BYTE:
125139 initial_data = Byte(defn.initial)
126140 elif type_ == TYPE_WORD:
136150 self.emitter.emit(initial_data)
137151
138152 # uninitialized, "BSS" data
139 for defn, label in defn_labels:
153 for defn, type_, label in declarations:
140154 if defn.initial is None and defn.addr is None:
141155 self.emitter.resolve_bss_label(label)
142156
161175 def compile_instr(self, instr):
162176 if isinstance(instr, SingleOp):
163177 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)
164182 elif isinstance(instr, If):
165183 return self.compile_if(instr)
166184 elif isinstance(instr, Repeat):
171189 return self.compile_with_interrupts_off(instr)
172190 elif isinstance(instr, Save):
173191 return self.compile_save(instr)
192 elif isinstance(instr, PointInto):
193 return self.compile_point_into(instr)
174194 else:
175195 raise NotImplementedError
176196
189209 elif isinstance(src, ConstantRef):
190210 self.emitter.emit(LDA(Immediate(Byte(src.value))))
191211 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))))
193213 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):
196216 self.emitter.emit(LDA(IndirectY(self.get_label(src.ref.name))))
197217 else:
198218 self.emitter.emit(LDA(self.absolute_or_zero_page(self.get_label(src.name))))
202222 elif isinstance(src, ConstantRef):
203223 self.emitter.emit(LDX(Immediate(Byte(src.value))))
204224 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))))
206226 else:
207227 self.emitter.emit(LDX(self.absolute_or_zero_page(self.get_label(src.name))))
208228 elif dest == REG_Y:
211231 elif isinstance(src, ConstantRef):
212232 self.emitter.emit(LDY(Immediate(Byte(src.value))))
213233 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))))
215235 else:
216236 self.emitter.emit(LDY(self.absolute_or_zero_page(self.get_label(src.name))))
217237 else:
233253 REG_X: AbsoluteX,
234254 REG_Y: AbsoluteY,
235255 }[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):
238258 operand = IndirectY(self.get_label(dest.ref.name))
239259 else:
240260 operand = self.absolute_or_zero_page(self.get_label(dest.name))
249269 if isinstance(src, ConstantRef):
250270 self.emitter.emit(ADC(Immediate(Byte(src.value))))
251271 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))))
253274 else:
254275 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:
256277 if isinstance(src, ConstantRef):
257278 dest_label = self.get_label(dest.name)
258279 self.emitter.emit(LDA(Absolute(dest_label)))
266287 self.emitter.emit(STA(Absolute(dest_label)))
267288 else:
268289 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:
270291 if isinstance(src, ConstantRef):
271292 dest_label = self.get_label(dest.name)
272293 self.emitter.emit(LDA(Absolute(dest_label)))
286307 self.emitter.emit(STA(Absolute(Offset(dest_label, 1))))
287308 else:
288309 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):
290311 if isinstance(src, ConstantRef):
291312 dest_label = self.get_label(dest.name)
292313 self.emitter.emit(LDA(ZeroPage(dest_label)))
315336 if isinstance(src, ConstantRef):
316337 self.emitter.emit(SBC(Immediate(Byte(src.value))))
317338 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))))
319341 else:
320342 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:
322344 if isinstance(src, ConstantRef):
323345 dest_label = self.get_label(dest.name)
324346 self.emitter.emit(LDA(Absolute(dest_label)))
332354 self.emitter.emit(STA(Absolute(dest_label)))
333355 else:
334356 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:
336358 if isinstance(src, ConstantRef):
337359 dest_label = self.get_label(dest.name)
338360 self.emitter.emit(LDA(Absolute(dest_label)))
366388 if isinstance(src, ConstantRef):
367389 self.emitter.emit(cls(Immediate(Byte(src.value))))
368390 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))))
370393 else:
371394 self.emitter.emit(cls(self.absolute_or_zero_page(self.get_label(src.name))))
372395 else:
383406 if dest == REG_A:
384407 self.emitter.emit(cls())
385408 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))))
387411 else:
388412 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
414413 elif opcode == 'copy':
415414 self.compile_copy(instr, instr.src, instr.dest)
416415 elif opcode == 'trash':
420419 else:
421420 raise NotImplementedError(opcode)
422421
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
423451 def compile_cmp(self, instr, src, dest):
424452 """`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:
426454 src_label = self.get_label(src.name)
427455 dest_label = self.get_label(dest.name)
428456 self.emitter.emit(LDA(Absolute(dest_label)))
433461 self.emitter.emit(CMP(Absolute(Offset(src_label, 1))))
434462 self.emitter.resolve_label(end_label)
435463 return
436 if isinstance(src, ConstantRef) and src.type == TYPE_WORD:
464 if isinstance(src, ConstantRef) and self.get_type(src) == TYPE_WORD:
437465 dest_label = self.get_label(dest.name)
438466 self.emitter.emit(LDA(Absolute(dest_label)))
439467 self.emitter.emit(CMP(Immediate(Byte(src.low_byte()))))
454482 self.emitter.emit(cls(Immediate(Byte(src.value))))
455483 elif isinstance(src, IndexedRef):
456484 # 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))))
458487 else:
459488 self.emitter.emit(cls(Absolute(self.get_label(src.name))))
460489
465494 elif dest == REG_Y:
466495 self.emitter.emit(INY())
467496 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))))
469499 else:
470500 self.emitter.emit(INC(Absolute(self.get_label(dest.name))))
471501
476506 elif dest == REG_Y:
477507 self.emitter.emit(DEY())
478508 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))))
480511 else:
481512 self.emitter.emit(DEC(Absolute(self.get_label(dest.name))))
482513
483514 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):
485527 ### copy 123, [ptr] + y
486528 dest_label = self.get_label(dest.ref.name)
487529 self.emitter.emit(LDA(Immediate(Byte(src.value))))
488530 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):
490532 ### copy b, [ptr] + y
491533 src_label = self.get_label(src.name)
492534 dest_label = self.get_label(dest.ref.name)
493535 self.emitter.emit(LDA(Absolute(src_label)))
494536 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):
496538 ### copy [ptr] + y, b
497539 src_label = self.get_label(src.ref.name)
498540 dest_label = self.get_label(dest.name)
499541 self.emitter.emit(LDA(IndirectY(src_label)))
500542 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):
502544 ### copy [ptra] + y, [ptrb] + y
503545 src_label = self.get_label(src.ref.name)
504546 dest_label = self.get_label(dest.ref.name)
505547 self.emitter.emit(LDA(IndirectY(src_label)))
506548 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):
516550 ### copy w, wtab + y
517551 src_label = self.get_label(src.name)
518552 dest_label = self.get_label(dest.ref.name)
553 mode = self.addressing_mode_for_index(dest.index)
519554 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))))
521556 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):
524559 ### copy vec, vtab + y
525560 # FIXME this is the exact same as above - can this be simplified?
526561 src_label = self.get_label(src.name)
527562 dest_label = self.get_label(dest.ref.name)
563 mode = self.addressing_mode_for_index(dest.index)
528564 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))))
530566 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):
533569 ### copy routine, vtab + y
534570 src_label = self.get_label(src.name)
535571 dest_label = self.get_label(dest.ref.name)
572 mode = self.addressing_mode_for_index(dest.index)
536573 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))))
538575 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):
541578 ### copy 9999, wtab + y
542579 dest_label = self.get_label(dest.ref.name)
580 mode = self.addressing_mode_for_index(dest.index)
543581 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))))
545583 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:
548586 ### copy wtab + y, w
549587 src_label = self.get_label(src.ref.name)
550588 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))))
552591 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))))
554593 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):
556595 ### copy vtab + y, vec
557596 # FIXME this is the exact same as above - can this be simplified?
558597 src_label = self.get_label(src.ref.name)
559598 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))))
561601 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))))
563603 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):
565605 ### copy b1, b2
566606 src_label = self.get_label(src.name)
567607 dest_label = self.get_label(dest.name)
568608 self.emitter.emit(LDA(Absolute(src_label)))
569609 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):
571611 ### copy 9999, w
572612 dest_label = self.get_label(dest.name)
573613 self.emitter.emit(LDA(Immediate(Byte(src.low_byte()))))
574614 self.emitter.emit(STA(Absolute(dest_label)))
575615 self.emitter.emit(LDA(Immediate(Byte(src.high_byte()))))
576616 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):
578618 ### copy w1, w2
579619 src_label = self.get_label(src.name)
580620 dest_label = self.get_label(dest.name)
582622 self.emitter.emit(STA(Absolute(dest_label)))
583623 self.emitter.emit(LDA(Absolute(Offset(src_label, 1))))
584624 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):
586626 ### copy v1, v2
587627 src_label = self.get_label(src.name)
588628 dest_label = self.get_label(dest.name)
590630 self.emitter.emit(STA(Absolute(dest_label)))
591631 self.emitter.emit(LDA(Absolute(Offset(src_label, 1))))
592632 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):
594634 ### copy routine, vec
595635 src_label = self.get_label(src.name)
596636 dest_label = self.get_label(dest.name)
599639 self.emitter.emit(LDA(Immediate(LowAddressByte(src_label))))
600640 self.emitter.emit(STA(Absolute(Offset(dest_label, 1))))
601641 else:
602 raise NotImplementedError(src.type)
642 raise NotImplementedError(src_type)
603643
604644 def compile_if(self, instr):
605645 cls = {
699739 src_label = self.get_label(location.name)
700740 self.emitter.emit(PLA())
701741 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)
66
77 class FallthruAnalyzer(object):
88
9 def __init__(self, debug=False):
9 def __init__(self, symtab, debug=False):
10 self.symtab = symtab
1011 self.debug = debug
1112
1213 def analyze_program(self, program):
1516 self.fallthru_map = {}
1617 for routine in program.routines:
1718 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):
1920 self.fallthru_map[routine.name] = encountered_gotos[0].name
2021 else:
2122 self.fallthru_map[routine.name] = None
00 """Data/storage model for SixtyPical."""
11
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
193
204
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()
2410
2511
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()
2617
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'])):
2827 """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)
3429
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)
5032
5133 @classmethod
5234 def executable_types_compatible(cls_, src, dest):
6648 return False
6749
6850
69 class VectorType(Type):
51 class VectorType(namedtuple('VectorType', ['typename', 'of_type'])):
7052 """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)
7454
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)
8557
8658
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'])):
9260
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)
9767
9868 @classmethod
9969 def is_a_table_type(cls_, x, of_type):
10070 return isinstance(x, TableType) and x.of_type == of_type
10171
10272
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')
10778
10879
109 class PointerType(Type):
110 def __init__(self):
111 self.name = 'pointer'
80 # --------------------------------------------------------
11281
11382
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)
15786
15887 @classmethod
15988 def format_set(cls, location_refs):
16089 return '{%s}' % ', '.join([str(loc) for loc in sorted(location_refs, key=lambda x: x.name)])
16190
16291
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)
17595
17696 @property
17797 def name(self):
17898 return '[{}]+y'.format(self.ref.name)
17999
180 def is_constant(self):
181 return False
182100
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)
197104
198105 @property
199106 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)
204108
205109
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)
277113
278114 def high_byte(self):
279115 return (self.value >> 8) & 255
295131 value -= 256
296132 return ConstantRef(self.type, value)
297133
134 @property
135 def name(self):
136 return 'constant({})'.format(self.value)
298137
299 REG_A = LocationRef(TYPE_BYTE, 'a')
300 REG_X = LocationRef(TYPE_BYTE, 'x')
301 REG_Y = LocationRef(TYPE_BYTE, 'y')
302138
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')
00 # encoding: UTF-8
11
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 )
35 from sixtypical.model import (
46 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,
79 )
810 from sixtypical.scanner import Scanner
911
1012
1113 class SymEntry(object):
12 def __init__(self, ast_node, model):
14 def __init__(self, ast_node, type_):
1315 self.ast_node = ast_node
14 self.model = model
16 self.type_ = type_
1517
1618 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_)
1820
1921
2022 class ForwardReference(object):
2527 return "%s(%r)" % (self.__class__.__name__, self.name)
2628
2729
28 class ParsingContext(object):
30 class SymbolTable(object):
2931 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)
3941
4042 def __str__(self):
4143 return "Symbols: {}\nStatics: {}\nTypedefs: {}\nConsts: {}".format(self.symbols, self.statics, self.typedefs, self.consts)
4244
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):
4655 if name in self.symbols:
47 return self.symbols[name].model
56 return LocationRef(name)
4857 return None
4958
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
5065
5166 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
5469 self.scanner = Scanner(text, filename)
70 self.current_routine_name = None
5571
5672 def syntax_error(self, msg):
5773 self.scanner.syntax_error(msg)
5874
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)
6181 if model is None:
6282 self.syntax_error('Undefined symbol "{}"'.format(name))
6383 return model
6484
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):
6787 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_)
7594
7695 # ---- symbol resolution
7796
7897 def resolve_symbols(self, program):
7998 # This could stand to be better unified.
8099
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_):
86104 if isinstance(type_, TableType):
87 backpatch_constraint_labels(type_.of_type)
105 return TableType(backpatched_type(type_.of_type), type_.size)
88106 elif isinstance(type_, VectorType):
89 backpatch_constraint_labels(type_.of_type)
107 return VectorType(backpatched_type(type_.of_type))
90108 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_)
99119
100120 def resolve_fwd_reference(obj, field):
101121 field_value = getattr(obj, field, None)
107127
108128 for node in program.all_children():
109129 if isinstance(node, SingleOp):
110 resolve_fwd_reference(node, 'location')
111130 resolve_fwd_reference(node, 'src')
112131 resolve_fwd_reference(node, 'dest')
132 if isinstance(node, (Call, GoTo)):
133 resolve_fwd_reference(node, 'location')
113134
114135 # --- grammar productions
115136
121142 self.typedef()
122143 if self.scanner.on('const'):
123144 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())
126147 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_)
129150 defns.append(defn)
130151 while self.scanner.consume('define'):
131152 name = self.scanner.token
132153 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_)
135157 routines.append(routine)
158 self.current_routine_name = None
136159 self.scanner.check_type('EOF')
137160
138161 program = Program(self.scanner.line_number, defns=defns, routines=routines)
143166 self.scanner.expect('typedef')
144167 type_ = self.defn_type()
145168 name = self.defn_name()
146 if name in self.context.typedefs:
169 if name in self.symtab.typedefs:
147170 self.syntax_error('Type "%s" already declared' % name)
148 self.context.typedefs[name] = type_
171 self.symtab.typedefs[name] = type_
149172 return type_
150173
151174 def defn_const(self):
152175 self.scanner.expect('const')
153176 name = self.defn_name()
154 if name in self.context.consts:
177 if name in self.symtab.consts:
155178 self.syntax_error('Const "%s" already declared' % name)
156179 loc = self.const()
157 self.context.consts[name] = loc
180 self.symtab.consts[name] = loc
158181 return loc
159182
160183 def defn(self):
184207 if initial is not None and addr is not None:
185208 self.syntax_error("Definition cannot have both initial value and explicit address")
186209
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)
190211
191212 def const(self):
192213 if self.scanner.token in ('on', 'off'):
203224 loc = ConstantRef(TYPE_WORD, int(self.scanner.token))
204225 self.scanner.scan()
205226 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]
208229 self.scanner.scan()
209230 return loc
210231 else:
221242
222243 if self.scanner.consume('table'):
223244 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")
226247 type_ = TableType(type_, size)
227248
228249 return type_
246267 type_ = VectorType(type_)
247268 elif self.scanner.consume('routine'):
248269 (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))
253271 elif self.scanner.consume('pointer'):
254272 type_ = PointerType()
255273 else:
256274 type_name = self.scanner.token
257275 self.scanner.scan()
258 if type_name not in self.context.typedefs:
276 if type_name not in self.symtab.typedefs:
259277 self.syntax_error("Undefined type '%s'" % type_name)
260 type_ = self.context.typedefs[type_name]
278 type_ = self.symtab.typedefs[type_name]
261279
262280 return type_
263281
286304 def routine(self, name):
287305 type_ = self.defn_type()
288306 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_)))
290308 statics = []
291309 if self.scanner.consume('@'):
292310 self.scanner.check_type('integer literal')
295313 self.scanner.scan()
296314 else:
297315 statics = self.statics()
298
299 self.clear_statics()
300 for defn in statics:
301 self.declare(defn.name, SymEntry(defn, defn.location), static=True)
302316 block = self.block()
303 self.clear_statics()
304
305317 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)
312319
313320 def labels(self):
314321 accum = []
332339 return accum
333340
334341 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'):
336343 return self.const()
337344 else:
338345 name = self.scanner.token
339346 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)
345348
346349 def indlocexpr(self):
347350 if self.scanner.consume('['):
350353 self.scanner.expect('+')
351354 self.scanner.expect('y')
352355 return IndirectRef(loc)
353 elif self.scanner.consume('^'):
354 loc = self.locexpr()
355 return AddressRef(loc)
356356 else:
357357 return self.indexed_locexpr()
358358
360360 loc = self.locexpr()
361361 if not isinstance(loc, str):
362362 index = None
363 offset = ConstantRef(TYPE_BYTE, 0)
363364 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('+')
364368 index = self.locexpr()
365 loc = IndexedRef(loc, index)
369 loc = IndexedRef(loc, offset, index)
366370 return loc
367371
368372 def statics(self):
369373 defns = []
370374 while self.scanner.consume('static'):
371 defn = self.defn()
375 type_, defn = self.defn()
372376 if defn.initial is None:
373377 self.syntax_error("Static definition {} must have initial value".format(defn))
378 self.declare_static(self.current_routine_name, defn.name, defn, type_)
374379 defns.append(defn)
375380 return defns
376381
379384 self.scanner.expect('{')
380385 while not self.scanner.on('}'):
381386 instrs.append(self.instr())
382 if isinstance(instrs[-1], SingleOp) and instrs[-1].opcode == 'goto':
387 if isinstance(instrs[-1], GoTo):
383388 break
384389 self.scanner.expect('}')
385390 return Block(self.scanner.line_number, instrs=instrs)
449454 opcode = self.scanner.token
450455 self.scanner.scan()
451456 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"):
455458 name = self.scanner.token
456459 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))
458466 return instr
459467 elif self.scanner.token in ("copy",):
460468 opcode = self.scanner.token
473481 locations = self.locexprs()
474482 block = self.block()
475483 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)
476490 elif self.scanner.consume("trash"):
477491 dest = self.locexpr()
478492 return SingleOp(self.scanner.line_number, opcode='trash', src=None, dest=dest)
501501 | ld a, many + x
502502 | }
503503 ? 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
504550
505551 There are other operations you can do on tables. (1/3)
506552
613659 | }
614660 = ok
615661
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
616679 #### tables: range checking ####
617680
618681 It is a static analysis error if it cannot be proven that a read or write
619682 to a table falls within the defined size of that table.
620683
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.
623691
624692 A SixtyPical implementation must be able to prove that the index is inside
625693 the range of the table in various ways. The simplest is to show that a
663731 | }
664732 ? RangeExceededError
665733
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
666761 This applies to `copy` as well.
667762
668763 | word one: 77
702797 | {
703798 | ld x, 32
704799 | 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
705828 | }
706829 ? RangeExceededError
707830
725848 | }
726849 = ok
727850
728 Test for "clipping", but not enough.
851 Tests for "clipping", but not enough.
729852
730853 | word one: 77
731854 | word table[32] many
739862 | ld x, a
740863 | copy one, many + x
741864 | 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
742880 | }
743881 ? RangeExceededError
744882
27782916 | }
27792917 ? TypeMismatchError
27802918
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
28012941 | pointer ptr
28022942 |
28032943 | define main routine
2804 | inputs buf
2805 | outputs y, buf
2944 | inputs tab, ptr
2945 | outputs y, tab
28062946 | trashes a, z, n, ptr
28072947 | {
28082948 | ld y, 0
2809 | copy ^buf, ptr
28102949 | copy 123, [ptr] + y
28112950 | }
2812 = ok
2813
2814 It does use `y`.
2815
2816 | buffer[2048] buf
2951 ? ForbiddenWriteError
2952
2953 | byte table[256] tab
28172954 | pointer ptr
28182955 |
28192956 | define main routine
2820 | inputs buf
2821 | outputs buf
2957 | inputs tab
2958 | outputs y, tab
28222959 | trashes a, z, n, ptr
28232960 | {
2824 | copy ^buf, ptr
2961 | ld y, 0
2962 | point ptr into tab {
2963 | copy 123, [ptr] + y
2964 | }
28252965 | copy 123, [ptr] + y
28262966 | }
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 | }
28273000 ? UnmeaningfulReadError
28283001
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
28323005 | pointer ptr
28333006 | byte foo
28343007 |
28353008 | define main routine
2836 | inputs foo, buf
2837 | outputs y, buf
3009 | inputs foo, tab
3010 | outputs y, tab
28383011 | trashes a, z, n, ptr
28393012 | {
28403013 | 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
28493023 | pointer ptr
28503024 | byte foo
28513025 |
28523026 | define main routine
2853 | inputs buf
3027 | inputs tab
28543028 | outputs foo
28553029 | trashes a, y, z, n, ptr
28563030 | {
28573031 | ld y, 0
2858 | copy ^buf, ptr
3032 | point ptr into tab {
28593033 | 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
28663041 | pointer ptra
28673042 | pointer ptrb
28683043 |
28693044 | define main routine
2870 | inputs buf
2871 | outputs buf
3045 | inputs tab
3046 | outputs tab
28723047 | trashes a, y, z, n, ptra, ptrb
28733048 | {
28743049 | 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`,
28823059 not `copy`.
28833060
2884 | buffer[2048] buf
3061 | byte table[256] tab
28853062 | pointer ptr
28863063 | byte foo
28873064 |
28883065 | define main routine
2889 | inputs buf
3066 | inputs tab
28903067 | outputs a
28913068 | trashes y, z, n, ptr
28923069 | {
28933070 | 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`,
29003078 not `copy`.
29013079
2902 | buffer[2048] buf
3080 | byte table[256] tab
29033081 | pointer ptr
29043082 | byte foo
29053083 |
29063084 | define main routine
2907 | inputs buf
2908 | outputs buf
3085 | inputs tab
3086 | outputs tab
29093087 | trashes a, y, z, n, ptr
29103088 | {
29113089 | 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 | }
29153251 | }
29163252 = ok
29173253
35843920 | }
35853921 = ok
35863922
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
35884000
35894001 Can't `goto` a routine that outputs or trashes more than the current routine.
35904002
384384 = $081B DEC $081F,X
385385 = $081E RTS
386386
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
387449 Compiling 16-bit `cmp`.
388450
389451 | word za @ 60001
874936 = $0817 RTS
875937 = $0818 INX
876938 = $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
877975
878976 Copy word to word table and back, with both `x` and `y` as indexes.
879977
9171015 = $0848 STA $084D
9181016 = $084B RTS
9191017
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
9201060 Indirect call.
9211061
9221062 | vector routine
10161156 = $082F LDA $0848,X
10171157 = $0832 STA $0846
10181158 = $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
10191210 = $0838 STA $0847
10201211 = $083B JSR $0842
10211212 = $083E RTS
12061397 = $081D STA $0822
12071398 = $0820 RTS
12081399
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
12141405 | pointer ptr @ 254
12151406 |
12161407 | define main routine
1217 | inputs buf
1218 | outputs buf, y
1408 | inputs tab
1409 | outputs tab, y
12191410 | trashes a, z, n, ptr
12201411 | {
12211412 | ld y, 0
1222 | copy ^buf, ptr
1413 | point ptr into tab {
1414 | }
12231415 | }
12241416 = $080D LDY #$00
12251417 = $080F LDA #$18
12301422
12311423 Write literal through a pointer.
12321424
1233 | buffer[2048] buf
1425 | byte table[256] tab
12341426 | pointer ptr @ 254
12351427 |
12361428 | define main routine
1237 | inputs buf
1238 | outputs buf, y
1429 | inputs tab
1430 | outputs tab, y
12391431 | trashes a, z, n, ptr
12401432 | {
12411433 | ld y, 0
1242 | copy ^buf, ptr
1243 | copy 123, [ptr] + y
1434 | point ptr into tab {
1435 | copy 123, [ptr] + y
1436 | }
12441437 | }
12451438 = $080D LDY #$00
12461439 = $080F LDA #$1C
12531446
12541447 Write stored value through a pointer.
12551448
1256 | buffer[2048] buf
1449 | byte table[256] tab
12571450 | pointer ptr @ 254
12581451 | byte foo
12591452 |
12601453 | define main routine
1261 | inputs foo, buf
1262 | outputs y, buf
1454 | inputs foo, tab
1455 | outputs y, tab
12631456 | trashes a, z, n, ptr
12641457 | {
12651458 | ld y, 0
1266 | copy ^buf, ptr
1267 | copy foo, [ptr] + y
1459 | point ptr into tab {
1460 | copy foo, [ptr] + y
1461 | }
12681462 | }
12691463 = $080D LDY #$00
12701464 = $080F LDA #$1D
12711465 = $0811 STA $FE
12721466 = $0813 LDA #$08
12731467 = $0815 STA $FF
1274 = $0817 LDA $101D
1468 = $0817 LDA $091D
12751469 = $081A STA ($FE),Y
12761470 = $081C RTS
12771471
12781472 Read through a pointer, into a byte storage location, or the `a` register.
12791473
1280 | buffer[2048] buf
1474 | byte table[256] tab
12811475 | pointer ptr @ 254
12821476 | byte foo
12831477 |
12841478 | define main routine
1285 | inputs buf
1479 | inputs tab
12861480 | outputs y, foo
12871481 | trashes a, z, n, ptr
12881482 | {
12891483 | 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 | }
12931488 | }
12941489 = $080D LDY #$00
12951490 = $080F LDA #$1F
12971492 = $0813 LDA #$08
12981493 = $0815 STA $FF
12991494 = $0817 LDA ($FE),Y
1300 = $0819 STA $101F
1495 = $0819 STA $091F
13011496 = $081C LDA ($FE),Y
13021497 = $081E RTS
13031498
13041499 Read and write through two pointers.
13051500
1306 | buffer[2048] buf
1501 | byte table[256] tab
13071502 | pointer ptra @ 252
13081503 | pointer ptrb @ 254
13091504 |
13101505 | define main routine
1311 | inputs buf
1312 | outputs buf
1506 | inputs tab
1507 | outputs tab
13131508 | trashes a, y, z, n, ptra, ptrb
13141509 | {
13151510 | 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 | }
13191516 | }
13201517 = $080D LDY #$00
13211518 = $080F LDA #$24
13321529
13331530 Write the `a` register through a pointer.
13341531
1335 | buffer[2048] buf
1532 | byte table[256] tab
13361533 | pointer ptr @ 254
13371534 | byte foo
13381535 |
13391536 | define main routine
1340 | inputs buf
1341 | outputs buf
1537 | inputs tab
1538 | outputs tab
13421539 | trashes a, y, z, n, ptr
13431540 | {
13441541 | 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 | }
13481546 | }
13491547 = $080D LDY #$00
13501548 = $080F LDA #$1C
13581556 Add a word memory location, and a literal word, to a pointer, and then read through it.
13591557 Note that this is *not* range-checked. (Yet.)
13601558
1361 | buffer[2048] buf
1559 | byte table[256] tab
13621560 | pointer ptr @ 254
13631561 | byte foo
13641562 | word delta
13651563 |
13661564 | define main routine
1367 | inputs buf
1565 | inputs tab
13681566 | outputs y, foo, delta
13691567 | trashes a, c, v, z, n, ptr
13701568 | {
13711569 | copy 619, delta
13721570 | ld y, 0
13731571 | 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 | }
13781577 | }
13791578 = $080D LDA #$6B
1380 = $080F STA $1043
1579 = $080F STA $0943
13811580 = $0812 LDA #$02
1382 = $0814 STA $1044
1581 = $0814 STA $0944
13831582 = $0817 LDY #$00
13841583 = $0819 CLC
13851584 = $081A LDA #$42
13871586 = $081E LDA #$08
13881587 = $0820 STA $FF
13891588 = $0822 LDA $FE
1390 = $0824 ADC $1043
1589 = $0824 ADC $0943
13911590 = $0827 STA $FE
13921591 = $0829 LDA $FF
1393 = $082B ADC $1044
1592 = $082B ADC $0944
13941593 = $082E STA $FF
13951594 = $0830 LDA $FE
13961595 = $0832 ADC #$01
13991598 = $0838 ADC #$00
14001599 = $083A STA $FF
14011600 = $083C LDA ($FE),Y
1402 = $083E STA $1042
1601 = $083E STA $0942
14031602 = $0841 RTS
14041603
14051604 ### Trash
162162
163163 Other blocks.
164164
165 | byte table[256] tab
166 | pointer ptr
167 |
165168 | define main routine trashes a, x, c, z, v {
166169 | with interrupts off {
167170 | save a, x, c {
171174 | save a, x, c {
172175 | ld a, 0
173176 | }
177 | point ptr into tab {
178 | ld a, [ptr] + y
179 | }
174180 | }
175181 = ok
176182
179185 | byte byt
180186 | word wor
181187 | vector routine trashes a vec
182 | buffer[2048] buf
188 | byte table[2048] buf
183189 | pointer ptr
184190 |
185191 | define main routine {
191197 | byte table[256] many
192198 | word table[256] wmany
193199 | vector (routine trashes a) table[256] vmany
200 | byte bval
201 | word wval
194202 |
195203 | define main routine {
196204 | ld x, 0
206214 | shr many + x
207215 | inc many + x
208216 | 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
209249 | }
210250 = ok
211251
212252 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.)
214259
215260 | word table[512] many
216261 |
221266 | {
222267 | ld x, 0
223268 | 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
224293 | }
225294 ? SyntaxError
226295
281350 |
282351 | define main routine {
283352 | 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
284366 | }
285367 = ok
286368
589671 | }
590672 ? Expected '}', but found 'ld'
591673
592 Buffers and pointers.
593
594 | buffer[2048] buf
674 Tables and pointers.
675
676 | byte table[2048] buf
595677 | pointer ptr
596678 | pointer ptrb
597679 | byte foo
598680 |
599681 | 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 | }
604687 | }
605688 = ok
606689