Tighten structure of AST more.
Chris Pressey
7 years ago
0 | 0 | # encoding: UTF-8 |
1 | 1 | |
2 | from sixtypical.ast import Program, Routine, Block, Instr | |
2 | from sixtypical.ast import Program, Routine, Block, Instr, SingleOp, BlockOp, IfOp | |
3 | 3 | from sixtypical.model import ( |
4 | 4 | TYPE_BYTE, TYPE_WORD, |
5 | 5 | TableType, BufferType, PointerType, VectorType, RoutineType, |
311 | 311 | self.analyze_instr(i, context) |
312 | 312 | |
313 | 313 | def analyze_instr(self, instr, context): |
314 | assert isinstance(instr, Instr) | |
314 | if isinstance(instr, SingleOp): | |
315 | return self.analyze_single_op(instr, context) | |
316 | elif isinstance(instr, BlockOp): | |
317 | return self.analyze_block_op(instr, context) | |
318 | elif isinstance(instr, IfOp): | |
319 | return self.analyze_if_op(instr, context) | |
320 | else: | |
321 | raise NotImplementedError | |
322 | ||
323 | def analyze_single_op(self, instr, context): | |
324 | ||
315 | 325 | opcode = instr.opcode |
316 | 326 | dest = instr.dest |
317 | 327 | src = instr.src |
428 | 438 | context.assert_writeable(ref) |
429 | 439 | context.set_touched(ref) |
430 | 440 | context.set_unmeaningful(ref) |
431 | elif opcode == 'if': | |
432 | incoming_meaningful = set(context.each_meaningful()) | |
433 | ||
434 | context1 = context.clone() | |
435 | context2 = context.clone() | |
436 | self.analyze_block(instr.block1, context1) | |
437 | if instr.block2 is not None: | |
438 | self.analyze_block(instr.block2, context2) | |
439 | ||
440 | outgoing_meaningful = set(context1.each_meaningful()) & set(context2.each_meaningful()) | |
441 | outgoing_trashes = incoming_meaningful - outgoing_meaningful | |
442 | ||
443 | # TODO may we need to deal with touched separately here too? | |
444 | # probably not; if it wasn't meaningful in the first place, it | |
445 | # doesn't really matter if you modified it or not, coming out. | |
446 | for ref in context1.each_meaningful(): | |
447 | if ref in outgoing_trashes: | |
448 | continue | |
449 | context2.assert_meaningful( | |
450 | ref, exception_class=InconsistentInitializationError, | |
451 | message='initialized in block 1 but not in block 2 of `if {}`'.format(src) | |
452 | ) | |
453 | for ref in context2.each_meaningful(): | |
454 | if ref in outgoing_trashes: | |
455 | continue | |
456 | context1.assert_meaningful( | |
457 | ref, exception_class=InconsistentInitializationError, | |
458 | message='initialized in block 2 but not in block 1 of `if {}`'.format(src) | |
459 | ) | |
460 | ||
461 | # merge the contexts. this used to be a method called `set_from` | |
462 | context._touched = set(context1._touched) | set(context2._touched) | |
463 | context.set_meaningful(*list(outgoing_meaningful)) | |
464 | context._writeable = set(context1._writeable) | set(context2._writeable) | |
465 | ||
466 | for ref in outgoing_trashes: | |
467 | context.set_touched(ref) | |
468 | context.set_unmeaningful(ref) | |
469 | ||
470 | elif opcode == 'repeat': | |
471 | # it will always be executed at least once, so analyze it having | |
472 | # been executed the first time. | |
473 | self.analyze_block(instr.block, context) | |
474 | if src is not None: # None indicates 'repeat forever' | |
475 | context.assert_meaningful(src) | |
476 | ||
477 | # now analyze it having been executed a second time, with the context | |
478 | # of it having already been executed. | |
479 | self.analyze_block(instr.block, context) | |
480 | if src is not None: | |
481 | context.assert_meaningful(src) | |
482 | ||
483 | 441 | elif opcode == 'copy': |
484 | 442 | if dest == REG_A: |
485 | 443 | raise ForbiddenWriteError("{} cannot be used as destination for copy".format(dest)) |
562 | 520 | |
563 | 521 | context.set_touched(REG_A, FLAG_Z, FLAG_N) |
564 | 522 | context.set_unmeaningful(REG_A, FLAG_Z, FLAG_N) |
565 | ||
566 | elif opcode == 'with-sei': | |
567 | self.analyze_block(instr.block, context) | |
568 | 523 | elif opcode == 'goto': |
569 | 524 | location = instr.location |
570 | 525 | type_ = location.type |
590 | 545 | context.set_unmeaningful(instr.dest) |
591 | 546 | else: |
592 | 547 | raise NotImplementedError(opcode) |
548 | ||
549 | def analyze_if_op(self, instr, context): | |
550 | incoming_meaningful = set(context.each_meaningful()) | |
551 | ||
552 | context1 = context.clone() | |
553 | context2 = context.clone() | |
554 | self.analyze_block(instr.block1, context1) | |
555 | if instr.block2 is not None: | |
556 | self.analyze_block(instr.block2, context2) | |
557 | ||
558 | outgoing_meaningful = set(context1.each_meaningful()) & set(context2.each_meaningful()) | |
559 | outgoing_trashes = incoming_meaningful - outgoing_meaningful | |
560 | ||
561 | # TODO may we need to deal with touched separately here too? | |
562 | # probably not; if it wasn't meaningful in the first place, it | |
563 | # doesn't really matter if you modified it or not, coming out. | |
564 | for ref in context1.each_meaningful(): | |
565 | if ref in outgoing_trashes: | |
566 | continue | |
567 | context2.assert_meaningful( | |
568 | ref, exception_class=InconsistentInitializationError, | |
569 | message='initialized in block 1 but not in block 2 of `if {}`'.format(instr.src) | |
570 | ) | |
571 | for ref in context2.each_meaningful(): | |
572 | if ref in outgoing_trashes: | |
573 | continue | |
574 | context1.assert_meaningful( | |
575 | ref, exception_class=InconsistentInitializationError, | |
576 | message='initialized in block 2 but not in block 1 of `if {}`'.format(instr.src) | |
577 | ) | |
578 | ||
579 | # merge the contexts. this used to be a method called `set_from` | |
580 | context._touched = set(context1._touched) | set(context2._touched) | |
581 | context.set_meaningful(*list(outgoing_meaningful)) | |
582 | context._writeable = set(context1._writeable) | set(context2._writeable) | |
583 | ||
584 | for ref in outgoing_trashes: | |
585 | context.set_touched(ref) | |
586 | context.set_unmeaningful(ref) | |
587 | ||
588 | def analyze_block_op(self, instr, context): | |
589 | if instr.opcode == 'repeat': | |
590 | # it will always be executed at least once, so analyze it having | |
591 | # been executed the first time. | |
592 | self.analyze_block(instr.block, context) | |
593 | if instr.src is not None: # None indicates 'repeat forever' | |
594 | context.assert_meaningful(instr.src) | |
595 | ||
596 | # now analyze it having been executed a second time, with the context | |
597 | # of it having already been executed. | |
598 | self.analyze_block(instr.block, context) | |
599 | if instr.src is not None: | |
600 | context.assert_meaningful(instr.src) | |
601 | elif instr.opcode == 'with-sei': | |
602 | self.analyze_block(instr.block, context) | |
603 | else: | |
604 | raise NotImplementedError(opcode) |
72 | 72 | |
73 | 73 | |
74 | 74 | class BlockOp(Instr): |
75 | value_attrs = ('opcode', 'dest', 'src', 'inverted') | |
75 | value_attrs = ('opcode', 'src', 'inverted') | |
76 | 76 | child_attrs = ('block',) |
77 | 77 | |
78 | 78 | |
79 | 79 | class IfOp(Instr): |
80 | value_attrs = ('opcode', 'dest', 'src', 'inverted') | |
80 | value_attrs = ('src', 'inverted') | |
81 | 81 | child_attrs = ('block1', 'block2',) |
0 | 0 | # encoding: UTF-8 |
1 | 1 | |
2 | from sixtypical.ast import Program, Routine, Block, Instr | |
2 | from sixtypical.ast import Program, Routine, Block, Instr, SingleOp, BlockOp, IfOp | |
3 | 3 | from sixtypical.model import ( |
4 | 4 | ConstantRef, LocationRef, IndexedRef, IndirectRef, AddressRef, |
5 | 5 | TYPE_BIT, TYPE_BYTE, TYPE_WORD, |
141 | 141 | self.compile_instr(instr) |
142 | 142 | |
143 | 143 | def compile_instr(self, instr): |
144 | assert isinstance(instr, Instr) | |
144 | if isinstance(instr, SingleOp): | |
145 | return self.compile_single_op(instr) | |
146 | elif isinstance(instr, BlockOp): | |
147 | return self.compile_block_op(instr) | |
148 | elif isinstance(instr, IfOp): | |
149 | return self.compile_if_op(instr) | |
150 | else: | |
151 | raise NotImplementedError | |
152 | ||
153 | def compile_single_op(self, instr): | |
154 | ||
145 | 155 | opcode = instr.opcode |
146 | 156 | dest = instr.dest |
147 | 157 | src = instr.src |
355 | 365 | self.emitter.emit(JMP(Indirect(label))) |
356 | 366 | else: |
357 | 367 | raise NotImplementedError |
358 | elif opcode == 'if': | |
359 | cls = { | |
360 | False: { | |
361 | 'c': BCC, | |
362 | 'z': BNE, | |
363 | }, | |
364 | True: { | |
365 | 'c': BCS, | |
366 | 'z': BEQ, | |
367 | }, | |
368 | }[instr.inverted].get(src.name) | |
369 | if cls is None: | |
370 | raise UnsupportedOpcodeError(instr) | |
371 | else_label = Label('else_label') | |
372 | self.emitter.emit(cls(Relative(else_label))) | |
373 | self.compile_block(instr.block1) | |
374 | if instr.block2 is not None: | |
375 | end_label = Label('end_label') | |
376 | self.emitter.emit(JMP(Absolute(end_label))) | |
377 | self.emitter.resolve_label(else_label) | |
378 | self.compile_block(instr.block2) | |
379 | self.emitter.resolve_label(end_label) | |
380 | else: | |
381 | self.emitter.resolve_label(else_label) | |
382 | elif opcode == 'repeat': | |
383 | top_label = self.emitter.make_label() | |
384 | self.compile_block(instr.block) | |
385 | if src is None: # indicates 'repeat forever' | |
386 | self.emitter.emit(JMP(Absolute(top_label))) | |
387 | else: | |
388 | cls = { | |
389 | False: { | |
390 | 'c': BCC, | |
391 | 'z': BNE, | |
392 | }, | |
393 | True: { | |
394 | 'c': BCS, | |
395 | 'z': BEQ, | |
396 | }, | |
397 | }[instr.inverted].get(src.name) | |
398 | if cls is None: | |
399 | raise UnsupportedOpcodeError(instr) | |
400 | self.emitter.emit(cls(Relative(top_label))) | |
401 | elif opcode == 'with-sei': | |
402 | self.emitter.emit(SEI()) | |
403 | self.compile_block(instr.block) | |
404 | self.emitter.emit(CLI()) | |
405 | 368 | elif opcode == 'copy': |
406 | 369 | if isinstance(src, (LocationRef, ConstantRef)) and isinstance(dest, IndirectRef): |
407 | 370 | if src.type == TYPE_BYTE and isinstance(dest.ref.type, PointerType): |
531 | 494 | pass |
532 | 495 | else: |
533 | 496 | raise NotImplementedError(opcode) |
497 | ||
498 | def compile_if_op(self, instr): | |
499 | cls = { | |
500 | False: { | |
501 | 'c': BCC, | |
502 | 'z': BNE, | |
503 | }, | |
504 | True: { | |
505 | 'c': BCS, | |
506 | 'z': BEQ, | |
507 | }, | |
508 | }[instr.inverted].get(instr.src.name) | |
509 | if cls is None: | |
510 | raise UnsupportedOpcodeError(instr) | |
511 | else_label = Label('else_label') | |
512 | self.emitter.emit(cls(Relative(else_label))) | |
513 | self.compile_block(instr.block1) | |
514 | if instr.block2 is not None: | |
515 | end_label = Label('end_label') | |
516 | self.emitter.emit(JMP(Absolute(end_label))) | |
517 | self.emitter.resolve_label(else_label) | |
518 | self.compile_block(instr.block2) | |
519 | self.emitter.resolve_label(end_label) | |
520 | else: | |
521 | self.emitter.resolve_label(else_label) | |
522 | ||
523 | def compile_block_op(self, instr): | |
524 | if instr.opcode == 'repeat': | |
525 | top_label = self.emitter.make_label() | |
526 | self.compile_block(instr.block) | |
527 | if instr.src is None: # indicates 'repeat forever' | |
528 | self.emitter.emit(JMP(Absolute(top_label))) | |
529 | else: | |
530 | cls = { | |
531 | False: { | |
532 | 'c': BCC, | |
533 | 'z': BNE, | |
534 | }, | |
535 | True: { | |
536 | 'c': BCS, | |
537 | 'z': BEQ, | |
538 | }, | |
539 | }[instr.inverted].get(instr.src.name) | |
540 | if cls is None: | |
541 | raise UnsupportedOpcodeError(instr) | |
542 | self.emitter.emit(cls(Relative(top_label))) | |
543 | elif instr.opcode == 'with-sei': | |
544 | self.emitter.emit(SEI()) | |
545 | self.compile_block(instr.block) | |
546 | self.emitter.emit(CLI()) | |
547 | else: | |
548 | raise NotImplementedError(opcode) |
351 | 351 | block2 = None |
352 | 352 | if self.scanner.consume('else'): |
353 | 353 | block2 = self.block() |
354 | return IfOp(opcode='if', dest=None, src=src, | |
355 | block1=block1, block2=block2, inverted=inverted) | |
354 | return IfOp(src=src, block1=block1, block2=block2, inverted=inverted) | |
356 | 355 | elif self.scanner.consume('repeat'): |
357 | 356 | inverted = False |
358 | 357 | src = None |
363 | 362 | src = self.locexpr() |
364 | 363 | else: |
365 | 364 | self.scanner.expect('forever') |
366 | return BlockOp(opcode='repeat', dest=None, src=src, | |
367 | block=block, inverted=inverted) | |
365 | return BlockOp(opcode='repeat', src=src, block=block, inverted=inverted) | |
368 | 366 | elif self.scanner.token in ("ld",): |
369 | 367 | # the same as add, sub, cmp etc below, except supports an indlocexpr for the src |
370 | 368 | opcode = self.scanner.token |
416 | 414 | self.scanner.expect("interrupts") |
417 | 415 | self.scanner.expect("off") |
418 | 416 | block = self.block() |
419 | return BlockOp(opcode='with-sei', dest=None, src=None, block=block) | |
417 | return BlockOp(opcode='with-sei', src=None, block=block) | |
420 | 418 | elif self.scanner.consume("trash"): |
421 | 419 | dest = self.locexpr() |
422 | 420 | return SingleOp(opcode='trash', src=None, dest=dest) |