From 0a83d905158a51df7d3eeefa6a81df6e1612880e Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Mon, 5 Mar 2018 12:13:15 +0000 Subject: [PATCH] Tighten structure of AST more. --- src/sixtypical/analyzer.py | 126 ++++++++++++++++++++----------------- src/sixtypical/ast.py | 4 +- src/sixtypical/compiler.py | 113 ++++++++++++++++++--------------- src/sixtypical/parser.py | 8 +-- 4 files changed, 138 insertions(+), 113 deletions(-) diff --git a/src/sixtypical/analyzer.py b/src/sixtypical/analyzer.py index 396e116..9c9589e 100644 --- a/src/sixtypical/analyzer.py +++ b/src/sixtypical/analyzer.py @@ -1,6 +1,6 @@ # encoding: UTF-8 -from sixtypical.ast import Program, Routine, Block, Instr +from sixtypical.ast import Program, Routine, Block, Instr, SingleOp, BlockOp, IfOp from sixtypical.model import ( TYPE_BYTE, TYPE_WORD, TableType, BufferType, PointerType, VectorType, RoutineType, @@ -312,7 +312,17 @@ class Analyzer(object): self.analyze_instr(i, context) def analyze_instr(self, instr, context): - assert isinstance(instr, Instr) + if isinstance(instr, SingleOp): + return self.analyze_single_op(instr, context) + elif isinstance(instr, BlockOp): + return self.analyze_block_op(instr, context) + elif isinstance(instr, IfOp): + return self.analyze_if_op(instr, context) + else: + raise NotImplementedError + + def analyze_single_op(self, instr, context): + opcode = instr.opcode dest = instr.dest src = instr.src @@ -429,58 +439,6 @@ class Analyzer(object): context.assert_writeable(ref) context.set_touched(ref) context.set_unmeaningful(ref) - elif opcode == 'if': - incoming_meaningful = set(context.each_meaningful()) - - context1 = context.clone() - context2 = context.clone() - self.analyze_block(instr.block1, context1) - if instr.block2 is not None: - self.analyze_block(instr.block2, context2) - - outgoing_meaningful = set(context1.each_meaningful()) & set(context2.each_meaningful()) - outgoing_trashes = incoming_meaningful - outgoing_meaningful - - # TODO may we need to deal with touched separately here too? - # probably not; if it wasn't meaningful in the first place, it - # doesn't really matter if you modified it or not, coming out. - for ref in context1.each_meaningful(): - if ref in outgoing_trashes: - continue - context2.assert_meaningful( - ref, exception_class=InconsistentInitializationError, - message='initialized in block 1 but not in block 2 of `if {}`'.format(src) - ) - for ref in context2.each_meaningful(): - if ref in outgoing_trashes: - continue - context1.assert_meaningful( - ref, exception_class=InconsistentInitializationError, - message='initialized in block 2 but not in block 1 of `if {}`'.format(src) - ) - - # merge the contexts. this used to be a method called `set_from` - context._touched = set(context1._touched) | set(context2._touched) - context.set_meaningful(*list(outgoing_meaningful)) - context._writeable = set(context1._writeable) | set(context2._writeable) - - for ref in outgoing_trashes: - context.set_touched(ref) - context.set_unmeaningful(ref) - - elif opcode == 'repeat': - # it will always be executed at least once, so analyze it having - # been executed the first time. - self.analyze_block(instr.block, context) - if src is not None: # None indicates 'repeat forever' - context.assert_meaningful(src) - - # now analyze it having been executed a second time, with the context - # of it having already been executed. - self.analyze_block(instr.block, context) - if src is not None: - context.assert_meaningful(src) - elif opcode == 'copy': if dest == REG_A: raise ForbiddenWriteError("{} cannot be used as destination for copy".format(dest)) @@ -563,9 +521,6 @@ class Analyzer(object): context.set_touched(REG_A, FLAG_Z, FLAG_N) context.set_unmeaningful(REG_A, FLAG_Z, FLAG_N) - - elif opcode == 'with-sei': - self.analyze_block(instr.block, context) elif opcode == 'goto': location = instr.location type_ = location.type @@ -591,3 +546,60 @@ class Analyzer(object): context.set_unmeaningful(instr.dest) else: raise NotImplementedError(opcode) + + def analyze_if_op(self, instr, context): + incoming_meaningful = set(context.each_meaningful()) + + context1 = context.clone() + context2 = context.clone() + self.analyze_block(instr.block1, context1) + if instr.block2 is not None: + self.analyze_block(instr.block2, context2) + + outgoing_meaningful = set(context1.each_meaningful()) & set(context2.each_meaningful()) + outgoing_trashes = incoming_meaningful - outgoing_meaningful + + # TODO may we need to deal with touched separately here too? + # probably not; if it wasn't meaningful in the first place, it + # doesn't really matter if you modified it or not, coming out. + for ref in context1.each_meaningful(): + if ref in outgoing_trashes: + continue + context2.assert_meaningful( + ref, exception_class=InconsistentInitializationError, + message='initialized in block 1 but not in block 2 of `if {}`'.format(instr.src) + ) + for ref in context2.each_meaningful(): + if ref in outgoing_trashes: + continue + context1.assert_meaningful( + ref, exception_class=InconsistentInitializationError, + message='initialized in block 2 but not in block 1 of `if {}`'.format(instr.src) + ) + + # merge the contexts. this used to be a method called `set_from` + context._touched = set(context1._touched) | set(context2._touched) + context.set_meaningful(*list(outgoing_meaningful)) + context._writeable = set(context1._writeable) | set(context2._writeable) + + for ref in outgoing_trashes: + context.set_touched(ref) + context.set_unmeaningful(ref) + + def analyze_block_op(self, instr, context): + if instr.opcode == 'repeat': + # it will always be executed at least once, so analyze it having + # been executed the first time. + self.analyze_block(instr.block, context) + if instr.src is not None: # None indicates 'repeat forever' + context.assert_meaningful(instr.src) + + # now analyze it having been executed a second time, with the context + # of it having already been executed. + self.analyze_block(instr.block, context) + if instr.src is not None: + context.assert_meaningful(instr.src) + elif instr.opcode == 'with-sei': + self.analyze_block(instr.block, context) + else: + raise NotImplementedError(opcode) diff --git a/src/sixtypical/ast.py b/src/sixtypical/ast.py index 771fc1d..e992381 100644 --- a/src/sixtypical/ast.py +++ b/src/sixtypical/ast.py @@ -73,10 +73,10 @@ class SingleOp(Instr): class BlockOp(Instr): - value_attrs = ('opcode', 'dest', 'src', 'inverted') + value_attrs = ('opcode', 'src', 'inverted') child_attrs = ('block',) class IfOp(Instr): - value_attrs = ('opcode', 'dest', 'src', 'inverted') + value_attrs = ('src', 'inverted') child_attrs = ('block1', 'block2',) diff --git a/src/sixtypical/compiler.py b/src/sixtypical/compiler.py index 6ea7c4e..8bf37c4 100644 --- a/src/sixtypical/compiler.py +++ b/src/sixtypical/compiler.py @@ -1,6 +1,6 @@ # encoding: UTF-8 -from sixtypical.ast import Program, Routine, Block, Instr +from sixtypical.ast import Program, Routine, Block, Instr, SingleOp, BlockOp, IfOp from sixtypical.model import ( ConstantRef, LocationRef, IndexedRef, IndirectRef, AddressRef, TYPE_BIT, TYPE_BYTE, TYPE_WORD, @@ -142,7 +142,17 @@ class Compiler(object): self.compile_instr(instr) def compile_instr(self, instr): - assert isinstance(instr, Instr) + if isinstance(instr, SingleOp): + return self.compile_single_op(instr) + elif isinstance(instr, BlockOp): + return self.compile_block_op(instr) + elif isinstance(instr, IfOp): + return self.compile_if_op(instr) + else: + raise NotImplementedError + + def compile_single_op(self, instr): + opcode = instr.opcode dest = instr.dest src = instr.src @@ -356,53 +366,6 @@ class Compiler(object): self.emitter.emit(JMP(Indirect(label))) else: raise NotImplementedError - elif opcode == 'if': - cls = { - False: { - 'c': BCC, - 'z': BNE, - }, - True: { - 'c': BCS, - 'z': BEQ, - }, - }[instr.inverted].get(src.name) - if cls is None: - raise UnsupportedOpcodeError(instr) - else_label = Label('else_label') - self.emitter.emit(cls(Relative(else_label))) - self.compile_block(instr.block1) - if instr.block2 is not None: - end_label = Label('end_label') - self.emitter.emit(JMP(Absolute(end_label))) - self.emitter.resolve_label(else_label) - self.compile_block(instr.block2) - self.emitter.resolve_label(end_label) - else: - self.emitter.resolve_label(else_label) - elif opcode == 'repeat': - top_label = self.emitter.make_label() - self.compile_block(instr.block) - if src is None: # indicates 'repeat forever' - self.emitter.emit(JMP(Absolute(top_label))) - else: - cls = { - False: { - 'c': BCC, - 'z': BNE, - }, - True: { - 'c': BCS, - 'z': BEQ, - }, - }[instr.inverted].get(src.name) - if cls is None: - raise UnsupportedOpcodeError(instr) - self.emitter.emit(cls(Relative(top_label))) - elif opcode == 'with-sei': - self.emitter.emit(SEI()) - self.compile_block(instr.block) - self.emitter.emit(CLI()) elif opcode == 'copy': if isinstance(src, (LocationRef, ConstantRef)) and isinstance(dest, IndirectRef): if src.type == TYPE_BYTE and isinstance(dest.ref.type, PointerType): @@ -532,3 +495,55 @@ class Compiler(object): pass else: raise NotImplementedError(opcode) + + def compile_if_op(self, instr): + cls = { + False: { + 'c': BCC, + 'z': BNE, + }, + True: { + 'c': BCS, + 'z': BEQ, + }, + }[instr.inverted].get(instr.src.name) + if cls is None: + raise UnsupportedOpcodeError(instr) + else_label = Label('else_label') + self.emitter.emit(cls(Relative(else_label))) + self.compile_block(instr.block1) + if instr.block2 is not None: + end_label = Label('end_label') + self.emitter.emit(JMP(Absolute(end_label))) + self.emitter.resolve_label(else_label) + self.compile_block(instr.block2) + self.emitter.resolve_label(end_label) + else: + self.emitter.resolve_label(else_label) + + def compile_block_op(self, instr): + if instr.opcode == 'repeat': + top_label = self.emitter.make_label() + self.compile_block(instr.block) + if instr.src is None: # indicates 'repeat forever' + self.emitter.emit(JMP(Absolute(top_label))) + else: + cls = { + False: { + 'c': BCC, + 'z': BNE, + }, + True: { + 'c': BCS, + 'z': BEQ, + }, + }[instr.inverted].get(instr.src.name) + if cls is None: + raise UnsupportedOpcodeError(instr) + self.emitter.emit(cls(Relative(top_label))) + elif instr.opcode == 'with-sei': + self.emitter.emit(SEI()) + self.compile_block(instr.block) + self.emitter.emit(CLI()) + else: + raise NotImplementedError(opcode) diff --git a/src/sixtypical/parser.py b/src/sixtypical/parser.py index 8fd1059..c4c67ca 100644 --- a/src/sixtypical/parser.py +++ b/src/sixtypical/parser.py @@ -352,8 +352,7 @@ class Parser(object): block2 = None if self.scanner.consume('else'): block2 = self.block() - return IfOp(opcode='if', dest=None, src=src, - block1=block1, block2=block2, inverted=inverted) + return IfOp(src=src, block1=block1, block2=block2, inverted=inverted) elif self.scanner.consume('repeat'): inverted = False src = None @@ -364,8 +363,7 @@ class Parser(object): src = self.locexpr() else: self.scanner.expect('forever') - return BlockOp(opcode='repeat', dest=None, src=src, - block=block, inverted=inverted) + return BlockOp(opcode='repeat', src=src, block=block, inverted=inverted) elif self.scanner.token in ("ld",): # the same as add, sub, cmp etc below, except supports an indlocexpr for the src opcode = self.scanner.token @@ -417,7 +415,7 @@ class Parser(object): self.scanner.expect("interrupts") self.scanner.expect("off") block = self.block() - return BlockOp(opcode='with-sei', dest=None, src=None, block=block) + return BlockOp(opcode='with-sei', src=None, block=block) elif self.scanner.consume("trash"): dest = self.locexpr() return SingleOp(opcode='trash', src=None, dest=dest)