mirror of
https://github.com/catseye/SixtyPical.git
synced 2025-04-07 23:37:23 +00:00
Distinct AST nodes for call and goto instructions.
This commit is contained in:
parent
bd462d6d8b
commit
4615d8d054
@ -1,7 +1,7 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
from sixtypical.ast import (
|
||||
Program, Routine, Block, SingleOp, If, Repeat, For, WithInterruptsOff, Save, PointInto
|
||||
Program, Routine, Block, SingleOp, Call, GoTo, If, Repeat, For, WithInterruptsOff, Save, PointInto
|
||||
)
|
||||
from sixtypical.model import (
|
||||
TYPE_BYTE, TYPE_WORD,
|
||||
@ -493,6 +493,10 @@ class Analyzer(object):
|
||||
def analyze_instr(self, instr, context):
|
||||
if isinstance(instr, SingleOp):
|
||||
self.analyze_single_op(instr, context)
|
||||
elif isinstance(instr, Call):
|
||||
self.analyze_call(instr, context)
|
||||
elif isinstance(instr, GoTo):
|
||||
self.analyze_goto(instr, context)
|
||||
elif isinstance(instr, If):
|
||||
self.analyze_if(instr, context)
|
||||
elif isinstance(instr, Repeat):
|
||||
@ -667,20 +671,6 @@ class Analyzer(object):
|
||||
self.assert_type(TYPE_BYTE, dest)
|
||||
context.set_written(dest, FLAG_Z, FLAG_N, FLAG_C)
|
||||
context.invalidate_range(dest)
|
||||
elif opcode == 'call':
|
||||
type = instr.location.type
|
||||
if not isinstance(type, (RoutineType, VectorType)):
|
||||
raise TypeMismatchError(instr, instr.location)
|
||||
if isinstance(type, VectorType):
|
||||
type = type.of_type
|
||||
for ref in type.inputs:
|
||||
context.assert_meaningful(ref)
|
||||
for ref in type.outputs:
|
||||
context.set_written(ref)
|
||||
for ref in type.trashes:
|
||||
context.assert_writeable(ref)
|
||||
context.set_touched(ref)
|
||||
context.set_unmeaningful(ref)
|
||||
elif opcode == 'copy':
|
||||
if dest == REG_A:
|
||||
raise ForbiddenWriteError(instr, "{} cannot be used as destination for copy".format(dest))
|
||||
@ -789,59 +779,6 @@ class Analyzer(object):
|
||||
|
||||
context.set_touched(REG_A, FLAG_Z, FLAG_N)
|
||||
context.set_unmeaningful(REG_A, FLAG_Z, FLAG_N)
|
||||
elif opcode == 'goto':
|
||||
location = instr.location
|
||||
type_ = location.type
|
||||
|
||||
if not isinstance(type_, (RoutineType, VectorType)):
|
||||
raise TypeMismatchError(instr, location)
|
||||
|
||||
# assert that the dest routine's inputs are all initialized
|
||||
if isinstance(type_, VectorType):
|
||||
type_ = type_.of_type
|
||||
for ref in type_.inputs:
|
||||
context.assert_meaningful(ref)
|
||||
|
||||
# and that this routine's trashes and output constraints are a
|
||||
# superset of the called routine's
|
||||
current_type = self.current_routine.location.type
|
||||
self.assert_affected_within('outputs', type_, current_type)
|
||||
self.assert_affected_within('trashes', type_, current_type)
|
||||
|
||||
context.encounter_gotos(set([instr.location]))
|
||||
|
||||
# Now that we have encountered a goto, we update the
|
||||
# context here to match what someone calling the goto'ed
|
||||
# function directly, would expect. (which makes sense
|
||||
# when you think about it; if this goto's F, then calling
|
||||
# this is like calling F, from the perspective of what is
|
||||
# returned.)
|
||||
#
|
||||
# However, this isn't the current context anymore. This
|
||||
# is an exit context of this routine.
|
||||
|
||||
exit_context = context.clone()
|
||||
|
||||
for ref in type_.outputs:
|
||||
exit_context.set_touched(ref) # ?
|
||||
exit_context.set_written(ref)
|
||||
|
||||
for ref in type_.trashes:
|
||||
exit_context.assert_writeable(ref)
|
||||
exit_context.set_touched(ref)
|
||||
exit_context.set_unmeaningful(ref)
|
||||
|
||||
self.exit_contexts.append(exit_context)
|
||||
|
||||
# When we get to the end, we'll check that all the
|
||||
# exit contexts are consistent with each other.
|
||||
|
||||
# We set the current context as having terminated.
|
||||
# If we are in a branch, the merge will deal with
|
||||
# having terminated. If we are at the end of the
|
||||
# routine, the routine end will deal with that.
|
||||
|
||||
context.set_terminated()
|
||||
|
||||
elif opcode == 'trash':
|
||||
context.set_touched(instr.dest)
|
||||
@ -851,6 +788,75 @@ class Analyzer(object):
|
||||
else:
|
||||
raise NotImplementedError(opcode)
|
||||
|
||||
def analyze_call(self, instr, context):
|
||||
type = instr.location.type
|
||||
if not isinstance(type, (RoutineType, VectorType)):
|
||||
raise TypeMismatchError(instr, instr.location)
|
||||
if isinstance(type, VectorType):
|
||||
type = type.of_type
|
||||
for ref in type.inputs:
|
||||
context.assert_meaningful(ref)
|
||||
for ref in type.outputs:
|
||||
context.set_written(ref)
|
||||
for ref in type.trashes:
|
||||
context.assert_writeable(ref)
|
||||
context.set_touched(ref)
|
||||
context.set_unmeaningful(ref)
|
||||
|
||||
def analyze_goto(self, instr, context):
|
||||
location = instr.location
|
||||
type_ = location.type
|
||||
|
||||
if not isinstance(type_, (RoutineType, VectorType)):
|
||||
raise TypeMismatchError(instr, location)
|
||||
|
||||
# assert that the dest routine's inputs are all initialized
|
||||
if isinstance(type_, VectorType):
|
||||
type_ = type_.of_type
|
||||
for ref in type_.inputs:
|
||||
context.assert_meaningful(ref)
|
||||
|
||||
# and that this routine's trashes and output constraints are a
|
||||
# superset of the called routine's
|
||||
current_type = self.current_routine.location.type
|
||||
self.assert_affected_within('outputs', type_, current_type)
|
||||
self.assert_affected_within('trashes', type_, current_type)
|
||||
|
||||
context.encounter_gotos(set([instr.location]))
|
||||
|
||||
# Now that we have encountered a goto, we update the
|
||||
# context here to match what someone calling the goto'ed
|
||||
# function directly, would expect. (which makes sense
|
||||
# when you think about it; if this goto's F, then calling
|
||||
# this is like calling F, from the perspective of what is
|
||||
# returned.)
|
||||
#
|
||||
# However, this isn't the current context anymore. This
|
||||
# is an exit context of this routine.
|
||||
|
||||
exit_context = context.clone()
|
||||
|
||||
for ref in type_.outputs:
|
||||
exit_context.set_touched(ref) # ?
|
||||
exit_context.set_written(ref)
|
||||
|
||||
for ref in type_.trashes:
|
||||
exit_context.assert_writeable(ref)
|
||||
exit_context.set_touched(ref)
|
||||
exit_context.set_unmeaningful(ref)
|
||||
|
||||
self.exit_contexts.append(exit_context)
|
||||
|
||||
# When we get to the end, we'll check that all the
|
||||
# exit contexts are consistent with each other.
|
||||
|
||||
# We set the current context as having terminated.
|
||||
# If we are in a branch, the merge will deal with
|
||||
# having terminated. If we are at the end of the
|
||||
# routine, the routine end will deal with that.
|
||||
|
||||
context.set_terminated()
|
||||
|
||||
def analyze_if(self, instr, context):
|
||||
incoming_meaningful = set(context.each_meaningful())
|
||||
|
||||
|
@ -72,7 +72,15 @@ class Instr(AST):
|
||||
|
||||
|
||||
class SingleOp(Instr):
|
||||
value_attrs = ('opcode', 'dest', 'src', 'location',)
|
||||
value_attrs = ('opcode', 'dest', 'src',)
|
||||
|
||||
|
||||
class Call(Instr):
|
||||
value_attrs = ('location',)
|
||||
|
||||
|
||||
class GoTo(Instr):
|
||||
value_attrs = ('location',)
|
||||
|
||||
|
||||
class If(Instr):
|
||||
|
@ -1,7 +1,7 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
from sixtypical.ast import (
|
||||
Program, Routine, Block, SingleOp, If, Repeat, For, WithInterruptsOff, Save, PointInto
|
||||
Program, Routine, Block, SingleOp, Call, GoTo, If, Repeat, For, WithInterruptsOff, Save, PointInto
|
||||
)
|
||||
from sixtypical.model import (
|
||||
ConstantRef, LocationRef, IndexedRef, IndirectRef,
|
||||
@ -162,6 +162,10 @@ class Compiler(object):
|
||||
def compile_instr(self, instr):
|
||||
if isinstance(instr, SingleOp):
|
||||
return self.compile_single_op(instr)
|
||||
elif isinstance(instr, Call):
|
||||
return self.compile_call(instr)
|
||||
elif isinstance(instr, GoTo):
|
||||
return self.compile_goto(instr)
|
||||
elif isinstance(instr, If):
|
||||
return self.compile_if(instr)
|
||||
elif isinstance(instr, Repeat):
|
||||
@ -393,31 +397,6 @@ class Compiler(object):
|
||||
self.emitter.emit(cls(mode(Offset(self.get_label(dest.ref.name), dest.offset.value))))
|
||||
else:
|
||||
self.emitter.emit(cls(self.absolute_or_zero_page(self.get_label(dest.name))))
|
||||
elif opcode == 'call':
|
||||
location = instr.location
|
||||
label = self.get_label(instr.location.name)
|
||||
if isinstance(location.type, RoutineType):
|
||||
self.emitter.emit(JSR(Absolute(label)))
|
||||
elif isinstance(location.type, VectorType):
|
||||
trampoline = self.trampolines.setdefault(
|
||||
location, Label(location.name + '_trampoline')
|
||||
)
|
||||
self.emitter.emit(JSR(Absolute(trampoline)))
|
||||
else:
|
||||
raise NotImplementedError
|
||||
elif opcode == 'goto':
|
||||
self.final_goto_seen = True
|
||||
if self.skip_final_goto:
|
||||
pass
|
||||
else:
|
||||
location = instr.location
|
||||
label = self.get_label(instr.location.name)
|
||||
if isinstance(location.type, RoutineType):
|
||||
self.emitter.emit(JMP(Absolute(label)))
|
||||
elif isinstance(location.type, VectorType):
|
||||
self.emitter.emit(JMP(Indirect(label)))
|
||||
else:
|
||||
raise NotImplementedError
|
||||
elif opcode == 'copy':
|
||||
self.compile_copy(instr, instr.src, instr.dest)
|
||||
elif opcode == 'trash':
|
||||
@ -427,6 +406,33 @@ class Compiler(object):
|
||||
else:
|
||||
raise NotImplementedError(opcode)
|
||||
|
||||
def compile_call(self, instr):
|
||||
location = instr.location
|
||||
label = self.get_label(instr.location.name)
|
||||
if isinstance(location.type, RoutineType):
|
||||
self.emitter.emit(JSR(Absolute(label)))
|
||||
elif isinstance(location.type, VectorType):
|
||||
trampoline = self.trampolines.setdefault(
|
||||
location, Label(location.name + '_trampoline')
|
||||
)
|
||||
self.emitter.emit(JSR(Absolute(trampoline)))
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
def compile_goto(self, instr):
|
||||
self.final_goto_seen = True
|
||||
if self.skip_final_goto:
|
||||
pass
|
||||
else:
|
||||
location = instr.location
|
||||
label = self.get_label(instr.location.name)
|
||||
if isinstance(location.type, RoutineType):
|
||||
self.emitter.emit(JMP(Absolute(label)))
|
||||
elif isinstance(location.type, VectorType):
|
||||
self.emitter.emit(JMP(Indirect(label)))
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
def compile_cmp(self, instr, src, dest):
|
||||
"""`instr` is only for reporting purposes"""
|
||||
if isinstance(src, LocationRef) and src.type == TYPE_WORD:
|
||||
|
@ -1,7 +1,7 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
from sixtypical.ast import (
|
||||
Program, Defn, Routine, Block, SingleOp, If, Repeat, For, WithInterruptsOff, Save, PointInto
|
||||
Program, Defn, Routine, Block, SingleOp, Call, GoTo, If, Repeat, For, WithInterruptsOff, Save, PointInto
|
||||
)
|
||||
from sixtypical.model import (
|
||||
TYPE_BIT, TYPE_BYTE, TYPE_WORD,
|
||||
@ -113,6 +113,8 @@ class Parser(object):
|
||||
resolve_fwd_reference(node, 'location')
|
||||
resolve_fwd_reference(node, 'src')
|
||||
resolve_fwd_reference(node, 'dest')
|
||||
if isinstance(node, (Call, GoTo)):
|
||||
resolve_fwd_reference(node, 'location')
|
||||
|
||||
# --- grammar productions
|
||||
|
||||
@ -380,7 +382,7 @@ class Parser(object):
|
||||
self.scanner.expect('{')
|
||||
while not self.scanner.on('}'):
|
||||
instrs.append(self.instr())
|
||||
if isinstance(instrs[-1], SingleOp) and instrs[-1].opcode == 'goto':
|
||||
if isinstance(instrs[-1], GoTo):
|
||||
break
|
||||
self.scanner.expect('}')
|
||||
return Block(self.scanner.line_number, instrs=instrs)
|
||||
@ -450,12 +452,15 @@ class Parser(object):
|
||||
opcode = self.scanner.token
|
||||
self.scanner.scan()
|
||||
return SingleOp(self.scanner.line_number, opcode=opcode, dest=None, src=None)
|
||||
elif self.scanner.token in ("call", "goto"):
|
||||
opcode = self.scanner.token
|
||||
self.scanner.scan()
|
||||
elif self.scanner.consume("call"):
|
||||
name = self.scanner.token
|
||||
self.scanner.scan()
|
||||
instr = SingleOp(self.scanner.line_number, opcode=opcode, location=ForwardReference(name), dest=None, src=None)
|
||||
instr = Call(self.scanner.line_number, location=ForwardReference(name))
|
||||
return instr
|
||||
elif self.scanner.consume("goto"):
|
||||
name = self.scanner.token
|
||||
self.scanner.scan()
|
||||
instr = GoTo(self.scanner.line_number, location=ForwardReference(name))
|
||||
return instr
|
||||
elif self.scanner.token in ("copy",):
|
||||
opcode = self.scanner.token
|
||||
|
Loading…
x
Reference in New Issue
Block a user