Merge branch 'develop-0.17' into remove-legacy-syntax
Chris Pressey
6 years ago
4 | 4 | ---- |
5 | 5 | |
6 | 6 | * `save X, Y, Z { }` now allowed as a shortcut for nested `save`s. |
7 | * If the name in a location expression isn't found in the symbol | |
8 | table, a forward reference will _always_ be generated; and forward | |
9 | references in _all_ operations will be resolved after parsing. | |
10 | * As a consequence, trying to call or goto a non-routine-typed symbol | |
11 | is now an analysis error, not a syntax error. | |
7 | 12 | * Split TODO off into own file. |
8 | 13 | * `sixtypical` no longer writes the compiled binary to standard |
9 | 14 | output. The `--output` command-line argument should be given |
0 | 0 | TODO for SixtyPical |
1 | 1 | =================== |
2 | 2 | |
3 | ### `low` and `high` address operators | |
3 | ### 16-bit `cmp` | |
4 | 4 | |
5 | To turn `word` type into `byte`. | |
5 | This is because we don't actually want `low` and `high` address operators | |
6 | that turn `word` type into `byte`. | |
6 | 7 | |
7 | Trying to remember if we have a compelling case for this or now. The best I can think | |
8 | of is for implementing 16-bit `cmp` in an efficient way. Maybe we should see if we | |
9 | can get by with 16-bit `cmp` instead though. | |
8 | This is because this immediately makes things harder (that is, effectively | |
9 | impossible) to analyze. | |
10 | 10 | |
11 | The problem is that once a byte is extracted, putting it back into a word is awkward. | |
12 | The address operators have to modify a destination in a special way. That is, when | |
13 | you say `st a, >word`, you are updating `word` to be `word & $ff | a << 8`, somelike. | |
14 | Is that consistent with `st`? Well, probably it is, but we have to explain it. | |
15 | It might make more sense, then, for it to be "part of the operation" instead of "part of | |
16 | the reference"; something like `st.hi x, word`; `st.lo y, word`. Dunno. | |
11 | 16-bit `cmp` also benefits from some special differences between `cmp` | |
12 | and `sub` on 6502, so it would be nice to capture them. | |
17 | 13 | |
18 | 14 | ### Save values to other-than-the-stack |
19 | 15 | |
26 | 22 | Which uses some other storage location instead of the stack. A local static |
27 | 23 | would be a good candidate for such. |
28 | 24 | |
29 | ### Make all symbols forward-referencable | |
30 | ||
31 | Basically, don't do symbol-table lookups when parsing, but do have a more formal | |
32 | "symbol resolution" (linking) phase right after parsing. | |
33 | ||
34 | 25 | ### Associate each pointer with the buffer it points into |
35 | 26 | |
36 | 27 | Check that the buffer being read or written to through pointer, appears in appropriate |
37 | 28 | inputs or outputs set. |
38 | 29 | |
39 | In the analysis, when we obtain a pointer, we need to record, in contect, what buffer | |
30 | In the analysis, when we obtain a pointer, we need to record, in context, what buffer | |
40 | 31 | that pointer came from. |
41 | 32 | |
42 | 33 | When we write through that pointer, we need to set that buffer as written. |
77 | 68 | More generally, define a block as having zero or one `goto`s at the end. (and `goto`s cannot |
78 | 69 | appear elsewhere.) |
79 | 70 | |
80 | If a block ends in a `call` can that be converted to end in a `goto`? Why not? I think it can. | |
81 | The constraints should iron out the same both ways. | |
71 | If a block ends in a `call` can that be converted to end in a `goto`? Why not? I think it can, | |
72 | if the block is in tail position. The constraints should iron out the same both ways. | |
82 | 73 | |
83 | 74 | And - once we have this - why do we need `goto` to be in tail position, strictly? |
84 | 75 | As long as the routine has consistent type context every place it exits, that should be fine. |
90 | 81 | One such library routine might be an `interrupt routine` type for various architectures. |
91 | 82 | Since "the supervisor" has stored values on the stack, we should be able to trash them |
92 | 83 | with impunity, in such a routine. |
84 | ||
85 | ### Line numbers in analysis error messages | |
86 | ||
87 | For analysis errors, there is a line number, but it's the line of the routine | |
88 | after the routine in which the analysis error occurred. Fix this. |
32 | 32 | typedef routine |
33 | 33 | inputs joy2, press_fire_msg, dispatch_game_state, |
34 | 34 | actor_pos, actor_delta, actor_logic, |
35 | player_died, | |
35 | 36 | screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 |
36 | 37 | outputs dispatch_game_state, |
37 | 38 | actor_pos, actor_delta, actor_logic, |
39 | player_died, | |
38 | 40 | screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 |
39 | 41 | trashes a, x, y, c, z, n, v, pos, new_pos, delta, ptr, dispatch_logic |
40 | 42 | game_state_routine |
44 | 46 | // |
45 | 47 | // Routines that conform to this type also follow this convention: |
46 | 48 | // |
47 | // Set carry if the player perished. Carry clear otherwise. | |
49 | // Set player_died to 1 if the player perished. Unchanged otherwise. | |
48 | 50 | // |
49 | 51 | |
50 | 52 | typedef routine |
51 | inputs pos, delta, joy2, screen | |
52 | outputs pos, delta, new_pos, screen, c | |
53 | trashes a, x, y, z, n, v, ptr | |
53 | inputs pos, delta, joy2, screen, player_died | |
54 | outputs pos, delta, new_pos, screen, player_died | |
55 | trashes a, x, y, z, n, v, c, ptr | |
54 | 56 | logic_routine |
55 | 57 | |
56 | 58 | // ---------------------------------------------------------------- |
85 | 87 | |
86 | 88 | word table[256] actor_delta |
87 | 89 | word delta |
90 | ||
91 | byte player_died | |
88 | 92 | |
89 | 93 | vector logic_routine table[256] actor_logic |
90 | 94 | vector logic_routine dispatch_logic |
240 | 244 | |
241 | 245 | routine init_game |
242 | 246 | inputs actor_pos, actor_delta, actor_logic |
243 | outputs actor_pos, actor_delta, actor_logic | |
247 | outputs actor_pos, actor_delta, actor_logic, player_died | |
244 | 248 | trashes pos, a, y, z, n, c, v |
245 | 249 | { |
246 | 250 | ld y, 0 |
258 | 262 | } until z |
259 | 263 | |
260 | 264 | ld y, 0 |
261 | copy word 0, actor_pos + y | |
265 | copy word 40, actor_pos + y | |
262 | 266 | copy word 0, actor_delta + y |
263 | 267 | copy player_logic, actor_logic + y |
268 | ||
269 | st y, player_died | |
264 | 270 | } |
265 | 271 | |
266 | 272 | // ---------------------------------------------------------------- |
300 | 306 | st off, c |
301 | 307 | add ptr, pos |
302 | 308 | copy 81, [ptr] + y |
303 | ||
304 | st off, c | |
305 | 309 | } else { |
306 | st on, c | |
310 | ld a, 1 | |
311 | st a, player_died | |
307 | 312 | } |
308 | 313 | |
309 | 314 | // FIXME these trashes, strictly speaking, probably shouldn't be needed, |
313 | 318 | trash ptr |
314 | 319 | trash y |
315 | 320 | trash v |
316 | } else { | |
317 | st off, c | |
318 | 321 | } |
319 | 322 | } |
320 | 323 | |
350 | 353 | st off, c |
351 | 354 | add ptr, pos |
352 | 355 | copy 82, [ptr] + y |
353 | ||
354 | st off, c | |
355 | } else { | |
356 | st on, c | |
357 | 356 | } |
358 | 357 | |
359 | 358 | // FIXME these trashes, strictly speaking, probably shouldn't be needed, |
372 | 371 | copy $ffd8, delta |
373 | 372 | } |
374 | 373 | } |
375 | ||
376 | st off, c | |
377 | 374 | } |
378 | 375 | |
379 | 376 | // ---------------------------------------------------------------- |
407 | 404 | define game_state_play game_state_routine |
408 | 405 | { |
409 | 406 | ld x, 0 |
407 | st x, player_died | |
410 | 408 | for x up to 15 { |
411 | 409 | copy actor_pos + x, pos |
412 | 410 | copy actor_delta + x, delta |
419 | 417 | save x { |
420 | 418 | copy actor_logic + x, dispatch_logic |
421 | 419 | call dispatch_logic |
422 | ||
423 | if c { | |
424 | // Player died! Want no dead! | |
425 | call clear_screen | |
426 | copy game_state_game_over, dispatch_game_state | |
427 | } | |
428 | 420 | } |
429 | 421 | |
430 | 422 | copy pos, actor_pos + x |
431 | 423 | copy delta, actor_delta + x |
424 | } | |
425 | ||
426 | ld a, player_died | |
427 | if not z { | |
428 | // Player died! Want no dead! | |
429 | call clear_screen | |
430 | copy game_state_game_over, dispatch_game_state | |
432 | 431 | } |
433 | 432 | |
434 | 433 | goto save_cinv |
549 | 549 | context.invalidate_range(dest) |
550 | 550 | elif opcode == 'call': |
551 | 551 | type = instr.location.type |
552 | if not isinstance(type, (RoutineType, VectorType)): | |
553 | raise TypeMismatchError(instr, instr.location) | |
552 | 554 | if isinstance(type, VectorType): |
553 | 555 | type = type.of_type |
554 | 556 | for ref in type.inputs: |
36 | 36 | def all_children(self): |
37 | 37 | for attr in self.children_attrs: |
38 | 38 | for child in self.attrs[attr]: |
39 | if child is not None: | |
40 | yield child | |
41 | for subchild in child.all_children(): | |
42 | yield subchild | |
43 | for attr in self.child_attrs: | |
44 | child = self.attrs[attr] | |
45 | if child is not None: | |
39 | 46 | yield child |
40 | 47 | for subchild in child.all_children(): |
41 | 48 | yield subchild |
42 | for attr in self.child_attrs: | |
43 | child = self.attrs[attr] | |
44 | yield child | |
45 | for subchild in child.all_children(): | |
46 | yield subchild | |
47 | 49 | |
48 | 50 | |
49 | 51 | class Program(AST): |
17 | 17 | def __hash__(self): |
18 | 18 | return hash(self.name) |
19 | 19 | |
20 | def backpatch_constraint_labels(self, resolver): | |
21 | def resolve(w): | |
22 | if not isinstance(w, str): | |
23 | return w | |
24 | return resolver(w) | |
25 | if isinstance(self, TableType): | |
26 | self.of_type.backpatch_constraint_labels(resolver) | |
27 | elif isinstance(self, VectorType): | |
28 | self.of_type.backpatch_constraint_labels(resolver) | |
29 | elif isinstance(self, RoutineType): | |
30 | self.inputs = set([resolve(w) for w in self.inputs]) | |
31 | self.outputs = set([resolve(w) for w in self.outputs]) | |
32 | self.trashes = set([resolve(w) for w in self.trashes]) | |
33 | ||
34 | 20 | |
35 | 21 | TYPE_BIT = Type('bit', max_range=(0, 1)) |
36 | 22 | TYPE_BYTE = Type('byte', max_range=(0, 255)) |
40 | 26 | |
41 | 27 | class RoutineType(Type): |
42 | 28 | """This memory location contains the code for a routine.""" |
43 | def __init__(self, inputs=None, outputs=None, trashes=None): | |
29 | def __init__(self, inputs, outputs, trashes): | |
44 | 30 | self.name = 'routine' |
45 | self.inputs = inputs or set() | |
46 | self.outputs = outputs or set() | |
47 | self.trashes = trashes or set() | |
31 | self.inputs = inputs | |
32 | self.outputs = outputs | |
33 | self.trashes = trashes | |
48 | 34 | |
49 | 35 | def __repr__(self): |
50 | 36 | return '%s(%r, inputs=%r, outputs=%r, trashes=%r)' % ( |
17 | 17 | return "%s(%r, %r)" % (self.__class__.__name__, self.ast_node, self.model) |
18 | 18 | |
19 | 19 | |
20 | class ForwardReference(object): | |
21 | def __init__(self, name): | |
22 | self.name = name | |
23 | ||
24 | def __repr__(self): | |
25 | return "%s(%r)" % (self.__class__.__name__, self.name) | |
26 | ||
27 | ||
20 | 28 | class ParsingContext(object): |
21 | 29 | def __init__(self): |
22 | 30 | self.symbols = {} # token -> SymEntry |
44 | 52 | def __init__(self, context, text, filename): |
45 | 53 | self.context = context |
46 | 54 | self.scanner = Scanner(text, filename) |
47 | self.backpatch_instrs = [] | |
48 | 55 | |
49 | 56 | def syntax_error(self, msg): |
50 | 57 | self.scanner.syntax_error(msg) |
65 | 72 | |
66 | 73 | def clear_statics(self): |
67 | 74 | self.context.statics = {} |
75 | ||
76 | # ---- symbol resolution | |
77 | ||
78 | def resolve_symbols(self, program): | |
79 | # This could stand to be better unified. | |
80 | ||
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) | |
86 | if isinstance(type_, TableType): | |
87 | backpatch_constraint_labels(type_.of_type) | |
88 | elif isinstance(type_, VectorType): | |
89 | backpatch_constraint_labels(type_.of_type) | |
90 | 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) | |
99 | ||
100 | def resolve_fwd_reference(obj, field): | |
101 | field_value = getattr(obj, field, None) | |
102 | if isinstance(field_value, ForwardReference): | |
103 | setattr(obj, field, self.lookup(field_value.name)) | |
104 | elif isinstance(field_value, IndexedRef): | |
105 | if isinstance(field_value.ref, ForwardReference): | |
106 | field_value.ref = self.lookup(field_value.ref.name) | |
107 | ||
108 | for node in program.all_children(): | |
109 | if isinstance(node, SingleOp): | |
110 | resolve_fwd_reference(node, 'location') | |
111 | resolve_fwd_reference(node, 'src') | |
112 | resolve_fwd_reference(node, 'dest') | |
68 | 113 | |
69 | 114 | # --- grammar productions |
70 | 115 | |
90 | 135 | routines.append(routine) |
91 | 136 | self.scanner.check_type('EOF') |
92 | 137 | |
93 | # now backpatch the executable types. | |
94 | #for type_name, type_ in self.context.typedefs.items(): | |
95 | # type_.backpatch_constraint_labels(lambda w: self.lookup(w)) | |
96 | for defn in defns: | |
97 | defn.location.type.backpatch_constraint_labels(lambda w: self.lookup(w)) | |
98 | for routine in routines: | |
99 | routine.location.type.backpatch_constraint_labels(lambda w: self.lookup(w)) | |
100 | for instr in self.backpatch_instrs: | |
101 | if instr.opcode in ('call', 'goto'): | |
102 | name = instr.location | |
103 | model = self.lookup(name) | |
104 | if not isinstance(model.type, (RoutineType, VectorType)): | |
105 | self.syntax_error('Illegal call of non-executable "%s"' % name) | |
106 | instr.location = model | |
107 | if instr.opcode in ('copy',) and isinstance(instr.src, str): | |
108 | name = instr.src | |
109 | model = self.lookup(name) | |
110 | if not isinstance(model.type, (RoutineType, VectorType)): | |
111 | self.syntax_error('Illegal copy of non-executable "%s"' % name) | |
112 | instr.src = model | |
113 | ||
114 | return Program(self.scanner.line_number, defns=defns, routines=routines) | |
138 | program = Program(self.scanner.line_number, defns=defns, routines=routines) | |
139 | self.resolve_symbols(program) | |
140 | return program | |
115 | 141 | |
116 | 142 | def typedef(self): |
117 | 143 | self.scanner.expect('typedef') |
251 | 277 | outputs = set(self.labels()) |
252 | 278 | if self.scanner.consume('trashes'): |
253 | 279 | trashes = set(self.labels()) |
254 | return (inputs, outputs, trashes) | |
280 | return ( | |
281 | set([ForwardReference(n) for n in inputs]), | |
282 | set([ForwardReference(n) for n in outputs]), | |
283 | set([ForwardReference(n) for n in trashes]) | |
284 | ) | |
255 | 285 | |
256 | 286 | def routine(self, name): |
257 | 287 | type_ = self.defn_type() |
301 | 331 | accum.append(self.locexpr()) |
302 | 332 | return accum |
303 | 333 | |
304 | def locexpr(self, forward=False): | |
334 | def locexpr(self): | |
305 | 335 | if self.scanner.token in ('on', 'off', 'word') or self.scanner.token in self.context.consts or self.scanner.on_type('integer literal'): |
306 | 336 | return self.const() |
307 | elif forward: | |
337 | else: | |
308 | 338 | name = self.scanner.token |
309 | 339 | self.scanner.scan() |
310 | 340 | loc = self.context.fetch(name) |
311 | if loc is not None: | |
341 | if loc: | |
312 | 342 | return loc |
313 | 343 | else: |
314 | return name | |
315 | else: | |
316 | loc = self.lookup(self.scanner.token) | |
317 | self.scanner.scan() | |
318 | return loc | |
319 | ||
320 | def indlocexpr(self, forward=False): | |
344 | return ForwardReference(name) | |
345 | ||
346 | def indlocexpr(self): | |
321 | 347 | if self.scanner.consume('['): |
322 | 348 | loc = self.locexpr() |
323 | 349 | self.scanner.expect(']') |
328 | 354 | loc = self.locexpr() |
329 | 355 | return AddressRef(loc) |
330 | 356 | else: |
331 | return self.indexed_locexpr(forward=forward) | |
332 | ||
333 | def indexed_locexpr(self, forward=False): | |
334 | loc = self.locexpr(forward=forward) | |
357 | return self.indexed_locexpr() | |
358 | ||
359 | def indexed_locexpr(self): | |
360 | loc = self.locexpr() | |
335 | 361 | if not isinstance(loc, str): |
336 | 362 | index = None |
337 | 363 | if self.scanner.consume('+'): |
426 | 452 | self.scanner.scan() |
427 | 453 | name = self.scanner.token |
428 | 454 | self.scanner.scan() |
429 | instr = SingleOp(self.scanner.line_number, opcode=opcode, location=name, dest=None, src=None) | |
430 | self.backpatch_instrs.append(instr) | |
455 | instr = SingleOp(self.scanner.line_number, opcode=opcode, location=ForwardReference(name), dest=None, src=None) | |
431 | 456 | return instr |
432 | 457 | elif self.scanner.token in ("copy",): |
433 | 458 | opcode = self.scanner.token |
434 | 459 | self.scanner.scan() |
435 | src = self.indlocexpr(forward=True) | |
460 | src = self.indlocexpr() | |
436 | 461 | self.scanner.expect(',') |
437 | 462 | dest = self.indlocexpr() |
438 | 463 | instr = SingleOp(self.scanner.line_number, opcode=opcode, dest=dest, src=src) |
439 | self.backpatch_instrs.append(instr) | |
440 | 464 | return instr |
441 | 465 | elif self.scanner.consume("with"): |
442 | 466 | self.scanner.expect("interrupts") |
194 | 194 | | copy w1, w2 |
195 | 195 | | } |
196 | 196 | = ok |
197 | ||
198 | ### call ### | |
199 | ||
200 | You can't call a non-routine. | |
201 | ||
202 | | byte up | |
203 | | | |
204 | | routine main outputs x, y trashes z, n { | |
205 | | ld x, 0 | |
206 | | ld y, 1 | |
207 | | call up | |
208 | | } | |
209 | ? TypeMismatchError: up | |
210 | ||
211 | | routine main outputs x, y trashes z, n { | |
212 | | ld x, 0 | |
213 | | ld y, 1 | |
214 | | call x | |
215 | | } | |
216 | ? TypeMismatchError: x | |
217 | ||
218 | Nor can you goto a non-routine. | |
219 | ||
220 | | byte foo | |
221 | | | |
222 | | routine main { | |
223 | | goto foo | |
224 | | } | |
225 | ? TypeMismatchError: foo | |
197 | 226 | |
198 | 227 | ### ld ### |
199 | 228 |
403 | 403 | | } |
404 | 404 | ? SyntaxError |
405 | 405 | |
406 | And you can't call a non-routine. | |
407 | ||
408 | | byte up | |
409 | | | |
410 | | define main routine { | |
411 | | ld x, 0 | |
412 | | ld y, 1 | |
413 | | call up | |
414 | | } | |
415 | ? SyntaxError | |
416 | ||
417 | | define main routine { | |
418 | | ld x, 0 | |
419 | | ld y, 1 | |
420 | | call x | |
421 | | } | |
422 | ? SyntaxError | |
423 | ||
424 | 406 | But you can call a routine that is yet to be defined, further on. |
425 | 407 | |
426 | 408 | | define main routine { |
588 | 570 | | } |
589 | 571 | ? SyntaxError |
590 | 572 | |
591 | | byte foo | |
592 | | | |
593 | | define main routine { | |
594 | | goto foo | |
595 | | } | |
596 | ? SyntaxError | |
597 | ||
598 | 573 | Buffers and pointers. |
599 | 574 | |
600 | 575 | | buffer[2048] buf |