1
0
mirror of https://github.com/catseye/SixtyPical.git synced 2025-01-10 02:29:23 +00:00

Tighten structure of AST more.

This commit is contained in:
Chris Pressey 2018-03-05 12:13:15 +00:00
parent 4b43ba734e
commit 0a83d90515
4 changed files with 138 additions and 113 deletions

View File

@ -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)

View File

@ -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',)

View File

@ -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)

View File

@ -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)