Merge pull request #2 from catseye/develop-0.8
Develop 0.8
Chris Pressey authored 4 years ago
GitHub committed 4 years ago
0 | 0 | History of SixtyPical |
1 | 1 | ===================== |
2 | ||
3 | 0.8 | |
4 | --- | |
5 | ||
6 | * Explicit word literals prefixed with `word` token. | |
7 | * Can `copy` literals into user-defined destinations. | |
8 | * Fixed bug where loop variable wasn't being checked at end of `repeat` loop. | |
9 | * `buffer` and `pointer` types. | |
10 | * `copy ^` syntax to load the addr of a buffer into a pointer. | |
11 | * `copy []+y` syntax to read and write values to and from memory through a pointer. | |
2 | 12 | |
3 | 13 | 0.7 |
4 | 14 | --- |
23 | 23 | |
24 | 24 | It is a **work in progress**, currently at the **proof-of-concept** stage. |
25 | 25 | |
26 | The current released version of SixtyPical is 0.7. | |
26 | The current development version of SixtyPical is 0.8. | |
27 | 27 | |
28 | 28 | Documentation |
29 | 29 | ------------- |
30 | 30 | |
31 | * Design Goals — coming soon. | |
31 | * [Design Goals](doc/Design%20Goals.md) | |
32 | 32 | * [SixtyPical specification](doc/SixtyPical.md) |
33 | * [SixtyPical history](HISTORY.md) | |
33 | * [SixtyPical revision history](HISTORY.md) | |
34 | 34 | * [Literate test suite for SixtyPical syntax](tests/SixtyPical%20Syntax.md) |
35 | 35 | * [Literate test suite for SixtyPical execution](tests/SixtyPical%20Execution.md) |
36 | 36 | * [Literate test suite for SixtyPical analysis](tests/SixtyPical%20Analysis.md) |
40 | 40 | TODO |
41 | 41 | ---- |
42 | 42 | |
43 | * `word table` type. | |
44 | * `vector table` type. | |
45 | * zero-page memory locations. | |
46 | * indirect addressing. | |
47 | * `low` and `high` address operators (turn `word` type into `byte`.) Possibly. | |
48 | * save registers on stack or in memory (this preserves them = not trashed) | |
43 | ### Add 16 bit values. | |
49 | 44 | |
50 | At some point... | |
45 | I guess this means making `add` a bit more like `copy`. | |
51 | 46 | |
47 | And then: add to pointer. (Not necessarily range-checked yet though.) | |
48 | ||
49 | And then write a little demo "game" where you can move a block around the screen with | |
50 | the joystick. | |
51 | ||
52 | ### `word table` and `vector table` types | |
53 | ||
54 | ### `low` and `high` address operators | |
55 | ||
56 | To turn `word` type into `byte`. | |
57 | ||
58 | ### save registers on stack | |
59 | ||
60 | This preserves them, so semantically, they can be used even though they | |
61 | are trashed inside the block. | |
62 | ||
63 | ### And at some point... | |
64 | ||
65 | * `copy x, [ptr] + y` | |
66 | * Maybe even `copy [ptra] + y, [ptrb] + y`, which can be compiled to indirect LDA then indirect STA! | |
67 | * Check that the buffer being read or written to through pointer, appears in approporiate inputs or outputs set. | |
52 | 68 | * initialized `byte table` memory locations |
53 | 69 | * always analyze before executing or compiling, unless told not to |
54 | 70 | * `trash` instruction. |
0 | Design Goals for SixtyPical | |
1 | =========================== | |
2 | ||
3 | (draft) | |
4 | ||
5 | The intent of SixtyPical is to have a very low-level language that | |
6 | benefits from abstract interpretation. | |
7 | ||
8 | "Very low-level" means, on a comparable level of abstraction as | |
9 | assembly language. | |
10 | ||
11 | In the original vision for SixtyPical, SixtyPical instructions mapped | |
12 | nearly 1:1 to 6502 instructions. However, many times when programming | |
13 | in 6502 you're using idioms (e.g. adding a 16-bit constant to a 16-bit | |
14 | value stored in 2 bytes) and it's just massively easier to analyze such | |
15 | actions when they are represented by a single instruction. | |
16 | ||
17 | So SixtyPical instructions are similar to, inspired by, and have | |
18 | analogous restrictions as 6502 instructions, but in many ways, they | |
19 | are more abstract. For example, `copy`. | |
20 | ||
21 | The intent is that programming in SixtyPical is a lot like programming | |
22 | in 6052 assembler, but it's harder to make a stupid error that you have | |
23 | to spend a lot of time debugging. | |
24 | ||
25 | The intent is not to make it absolutely impossible to make such errors, | |
26 | just harder. | |
27 | ||
28 | ### Some Background ### | |
29 | ||
30 | The ideas in SixtyPical came from a couple of places. | |
31 | ||
32 | One major impetus was when I was working on [Shelta][], trying to cram | |
33 | all that code for that compiler into 512 bytes. This involved looking | |
34 | at the x86 registers and thinking hard about which ones were preserved | |
35 | when (and which ones weren't) and making the best use of that. And | |
36 | while doing that, one thing that came to mind was: I Bet The Assembler | |
37 | Could Track This. | |
38 | ||
39 | Another influence was around 2007 when "Typed Assembly Language" (and | |
40 | "Proof Carrying Code") were all the rage. I haven't heard about them | |
41 | in a while, so I guess they turned out to be research fads? But for a | |
42 | while there, it was all Necula, Necula, Necula. Anyway, I remember at | |
43 | the time looking into TAL and expecting to find something that matched | |
44 | the impression I had pre-formulated about what a "Typed Assembly" | |
45 | might be like. And finding that it didn't match my vision very well. | |
46 | ||
47 | I don't actually remember what TAL seemed like to me at the time, but | |
48 | what I had in mind was more like SixtyPical. | |
49 | ||
50 | (I'll also write something about abstract interpretation here at some | |
51 | point, hopefully.) |
0 | 0 | SixtyPical |
1 | 1 | ========== |
2 | 2 | |
3 | This document describes the SixtyPical programming language version 0.7, | |
3 | This document describes the SixtyPical programming language version 0.8, | |
4 | 4 | both its execution aspect and its static analysis aspect (even though |
5 | 5 | these are, technically speaking, separate concepts.) |
6 | 6 | |
13 | 13 | Types |
14 | 14 | ----- |
15 | 15 | |
16 | There are five TYPES in SixtyPical: | |
16 | There are six *primitive types* in SixtyPical: | |
17 | 17 | |
18 | 18 | * bit (2 possible values) |
19 | 19 | * byte (256 possible values) |
20 | * byte table (256 entries, each holding a byte) | |
20 | * word (65536 possible values) | |
21 | 21 | * routine (code stored somewhere in memory, read-only) |
22 | 22 | * vector (address of a routine) |
23 | * pointer (address of a byte in a buffer) | |
24 | ||
25 | There are also two *type constructors*: | |
26 | ||
27 | * X table (256 entries, each holding a value of type X, where X is `byte`) | |
28 | * buffer[N] (N entries; each entry is a byte; N is a power of 2, ≤ 64K) | |
23 | 29 | |
24 | 30 | Memory locations |
25 | 31 | ---------------- |
26 | 32 | |
27 | A primary concept in SixtyPical is the MEMORY LOCATION. At any given point | |
28 | in time during execution, each memory location is either UNINITIALIZED or | |
29 | INITIALIZED. At any given point in the program text, too, each memory | |
33 | A primary concept in SixtyPical is the *memory location*. At any given point | |
34 | in time during execution, each memory location is either *uninitialized* or | |
35 | *initialized*. At any given point in the program text, too, each memory | |
30 | 36 | location is either uninitialized or initialized. Where-ever it is one or |
31 | 37 | the other during execution, it is the same in the corresponding place in |
32 | 38 | the program text; thus, it is a static property. |
63 | 69 | off |
64 | 70 | on |
65 | 71 | |
66 | and two-hundred and fifty-six byte constants, | |
72 | two hundred and fifty-six byte constants, | |
67 | 73 | |
68 | 74 | 0 |
69 | 75 | 1 |
70 | 76 | ... |
71 | 77 | 255 |
72 | 78 | |
79 | and sixty-five thousand five hundred and thirty-six word constants, | |
80 | ||
81 | word 0 | |
82 | word 1 | |
83 | ... | |
84 | word 65535 | |
85 | ||
86 | Note that if a word constant is between 256 and 65535, the leading `word` | |
87 | token can be omitted. | |
88 | ||
73 | 89 | ### User-defined ### |
74 | 90 | |
75 | 91 | There may be any number of user-defined memory locations. They are defined |
76 | by giving the type, which must be `byte`, `byte table`, or `vector`, and the | |
92 | by giving the type (which may be any type except `bit` and `routine`) and the | |
77 | 93 | name. |
78 | 94 | |
79 | 95 | byte pos |
87 | 103 | |
88 | 104 | byte pos : 0 |
89 | 105 | |
90 | A user-defined vector memory location is decorated with READS and WRITES lists | |
91 | like a routine (see below), and it may only hold addresses of routines which | |
92 | are compatible. (Meaning, the routine's inputs (resp. outputs, trashes) | |
93 | must be a subset of the vector's inputs (resp. outputs, trashes.)) | |
106 | A user-defined vector memory location is decorated with `inputs`, `outputs` | |
107 | and `trashes` lists like a routine (see below), and it may only hold addresses | |
108 | of routines which are compatible. (Meaning, the routine's inputs (resp. outputs, | |
109 | trashes) must be a subset of the vector's inputs (resp. outputs, trashes.)) | |
94 | 110 | |
95 | 111 | vector actor_logic |
96 | 112 | inputs a, score |
98 | 114 | trashes y |
99 | 115 | @ $c000 |
100 | 116 | |
117 | Note that in the code of a routine, if a memory location is named by a | |
118 | user-defined symbol, it is an address in memory, and can be read and written. | |
119 | But if it is named by a literal integer, either decimal or hexadecimal, it | |
120 | is a constant and can only be read (and when read always yields that constant | |
121 | value. So, for instance, to read the value at `screen` above, in the code, | |
122 | you would need to reference the symbol `screen`; attempting to read 1024 | |
123 | would not work. | |
124 | ||
125 | This is actually useful, at least at this point, as you can rely on the fact | |
126 | that literal integers in the code are always immediate values. (But this | |
127 | may change at some point.) | |
128 | ||
129 | ### Buffers and Pointers ### | |
130 | ||
131 | Roughly speaking, a `buffer` is a table that can be longer than 256 bytes, | |
132 | and a `pointer` is an address within a buffer. | |
133 | ||
134 | A `pointer` is implemented as a zero-page memory location, and accessing the | |
135 | buffer pointed to is implemented with "indirect indexed" addressing, as in | |
136 | ||
137 | LDA ($02), Y | |
138 | STA ($02), Y | |
139 | ||
140 | There are extended modes of `copy` for using these types of memory location. | |
141 | See `copy` below, but here is some illustrative example code: | |
142 | ||
143 | copy ^buf, ptr // this is the only way to initialize a pointer | |
144 | add ptr, 4 // ok, but only if it does not exceed buffer's size | |
145 | ld y, 0 // you must set this to something yourself | |
146 | copy [ptr] + y, byt // read memory through pointer, into byte | |
147 | copy 100, [ptr] + y // write memory through pointer (still trashes a) | |
148 | ||
149 | where `ptr` is a user-defined storage location of `pointer` type, and the | |
150 | `+ y` part is mandatory. | |
151 | ||
101 | 152 | Routines |
102 | 153 | -------- |
103 | 154 | |
104 | Every routine must list all the memory locations it READS from, i.e. its | |
105 | INPUTS, and all the memory locations it WRITES to, whether they are OUTPUTS | |
106 | or merely TRASHED. Every memory location that is not written to by the | |
107 | routine (or any routines that the routine calls) is PRESERVED by the routine. | |
155 | Every routine must list all the memory locations it *reads from*, which we | |
156 | call its `inputs`, and all the memory locations it *writes to*. The latter | |
157 | we divide into two groups: its `outputs` which it intentionally initializes, | |
158 | and its `trashes`, which it does not care about, and leaves uninitialized. | |
159 | For example, if it uses a register to temporarily store an intermediate | |
160 | value used in a multiplication, that register has no meaning outside of | |
161 | the multiplication, and is one of the routine's `trashes`. | |
162 | ||
163 | It is common to say that the `trashes` are the memory locations that are | |
164 | *not preserved* by the routine. | |
108 | 165 | |
109 | 166 | routine foo |
110 | 167 | inputs a, score |
113 | 170 | ... |
114 | 171 | } |
115 | 172 | |
173 | The union of the `outputs` and `trashes` is sometimes collectively called | |
174 | "the WRITES" of the routine, for historical reasons and as shorthand. | |
175 | ||
116 | 176 | Routines may call only routines previously defined in the program source. |
117 | 177 | Thus, directly recursive routines are not allowed. (However, routines may |
118 | 178 | also call routines via vectors, which are dynamically assigned. In this |
121 | 181 | For a SixtyPical program to be run, there must be one routine called `main`. |
122 | 182 | This routine is executed when the program is run. |
123 | 183 | |
124 | The memory locations given given as inputs are considered to be initialized | |
184 | The memory locations given as inputs to a routine are considered to be initialized | |
125 | 185 | at the beginning of the routine. Various instructions cause memory locations |
126 | 186 | to be initialized after they are executed. Calling a routine which trashes |
127 | 187 | some memory locations causes those memory locations to be uninitialized after |
128 | 188 | that routine is called. At the end of a routine, all memory locations listed |
129 | as outputs must be initialised. | |
130 | ||
131 | A routine can also be declared as "external", in which case its body need | |
132 | not be defined but an absolute address must be given for where the routine | |
133 | is located in memory. | |
189 | as outputs must be initialized. | |
190 | ||
191 | A literal word can given instead of the body of the routine. This word is the | |
192 | absolute address of an "external" routine located in memory but not defined by | |
193 | the SixtyPical program. | |
134 | 194 | |
135 | 195 | routine chrout |
136 | 196 | inputs a |
140 | 200 | Instructions |
141 | 201 | ------------ |
142 | 202 | |
203 | Instructions are inspired by, and in many cases closely resemble, the 6502 | |
204 | instruction set. However, in many cases they do not map 1:1 to 6502 instructions. | |
205 | If a SixtyPical instruction cannot be translated validly to one more more 6502 | |
206 | instructions while retaining all the stated constraints, that's a static error | |
207 | in a SixtyPical program, and technically any implementation of SixtyPical, even | |
208 | an interpreter, should flag it up. | |
209 | ||
143 | 210 | ### ld ### |
144 | 211 | |
145 | 212 | ld <dest-memory-location>, <src-memory-location> [+ <index-memory-location>] |
147 | 214 | Reads from src and writes to dest. |
148 | 215 | |
149 | 216 | * It is illegal if dest is not a register. |
150 | * It is illegal if dest does not occur in the WRITES lists of the current | |
151 | routine. | |
217 | * It is illegal if dest does not occur in the WRITES of the current routine. | |
152 | 218 | * It is illegal if src is not of same type as dest (i.e., is not a byte.) |
153 | 219 | * It is illegal if src is uninitialized. |
154 | 220 | |
155 | 221 | After execution, dest is considered initialized. The flags `z` and `n` may be |
156 | changed by this instruction; they must be named in the WRITES lists, and they | |
222 | changed by this instruction; they must be named in the WRITES, and they | |
157 | 223 | are considered initialized after it has executed. |
158 | 224 | |
159 | 225 | If and only if src is a byte table, the index-memory-location must be given. |
168 | 234 | Reads from src and writes to dest. |
169 | 235 | |
170 | 236 | * It is illegal if dest is a register or if dest is read-only. |
171 | * It is illegal if dest does not occur in the WRITES lists of the current | |
172 | routine. | |
237 | * It is illegal if dest does not occur in the WRITES of the current routine. | |
173 | 238 | * It is illegal if src is not of same type as dest. |
174 | 239 | * It is illegal if src is uninitialized. |
175 | 240 | |
178 | 243 | |
179 | 244 | If and only if dest is a byte table, the index-memory-location must be given. |
180 | 245 | |
246 | ### copy ### | |
247 | ||
248 | copy <src-memory-location>, <dest-memory-location> | |
249 | ||
250 | Reads from src and writes to dest. Differs from `st` in that is able to | |
251 | copy more general types of data (for example, vectors,) and it trashes the | |
252 | `z` and `n` flags and the `a` register. | |
253 | ||
254 | * It is illegal if dest is read-only. | |
255 | * It is illegal if dest does not occur in the WRITES of the current routine. | |
256 | * It is illegal if src is not of same type as dest. | |
257 | * It is illegal if src is uninitialized. | |
258 | ||
259 | After execution, dest is considered initialized, and `z` and `n`, and | |
260 | `a` are considered uninitialized. | |
261 | ||
262 | There are two extra modes that this instruction can be used in. The first is | |
263 | to load an address into a pointer: | |
264 | ||
265 | copy ^<src-memory-location>, <dest-memory-location> | |
266 | ||
267 | This copies the address of src into dest. In this case, src must be | |
268 | of type buffer, and dest must be of type pointer. src will not be | |
269 | considered a memory location that is read, since it is only its address | |
270 | that is being retrieved. | |
271 | ||
272 | The second is to read or write indirectly through a pointer. | |
273 | ||
274 | copy [<src-memory-location>] + y, <dest-memory-location> | |
275 | copy <src-memory-location>, [<dest-memory-location>] + y | |
276 | ||
277 | In both of these, the memory location in the `[]+y` syntax must be | |
278 | a pointer. | |
279 | ||
280 | The first copies the contents of memory at the pointer (offset by the `y` | |
281 | register) into a byte memory location. | |
282 | ||
283 | The second copies a literal byte, or a byte memory location, into | |
284 | the contents of memory at the pointer (offset by the `y` register). | |
285 | ||
286 | In addition to the constraints above, `y` must be initialized before | |
287 | this mode is used. | |
288 | ||
181 | 289 | ### add dest, src ### |
182 | 290 | |
183 | 291 | add <dest-memory-location>, <src-memory-location> |
186 | 294 | |
187 | 295 | * It is illegal if src OR dest OR c is uninitialized. |
188 | 296 | * It is illegal if dest is read-only. |
189 | * It is illegal if dest does not occur in the WRITES lists | |
190 | of the current routine. | |
191 | ||
192 | Affects n, z, c, and v flags, requiring that they be in the WRITES lists, | |
297 | * It is illegal if dest does not occur in the WRITES of the current routine. | |
298 | ||
299 | Affects n, z, c, and v flags, requiring that they be in the WRITES, | |
193 | 300 | and initializing them afterwards. |
194 | 301 | |
195 | 302 | dest and src continue to be initialized afterwards. |
202 | 309 | |
203 | 310 | * It is illegal if dest is uninitialized. |
204 | 311 | * It is illegal if dest is read-only. |
205 | * It is illegal if dest does not occur in the WRITES lists | |
206 | of the current routine. | |
207 | ||
208 | Affects n and z flags, requiring that they be in the WRITES lists, | |
312 | * It is illegal if dest does not occur in the WRITES of the current routine. | |
313 | ||
314 | Affects n and z flags, requiring that they be in the WRITES, | |
209 | 315 | and initializing them afterwards. |
210 | 316 | |
211 | 317 | ### sub ### |
216 | 322 | |
217 | 323 | * It is illegal if src OR dest OR c is uninitialized. |
218 | 324 | * It is illegal if dest is read-only. |
219 | * It is illegal if dest does not occur in the WRITES lists | |
220 | of the current routine. | |
221 | ||
222 | Affects n, z, c, and v flags, requiring that they be in the WRITES lists, | |
325 | * It is illegal if dest does not occur in the WRITES of the current routine. | |
326 | ||
327 | Affects n, z, c, and v flags, requiring that they be in the WRITES, | |
223 | 328 | and initializing them afterwards. |
224 | 329 | |
225 | 330 | dest and src continue to be initialized afterwards. |
232 | 337 | |
233 | 338 | * It is illegal if dest is uninitialized. |
234 | 339 | * It is illegal if dest is read-only. |
235 | * It is illegal if dest does not occur in the WRITES lists | |
236 | of the current routine. | |
237 | ||
238 | Affects n and z flags, requiring that they be in the WRITES lists, | |
340 | * It is illegal if dest does not occur in the WRITES of the current routine. | |
341 | ||
342 | Affects n and z flags, requiring that they be in the WRITES, | |
239 | 343 | and initializing them afterwards. |
240 | 344 | |
241 | 345 | ### cmp ### |
247 | 351 | |
248 | 352 | * It is illegal if src OR dest is uninitialized. |
249 | 353 | |
250 | Affects n, z, and c flags, requiring that they be in the WRITES lists, | |
354 | Affects n, z, and c flags, requiring that they be in the WRITES, | |
251 | 355 | and initializing them afterwards. |
252 | 356 | |
253 | 357 | ### and, or, xor ### |
261 | 365 | |
262 | 366 | * It is illegal if src OR dest OR is uninitialized. |
263 | 367 | * It is illegal if dest is read-only. |
264 | * It is illegal if dest does not occur in the WRITES lists | |
265 | of the current routine. | |
266 | ||
267 | Affects n and z flags, requiring that they be in the WRITES lists of the | |
368 | * It is illegal if dest does not occur in the WRITES of the current routine. | |
369 | ||
370 | Affects n and z flags, requiring that they be in the WRITES of the | |
268 | 371 | current routine, and sets them as initialized afterwards. |
269 | 372 | |
270 | 373 | dest and src continue to be initialized afterwards. |
283 | 386 | * It is illegal if dest is a register besides `a`. |
284 | 387 | * It is illegal if dest is read-only. |
285 | 388 | * It is illegal if dest OR c is uninitialized. |
286 | * It is illegal if dest does not occur in the WRITES lists | |
287 | of the current routine. | |
288 | ||
289 | Affects the c flag, requiring that it be in the WRITES lists of the | |
389 | * It is illegal if dest does not occur in the WRITES of the current routine. | |
390 | ||
391 | Affects the c flag, requiring that it be in the WRITES of the | |
290 | 392 | current routine, and it continues to be initialized afterwards. |
291 | 393 | |
292 | 394 | ### call ### |
298 | 400 | which will be called indirectly. Execution will be transferred back to the |
299 | 401 | current routine, when execution of the executable is finished. |
300 | 402 | |
301 | Just before the call, | |
302 | ||
303 | * It is illegal if any of the memory locations in the target executable's | |
304 | READS list is uninitialized. | |
403 | * It is illegal if any of the memory locations listed in the called routine's | |
404 | `inputs` are uninitialized immediately before the call. | |
305 | 405 | |
306 | 406 | Just after the call, |
307 | 407 | |
308 | * All memory locations listed as TRASHED in the called routine's WRITES | |
309 | list are considered uninitialized. | |
310 | * All memory locations listed as TRASHED in the called routine's OUTPUTS | |
311 | list are considered initialized. | |
408 | * All memory locations listed in the called routine's `trashes` are considered | |
409 | to now be uninitialized. | |
410 | * All memory locations listed in the called routine's `outputs` are considered | |
411 | to now be initialized. | |
312 | 412 | |
313 | 413 | ### goto ### |
314 | 414 | |
324 | 424 | |
325 | 425 | Just before the goto, |
326 | 426 | |
327 | * It is illegal if any of the memory locations in the target executable's | |
328 | READS list is uninitialized. | |
427 | * It is illegal if any of the memory locations in the target routine's | |
428 | `inputs` list is uninitialized. | |
329 | 429 | |
330 | 430 | In addition, |
331 | 431 | |
332 | * The target executable's WRITES lists must not include any locations | |
333 | that are not already included in the current routine's WRITES lists. | |
432 | * The target executable's WRITES must not include any locations | |
433 | that are not already included in the current routine's WRITES. | |
334 | 434 | |
335 | 435 | ### if ### |
336 | 436 | |
349 | 449 | * It is illegal if any location initialized at the end of the true-branch |
350 | 450 | is not initialized at the end of the false-branch, and vice versa. |
351 | 451 | |
452 | The sense of the test can be inverted with `not`. | |
453 | ||
352 | 454 | ### repeat ### |
353 | 455 | |
354 | 456 | repeat { |
371 | 473 | } |
372 | 474 | } until z |
373 | 475 | |
374 | "until" is optional, but if omitted, must be replaced with "forever". | |
375 | ||
376 | ### copy ### | |
377 | ||
378 | copy <src-memory-location>, <dest-memory-location> | |
379 | ||
380 | Reads from src and writes to dest. Differs from `st` in that is able to | |
381 | copy more general types of data (for example, vectors,) and it trashes the | |
382 | `z` and `n` flags and the `a` register. | |
383 | ||
384 | * It is illegal if dest is read-only. | |
385 | * It is illegal if dest does not occur in the WRITES lists of the current | |
386 | routine. | |
387 | * It is illegal if src is not of same type as dest. | |
388 | * It is illegal if src is uninitialized. | |
389 | ||
390 | After execution, dest is considered initialized, and `z` and `n`, and | |
391 | `a` are considered uninitialized. | |
476 | "until" is optional, but if omitted, must be replaced with "forever": | |
477 | ||
478 | repeat { | |
479 | cmp y, 25 | |
480 | if z { | |
481 | } | |
482 | } forever | |
483 | ||
484 | The sense of the test can be inverted with `not`. | |
485 | ||
486 | repeat { | |
487 | cmp y, 25 | |
488 | if z { | |
489 | } | |
490 | } until not z | |
392 | 491 | |
393 | 492 | Grammar |
394 | 493 | ------- |
0 | buffer[2048] buf | |
1 | pointer ptr @ 254 | |
2 | byte foo | |
3 | ||
4 | routine main | |
5 | inputs buf | |
6 | outputs buf, y, foo | |
7 | trashes a, z, n, ptr | |
8 | { | |
9 | ld y, 0 | |
10 | copy ^buf, ptr | |
11 | copy 123, [ptr] + y | |
12 | copy [ptr] + y, foo | |
13 | copy foo, [ptr] + y | |
14 | } |
0 | word screen @ 1024 | |
1 | byte joy2 @ $dc00 | |
2 | ||
3 | word delta | |
4 | ||
5 | routine read_stick | |
6 | inputs joy2 | |
7 | outputs delta | |
8 | trashes a, x, z, n | |
9 | { | |
10 | ld x, joy2 | |
11 | ld a, x | |
12 | and a, 1 // up | |
13 | if z { | |
14 | copy $ffd8, delta // -40 | |
15 | } else { | |
16 | ld a, x | |
17 | and a, 2 // down | |
18 | if z { | |
19 | copy word 40, delta | |
20 | } else { | |
21 | ld a, x | |
22 | and a, 4 // left | |
23 | if z { | |
24 | copy $ffff, delta // -1 | |
25 | } else { | |
26 | ld a, x | |
27 | and a, 8 // right | |
28 | if z { | |
29 | copy word 1, delta | |
30 | } else { | |
31 | copy word 0, delta | |
32 | } | |
33 | } | |
34 | } | |
35 | } | |
36 | } | |
37 | ||
38 | routine main | |
39 | inputs joy2 | |
40 | outputs delta | |
41 | trashes a, x, z, n, screen | |
42 | { | |
43 | repeat { | |
44 | call read_stick | |
45 | copy delta, screen | |
46 | ld a, 1 | |
47 | } until z | |
48 | } |
1 | 1 | |
2 | 2 | from sixtypical.ast import Program, Routine, Block, Instr |
3 | 3 | from sixtypical.model import ( |
4 | TYPE_BYTE, TYPE_BYTE_TABLE, | |
5 | VectorType, ExecutableType, | |
6 | ConstantRef, LocationRef, | |
7 | REG_A, FLAG_Z, FLAG_N, FLAG_V, FLAG_C | |
4 | TYPE_BYTE, TYPE_BYTE_TABLE, BufferType, PointerType, VectorType, ExecutableType, | |
5 | ConstantRef, LocationRef, IndirectRef, AddressRef, | |
6 | REG_A, REG_Y, FLAG_Z, FLAG_N, FLAG_V, FLAG_C | |
8 | 7 | ) |
9 | 8 | |
10 | 9 | |
113 | 112 | def assert_meaningful(self, *refs, **kwargs): |
114 | 113 | exception_class = kwargs.get('exception_class', UnmeaningfulReadError) |
115 | 114 | for ref in refs: |
116 | if isinstance(ref, ConstantRef) or ref in self.routines: | |
115 | if ref.is_constant() or ref in self.routines: | |
117 | 116 | pass |
118 | 117 | elif isinstance(ref, LocationRef): |
119 | 118 | if ref not in self._meaningful: |
120 | 119 | message = '%s in %s' % (ref.name, self.routine.name) |
120 | if kwargs.get('message'): | |
121 | message += ' (%s)' % kwargs['message'] | |
121 | 122 | raise exception_class(message) |
122 | 123 | else: |
123 | 124 | raise NotImplementedError(ref) |
127 | 128 | for ref in refs: |
128 | 129 | if ref not in self._writeable: |
129 | 130 | message = '%s in %s' % (ref.name, self.routine.name) |
131 | if kwargs.get('message'): | |
132 | message += ' (%s)' % kwargs['message'] | |
130 | 133 | raise exception_class(message) |
131 | 134 | |
132 | 135 | def set_touched(self, *refs): |
269 | 272 | # probably not; if it wasn't meaningful in the first place, it |
270 | 273 | # doesn't really matter if you modified it or not, coming out. |
271 | 274 | for ref in context1.each_meaningful(): |
272 | context2.assert_meaningful(ref, exception_class=InconsistentInitializationError) | |
275 | context2.assert_meaningful( | |
276 | ref, exception_class=InconsistentInitializationError, message='initialized in block 1 but not in block 2' | |
277 | ) | |
273 | 278 | for ref in context2.each_meaningful(): |
274 | context1.assert_meaningful(ref, exception_class=InconsistentInitializationError) | |
279 | context1.assert_meaningful( | |
280 | ref, exception_class=InconsistentInitializationError, message='initialized in block 2 but not in block 1' | |
281 | ) | |
275 | 282 | context.set_from(context1) |
276 | 283 | elif opcode == 'repeat': |
277 | 284 | # it will always be executed at least once, so analyze it having |
278 | 285 | # been executed the first time. |
279 | 286 | self.analyze_block(instr.block, context) |
280 | ||
287 | context.assert_meaningful(src) | |
288 | ||
281 | 289 | # now analyze it having been executed a second time, with the context |
282 | 290 | # of it having already been executed. |
283 | 291 | self.analyze_block(instr.block, context) |
284 | ||
285 | # NB I *think* that's enough... but it might not be? | |
292 | context.assert_meaningful(src) | |
293 | ||
286 | 294 | elif opcode == 'copy': |
287 | # check that their types are basically compatible | |
288 | if src.type == dest.type: | |
289 | pass | |
290 | elif isinstance(src.type, ExecutableType) and \ | |
291 | isinstance(dest.type, VectorType): | |
292 | pass | |
295 | # 1. check that their types are compatible | |
296 | ||
297 | if isinstance(src, AddressRef) and isinstance(dest, LocationRef): | |
298 | if isinstance(src.ref.type, BufferType) and isinstance(dest.type, PointerType): | |
299 | pass | |
300 | else: | |
301 | raise TypeMismatchError((src, dest)) | |
302 | elif isinstance(src, (LocationRef, ConstantRef)) and isinstance(dest, IndirectRef): | |
303 | if src.type == TYPE_BYTE and isinstance(dest.ref.type, PointerType): | |
304 | pass | |
305 | else: | |
306 | raise TypeMismatchError((src, dest)) | |
307 | elif isinstance(src, IndirectRef) and isinstance(dest, LocationRef): | |
308 | if isinstance(src.ref.type, PointerType) and dest.type == TYPE_BYTE: | |
309 | pass | |
310 | else: | |
311 | raise TypeMismatchError((src, dest)) | |
312 | elif isinstance(src, (LocationRef, ConstantRef)) and isinstance(dest, LocationRef): | |
313 | if src.type == dest.type: | |
314 | pass | |
315 | elif isinstance(src.type, ExecutableType) and \ | |
316 | isinstance(dest.type, VectorType): | |
317 | # if dealing with routines and vectors, | |
318 | # check that they're not incompatible | |
319 | if not (src.type.inputs <= dest.type.inputs): | |
320 | raise IncompatibleConstraintsError(src.type.inputs - dest.type.inputs) | |
321 | if not (src.type.outputs <= dest.type.outputs): | |
322 | raise IncompatibleConstraintsError(src.type.outputs - dest.type.outputs) | |
323 | if not (src.type.trashes <= dest.type.trashes): | |
324 | raise IncompatibleConstraintsError(src.type.trashes - dest.type.trashes) | |
325 | else: | |
326 | raise TypeMismatchError((src, dest)) | |
293 | 327 | else: |
294 | 328 | raise TypeMismatchError((src, dest)) |
295 | ||
296 | # if dealing with routines and vectors, | |
297 | # check that they're not incompatible | |
298 | if isinstance(src.type, ExecutableType) and \ | |
299 | isinstance(dest.type, VectorType): | |
300 | if not (src.type.inputs <= dest.type.inputs): | |
301 | raise IncompatibleConstraintsError(src.type.inputs - dest.type.inputs) | |
302 | if not (src.type.outputs <= dest.type.outputs): | |
303 | raise IncompatibleConstraintsError(src.type.outputs - dest.type.outputs) | |
304 | if not (src.type.trashes <= dest.type.trashes): | |
305 | raise IncompatibleConstraintsError(src.type.trashes - dest.type.trashes) | |
306 | ||
307 | context.assert_meaningful(src) | |
308 | context.set_written(dest) | |
329 | ||
330 | # 2. check that the context is meaningful | |
331 | ||
332 | if isinstance(src, (LocationRef, ConstantRef)) and isinstance(dest, IndirectRef): | |
333 | context.assert_meaningful(src, REG_Y) | |
334 | # TODO this will need to be more sophisticated. it's the thing ref points to that is written, not ref itself. | |
335 | context.set_written(dest.ref) | |
336 | elif isinstance(src, IndirectRef) and isinstance(dest, LocationRef): | |
337 | context.assert_meaningful(src.ref, REG_Y) | |
338 | # TODO this will need to be more sophisticated. the thing ref points to is touched, as well. | |
339 | context.set_touched(src.ref) | |
340 | context.set_written(dest) | |
341 | else: | |
342 | context.assert_meaningful(src) | |
343 | context.set_written(dest) | |
344 | ||
309 | 345 | context.set_touched(REG_A, FLAG_Z, FLAG_N) |
310 | 346 | context.set_unmeaningful(REG_A, FLAG_Z, FLAG_N) |
347 | ||
311 | 348 | elif opcode == 'with-sei': |
312 | 349 | self.analyze_block(instr.block, context) |
313 | 350 | elif opcode == 'goto': |
1 | 1 | |
2 | 2 | from sixtypical.ast import Program, Routine, Block, Instr |
3 | 3 | from sixtypical.model import ( |
4 | ConstantRef, | |
5 | TYPE_BIT, TYPE_BYTE, TYPE_WORD, | |
6 | RoutineType, VectorType, | |
4 | ConstantRef, LocationRef, IndirectRef, AddressRef, | |
5 | TYPE_BIT, TYPE_BYTE, TYPE_WORD, BufferType, PointerType, RoutineType, VectorType, | |
7 | 6 | REG_A, REG_X, REG_Y, FLAG_C |
8 | 7 | ) |
9 | 8 | from sixtypical.emitter import Byte, Label, Offset, LowAddressByte, HighAddressByte |
10 | 9 | from sixtypical.gen6502 import ( |
11 | Immediate, Absolute, AbsoluteX, AbsoluteY, Indirect, Relative, | |
10 | Immediate, Absolute, AbsoluteX, AbsoluteY, ZeroPage, Indirect, IndirectY, Relative, | |
12 | 11 | LDA, LDX, LDY, STA, STX, STY, |
13 | 12 | TAX, TAY, TXA, TYA, |
14 | 13 | CLC, SEC, ADC, SBC, ROL, ROR, |
274 | 273 | self.compile_block(instr.block) |
275 | 274 | self.emitter.emit(CLI()) |
276 | 275 | elif opcode == 'copy': |
277 | if src.type == TYPE_BYTE and dest.type == TYPE_BYTE: | |
278 | src_label = self.labels[src.name] | |
276 | if isinstance(src, (LocationRef, ConstantRef)) and isinstance(dest, IndirectRef): | |
277 | if src.type == TYPE_BYTE and isinstance(dest.ref.type, PointerType): | |
278 | if isinstance(src, ConstantRef): | |
279 | dest_label = self.labels[dest.ref.name] | |
280 | self.emitter.emit(LDA(Immediate(Byte(src.value)))) | |
281 | self.emitter.emit(STA(IndirectY(dest_label))) | |
282 | elif isinstance(src, LocationRef): | |
283 | src_label = self.labels[src.name] | |
284 | dest_label = self.labels[dest.ref.name] | |
285 | self.emitter.emit(LDA(Absolute(src_label))) | |
286 | self.emitter.emit(STA(IndirectY(dest_label))) | |
287 | else: | |
288 | raise NotImplementedError((src, dest)) | |
289 | else: | |
290 | raise NotImplementedError((src, dest)) | |
291 | elif isinstance(src, IndirectRef) and isinstance(dest, LocationRef): | |
292 | if dest.type == TYPE_BYTE and isinstance(src.ref.type, PointerType): | |
293 | src_label = self.labels[src.ref.name] | |
294 | dest_label = self.labels[dest.name] | |
295 | self.emitter.emit(LDA(IndirectY(src_label))) | |
296 | self.emitter.emit(STA(Absolute(dest_label))) | |
297 | else: | |
298 | raise NotImplementedError((src, dest)) | |
299 | elif isinstance(src, AddressRef) and isinstance(dest, LocationRef) and \ | |
300 | isinstance(src.ref.type, BufferType) and isinstance(dest.type, PointerType): | |
301 | src_label = self.labels[src.ref.name] | |
279 | 302 | dest_label = self.labels[dest.name] |
280 | self.emitter.emit(LDA(Absolute(src_label))) | |
281 | self.emitter.emit(STA(Absolute(dest_label))) | |
303 | self.emitter.emit(LDA(Immediate(HighAddressByte(src_label)))) | |
304 | self.emitter.emit(STA(ZeroPage(dest_label))) | |
305 | self.emitter.emit(LDA(Immediate(LowAddressByte(src_label)))) | |
306 | self.emitter.emit(STA(ZeroPage(Offset(dest_label, 1)))) | |
307 | elif not isinstance(src, (ConstantRef, LocationRef)) or not isinstance(dest, LocationRef): | |
308 | raise NotImplementedError((src, dest)) | |
309 | elif src.type == TYPE_BYTE and dest.type == TYPE_BYTE: | |
310 | if isinstance(src, ConstantRef): | |
311 | raise NotImplementedError | |
312 | else: | |
313 | src_label = self.labels[src.name] | |
314 | dest_label = self.labels[dest.name] | |
315 | self.emitter.emit(LDA(Absolute(src_label))) | |
316 | self.emitter.emit(STA(Absolute(dest_label))) | |
282 | 317 | elif src.type == TYPE_WORD and dest.type == TYPE_WORD: |
283 | src_label = self.labels[src.name] | |
284 | dest_label = self.labels[dest.name] | |
285 | self.emitter.emit(LDA(Absolute(src_label))) | |
286 | self.emitter.emit(STA(Absolute(dest_label))) | |
287 | self.emitter.emit(LDA(Absolute(Offset(src_label, 1)))) | |
288 | self.emitter.emit(STA(Absolute(Offset(dest_label, 1)))) | |
318 | if isinstance(src, ConstantRef): | |
319 | dest_label = self.labels[dest.name] | |
320 | hi = (src.value >> 8) & 255 | |
321 | lo = src.value & 255 | |
322 | self.emitter.emit(LDA(Immediate(Byte(hi)))) | |
323 | self.emitter.emit(STA(Absolute(dest_label))) | |
324 | self.emitter.emit(LDA(Immediate(Byte(lo)))) | |
325 | self.emitter.emit(STA(Absolute(Offset(dest_label, 1)))) | |
326 | else: | |
327 | src_label = self.labels[src.name] | |
328 | dest_label = self.labels[dest.name] | |
329 | self.emitter.emit(LDA(Absolute(src_label))) | |
330 | self.emitter.emit(STA(Absolute(dest_label))) | |
331 | self.emitter.emit(LDA(Absolute(Offset(src_label, 1)))) | |
332 | self.emitter.emit(STA(Absolute(Offset(dest_label, 1)))) | |
289 | 333 | elif isinstance(src.type, VectorType) and isinstance(dest.type, VectorType): |
290 | 334 | src_label = self.labels[src.name] |
291 | 335 | dest_label = self.labels[dest.name] |
60 | 60 | assert self.addr is not None, "unresolved label: %s" % self.name |
61 | 61 | return Byte(self.addr - (addr + 2)).serialize() |
62 | 62 | |
63 | def serialize_as_zero_page(self, offset=0): | |
64 | assert self.addr is not None, "unresolved label: %s" % self.name | |
65 | return Byte(self.addr + offset).serialize() | |
66 | ||
63 | 67 | def __repr__(self): |
64 | 68 | addrs = ', addr=%r' % self.addr if self.addr is not None else '' |
65 | 69 | return "%s(%r%s)" % (self.__class__.__name__, self.name, addrs) |
76 | 80 | |
77 | 81 | def serialize(self, addr=None): |
78 | 82 | return self.label.serialize(offset=self.offset) |
83 | ||
84 | def serialize_as_zero_page(self, offset=0): | |
85 | return self.label.serialize_as_zero_page(offset=self.offset) | |
79 | 86 | |
80 | 87 | def __repr__(self): |
81 | 88 | return "%s(%r, %r)" % (self.__class__.__name__, self.label, self.offset) |
1 | 1 | |
2 | 2 | from sixtypical.ast import Program, Routine, Block, Instr |
3 | 3 | from sixtypical.model import ( |
4 | ConstantRef, LocationRef, PartRef, | |
4 | ConstantRef, LocationRef, PartRef, IndirectRef, | |
5 | 5 | REG_A, REG_X, REG_Y, FLAG_Z, FLAG_N, FLAG_V, FLAG_C |
6 | 6 | ) |
7 | 7 | |
190 | 190 | while context.get(src) == 0: |
191 | 191 | self.eval_block(instr.block, context) |
192 | 192 | elif opcode == 'copy': |
193 | if isinstance(src, IndirectRef): | |
194 | raise NotImplementedError("this doesn't actually work") | |
195 | src = src.ref | |
196 | if isinstance(dest, IndirectRef): | |
197 | raise NotImplementedError("this doesn't actually work") | |
198 | dest = dest.ref | |
193 | 199 | context.set(dest, context.get(src)) |
194 | 200 | # these are trashed; so could be anything really |
195 | 201 | context.set(REG_A, 0) |
57 | 57 | pass |
58 | 58 | |
59 | 59 | |
60 | class ZeroPage(AddressingMode): | |
61 | def __init__(self, value): | |
62 | assert isinstance(value, (Label, Offset)) | |
63 | self.value = value | |
64 | ||
65 | def size(self): | |
66 | return 1 | |
67 | ||
68 | def serialize(self, addr=None): | |
69 | return self.value.serialize_as_zero_page() | |
70 | ||
71 | ||
60 | 72 | class Indirect(AddressingMode): |
61 | 73 | def __init__(self, value): |
62 | 74 | assert isinstance(value, Label) |
67 | 79 | |
68 | 80 | def serialize(self, addr=None): |
69 | 81 | return self.value.serialize() |
82 | ||
83 | ||
84 | class IndirectY(ZeroPage): | |
85 | pass | |
70 | 86 | |
71 | 87 | |
72 | 88 | class Relative(AddressingMode): |
243 | 259 | Absolute: 0xad, |
244 | 260 | AbsoluteX: 0xbd, |
245 | 261 | AbsoluteY: 0xb9, |
262 | IndirectY: 0xb1, | |
263 | ZeroPage: 0xa5, | |
246 | 264 | } |
247 | 265 | |
248 | 266 | |
319 | 337 | Absolute: 0x8d, |
320 | 338 | AbsoluteX: 0x9d, |
321 | 339 | AbsoluteY: 0x99, |
340 | IndirectY: 0x91, | |
341 | ZeroPage: 0x85, | |
322 | 342 | } |
323 | 343 | |
324 | 344 |
57 | 57 | super(VectorType, self).__init__('vector', **kwargs) |
58 | 58 | |
59 | 59 | |
60 | class BufferType(Type): | |
61 | def __init__(self, size): | |
62 | self.size = size | |
63 | self.name = 'buffer[%s]' % self.size | |
64 | ||
65 | ||
66 | class PointerType(Type): | |
67 | def __init__(self): | |
68 | self.name = 'pointer' | |
69 | ||
70 | ||
60 | 71 | class Ref(object): |
61 | 72 | def is_constant(self): |
62 | 73 | """read-only means that the program cannot change the value |
75 | 86 | # but because we store the type in here and we want to treat |
76 | 87 | # these objects as immutable, we compare the types, too. |
77 | 88 | # Not sure if very wise. |
78 | return isinstance(other, LocationRef) and ( | |
89 | return isinstance(other, self.__class__) and ( | |
79 | 90 | other.name == self.name and other.type == self.type |
80 | 91 | ) |
81 | 92 | |
87 | 98 | |
88 | 99 | def is_constant(self): |
89 | 100 | return isinstance(self.type, RoutineType) |
101 | ||
102 | ||
103 | class IndirectRef(Ref): | |
104 | def __init__(self, ref): | |
105 | self.ref = ref | |
106 | ||
107 | def __eq__(self, other): | |
108 | return self.ref == other.ref | |
109 | ||
110 | def __hash__(self): | |
111 | return hash(self.__class__.name) ^ hash(self.ref) | |
112 | ||
113 | def __repr__(self): | |
114 | return '%s(%r)' % (self.__class__.__name__, self.ref) | |
115 | ||
116 | @property | |
117 | def name(self): | |
118 | return '[{}]+y'.format(self.ref.name) | |
119 | ||
120 | def is_constant(self): | |
121 | return False | |
122 | ||
123 | ||
124 | class AddressRef(Ref): | |
125 | def __init__(self, ref): | |
126 | self.ref = ref | |
127 | ||
128 | def __eq__(self, other): | |
129 | return self.ref == other.ref | |
130 | ||
131 | def __hash__(self): | |
132 | return hash(self.__class__.name) ^ hash(self.ref) | |
133 | ||
134 | def __repr__(self): | |
135 | return '%s(%r)' % (self.__class__.__name__, self.ref) | |
136 | ||
137 | @property | |
138 | def name(self): | |
139 | return '^{}'.format(self.ref.name) | |
140 | ||
141 | def is_constant(self): | |
142 | return True | |
90 | 143 | |
91 | 144 | |
92 | 145 | class PartRef(Ref): |
2 | 2 | from sixtypical.ast import Program, Defn, Routine, Block, Instr |
3 | 3 | from sixtypical.model import ( |
4 | 4 | TYPE_BIT, TYPE_BYTE, TYPE_BYTE_TABLE, TYPE_WORD, TYPE_WORD_TABLE, |
5 | RoutineType, VectorType, ExecutableType, | |
6 | LocationRef, ConstantRef | |
5 | RoutineType, VectorType, ExecutableType, BufferType, PointerType, | |
6 | LocationRef, ConstantRef, IndirectRef, AddressRef, | |
7 | 7 | ) |
8 | 8 | from sixtypical.scanner import Scanner |
9 | 9 | |
31 | 31 | def program(self): |
32 | 32 | defns = [] |
33 | 33 | routines = [] |
34 | while self.scanner.on('byte', 'word', 'vector'): | |
34 | while self.scanner.on('byte', 'word', 'vector', 'buffer', 'pointer'): | |
35 | 35 | defn = self.defn() |
36 | 36 | name = defn.name |
37 | 37 | if name in self.symbols: |
49 | 49 | return Program(defns=defns, routines=routines) |
50 | 50 | |
51 | 51 | def defn(self): |
52 | type = None | |
53 | if self.scanner.consume('byte'): | |
54 | type = TYPE_BYTE | |
55 | if self.scanner.consume('table'): | |
56 | type = TYPE_BYTE_TABLE | |
57 | elif self.scanner.consume('word'): | |
58 | type = TYPE_WORD | |
59 | if self.scanner.consume('table'): | |
60 | type = TYPE_WORD_TABLE | |
61 | else: | |
62 | self.scanner.expect('vector') | |
63 | type = 'vector' # will be resolved to a Type below | |
52 | type_ = self.defn_type() | |
53 | ||
64 | 54 | self.scanner.check_type('identifier') |
65 | 55 | name = self.scanner.token |
66 | 56 | self.scanner.scan() |
67 | 57 | |
68 | 58 | (inputs, outputs, trashes) = self.constraints() |
69 | if type == 'vector': | |
70 | type = VectorType(inputs=inputs, outputs=outputs, trashes=trashes) | |
59 | if type_ == 'vector': | |
60 | type_ = VectorType(inputs=inputs, outputs=outputs, trashes=trashes) | |
71 | 61 | elif inputs or outputs or trashes: |
72 | 62 | raise SyntaxError("Cannot apply constraints to non-vector type") |
73 | 63 | |
86 | 76 | if initial is not None and addr is not None: |
87 | 77 | raise SyntaxError("Definition cannot have both initial value and explicit address") |
88 | 78 | |
89 | location = LocationRef(type, name) | |
79 | location = LocationRef(type_, name) | |
90 | 80 | |
91 | 81 | return Defn(name=name, addr=addr, initial=initial, location=location) |
82 | ||
83 | def defn_type(self): | |
84 | if self.scanner.consume('byte'): | |
85 | if self.scanner.consume('table'): | |
86 | return TYPE_BYTE_TABLE | |
87 | return TYPE_BYTE | |
88 | elif self.scanner.consume('word'): | |
89 | if self.scanner.consume('table'): | |
90 | return TYPE_WORD_TABLE | |
91 | return TYPE_WORD | |
92 | elif self.scanner.consume('vector'): | |
93 | return 'vector' # will be resolved to a Type by caller | |
94 | elif self.scanner.consume('buffer'): | |
95 | self.scanner.expect('[') | |
96 | self.scanner.check_type('integer literal') | |
97 | size = int(self.scanner.token) | |
98 | self.scanner.scan() | |
99 | self.scanner.expect(']') | |
100 | return BufferType(size) | |
101 | else: | |
102 | self.scanner.expect('pointer') | |
103 | return PointerType() | |
92 | 104 | |
93 | 105 | def constraints(self): |
94 | 106 | inputs = set() |
137 | 149 | self.scanner.scan() |
138 | 150 | return loc |
139 | 151 | elif self.scanner.on_type('integer literal'): |
140 | loc = ConstantRef(TYPE_BYTE, int(self.scanner.token)) | |
152 | value = int(self.scanner.token) | |
153 | type_ = TYPE_WORD if value > 255 else TYPE_BYTE | |
154 | loc = ConstantRef(type_, value) | |
155 | self.scanner.scan() | |
156 | return loc | |
157 | elif self.scanner.consume('word'): | |
158 | loc = ConstantRef(TYPE_WORD, int(self.scanner.token)) | |
141 | 159 | self.scanner.scan() |
142 | 160 | return loc |
143 | 161 | else: |
144 | 162 | loc = self.lookup(self.scanner.token) |
145 | 163 | self.scanner.scan() |
146 | 164 | return loc |
165 | ||
166 | def indlocexpr(self): | |
167 | if self.scanner.consume('['): | |
168 | loc = self.locexpr() | |
169 | self.scanner.expect(']') | |
170 | self.scanner.expect('+') | |
171 | self.scanner.expect('y') | |
172 | return IndirectRef(loc) | |
173 | elif self.scanner.consume('^'): | |
174 | loc = self.locexpr() | |
175 | return AddressRef(loc) | |
176 | else: | |
177 | return self.locexpr() | |
147 | 178 | |
148 | 179 | def block(self): |
149 | 180 | instrs = [] |
215 | 246 | elif self.scanner.token in ("copy",): |
216 | 247 | opcode = self.scanner.token |
217 | 248 | self.scanner.scan() |
218 | src = self.locexpr() | |
249 | src = self.indlocexpr() | |
219 | 250 | self.scanner.expect(',') |
220 | dest = self.locexpr() | |
251 | dest = self.indlocexpr() | |
221 | 252 | return Instr(opcode=opcode, dest=dest, src=src) |
222 | 253 | elif self.scanner.consume("with"): |
223 | 254 | self.scanner.expect("interrupts") |
28 | 28 | self.token = None |
29 | 29 | self.type = 'EOF' |
30 | 30 | return |
31 | if self.scan_pattern(r'\,|\@|\+|\:|\<|\>|\{|\}', 'operator'): | |
31 | if self.scan_pattern(r'\,|\@|\+|\:|\<|\>|\{|\}|\[|\]|\^', 'operator'): | |
32 | 32 | return |
33 | 33 | if self.scan_pattern(r'\d+', 'integer literal'): |
34 | 34 | return |
1016 | 1016 | | } |
1017 | 1017 | ? UnmeaningfulReadError: y in main |
1018 | 1018 | |
1019 | And if you trash the test expression (i.e. `z` in the below) inside the loop, | |
1020 | this is an error too. | |
1021 | ||
1022 | | word one : 0 | |
1023 | | word two : 0 | |
1024 | | | |
1025 | | routine main | |
1026 | | inputs one, two | |
1027 | | outputs two | |
1028 | | trashes a, z, n | |
1029 | | { | |
1030 | | repeat { | |
1031 | | copy one, two | |
1032 | | } until z | |
1033 | | } | |
1034 | ? UnmeaningfulReadError: z in main | |
1035 | ||
1019 | 1036 | ### copy ### |
1020 | 1037 | |
1021 | 1038 | Can't `copy` from a memory location that isn't initialized. |
1153 | 1170 | | copy source, dest |
1154 | 1171 | | } |
1155 | 1172 | ? TypeMismatchError |
1173 | ||
1174 | ### copy[] ### | |
1175 | ||
1176 | Buffers and pointers. | |
1177 | ||
1178 | Note that `^buf` is a constant value, so it by itself does not require `buf` to be | |
1179 | listed in any input/output sets. | |
1180 | ||
1181 | However, if the code reads from it through a pointer, it *should* be in `inputs`. | |
1182 | ||
1183 | Likewise, if the code writes to it through a pointer, it *should* be in `outputs`. | |
1184 | ||
1185 | Of course, unless you write to *all* the bytes in a buffer, some of those bytes | |
1186 | might not be meaningful. So how meaningful is this check? | |
1187 | ||
1188 | This is an open problem. | |
1189 | ||
1190 | For now, convention says: if it is being read, list it in `inputs`, and if it is | |
1191 | being modified, list it in both `inputs` and `outputs`. | |
1192 | ||
1193 | Write literal through a pointer. | |
1194 | ||
1195 | | buffer[2048] buf | |
1196 | | pointer ptr | |
1197 | | | |
1198 | | routine main | |
1199 | | inputs buf | |
1200 | | outputs y, buf | |
1201 | | trashes a, z, n, ptr | |
1202 | | { | |
1203 | | ld y, 0 | |
1204 | | copy ^buf, ptr | |
1205 | | copy 123, [ptr] + y | |
1206 | | } | |
1207 | = ok | |
1208 | ||
1209 | It does use `y`. | |
1210 | ||
1211 | | buffer[2048] buf | |
1212 | | pointer ptr | |
1213 | | | |
1214 | | routine main | |
1215 | | inputs buf | |
1216 | | outputs buf | |
1217 | | trashes a, z, n, ptr | |
1218 | | { | |
1219 | | copy ^buf, ptr | |
1220 | | copy 123, [ptr] + y | |
1221 | | } | |
1222 | ? UnmeaningfulReadError | |
1223 | ||
1224 | Write stored value through a pointer. | |
1225 | ||
1226 | | buffer[2048] buf | |
1227 | | pointer ptr | |
1228 | | byte foo | |
1229 | | | |
1230 | | routine main | |
1231 | | inputs foo, buf | |
1232 | | outputs y, buf | |
1233 | | trashes a, z, n, ptr | |
1234 | | { | |
1235 | | ld y, 0 | |
1236 | | copy ^buf, ptr | |
1237 | | copy foo, [ptr] + y | |
1238 | | } | |
1239 | = ok | |
1240 | ||
1241 | Read through a pointer. | |
1242 | ||
1243 | | buffer[2048] buf | |
1244 | | pointer ptr | |
1245 | | byte foo | |
1246 | | | |
1247 | | routine main | |
1248 | | inputs buf | |
1249 | | outputs foo | |
1250 | | trashes a, y, z, n, ptr | |
1251 | | { | |
1252 | | ld y, 0 | |
1253 | | copy ^buf, ptr | |
1254 | | copy [ptr] + y, foo | |
1255 | | } | |
1256 | = ok | |
1156 | 1257 | |
1157 | 1258 | ### routines ### |
1158 | 1259 |
0 | Sixtypical Compilation | |
0 | SixtyPical Compilation | |
1 | 1 | ====================== |
2 | 2 | |
3 | 3 | This is a test suite, written in [Falderal][] format, for compiling |
4 | Sixtypical to 6502 machine code. | |
4 | SixtyPical to 6502 machine code. | |
5 | 5 | |
6 | 6 | [Falderal]: http://catseye.tc/node/Falderal |
7 | 7 | |
8 | -> Functionality "Compile Sixtypical program" is implemented by | |
8 | -> Functionality "Compile SixtyPical program" is implemented by | |
9 | 9 | -> shell command "bin/sixtypical --compile %(test-body-file) | fa-bin-to-hex" |
10 | 10 | |
11 | -> Tests for functionality "Compile Sixtypical program" | |
11 | -> Tests for functionality "Compile SixtyPical program" | |
12 | 12 | |
13 | 13 | Null program. |
14 | 14 | |
266 | 266 | | } |
267 | 267 | = 00c0ad0fc08d0dc0ad10c08d0ec060 |
268 | 268 | |
269 | Copy literal word to word. | |
270 | ||
271 | | word bar | |
272 | | | |
273 | | routine main | |
274 | | outputs bar | |
275 | | trashes a, n, z | |
276 | | { | |
277 | | copy 65535, bar | |
278 | | } | |
279 | = 00c0a9ff8d0bc0a9ff8d0cc060 | |
280 | ||
269 | 281 | Copy vector to vector. |
270 | 282 | |
271 | 283 | | vector bar |
280 | 292 | | } |
281 | 293 | = 00c0ad0fc08d0dc0ad10c08d0ec060 |
282 | 294 | |
283 | Copy instruction inside an `interrupts off` block. | |
295 | Copy routine to vector, inside an `interrupts off` block. | |
284 | 296 | |
285 | 297 | | vector bar |
286 | 298 | | |
328 | 340 | | goto bar |
329 | 341 | | } |
330 | 342 | = 00c0a0c84c06c060a2c860 |
343 | ||
344 | ### Buffers and Pointers | |
345 | ||
346 | Load address into pointer. | |
347 | ||
348 | | buffer[2048] buf | |
349 | | pointer ptr @ 254 | |
350 | | | |
351 | | routine main | |
352 | | inputs buf | |
353 | | outputs buf, y | |
354 | | trashes a, z, n, ptr | |
355 | | { | |
356 | | ld y, 0 | |
357 | | copy ^buf, ptr | |
358 | | } | |
359 | = 00c0a000a90b85fea9c085ff60 | |
360 | ||
361 | Write literal through a pointer. | |
362 | ||
363 | | buffer[2048] buf | |
364 | | pointer ptr @ 254 | |
365 | | | |
366 | | routine main | |
367 | | inputs buf | |
368 | | outputs buf, y | |
369 | | trashes a, z, n, ptr | |
370 | | { | |
371 | | ld y, 0 | |
372 | | copy ^buf, ptr | |
373 | | copy 123, [ptr] + y | |
374 | | } | |
375 | = 00c0a000a90f85fea9c085ffa97b91fe60 | |
376 | ||
377 | Write stored value through a pointer. | |
378 | ||
379 | | buffer[2048] buf | |
380 | | pointer ptr @ 254 | |
381 | | byte foo | |
382 | | | |
383 | | routine main | |
384 | | inputs foo, buf | |
385 | | outputs y, buf | |
386 | | trashes a, z, n, ptr | |
387 | | { | |
388 | | ld y, 0 | |
389 | | copy ^buf, ptr | |
390 | | copy foo, [ptr] + y | |
391 | | } | |
392 | = 00c0a000a91085fea9c085ffad12c091fe60 | |
393 | ||
394 | Read through a pointer. | |
395 | ||
396 | | buffer[2048] buf | |
397 | | pointer ptr @ 254 | |
398 | | byte foo | |
399 | | | |
400 | | routine main | |
401 | | inputs buf | |
402 | | outputs y, foo | |
403 | | trashes a, z, n, ptr | |
404 | | { | |
405 | | ld y, 0 | |
406 | | copy ^buf, ptr | |
407 | | copy [ptr] + y, foo | |
408 | | } | |
409 | = 00c0a000a91085fea9c085ffb1fe8d12c060 |
0 | Sixtypical Execution | |
0 | SixtyPical Execution | |
1 | 1 | ==================== |
2 | 2 | |
3 | 3 | This is a test suite, written in [Falderal][] format, for the dynamic |
5 | 5 | |
6 | 6 | [Falderal]: http://catseye.tc/node/Falderal |
7 | 7 | |
8 | -> Functionality "Execute Sixtypical program" is implemented by | |
8 | -> Functionality "Execute SixtyPical program" is implemented by | |
9 | 9 | -> shell command "bin/sixtypical --execute %(test-body-file)" |
10 | 10 | |
11 | -> Tests for functionality "Execute Sixtypical program" | |
11 | -> Tests for functionality "Execute SixtyPical program" | |
12 | 12 | |
13 | 13 | Rudimentary program. |
14 | 14 | |
434 | 434 | = y: 0 |
435 | 435 | = z: 0 |
436 | 436 | |
437 | Copy literal word to word. | |
438 | ||
439 | | word bar | |
440 | | | |
441 | | routine main { | |
442 | | copy word 2000, bar | |
443 | | } | |
444 | = a: 0 | |
445 | = bar: 2000 | |
446 | = c: 0 | |
447 | = n: 0 | |
448 | = v: 0 | |
449 | = x: 0 | |
450 | = y: 0 | |
451 | = z: 0 | |
452 | ||
437 | 453 | Indirect call. |
438 | 454 | |
439 | 455 | | vector foo outputs x trashes z, n |
0 | Sixtypical Execution | |
1 | ==================== | |
0 | SixtyPical Syntax | |
1 | ================= | |
2 | 2 | |
3 | 3 | This is a test suite, written in [Falderal][] format, for the syntax of |
4 | 4 | the Sixtypical language, disgregarding execution, static analysis, etc. |
8 | 8 | |
9 | 9 | [Falderal]: http://catseye.tc/node/Falderal |
10 | 10 | |
11 | -> Functionality "Check syntax of Sixtypical program" is implemented by | |
11 | -> Functionality "Check syntax of SixtyPical program" is implemented by | |
12 | 12 | -> shell command "bin/sixtypical %(test-body-file) && echo ok" |
13 | 13 | |
14 | -> Tests for functionality "Check syntax of Sixtypical program" | |
14 | -> Tests for functionality "Check syntax of SixtyPical program" | |
15 | 15 | |
16 | 16 | Rudimentary program. |
17 | 17 | |
122 | 122 | | } |
123 | 123 | = ok |
124 | 124 | |
125 | User-defined memory addresses of different types. | |
126 | ||
127 | | byte byt | |
128 | | word wor | |
129 | | vector vec | |
130 | | byte table tab | |
131 | | buffer[2048] buf | |
132 | | pointer ptr | |
133 | | | |
134 | | routine main { | |
135 | | } | |
136 | = ok | |
137 | ||
125 | 138 | Explicit memory address. |
126 | 139 | |
127 | 140 | | byte screen @ 1024 |
307 | 320 | | goto foo |
308 | 321 | | } |
309 | 322 | ? SyntaxError |
323 | ||
324 | Buffers and pointers. | |
325 | ||
326 | | buffer[2048] buf | |
327 | | pointer ptr | |
328 | | byte foo | |
329 | | | |
330 | | routine main { | |
331 | | copy ^buf, ptr | |
332 | | copy 123, [ptr] + y | |
333 | | copy [ptr] + y, foo | |
334 | | } | |
335 | = ok |