git @ Cat's Eye Technologies SixtyPical / 0c63de9
Merge branch 'develop-0.17' into remove-legacy-syntax Chris Pressey 3 years ago
9 changed file(s) with 150 addition(s) and 132 deletion(s). Raw diff Collapse all Expand all
44 ----
55
66 * `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.
712 * Split TODO off into own file.
813 * `sixtypical` no longer writes the compiled binary to standard
914 output. The `--output` command-line argument should be given
00 TODO for SixtyPical
11 ===================
22
3 ### `low` and `high` address operators
3 ### 16-bit `cmp`
44
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`.
67
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.
1010
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.
1713
1814 ### Save values to other-than-the-stack
1915
2622 Which uses some other storage location instead of the stack. A local static
2723 would be a good candidate for such.
2824
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
3425 ### Associate each pointer with the buffer it points into
3526
3627 Check that the buffer being read or written to through pointer, appears in appropriate
3728 inputs or outputs set.
3829
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
4031 that pointer came from.
4132
4233 When we write through that pointer, we need to set that buffer as written.
7768 More generally, define a block as having zero or one `goto`s at the end. (and `goto`s cannot
7869 appear elsewhere.)
7970
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.
8273
8374 And - once we have this - why do we need `goto` to be in tail position, strictly?
8475 As long as the routine has consistent type context every place it exits, that should be fine.
9081 One such library routine might be an `interrupt routine` type for various architectures.
9182 Since "the supervisor" has stored values on the stack, we should be able to trash them
9283 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.
3232 typedef routine
3333 inputs joy2, press_fire_msg, dispatch_game_state,
3434 actor_pos, actor_delta, actor_logic,
35 player_died,
3536 screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4
3637 outputs dispatch_game_state,
3738 actor_pos, actor_delta, actor_logic,
39 player_died,
3840 screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4
3941 trashes a, x, y, c, z, n, v, pos, new_pos, delta, ptr, dispatch_logic
4042 game_state_routine
4446 //
4547 // Routines that conform to this type also follow this convention:
4648 //
47 // Set carry if the player perished. Carry clear otherwise.
49 // Set player_died to 1 if the player perished. Unchanged otherwise.
4850 //
4951
5052 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
5456 logic_routine
5557
5658 // ----------------------------------------------------------------
8587
8688 word table[256] actor_delta
8789 word delta
90
91 byte player_died
8892
8993 vector logic_routine table[256] actor_logic
9094 vector logic_routine dispatch_logic
240244
241245 routine init_game
242246 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
244248 trashes pos, a, y, z, n, c, v
245249 {
246250 ld y, 0
258262 } until z
259263
260264 ld y, 0
261 copy word 0, actor_pos + y
265 copy word 40, actor_pos + y
262266 copy word 0, actor_delta + y
263267 copy player_logic, actor_logic + y
268
269 st y, player_died
264270 }
265271
266272 // ----------------------------------------------------------------
300306 st off, c
301307 add ptr, pos
302308 copy 81, [ptr] + y
303
304 st off, c
305309 } else {
306 st on, c
310 ld a, 1
311 st a, player_died
307312 }
308313
309314 // FIXME these trashes, strictly speaking, probably shouldn't be needed,
313318 trash ptr
314319 trash y
315320 trash v
316 } else {
317 st off, c
318321 }
319322 }
320323
350353 st off, c
351354 add ptr, pos
352355 copy 82, [ptr] + y
353
354 st off, c
355 } else {
356 st on, c
357356 }
358357
359358 // FIXME these trashes, strictly speaking, probably shouldn't be needed,
372371 copy $ffd8, delta
373372 }
374373 }
375
376 st off, c
377374 }
378375
379376 // ----------------------------------------------------------------
407404 define game_state_play game_state_routine
408405 {
409406 ld x, 0
407 st x, player_died
410408 for x up to 15 {
411409 copy actor_pos + x, pos
412410 copy actor_delta + x, delta
419417 save x {
420418 copy actor_logic + x, dispatch_logic
421419 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 }
428420 }
429421
430422 copy pos, actor_pos + x
431423 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
432431 }
433432
434433 goto save_cinv
549549 context.invalidate_range(dest)
550550 elif opcode == 'call':
551551 type = instr.location.type
552 if not isinstance(type, (RoutineType, VectorType)):
553 raise TypeMismatchError(instr, instr.location)
552554 if isinstance(type, VectorType):
553555 type = type.of_type
554556 for ref in type.inputs:
3636 def all_children(self):
3737 for attr in self.children_attrs:
3838 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:
3946 yield child
4047 for subchild in child.all_children():
4148 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
4749
4850
4951 class Program(AST):
1717 def __hash__(self):
1818 return hash(self.name)
1919
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
3420
3521 TYPE_BIT = Type('bit', max_range=(0, 1))
3622 TYPE_BYTE = Type('byte', max_range=(0, 255))
4026
4127 class RoutineType(Type):
4228 """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):
4430 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
4834
4935 def __repr__(self):
5036 return '%s(%r, inputs=%r, outputs=%r, trashes=%r)' % (
1717 return "%s(%r, %r)" % (self.__class__.__name__, self.ast_node, self.model)
1818
1919
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
2028 class ParsingContext(object):
2129 def __init__(self):
2230 self.symbols = {} # token -> SymEntry
4452 def __init__(self, context, text, filename):
4553 self.context = context
4654 self.scanner = Scanner(text, filename)
47 self.backpatch_instrs = []
4855
4956 def syntax_error(self, msg):
5057 self.scanner.syntax_error(msg)
6572
6673 def clear_statics(self):
6774 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')
68113
69114 # --- grammar productions
70115
90135 routines.append(routine)
91136 self.scanner.check_type('EOF')
92137
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
115141
116142 def typedef(self):
117143 self.scanner.expect('typedef')
251277 outputs = set(self.labels())
252278 if self.scanner.consume('trashes'):
253279 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 )
255285
256286 def routine(self, name):
257287 type_ = self.defn_type()
301331 accum.append(self.locexpr())
302332 return accum
303333
304 def locexpr(self, forward=False):
334 def locexpr(self):
305335 if self.scanner.token in ('on', 'off', 'word') or self.scanner.token in self.context.consts or self.scanner.on_type('integer literal'):
306336 return self.const()
307 elif forward:
337 else:
308338 name = self.scanner.token
309339 self.scanner.scan()
310340 loc = self.context.fetch(name)
311 if loc is not None:
341 if loc:
312342 return loc
313343 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):
321347 if self.scanner.consume('['):
322348 loc = self.locexpr()
323349 self.scanner.expect(']')
328354 loc = self.locexpr()
329355 return AddressRef(loc)
330356 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()
335361 if not isinstance(loc, str):
336362 index = None
337363 if self.scanner.consume('+'):
426452 self.scanner.scan()
427453 name = self.scanner.token
428454 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)
431456 return instr
432457 elif self.scanner.token in ("copy",):
433458 opcode = self.scanner.token
434459 self.scanner.scan()
435 src = self.indlocexpr(forward=True)
460 src = self.indlocexpr()
436461 self.scanner.expect(',')
437462 dest = self.indlocexpr()
438463 instr = SingleOp(self.scanner.line_number, opcode=opcode, dest=dest, src=src)
439 self.backpatch_instrs.append(instr)
440464 return instr
441465 elif self.scanner.consume("with"):
442466 self.scanner.expect("interrupts")
194194 | copy w1, w2
195195 | }
196196 = 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
197226
198227 ### ld ###
199228
403403 | }
404404 ? SyntaxError
405405
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
424406 But you can call a routine that is yet to be defined, further on.
425407
426408 | define main routine {
588570 | }
589571 ? SyntaxError
590572
591 | byte foo
592 |
593 | define main routine {
594 | goto foo
595 | }
596 ? SyntaxError
597
598573 Buffers and pointers.
599574
600575 | buffer[2048] buf