mirror of
https://github.com/catseye/SixtyPical.git
synced 2024-06-15 14:29:32 +00:00
commit
9536dd011f
|
@ -1,6 +1,6 @@
|
|||
# encoding: UTF-8
|
||||
|
||||
from sixtypical.ast import Program, Routine, Block, Instr, SingleOp, If, Repeat, For, WithInterruptsOff
|
||||
from sixtypical.ast import Program, Routine, Block, Instr, SingleOp, If, Repeat, For, WithInterruptsOff, Save
|
||||
from sixtypical.model import (
|
||||
TYPE_BYTE, TYPE_WORD,
|
||||
TableType, BufferType, PointerType, VectorType, RoutineType,
|
||||
|
@ -269,7 +269,7 @@ class Context(object):
|
|||
self._writeable.remove(ref)
|
||||
|
||||
def set_writeable(self, *refs):
|
||||
"""Intended to be used for implementing analyzing `for`."""
|
||||
"""Intended to be used for implementing analyzing `for`, but also used in `save`."""
|
||||
for ref in refs:
|
||||
self._writeable.add(ref)
|
||||
|
||||
|
@ -292,6 +292,41 @@ class Context(object):
|
|||
self.assert_in_range(dest.index, dest.ref)
|
||||
self.set_written(dest.ref)
|
||||
|
||||
def extract(self, location):
|
||||
"""Sets the given location as writeable in the context, and returns a 'baton' representing
|
||||
the previous state of context for that location. This 'baton' can be used to later restore
|
||||
this state of context."""
|
||||
# Used in `save`.
|
||||
baton = (
|
||||
location,
|
||||
location in self._touched,
|
||||
self._range.get(location, None),
|
||||
location in self._writeable,
|
||||
)
|
||||
self.set_writeable(location)
|
||||
return baton
|
||||
|
||||
def re_introduce(self, baton):
|
||||
"""Given a 'baton' produced by `extract()`, restores the context for that saved location
|
||||
to what it was before `extract()` was called."""
|
||||
# Used in `save`.
|
||||
location, was_touched, was_range, was_writeable = baton
|
||||
|
||||
if was_touched:
|
||||
self._touched.add(location)
|
||||
elif location in self._touched:
|
||||
self._touched.remove(location)
|
||||
|
||||
if was_range is not None:
|
||||
self._range[location] = was_range
|
||||
elif location in self._range:
|
||||
del self._range[location]
|
||||
|
||||
if was_writeable:
|
||||
self._writeable.add(location)
|
||||
elif location in self._writeable:
|
||||
self._writeable.remove(location)
|
||||
|
||||
|
||||
class Analyzer(object):
|
||||
|
||||
|
@ -300,9 +335,9 @@ class Analyzer(object):
|
|||
self.routines = {}
|
||||
self.debug = debug
|
||||
|
||||
def assert_type(self, type, *locations):
|
||||
def assert_type(self, type_, *locations):
|
||||
for location in locations:
|
||||
if location.type != type:
|
||||
if location.type != type_:
|
||||
raise TypeMismatchError(self.current_routine, location.name)
|
||||
|
||||
def assert_affected_within(self, name, affecting_type, limiting_type):
|
||||
|
@ -385,6 +420,10 @@ class Analyzer(object):
|
|||
self.analyze_for(instr, context)
|
||||
elif isinstance(instr, WithInterruptsOff):
|
||||
self.analyze_block(instr.block, context)
|
||||
if context.encountered_gotos():
|
||||
raise IllegalJumpError(instr, instr)
|
||||
elif isinstance(instr, Save):
|
||||
self.analyze_save(instr, context)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
|
@ -427,7 +466,7 @@ class Analyzer(object):
|
|||
context.assert_meaningful(dest.ref, REG_Y)
|
||||
context.set_written(dest.ref)
|
||||
elif src.type != dest.type:
|
||||
raise TypeMismatchError(instr, '{} and {}'.format(src, name))
|
||||
raise TypeMismatchError(instr, '{} and {}'.format(src, dest))
|
||||
else:
|
||||
context.set_written(dest)
|
||||
# FIXME: context.copy_range(src, dest) ?
|
||||
|
@ -730,3 +769,21 @@ class Analyzer(object):
|
|||
# after it is executed, we know the range of the loop variable.
|
||||
context.set_range(instr.dest, instr.final, instr.final)
|
||||
context.set_writeable(instr.dest)
|
||||
|
||||
def analyze_save(self, instr, context):
|
||||
if len(instr.locations) != 1:
|
||||
raise NotImplementedError("Only 1 location in save is supported right now")
|
||||
location = instr.locations[0]
|
||||
self.assert_type(TYPE_BYTE, location)
|
||||
|
||||
baton = context.extract(location)
|
||||
self.analyze_block(instr.block, context)
|
||||
if context.encountered_gotos():
|
||||
raise IllegalJumpError(instr, instr)
|
||||
context.re_introduce(baton)
|
||||
|
||||
if location == REG_A:
|
||||
pass
|
||||
else:
|
||||
context.set_touched(REG_A)
|
||||
context.set_unmeaningful(REG_A)
|
||||
|
|
|
@ -90,3 +90,8 @@ class For(Instr):
|
|||
|
||||
class WithInterruptsOff(Instr):
|
||||
child_attrs = ('block',)
|
||||
|
||||
|
||||
class Save(Instr):
|
||||
value_attrs = ('locations',)
|
||||
child_attrs = ('block',)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# encoding: UTF-8
|
||||
|
||||
from sixtypical.ast import Program, Routine, Block, Instr, SingleOp, If, Repeat, For, WithInterruptsOff
|
||||
from sixtypical.ast import Program, Routine, Block, Instr, SingleOp, If, Repeat, For, WithInterruptsOff, Save
|
||||
from sixtypical.model import (
|
||||
ConstantRef, LocationRef, IndexedRef, IndirectRef, AddressRef,
|
||||
TYPE_BIT, TYPE_BYTE, TYPE_WORD,
|
||||
|
@ -12,6 +12,7 @@ from sixtypical.gen6502 import (
|
|||
Immediate, Absolute, AbsoluteX, AbsoluteY, ZeroPage, Indirect, IndirectY, Relative,
|
||||
LDA, LDX, LDY, STA, STX, STY,
|
||||
TAX, TAY, TXA, TYA,
|
||||
PHA, PLA,
|
||||
CLC, SEC, ADC, SBC, ROL, ROR,
|
||||
INC, INX, INY, DEC, DEX, DEY,
|
||||
CMP, CPX, CPY, AND, ORA, EOR,
|
||||
|
@ -169,6 +170,8 @@ class Compiler(object):
|
|||
return self.compile_for(instr)
|
||||
elif isinstance(instr, WithInterruptsOff):
|
||||
return self.compile_with_interrupts_off(instr)
|
||||
elif isinstance(instr, Save):
|
||||
return self.compile_save(instr)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
|
@ -613,3 +616,29 @@ class Compiler(object):
|
|||
self.emitter.emit(SEI())
|
||||
self.compile_block(instr.block)
|
||||
self.emitter.emit(CLI())
|
||||
|
||||
def compile_save(self, instr):
|
||||
location = instr.locations[0]
|
||||
if location == REG_A:
|
||||
self.emitter.emit(PHA())
|
||||
self.compile_block(instr.block)
|
||||
self.emitter.emit(PLA())
|
||||
elif location == REG_X:
|
||||
self.emitter.emit(TXA())
|
||||
self.emitter.emit(PHA())
|
||||
self.compile_block(instr.block)
|
||||
self.emitter.emit(PLA())
|
||||
self.emitter.emit(TAX())
|
||||
elif location == REG_Y:
|
||||
self.emitter.emit(TYA())
|
||||
self.emitter.emit(PHA())
|
||||
self.compile_block(instr.block)
|
||||
self.emitter.emit(PLA())
|
||||
self.emitter.emit(TAY())
|
||||
else:
|
||||
src_label = self.get_label(location.name)
|
||||
self.emitter.emit(LDA(Absolute(src_label)))
|
||||
self.emitter.emit(PHA())
|
||||
self.compile_block(instr.block)
|
||||
self.emitter.emit(PLA())
|
||||
self.emitter.emit(STA(Absolute(src_label)))
|
||||
|
|
|
@ -306,6 +306,18 @@ class ORA(Instruction):
|
|||
}
|
||||
|
||||
|
||||
class PHA(Instruction):
|
||||
opcodes = {
|
||||
Implied: 0x48,
|
||||
}
|
||||
|
||||
|
||||
class PLA(Instruction):
|
||||
opcodes = {
|
||||
Implied: 0x68,
|
||||
}
|
||||
|
||||
|
||||
class ROL(Instruction):
|
||||
opcodes = {
|
||||
Implied: 0x2a, # Accumulator
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# encoding: UTF-8
|
||||
|
||||
from sixtypical.ast import Program, Defn, Routine, Block, SingleOp, If, Repeat, For, WithInterruptsOff
|
||||
from sixtypical.ast import Program, Defn, Routine, Block, SingleOp, If, Repeat, For, WithInterruptsOff, Save
|
||||
from sixtypical.model import (
|
||||
TYPE_BIT, TYPE_BYTE, TYPE_WORD,
|
||||
RoutineType, VectorType, TableType, BufferType, PointerType,
|
||||
|
@ -470,6 +470,10 @@ class Parser(object):
|
|||
self.scanner.expect("off")
|
||||
block = self.block()
|
||||
return WithInterruptsOff(self.scanner.line_number, block=block)
|
||||
elif self.scanner.consume("save"):
|
||||
locations = self.locexprs()
|
||||
block = self.block()
|
||||
return Save(self.scanner.line_number, locations=locations, block=block)
|
||||
elif self.scanner.consume("trash"):
|
||||
dest = self.locexpr()
|
||||
return SingleOp(self.scanner.line_number, opcode='trash', src=None, dest=dest)
|
||||
|
|
|
@ -1938,6 +1938,290 @@ initialized at the start of that loop.
|
|||
| }
|
||||
? UnmeaningfulReadError: y
|
||||
|
||||
### save ###
|
||||
|
||||
Basic neutral test, where the `save` makes no difference.
|
||||
|
||||
| routine main
|
||||
| inputs a, x
|
||||
| outputs a, x
|
||||
| trashes z, n
|
||||
| {
|
||||
| ld a, 1
|
||||
| save x {
|
||||
| ld a, 2
|
||||
| }
|
||||
| ld a, 3
|
||||
| }
|
||||
= ok
|
||||
|
||||
Saving any location (other than `a`) will trash `a`.
|
||||
|
||||
| routine main
|
||||
| inputs a, x
|
||||
| outputs a, x
|
||||
| trashes z, n
|
||||
| {
|
||||
| ld a, 1
|
||||
| save x {
|
||||
| ld a, 2
|
||||
| }
|
||||
| }
|
||||
? UnmeaningfulOutputError
|
||||
|
||||
Saving `a` does not trash anything.
|
||||
|
||||
| routine main
|
||||
| inputs a, x
|
||||
| outputs a, x
|
||||
| trashes z, n
|
||||
| {
|
||||
| ld x, 1
|
||||
| save a {
|
||||
| ld x, 2
|
||||
| }
|
||||
| ld x, 3
|
||||
| }
|
||||
= ok
|
||||
|
||||
A defined value that has been saved can be trashed inside the block.
|
||||
It will continue to be defined outside the block.
|
||||
|
||||
| routine main
|
||||
| outputs x, y
|
||||
| trashes a, z, n
|
||||
| {
|
||||
| ld x, 0
|
||||
| save x {
|
||||
| ld y, 0
|
||||
| trash x
|
||||
| }
|
||||
| }
|
||||
= ok
|
||||
|
||||
A trashed value that has been saved can be used inside the block.
|
||||
It will continue to be trashed outside the block.
|
||||
|
||||
| routine main
|
||||
| inputs a
|
||||
| outputs a, x
|
||||
| trashes z, n
|
||||
| {
|
||||
| ld x, 0
|
||||
| trash x
|
||||
| save x {
|
||||
| ld a, 0
|
||||
| ld x, 1
|
||||
| }
|
||||
| }
|
||||
? UnmeaningfulOutputError: x
|
||||
|
||||
The known range of a value will be preserved outside the block as well.
|
||||
|
||||
| word one: 77
|
||||
| word table[32] many
|
||||
|
|
||||
| routine main
|
||||
| inputs a, many, one
|
||||
| outputs many, one
|
||||
| trashes a, x, n, z
|
||||
| {
|
||||
| and a, 31
|
||||
| ld x, a
|
||||
| save x {
|
||||
| ld x, 255
|
||||
| }
|
||||
| copy one, many + x
|
||||
| copy many + x, one
|
||||
| }
|
||||
= ok
|
||||
|
||||
| word one: 77
|
||||
| word table[32] many
|
||||
|
|
||||
| routine main
|
||||
| inputs a, many, one
|
||||
| outputs many, one
|
||||
| trashes a, x, n, z
|
||||
| {
|
||||
| and a, 63
|
||||
| ld x, a
|
||||
| save x {
|
||||
| ld x, 1
|
||||
| }
|
||||
| copy one, many + x
|
||||
| copy many + x, one
|
||||
| }
|
||||
? RangeExceededError
|
||||
|
||||
The known properties of a value are preserved inside the block, too.
|
||||
|
||||
| word one: 77
|
||||
| word table[32] many
|
||||
|
|
||||
| routine main
|
||||
| inputs a, many, one
|
||||
| outputs many, one
|
||||
| trashes a, x, n, z
|
||||
| {
|
||||
| and a, 31
|
||||
| ld x, a
|
||||
| save x {
|
||||
| copy one, many + x
|
||||
| copy many + x, one
|
||||
| }
|
||||
| copy one, many + x
|
||||
| copy many + x, one
|
||||
| }
|
||||
= ok
|
||||
|
||||
A value which is not output from the routine, is preserved by the
|
||||
routine; and can appear in a `save` exactly because a `save` preserves it.
|
||||
|
||||
| routine main
|
||||
| outputs y
|
||||
| trashes a, z, n
|
||||
| {
|
||||
| save x {
|
||||
| ld y, 0
|
||||
| ld x, 1
|
||||
| }
|
||||
| }
|
||||
= ok
|
||||
|
||||
Because saving anything except `a` trashes `a`, a common idiom is to save `a`
|
||||
first in a nested series of `save`s.
|
||||
|
||||
| routine main
|
||||
| inputs a
|
||||
| outputs a
|
||||
| trashes z, n
|
||||
| {
|
||||
| save a {
|
||||
| save x {
|
||||
| ld a, 0
|
||||
| ld x, 1
|
||||
| }
|
||||
| }
|
||||
| }
|
||||
= ok
|
||||
|
||||
Not just registers, but also user-defined locations can be saved.
|
||||
|
||||
| byte foo
|
||||
|
|
||||
| routine main
|
||||
| trashes a, z, n
|
||||
| {
|
||||
| save foo {
|
||||
| st 5, foo
|
||||
| }
|
||||
| }
|
||||
= ok
|
||||
|
||||
But only if they are bytes.
|
||||
|
||||
| word foo
|
||||
|
|
||||
| routine main
|
||||
| trashes a, z, n
|
||||
| {
|
||||
| save foo {
|
||||
| copy 555, foo
|
||||
| }
|
||||
| }
|
||||
? TypeMismatchError
|
||||
|
||||
| byte table[16] tab
|
||||
|
|
||||
| routine main
|
||||
| trashes a, y, z, n
|
||||
| {
|
||||
| save tab {
|
||||
| ld y, 0
|
||||
| st 5, tab + y
|
||||
| }
|
||||
| }
|
||||
? TypeMismatchError
|
||||
|
||||
A `goto` cannot appear within a `save` block, even if it is otherwise in tail position.
|
||||
|
||||
| routine other
|
||||
| trashes a, z, n
|
||||
| {
|
||||
| ld a, 0
|
||||
| }
|
||||
|
|
||||
| routine main
|
||||
| trashes a, z, n
|
||||
| {
|
||||
| ld a, 1
|
||||
| save x {
|
||||
| ld x, 2
|
||||
| goto other
|
||||
| }
|
||||
| }
|
||||
? IllegalJumpError
|
||||
|
||||
### with interrupts ###
|
||||
|
||||
| vector routine
|
||||
| inputs x
|
||||
| outputs x
|
||||
| trashes z, n
|
||||
| bar
|
||||
|
|
||||
| routine foo
|
||||
| inputs x
|
||||
| outputs x
|
||||
| trashes z, n
|
||||
| {
|
||||
| inc x
|
||||
| }
|
||||
|
|
||||
| routine main
|
||||
| outputs bar
|
||||
| trashes a, n, z
|
||||
| {
|
||||
| with interrupts off {
|
||||
| copy foo, bar
|
||||
| }
|
||||
| }
|
||||
= ok
|
||||
|
||||
A `goto` cannot appear within a `with interrupts` block, even if it is
|
||||
otherwise in tail position.
|
||||
|
||||
| vector routine
|
||||
| inputs x
|
||||
| outputs x
|
||||
| trashes z, n
|
||||
| bar
|
||||
|
|
||||
| routine foo
|
||||
| inputs x
|
||||
| outputs x
|
||||
| trashes z, n
|
||||
| {
|
||||
| inc x
|
||||
| }
|
||||
|
|
||||
| routine other
|
||||
| trashes bar, a, n, z
|
||||
| {
|
||||
| ld a, 0
|
||||
| }
|
||||
|
|
||||
| routine main
|
||||
| trashes bar, a, n, z
|
||||
| {
|
||||
| with interrupts off {
|
||||
| copy foo, bar
|
||||
| goto other
|
||||
| }
|
||||
| }
|
||||
? IllegalJumpError
|
||||
|
||||
### copy ###
|
||||
|
||||
Can't `copy` from a memory location that isn't initialized.
|
||||
|
|
|
@ -579,6 +579,49 @@ Compiling `for ... down to`.
|
|||
= $0815 BNE $080F
|
||||
= $0817 RTS
|
||||
|
||||
Compiling `save`.
|
||||
|
||||
| routine main
|
||||
| inputs a
|
||||
| outputs a
|
||||
| trashes z, n
|
||||
| {
|
||||
| save a {
|
||||
| save x {
|
||||
| ld a, 0
|
||||
| ld x, 1
|
||||
| }
|
||||
| }
|
||||
| }
|
||||
= $080D PHA
|
||||
= $080E TXA
|
||||
= $080F PHA
|
||||
= $0810 LDA #$00
|
||||
= $0812 LDX #$01
|
||||
= $0814 PLA
|
||||
= $0815 TAX
|
||||
= $0816 PLA
|
||||
= $0817 RTS
|
||||
|
||||
Compiling `save` on a user-defined location.
|
||||
|
||||
| byte foo
|
||||
| routine main
|
||||
| trashes a, z, n
|
||||
| {
|
||||
| save foo {
|
||||
| ld a, 0
|
||||
| st a, foo
|
||||
| }
|
||||
| }
|
||||
= $080D LDA $081B
|
||||
= $0810 PHA
|
||||
= $0811 LDA #$00
|
||||
= $0813 STA $081B
|
||||
= $0816 PLA
|
||||
= $0817 STA $081B
|
||||
= $081A RTS
|
||||
|
||||
Indexed access.
|
||||
|
||||
| byte one
|
||||
|
|
|
@ -161,6 +161,20 @@ Basic "open-faced for" loops, up and down.
|
|||
| }
|
||||
= ok
|
||||
|
||||
Other blocks.
|
||||
|
||||
| routine main trashes a, x, c, z, v {
|
||||
| with interrupts off {
|
||||
| save a, x, c {
|
||||
| ld a, 0
|
||||
| }
|
||||
| }
|
||||
| save a, x, c {
|
||||
| ld a, 0
|
||||
| }
|
||||
| }
|
||||
= ok
|
||||
|
||||
User-defined memory addresses of different types.
|
||||
|
||||
| byte byt
|
||||
|
|
Loading…
Reference in New Issue
Block a user