git @ Cat's Eye Technologies SixtyPical / 1c7efb0
Merge pull request #20 from catseye/develop-0.19 Develop 0.19 Chris Pressey authored 2 years ago GitHub committed 2 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