mirror of
https://github.com/catseye/SixtyPical.git
synced 2025-01-06 20:35:28 +00:00
Add reset
instruction; much refactoring.
This commit is contained in:
parent
dd29b6fd4a
commit
3f666f4385
12
HISTORY.md
12
HISTORY.md
@ -4,10 +4,18 @@ History of SixtyPical
|
||||
0.20
|
||||
----
|
||||
|
||||
* A `point ... into` block no longer initializes the pointer
|
||||
by default. A subequent `reset` instruction must be used
|
||||
to initialize the pointer. The pointer may be reset to any
|
||||
valid offset within the table (not only 0) and it may be
|
||||
reset multiple times inside the block.
|
||||
* Local locations need no longer be static. If they are not
|
||||
static, they are considered uninitialized until assigned,
|
||||
and they can be declared with an explicit fixed address.
|
||||
* Fixed a bug where two local statics could be declared with
|
||||
the same name.
|
||||
* Local locations need no longer be static. If they are not
|
||||
static, they are considered uninitialized until assigned.
|
||||
* Split context off from analyzer and put it in its own module.
|
||||
* Split the SixtyPical Analysis tests across three files.
|
||||
|
||||
0.19
|
||||
----
|
||||
|
20
TODO.md
20
TODO.md
@ -29,15 +29,7 @@ inner block has finished -- even if there is no `call`.)
|
||||
|
||||
These holes need to be plugged.
|
||||
|
||||
### Reset pointer in `point into` blocks
|
||||
|
||||
We have `point into` blocks, but maybe the action when entering such a
|
||||
block shouldn't always be to set the given pointer to the start of the given table.
|
||||
|
||||
That is, sometimes we would like to start at some fixed offset. And
|
||||
sometimes we want to (re)set the pointer, without closing and starting a new block.
|
||||
|
||||
### Pointers associated globally with a table
|
||||
### Pointers associated globally with a table(?)
|
||||
|
||||
We have `point into` blocks, but we would also like to sometimes pass a pointer
|
||||
around to different routines, and have them all "know" what table it operates on.
|
||||
@ -62,16 +54,6 @@ be allocated at the same space.
|
||||
This is more an impressive trick than a really useful feature, but still.
|
||||
Impressive tricks are impressive.
|
||||
|
||||
### Locals with explicit addresses
|
||||
|
||||
A local could also be given an explicit address. In this case, two locals in
|
||||
different routines could be given the same address, and as long as the condition
|
||||
in the above paragraph holds, that's okay. (If it doesn't, the analyzer should
|
||||
detect it.)
|
||||
|
||||
This would permit local pointers, which would be one way of addressing the
|
||||
"same pointer to different tables" problem.
|
||||
|
||||
### Copy byte to/from table
|
||||
|
||||
Do we want a `copy bytevar, table + x` instruction? We don't currently have one.
|
||||
|
@ -265,6 +265,7 @@ define player_logic logic_routine
|
||||
|
||||
if c {
|
||||
point ptr into screen {
|
||||
reset ptr 0
|
||||
st off, c
|
||||
add ptr, new_pos
|
||||
ld y, 0
|
||||
@ -280,6 +281,7 @@ define player_logic logic_routine
|
||||
cmp a, 32
|
||||
if z {
|
||||
point ptr into screen {
|
||||
reset ptr 0
|
||||
st off, c
|
||||
add ptr, pos
|
||||
copy 32, [ptr] + y
|
||||
@ -288,6 +290,7 @@ define player_logic logic_routine
|
||||
copy new_pos, pos
|
||||
|
||||
point ptr into screen {
|
||||
reset ptr 0
|
||||
st off, c
|
||||
add ptr, pos
|
||||
copy 81, [ptr] + y
|
||||
@ -307,6 +310,7 @@ define enemy_logic logic_routine
|
||||
|
||||
if c {
|
||||
point ptr into screen {
|
||||
reset ptr 0
|
||||
st off, c
|
||||
add ptr, new_pos
|
||||
ld y, 0
|
||||
@ -321,6 +325,7 @@ define enemy_logic logic_routine
|
||||
cmp a, 32
|
||||
if z {
|
||||
point ptr into screen {
|
||||
reset ptr 0
|
||||
st off, c
|
||||
add ptr, pos
|
||||
copy 32, [ptr] + y
|
||||
@ -329,6 +334,7 @@ define enemy_logic logic_routine
|
||||
copy new_pos, pos
|
||||
|
||||
point ptr into screen {
|
||||
reset ptr 0
|
||||
st off, c
|
||||
add ptr, pos
|
||||
copy 82, [ptr] + y
|
||||
|
@ -11,10 +11,12 @@ define main routine
|
||||
trashes a, z, n, c, ptr
|
||||
{
|
||||
ld y, 0
|
||||
copy ^buf, ptr
|
||||
copy 123, [ptr] + y
|
||||
copy [ptr] + y, foo
|
||||
copy foo, [ptr] + y
|
||||
point ptr into buf {
|
||||
reset ptr 0
|
||||
copy 123, [ptr] + y
|
||||
copy [ptr] + y, foo
|
||||
copy foo, [ptr] + y
|
||||
}
|
||||
|
||||
// TODO: support saying `cmp foo, 123`, maybe
|
||||
ld a, foo
|
||||
|
@ -1,8 +1,9 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
from sixtypical.ast import (
|
||||
Program, Routine, Block, SingleOp, Call, GoTo, If, Repeat, For, WithInterruptsOff, Save, PointInto
|
||||
Program, Routine, Block, SingleOp, Reset, Call, GoTo, If, Repeat, For, WithInterruptsOff, Save, PointInto
|
||||
)
|
||||
from sixtypical.context import AnalysisContext
|
||||
from sixtypical.model import (
|
||||
TYPE_BYTE, TYPE_WORD,
|
||||
TableType, PointerType, VectorType, RoutineType,
|
||||
@ -80,321 +81,6 @@ class IncompatibleConstraintsError(ConstraintsError):
|
||||
pass
|
||||
|
||||
|
||||
class AnalysisContext(object):
|
||||
"""
|
||||
A location is touched if it was changed (or even potentially
|
||||
changed) during this routine, or some routine called by this routine.
|
||||
|
||||
A location is meaningful if it was an input to this routine,
|
||||
or if it was set to a meaningful value by some operation in this
|
||||
routine (or some routine called by this routine).
|
||||
|
||||
If a location is meaningful, it has a range. This range represents
|
||||
the lowest and highest values that it might possibly be (i.e. we know
|
||||
it cannot possibly be below the lowest or above the highest.) In the
|
||||
absence of any usage information, the range of a byte, is 0..255 and
|
||||
the range of a word is 0..65535.
|
||||
|
||||
A location is writeable if it was listed in the outputs and trashes
|
||||
lists of this routine. A location can also be temporarily marked
|
||||
unwriteable in certain contexts, such as `for` loops.
|
||||
"""
|
||||
def __init__(self, symtab, routine, inputs, outputs, trashes):
|
||||
self.symtab = symtab
|
||||
self.routine = routine # Routine (AST node)
|
||||
self._touched = set() # {LocationRef}
|
||||
self._range = dict() # LocationRef -> (Int, Int)
|
||||
self._writeable = set() # {LocationRef}
|
||||
self._terminated = False
|
||||
self._gotos_encountered = set()
|
||||
self._pointer_assoc = dict()
|
||||
|
||||
for ref in inputs:
|
||||
if self.is_constant(ref):
|
||||
raise ConstantConstraintError(self.routine, ref.name)
|
||||
self._range[ref] = self.max_range(ref)
|
||||
output_names = set()
|
||||
for ref in outputs:
|
||||
if self.is_constant(ref):
|
||||
raise ConstantConstraintError(self.routine, ref.name)
|
||||
output_names.add(ref.name)
|
||||
self._writeable.add(ref)
|
||||
for ref in trashes:
|
||||
if self.is_constant(ref):
|
||||
raise ConstantConstraintError(self.routine, ref.name)
|
||||
if ref.name in output_names:
|
||||
raise InconsistentConstraintsError(self.routine, ref.name)
|
||||
self._writeable.add(ref)
|
||||
|
||||
def __str__(self):
|
||||
return "{}(\n _touched={},\n _range={},\n _writeable={}\n)".format(
|
||||
self.__class__.__name__,
|
||||
LocationRef.format_set(self._touched), LocationRef.format_set(self._range), LocationRef.format_set(self._writeable)
|
||||
)
|
||||
|
||||
def to_json_data(self):
|
||||
type_ = self.symtab.fetch_global_type(self.routine.name)
|
||||
return {
|
||||
'routine_inputs': ','.join(sorted(loc.name for loc in type_.inputs)),
|
||||
'routine_outputs': ','.join(sorted(loc.name for loc in type_.outputs)),
|
||||
'routine_trashes': ','.join(sorted(loc.name for loc in type_.trashes)),
|
||||
'touched': ','.join(sorted(loc.name for loc in self._touched)),
|
||||
'range': dict((loc.name, '{}-{}'.format(rng[0], rng[1])) for (loc, rng) in self._range.items()),
|
||||
'writeable': ','.join(sorted(loc.name for loc in self._writeable)),
|
||||
'terminated': self._terminated,
|
||||
'gotos_encountered': ','.join(sorted(loc.name for loc in self._gotos_encountered)),
|
||||
}
|
||||
|
||||
def clone(self):
|
||||
c = AnalysisContext(self.symtab, self.routine, [], [], [])
|
||||
c._touched = set(self._touched)
|
||||
c._range = dict(self._range)
|
||||
c._writeable = set(self._writeable)
|
||||
c._pointer_assoc = dict(self._pointer_assoc)
|
||||
c._gotos_encountered = set(self._gotos_encountered)
|
||||
return c
|
||||
|
||||
def update_from(self, other):
|
||||
"""Replaces the information in this context, with the information from the other context.
|
||||
This is an overwriting action - it does not attempt to merge the contexts.
|
||||
|
||||
We do not replace the gotos_encountered for technical reasons. (In `analyze_if`,
|
||||
we merge those sets afterwards; at the end of `analyze_routine`, they are not distinct in the
|
||||
set of contexts we are updating from, and we want to retain our own.)"""
|
||||
self.routine = other.routine
|
||||
self._touched = set(other._touched)
|
||||
self._range = dict(other._range)
|
||||
self._writeable = set(other._writeable)
|
||||
self._terminated = other._terminated
|
||||
self._pointer_assoc = dict(other._pointer_assoc)
|
||||
|
||||
def each_meaningful(self):
|
||||
for ref in self._range.keys():
|
||||
yield ref
|
||||
|
||||
def each_touched(self):
|
||||
for ref in self._touched:
|
||||
yield ref
|
||||
|
||||
def each_writeable(self):
|
||||
for ref in self._writeable:
|
||||
yield ref
|
||||
|
||||
def assert_meaningful(self, *refs, **kwargs):
|
||||
exception_class = kwargs.get('exception_class', UnmeaningfulReadError)
|
||||
for ref in refs:
|
||||
if self.symtab.has_local(self.routine.name, ref.name):
|
||||
if ref not in self._range:
|
||||
message = ref.name
|
||||
if kwargs.get('message'):
|
||||
message += ' (%s)' % kwargs['message']
|
||||
raise exception_class(self.routine, message)
|
||||
else:
|
||||
continue
|
||||
if self.is_constant(ref):
|
||||
pass
|
||||
elif isinstance(ref, LocationRef):
|
||||
if ref not in self._range:
|
||||
message = ref.name
|
||||
if kwargs.get('message'):
|
||||
message += ' (%s)' % kwargs['message']
|
||||
raise exception_class(self.routine, message)
|
||||
elif isinstance(ref, IndexedRef):
|
||||
self.assert_meaningful(ref.ref, **kwargs)
|
||||
self.assert_meaningful(ref.index, **kwargs)
|
||||
else:
|
||||
raise NotImplementedError(ref)
|
||||
|
||||
def assert_writeable(self, *refs, **kwargs):
|
||||
exception_class = kwargs.get('exception_class', ForbiddenWriteError)
|
||||
for ref in refs:
|
||||
# locals are always writeable
|
||||
if self.symtab.has_local(self.routine.name, ref.name):
|
||||
continue
|
||||
if ref not in self._writeable:
|
||||
message = ref.name
|
||||
if kwargs.get('message'):
|
||||
message += ' (%s)' % kwargs['message']
|
||||
raise exception_class(self.routine, message)
|
||||
|
||||
def assert_in_range(self, inside, outside, offset):
|
||||
"""Given two locations, assert that the first location, offset by the given offset,
|
||||
is contained 'inside' the second location."""
|
||||
assert isinstance(inside, LocationRef)
|
||||
assert isinstance(outside, LocationRef)
|
||||
|
||||
# inside should always be meaningful
|
||||
inside_range = self._range[inside]
|
||||
|
||||
# outside might not be meaningful, so default to max range if necessary
|
||||
if outside in self._range:
|
||||
outside_range = self._range[outside]
|
||||
else:
|
||||
outside_range = self.max_range(outside)
|
||||
|
||||
if (inside_range[0] + offset.value) < outside_range[0] or (inside_range[1] + offset.value) > outside_range[1]:
|
||||
raise RangeExceededError(self.routine,
|
||||
"Possible range of {} {} (+{}) exceeds acceptable range of {} {}".format(
|
||||
inside, inside_range, offset, outside, outside_range
|
||||
)
|
||||
)
|
||||
|
||||
def set_touched(self, *refs):
|
||||
for ref in refs:
|
||||
self._touched.add(ref)
|
||||
# TODO: it might be possible to invalidate the range here
|
||||
|
||||
def set_meaningful(self, *refs):
|
||||
for ref in refs:
|
||||
if ref not in self._range:
|
||||
self._range[ref] = self.max_range(ref)
|
||||
|
||||
def set_top_of_range(self, ref, top):
|
||||
self.assert_meaningful(ref)
|
||||
(bottom, _) = self._range[ref]
|
||||
self._range[ref] = (bottom, top)
|
||||
|
||||
def set_bottom_of_range(self, ref, bottom):
|
||||
self.assert_meaningful(ref)
|
||||
(top, _) = self._range[ref]
|
||||
self._range[ref] = (bottom, top)
|
||||
|
||||
def set_range(self, ref, bottom, top):
|
||||
self.assert_meaningful(ref)
|
||||
self._range[ref] = (bottom, top)
|
||||
|
||||
def get_top_of_range(self, ref):
|
||||
if isinstance(ref, ConstantRef):
|
||||
return ref.value
|
||||
self.assert_meaningful(ref)
|
||||
(_, top) = self._range[ref]
|
||||
return top
|
||||
|
||||
def get_bottom_of_range(self, ref):
|
||||
if isinstance(ref, ConstantRef):
|
||||
return ref.value
|
||||
self.assert_meaningful(ref)
|
||||
(bottom, _) = self._range[ref]
|
||||
return bottom
|
||||
|
||||
def get_range(self, ref):
|
||||
if isinstance(ref, ConstantRef):
|
||||
return (ref.value, ref.value)
|
||||
self.assert_meaningful(ref)
|
||||
(bottom, top) = self._range[ref]
|
||||
return bottom, top
|
||||
|
||||
def copy_range(self, src, dest):
|
||||
self.assert_meaningful(src)
|
||||
if src in self._range:
|
||||
src_range = self._range[src]
|
||||
else:
|
||||
src_range = self.max_range(src)
|
||||
self._range[dest] = src_range
|
||||
|
||||
def invalidate_range(self, ref):
|
||||
self.assert_meaningful(ref)
|
||||
self._range[ref] = self.max_range(ref)
|
||||
|
||||
def set_unmeaningful(self, *refs):
|
||||
for ref in refs:
|
||||
if ref in self._range:
|
||||
del self._range[ref]
|
||||
|
||||
def set_written(self, *refs):
|
||||
"""A "helper" method which does the following common sequence for
|
||||
the given refs: asserts they're all writable, and sets them all
|
||||
as touched and meaningful."""
|
||||
self.assert_writeable(*refs)
|
||||
self.set_touched(*refs)
|
||||
self.set_meaningful(*refs)
|
||||
|
||||
def set_unwriteable(self, *refs):
|
||||
"""Intended to be used for implementing analyzing `for`."""
|
||||
for ref in refs:
|
||||
self._writeable.remove(ref)
|
||||
|
||||
def set_writeable(self, *refs):
|
||||
"""Intended to be used for implementing analyzing `for`, but also used in `save`."""
|
||||
for ref in refs:
|
||||
self._writeable.add(ref)
|
||||
|
||||
def encounter_gotos(self, gotos):
|
||||
self._gotos_encountered |= gotos
|
||||
|
||||
def encountered_gotos(self):
|
||||
return self._gotos_encountered
|
||||
|
||||
def set_terminated(self):
|
||||
# Having a terminated context and having encountered gotos is not the same thing.
|
||||
self._terminated = True
|
||||
|
||||
def has_terminated(self):
|
||||
return self._terminated
|
||||
|
||||
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)
|
||||
|
||||
def get_assoc(self, pointer):
|
||||
return self._pointer_assoc.get(pointer)
|
||||
|
||||
def set_assoc(self, pointer, table):
|
||||
self._pointer_assoc[pointer] = table
|
||||
|
||||
def is_constant(self, ref):
|
||||
"""read-only means that the program cannot change the value
|
||||
of a location. constant means that the value of the location
|
||||
will not change during the lifetime of the program."""
|
||||
if isinstance(ref, ConstantRef):
|
||||
return True
|
||||
if isinstance(ref, (IndirectRef, IndexedRef)):
|
||||
return False
|
||||
if isinstance(ref, LocationRef):
|
||||
type_ = self.symtab.fetch_global_type(ref.name)
|
||||
return isinstance(type_, RoutineType)
|
||||
raise NotImplementedError
|
||||
|
||||
def max_range(self, ref):
|
||||
if isinstance(ref, ConstantRef):
|
||||
return (ref.value, ref.value)
|
||||
elif self.symtab.has_local(self.routine.name, ref.name):
|
||||
return self.symtab.fetch_local_type(self.routine.name, ref.name).max_range
|
||||
else:
|
||||
return self.symtab.fetch_global_type(ref.name).max_range
|
||||
|
||||
|
||||
class Analyzer(object):
|
||||
|
||||
def __init__(self, symtab, debug=False):
|
||||
@ -414,7 +100,7 @@ class Analyzer(object):
|
||||
if isinstance(ref, ConstantRef):
|
||||
return ref.type
|
||||
if not isinstance(ref, LocationRef):
|
||||
raise NotImplementedError
|
||||
raise NotImplementedError(str(ref))
|
||||
return self.get_type_for_name(ref.name)
|
||||
|
||||
def assert_type(self, type_, *locations):
|
||||
@ -551,8 +237,10 @@ class Analyzer(object):
|
||||
self.analyze_save(instr, context)
|
||||
elif isinstance(instr, PointInto):
|
||||
self.analyze_point_into(instr, context)
|
||||
elif isinstance(instr, Reset):
|
||||
self.analyze_reset(instr, context)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
raise NotImplementedError(str(instr))
|
||||
|
||||
def analyze_single_op(self, instr, context):
|
||||
|
||||
@ -772,11 +460,12 @@ class Analyzer(object):
|
||||
# 2. check that the context is meaningful
|
||||
|
||||
if isinstance(src, (LocationRef, ConstantRef)) and isinstance(dest, IndirectRef):
|
||||
context.assert_meaningful(src, REG_Y)
|
||||
context.assert_meaningful(src, dest.ref, REG_Y)
|
||||
|
||||
target = context.get_assoc(dest.ref)
|
||||
if not target:
|
||||
raise ForbiddenWriteError(instr, dest.ref)
|
||||
context.assert_writeable(target)
|
||||
context.set_touched(target)
|
||||
context.set_written(target)
|
||||
|
||||
@ -788,10 +477,11 @@ class Analyzer(object):
|
||||
raise UnmeaningfulReadError(instr, src.ref)
|
||||
context.assert_meaningful(origin)
|
||||
|
||||
context.assert_writeable(dest)
|
||||
context.set_touched(dest)
|
||||
context.set_written(dest)
|
||||
elif isinstance(src, IndirectRef) and isinstance(dest, IndirectRef):
|
||||
context.assert_meaningful(src.ref, REG_Y)
|
||||
context.assert_meaningful(src.ref, dest.ref, REG_Y)
|
||||
|
||||
origin = context.get_assoc(src.ref)
|
||||
if not origin:
|
||||
@ -801,6 +491,7 @@ class Analyzer(object):
|
||||
target = context.get_assoc(dest.ref)
|
||||
if not target:
|
||||
raise ForbiddenWriteError(instr, dest.ref)
|
||||
context.assert_writeable(target)
|
||||
context.set_touched(target)
|
||||
context.set_written(target)
|
||||
|
||||
@ -1020,11 +711,11 @@ class Analyzer(object):
|
||||
if context.get_assoc(instr.pointer):
|
||||
raise ForbiddenWriteError(instr, instr.pointer)
|
||||
|
||||
# associate pointer with table, mark it as meaningful.
|
||||
# associate pointer with table
|
||||
# (do not mark it as meaningful yet - that's reset's job.)
|
||||
|
||||
context.set_assoc(instr.pointer, instr.table)
|
||||
context.set_meaningful(instr.pointer)
|
||||
context.set_touched(instr.pointer)
|
||||
context.set_unmeaningful(instr.pointer)
|
||||
|
||||
self.analyze_block(instr.block, context)
|
||||
if context.encountered_gotos():
|
||||
@ -1034,3 +725,21 @@ class Analyzer(object):
|
||||
|
||||
context.set_assoc(instr.pointer, None)
|
||||
context.set_unmeaningful(instr.pointer)
|
||||
|
||||
def analyze_reset(self, instr, context):
|
||||
type = self.get_type(instr.pointer)
|
||||
if not isinstance(type, (PointerType)):
|
||||
raise TypeMismatchError(instr, instr.pointer.name)
|
||||
|
||||
table = context.get_assoc(instr.pointer)
|
||||
if not table:
|
||||
raise ForbiddenWriteError(instr, '{} is not associated with any table'.format(instr.pointer.name))
|
||||
context.assert_meaningful(table)
|
||||
low_limit, high_limit = context.get_range(table)
|
||||
|
||||
assert isinstance(instr.offset, ConstantRef)
|
||||
if instr.offset.value < low_limit or instr.offset.value > high_limit:
|
||||
raise RangeExceededError(instr, instr.pointer.name)
|
||||
|
||||
context.set_meaningful(instr.pointer)
|
||||
context.set_touched(instr.pointer)
|
||||
|
@ -75,6 +75,10 @@ class SingleOp(Instr):
|
||||
value_attrs = ('opcode', 'dest', 'src',)
|
||||
|
||||
|
||||
class Reset(Instr):
|
||||
value_attrs = ('pointer', 'offset',)
|
||||
|
||||
|
||||
class Call(Instr):
|
||||
value_attrs = ('location',)
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
from sixtypical.ast import (
|
||||
Program, Routine, Block, SingleOp, Call, GoTo, If, Repeat, For, WithInterruptsOff, Save, PointInto
|
||||
Program, Routine, Block, SingleOp, Reset, Call, GoTo, If, Repeat, For, WithInterruptsOff, Save, PointInto
|
||||
)
|
||||
from sixtypical.model import (
|
||||
ConstantRef, LocationRef, IndexedRef, IndirectRef,
|
||||
@ -37,6 +37,7 @@ class Compiler(object):
|
||||
self.routine_locals = {} # routine.name -> { local.name -> Label }
|
||||
self.labels = {} # global.name -> Label ("global" includes routines)
|
||||
self.trampolines = {} # Location -> Label
|
||||
self.pointer_assoc = {} # pointer name -> table name (I'm not entirely happy about this)
|
||||
self.current_routine = None
|
||||
|
||||
# - - - - helper methods - - - -
|
||||
@ -191,6 +192,8 @@ class Compiler(object):
|
||||
return self.compile_save(instr)
|
||||
elif isinstance(instr, PointInto):
|
||||
return self.compile_point_into(instr)
|
||||
elif isinstance(instr, Reset):
|
||||
return self.compile_reset(instr)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
@ -741,12 +744,16 @@ class Compiler(object):
|
||||
self.emitter.emit(STA(Absolute(src_label)))
|
||||
|
||||
def compile_point_into(self, instr):
|
||||
src_label = self.get_label(instr.table.name)
|
||||
self.pointer_assoc[instr.pointer.name] = instr.table.name
|
||||
self.compile_block(instr.block)
|
||||
del self.pointer_assoc[instr.pointer.name]
|
||||
|
||||
def compile_reset(self, instr):
|
||||
table_name = self.pointer_assoc[instr.pointer.name]
|
||||
src_label = Offset(self.get_label(table_name), instr.offset.value)
|
||||
dest_label = self.get_label(instr.pointer.name)
|
||||
|
||||
self.emitter.emit(LDA(Immediate(HighAddressByte(src_label))))
|
||||
self.emitter.emit(STA(ZeroPage(dest_label)))
|
||||
self.emitter.emit(LDA(Immediate(LowAddressByte(src_label))))
|
||||
self.emitter.emit(STA(ZeroPage(Offset(dest_label, 1))))
|
||||
|
||||
self.compile_block(instr.block)
|
||||
|
328
src/sixtypical/context.py
Normal file
328
src/sixtypical/context.py
Normal file
@ -0,0 +1,328 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
from sixtypical.model import (
|
||||
RoutineType, ConstantRef, LocationRef, IndirectRef, IndexedRef,
|
||||
)
|
||||
|
||||
|
||||
class AnalysisContext(object):
|
||||
"""
|
||||
A location is touched if it was changed (or even potentially
|
||||
changed) during this routine, or some routine called by this routine.
|
||||
|
||||
A location is meaningful if it was an input to this routine,
|
||||
or if it was set to a meaningful value by some operation in this
|
||||
routine (or some routine called by this routine).
|
||||
|
||||
If a location is meaningful, it has a range. This range represents
|
||||
the lowest and highest values that it might possibly be (i.e. we know
|
||||
it cannot possibly be below the lowest or above the highest.) In the
|
||||
absence of any usage information, the range of a byte, is 0..255 and
|
||||
the range of a word is 0..65535.
|
||||
|
||||
A location is writeable if it was listed in the outputs and trashes
|
||||
lists of this routine. A location can also be temporarily marked
|
||||
unwriteable in certain contexts, such as `for` loops.
|
||||
"""
|
||||
def __init__(self, symtab, routine, inputs, outputs, trashes):
|
||||
from sixtypical.analyzer import ConstantConstraintError, InconsistentConstraintsError
|
||||
|
||||
self.symtab = symtab
|
||||
self.routine = routine # Routine (AST node)
|
||||
self._touched = set() # {LocationRef}
|
||||
self._range = dict() # LocationRef -> (Int, Int)
|
||||
self._writeable = set() # {LocationRef}
|
||||
self._terminated = False
|
||||
self._gotos_encountered = set()
|
||||
self._pointer_assoc = dict()
|
||||
|
||||
for ref in inputs:
|
||||
if self.is_constant(ref):
|
||||
raise ConstantConstraintError(self.routine, ref.name)
|
||||
self._range[ref] = self.max_range(ref)
|
||||
output_names = set()
|
||||
for ref in outputs:
|
||||
if self.is_constant(ref):
|
||||
raise ConstantConstraintError(self.routine, ref.name)
|
||||
output_names.add(ref.name)
|
||||
self._writeable.add(ref)
|
||||
for ref in trashes:
|
||||
if self.is_constant(ref):
|
||||
raise ConstantConstraintError(self.routine, ref.name)
|
||||
if ref.name in output_names:
|
||||
raise InconsistentConstraintsError(self.routine, ref.name)
|
||||
self._writeable.add(ref)
|
||||
|
||||
def __str__(self):
|
||||
return "{}(\n _touched={},\n _range={},\n _writeable={}\n)".format(
|
||||
self.__class__.__name__,
|
||||
LocationRef.format_set(self._touched), LocationRef.format_set(self._range), LocationRef.format_set(self._writeable)
|
||||
)
|
||||
|
||||
def to_json_data(self):
|
||||
type_ = self.symtab.fetch_global_type(self.routine.name)
|
||||
return {
|
||||
'routine_inputs': ','.join(sorted(loc.name for loc in type_.inputs)),
|
||||
'routine_outputs': ','.join(sorted(loc.name for loc in type_.outputs)),
|
||||
'routine_trashes': ','.join(sorted(loc.name for loc in type_.trashes)),
|
||||
'touched': ','.join(sorted(loc.name for loc in self._touched)),
|
||||
'range': dict((loc.name, '{}-{}'.format(rng[0], rng[1])) for (loc, rng) in self._range.items()),
|
||||
'writeable': ','.join(sorted(loc.name for loc in self._writeable)),
|
||||
'terminated': self._terminated,
|
||||
'gotos_encountered': ','.join(sorted(loc.name for loc in self._gotos_encountered)),
|
||||
}
|
||||
|
||||
def clone(self):
|
||||
c = AnalysisContext(self.symtab, self.routine, [], [], [])
|
||||
c._touched = set(self._touched)
|
||||
c._range = dict(self._range)
|
||||
c._writeable = set(self._writeable)
|
||||
c._pointer_assoc = dict(self._pointer_assoc)
|
||||
c._gotos_encountered = set(self._gotos_encountered)
|
||||
return c
|
||||
|
||||
def update_from(self, other):
|
||||
"""Replaces the information in this context, with the information from the other context.
|
||||
This is an overwriting action - it does not attempt to merge the contexts.
|
||||
|
||||
We do not replace the gotos_encountered for technical reasons. (In `analyze_if`,
|
||||
we merge those sets afterwards; at the end of `analyze_routine`, they are not distinct in the
|
||||
set of contexts we are updating from, and we want to retain our own.)"""
|
||||
self.routine = other.routine
|
||||
self._touched = set(other._touched)
|
||||
self._range = dict(other._range)
|
||||
self._writeable = set(other._writeable)
|
||||
self._terminated = other._terminated
|
||||
self._pointer_assoc = dict(other._pointer_assoc)
|
||||
|
||||
def each_meaningful(self):
|
||||
for ref in self._range.keys():
|
||||
yield ref
|
||||
|
||||
def each_touched(self):
|
||||
for ref in self._touched:
|
||||
yield ref
|
||||
|
||||
def each_writeable(self):
|
||||
for ref in self._writeable:
|
||||
yield ref
|
||||
|
||||
def assert_meaningful(self, *refs, **kwargs):
|
||||
from sixtypical.analyzer import UnmeaningfulReadError
|
||||
|
||||
exception_class = kwargs.get('exception_class', UnmeaningfulReadError)
|
||||
for ref in refs:
|
||||
if self.symtab.has_local(self.routine.name, ref.name):
|
||||
if ref not in self._range:
|
||||
message = ref.name
|
||||
if kwargs.get('message'):
|
||||
message += ' (%s)' % kwargs['message']
|
||||
raise exception_class(self.routine, message)
|
||||
else:
|
||||
continue
|
||||
if self.is_constant(ref):
|
||||
pass
|
||||
elif isinstance(ref, LocationRef):
|
||||
if ref not in self._range:
|
||||
message = ref.name
|
||||
if kwargs.get('message'):
|
||||
message += ' (%s)' % kwargs['message']
|
||||
raise exception_class(self.routine, message)
|
||||
elif isinstance(ref, IndexedRef):
|
||||
self.assert_meaningful(ref.ref, **kwargs)
|
||||
self.assert_meaningful(ref.index, **kwargs)
|
||||
else:
|
||||
raise NotImplementedError(ref)
|
||||
|
||||
def assert_writeable(self, *refs, **kwargs):
|
||||
from sixtypical.analyzer import ForbiddenWriteError
|
||||
|
||||
exception_class = kwargs.get('exception_class', ForbiddenWriteError)
|
||||
for ref in refs:
|
||||
# locals are always writeable
|
||||
if self.symtab.has_local(self.routine.name, ref.name):
|
||||
continue
|
||||
if ref not in self._writeable:
|
||||
message = ref.name
|
||||
if kwargs.get('message'):
|
||||
message += ' (%s)' % kwargs['message']
|
||||
raise exception_class(self.routine, message)
|
||||
|
||||
def assert_in_range(self, inside, outside, offset):
|
||||
"""Given two locations, assert that the first location, offset by the given offset,
|
||||
is contained 'inside' the second location."""
|
||||
from sixtypical.analyzer import RangeExceededError
|
||||
|
||||
assert isinstance(inside, LocationRef)
|
||||
assert isinstance(outside, LocationRef)
|
||||
|
||||
# inside should always be meaningful
|
||||
inside_range = self._range[inside]
|
||||
|
||||
# outside might not be meaningful, so default to max range if necessary
|
||||
if outside in self._range:
|
||||
outside_range = self._range[outside]
|
||||
else:
|
||||
outside_range = self.max_range(outside)
|
||||
|
||||
if (inside_range[0] + offset.value) < outside_range[0] or (inside_range[1] + offset.value) > outside_range[1]:
|
||||
raise RangeExceededError(self.routine,
|
||||
"Possible range of {} {} (+{}) exceeds acceptable range of {} {}".format(
|
||||
inside, inside_range, offset, outside, outside_range
|
||||
)
|
||||
)
|
||||
|
||||
def set_touched(self, *refs):
|
||||
for ref in refs:
|
||||
self._touched.add(ref)
|
||||
# TODO: it might be possible to invalidate the range here
|
||||
|
||||
def set_meaningful(self, *refs):
|
||||
for ref in refs:
|
||||
if ref not in self._range:
|
||||
self._range[ref] = self.max_range(ref)
|
||||
|
||||
def set_top_of_range(self, ref, top):
|
||||
self.assert_meaningful(ref)
|
||||
(bottom, _) = self._range[ref]
|
||||
self._range[ref] = (bottom, top)
|
||||
|
||||
def set_bottom_of_range(self, ref, bottom):
|
||||
self.assert_meaningful(ref)
|
||||
(top, _) = self._range[ref]
|
||||
self._range[ref] = (bottom, top)
|
||||
|
||||
def set_range(self, ref, bottom, top):
|
||||
self.assert_meaningful(ref)
|
||||
self._range[ref] = (bottom, top)
|
||||
|
||||
def get_top_of_range(self, ref):
|
||||
if isinstance(ref, ConstantRef):
|
||||
return ref.value
|
||||
self.assert_meaningful(ref)
|
||||
(_, top) = self._range[ref]
|
||||
return top
|
||||
|
||||
def get_bottom_of_range(self, ref):
|
||||
if isinstance(ref, ConstantRef):
|
||||
return ref.value
|
||||
self.assert_meaningful(ref)
|
||||
(bottom, _) = self._range[ref]
|
||||
return bottom
|
||||
|
||||
def get_range(self, ref):
|
||||
if isinstance(ref, ConstantRef):
|
||||
return (ref.value, ref.value)
|
||||
self.assert_meaningful(ref)
|
||||
(bottom, top) = self._range[ref]
|
||||
return bottom, top
|
||||
|
||||
def copy_range(self, src, dest):
|
||||
self.assert_meaningful(src)
|
||||
if src in self._range:
|
||||
src_range = self._range[src]
|
||||
else:
|
||||
src_range = self.max_range(src)
|
||||
self._range[dest] = src_range
|
||||
|
||||
def invalidate_range(self, ref):
|
||||
self.assert_meaningful(ref)
|
||||
self._range[ref] = self.max_range(ref)
|
||||
|
||||
def set_unmeaningful(self, *refs):
|
||||
for ref in refs:
|
||||
if ref in self._range:
|
||||
del self._range[ref]
|
||||
|
||||
def set_written(self, *refs):
|
||||
"""A "helper" method which does the following common sequence for
|
||||
the given refs: asserts they're all writable, and sets them all
|
||||
as touched and meaningful."""
|
||||
self.assert_writeable(*refs)
|
||||
self.set_touched(*refs)
|
||||
self.set_meaningful(*refs)
|
||||
|
||||
def set_unwriteable(self, *refs):
|
||||
"""Intended to be used for implementing analyzing `for`."""
|
||||
for ref in refs:
|
||||
self._writeable.remove(ref)
|
||||
|
||||
def set_writeable(self, *refs):
|
||||
"""Intended to be used for implementing analyzing `for`, but also used in `save`."""
|
||||
for ref in refs:
|
||||
self._writeable.add(ref)
|
||||
|
||||
def encounter_gotos(self, gotos):
|
||||
self._gotos_encountered |= gotos
|
||||
|
||||
def encountered_gotos(self):
|
||||
return self._gotos_encountered
|
||||
|
||||
def set_terminated(self):
|
||||
# Having a terminated context and having encountered gotos is not the same thing.
|
||||
self._terminated = True
|
||||
|
||||
def has_terminated(self):
|
||||
return self._terminated
|
||||
|
||||
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)
|
||||
|
||||
def get_assoc(self, pointer):
|
||||
return self._pointer_assoc.get(pointer)
|
||||
|
||||
def set_assoc(self, pointer, table):
|
||||
self._pointer_assoc[pointer] = table
|
||||
|
||||
def is_constant(self, ref):
|
||||
"""read-only means that the program cannot change the value
|
||||
of a location. constant means that the value of the location
|
||||
will not change during the lifetime of the program."""
|
||||
if isinstance(ref, ConstantRef):
|
||||
return True
|
||||
if isinstance(ref, (IndirectRef, IndexedRef)):
|
||||
return False
|
||||
if isinstance(ref, LocationRef):
|
||||
type_ = self.symtab.fetch_global_type(ref.name)
|
||||
return isinstance(type_, RoutineType)
|
||||
raise NotImplementedError
|
||||
|
||||
def max_range(self, ref):
|
||||
if isinstance(ref, ConstantRef):
|
||||
return (ref.value, ref.value)
|
||||
elif self.symtab.has_local(self.routine.name, ref.name):
|
||||
return self.symtab.fetch_local_type(self.routine.name, ref.name).max_range
|
||||
else:
|
||||
return self.symtab.fetch_global_type(ref.name).max_range
|
@ -128,7 +128,7 @@ class Offset(Emittable):
|
||||
|
||||
class HighAddressByte(Emittable):
|
||||
def __init__(self, label):
|
||||
assert isinstance(label, Label)
|
||||
assert isinstance(label, (Label, Offset))
|
||||
self.label = label
|
||||
|
||||
def size(self):
|
||||
@ -143,7 +143,7 @@ class HighAddressByte(Emittable):
|
||||
|
||||
class LowAddressByte(Emittable):
|
||||
def __init__(self, label):
|
||||
assert isinstance(label, Label)
|
||||
assert isinstance(label, (Label, Offset))
|
||||
self.label = label
|
||||
|
||||
def size(self):
|
||||
|
@ -1,7 +1,7 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
from sixtypical.ast import (
|
||||
Program, Defn, Routine, Block, SingleOp, Call, GoTo, If, Repeat, For, WithInterruptsOff, Save, PointInto
|
||||
Program, Defn, Routine, Block, SingleOp, Reset, Call, GoTo, If, Repeat, For, WithInterruptsOff, Save, PointInto
|
||||
)
|
||||
from sixtypical.model import (
|
||||
TYPE_BIT, TYPE_BYTE, TYPE_WORD,
|
||||
@ -432,6 +432,10 @@ class Parser(object):
|
||||
final = self.const()
|
||||
block = self.block()
|
||||
return For(self.scanner.line_number, dest=dest, direction=direction, final=final, block=block)
|
||||
elif self.scanner.consume('reset'):
|
||||
pointer = self.locexpr()
|
||||
offset = self.const()
|
||||
return Reset(self.scanner.line_number, pointer=pointer, offset=offset)
|
||||
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
|
||||
|
10
test.sh
10
test.sh
@ -1,7 +1,9 @@
|
||||
#!/bin/sh
|
||||
|
||||
falderal --substring-error \
|
||||
tests/SixtyPical\ Syntax.md \
|
||||
tests/SixtyPical\ Analysis.md \
|
||||
tests/SixtyPical\ Fallthru.md \
|
||||
tests/SixtyPical\ Compilation.md
|
||||
"tests/SixtyPical Syntax.md" \
|
||||
"tests/SixtyPical Analysis.md" \
|
||||
"tests/SixtyPical Storage.md" \
|
||||
"tests/SixtyPical Control Flow.md" \
|
||||
"tests/SixtyPical Fallthru.md" \
|
||||
"tests/SixtyPical Compilation.md"
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1400,7 +1400,7 @@ Subtracting a word memory location from another word memory location.
|
||||
|
||||
### Tables and Pointers
|
||||
|
||||
Load address of table into pointer.
|
||||
Associate pointer with table. Does nothing by itself.
|
||||
|
||||
| byte table[256] tab
|
||||
| pointer ptr @ 254
|
||||
@ -1415,6 +1415,24 @@ Load address of table into pointer.
|
||||
| }
|
||||
| }
|
||||
= $080D LDY #$00
|
||||
= $080F RTS
|
||||
|
||||
Reset pointer to table.
|
||||
|
||||
| byte table[256] tab
|
||||
| pointer ptr @ 254
|
||||
|
|
||||
| define main routine
|
||||
| inputs tab
|
||||
| outputs tab, y
|
||||
| trashes a, z, n, ptr
|
||||
| {
|
||||
| ld y, 0
|
||||
| point ptr into tab {
|
||||
| reset ptr 0
|
||||
| }
|
||||
| }
|
||||
= $080D LDY #$00
|
||||
= $080F LDA #$18
|
||||
= $0811 STA $FE
|
||||
= $0813 LDA #$08
|
||||
@ -1433,6 +1451,7 @@ Write literal through a pointer.
|
||||
| {
|
||||
| ld y, 0
|
||||
| point ptr into tab {
|
||||
| reset ptr 0
|
||||
| copy 123, [ptr] + y
|
||||
| }
|
||||
| }
|
||||
@ -1458,6 +1477,7 @@ Write stored value through a pointer.
|
||||
| {
|
||||
| ld y, 0
|
||||
| point ptr into tab {
|
||||
| reset ptr 0
|
||||
| copy foo, [ptr] + y
|
||||
| }
|
||||
| }
|
||||
@ -1483,6 +1503,7 @@ Read through a pointer, into a byte storage location, or the `a` register.
|
||||
| {
|
||||
| ld y, 0
|
||||
| point ptr into tab {
|
||||
| reset ptr 0
|
||||
| copy [ptr] + y, foo
|
||||
| ld a, [ptr] + y
|
||||
| }
|
||||
@ -1497,6 +1518,39 @@ Read through a pointer, into a byte storage location, or the `a` register.
|
||||
= $081C LDA ($FE),Y
|
||||
= $081E RTS
|
||||
|
||||
Multiple `reset`s may occur inside the same block.
|
||||
|
||||
| byte table[256] tab @ 1024
|
||||
| pointer ptr @ 254
|
||||
| byte foo
|
||||
|
|
||||
| define main routine
|
||||
| inputs tab
|
||||
| outputs y, foo
|
||||
| trashes a, z, n, ptr
|
||||
| {
|
||||
| ld y, 0
|
||||
| point ptr into tab {
|
||||
| reset ptr 16
|
||||
| copy [ptr] + y, foo
|
||||
| reset ptr 18
|
||||
| ld a, [ptr] + y
|
||||
| }
|
||||
| }
|
||||
= $080D LDY #$00
|
||||
= $080F LDA #$10
|
||||
= $0811 STA $FE
|
||||
= $0813 LDA #$04
|
||||
= $0815 STA $FF
|
||||
= $0817 LDA ($FE),Y
|
||||
= $0819 STA $0827
|
||||
= $081C LDA #$12
|
||||
= $081E STA $FE
|
||||
= $0820 LDA #$04
|
||||
= $0822 STA $FF
|
||||
= $0824 LDA ($FE),Y
|
||||
= $0826 RTS
|
||||
|
||||
Read and write through two pointers.
|
||||
|
||||
| byte table[256] tab
|
||||
@ -1510,7 +1564,9 @@ Read and write through two pointers.
|
||||
| {
|
||||
| ld y, 0
|
||||
| point ptra into tab {
|
||||
| reset ptra 0
|
||||
| point ptrb into tab {
|
||||
| reset ptrb 0
|
||||
| copy [ptra] + y, [ptrb] + y
|
||||
| }
|
||||
| }
|
||||
@ -1541,6 +1597,7 @@ Write the `a` register through a pointer.
|
||||
| {
|
||||
| ld y, 0
|
||||
| point ptr into tab {
|
||||
| reset ptr 0
|
||||
| ld a, 255
|
||||
| st a, [ptr] + y
|
||||
| }
|
||||
@ -1571,6 +1628,7 @@ Note that this is *not* range-checked. (Yet.)
|
||||
| ld y, 0
|
||||
| st off, c
|
||||
| point ptr into tab {
|
||||
| reset ptr 0
|
||||
| add ptr, delta
|
||||
| add ptr, word 1
|
||||
| copy [ptr] + y, foo
|
||||
@ -1619,9 +1677,9 @@ Trash does nothing except indicate that we do not care about the value anymore.
|
||||
= $080E LDA #$00
|
||||
= $0810 RTS
|
||||
|
||||
### static ###
|
||||
### locals ###
|
||||
|
||||
Memory locations defined static to a routine are allocated
|
||||
Memory locations defined local static to a routine are allocated
|
||||
just the same as initialized global storage locations are.
|
||||
|
||||
| define foo routine
|
||||
@ -1651,3 +1709,66 @@ just the same as initialized global storage locations are.
|
||||
= $081D RTS
|
||||
= $081E .byte $FF
|
||||
= $081F .byte $07
|
||||
|
||||
Memory locations defined local dynamic to a routine are allocated
|
||||
just the same as uninitialized global storage locations are.
|
||||
|
||||
| define foo routine
|
||||
| inputs x
|
||||
| outputs x
|
||||
| trashes z, n
|
||||
| local byte t
|
||||
| {
|
||||
| st x, t
|
||||
| inc t
|
||||
| ld x, t
|
||||
| }
|
||||
|
|
||||
| define main routine
|
||||
| trashes a, x, z, n
|
||||
| local byte t
|
||||
| {
|
||||
| ld x, 0
|
||||
| st x, t
|
||||
| call foo
|
||||
| }
|
||||
= $080D LDX #$00
|
||||
= $080F STX $0821
|
||||
= $0812 JSR $0816
|
||||
= $0815 RTS
|
||||
= $0816 STX $0820
|
||||
= $0819 INC $0820
|
||||
= $081C LDX $0820
|
||||
= $081F RTS
|
||||
|
||||
Memory locations defined local dynamic to a routine are allocated
|
||||
just the same as uninitialized global storage locations are, even
|
||||
when declared with a fixed address.
|
||||
|
||||
| define foo routine
|
||||
| inputs x
|
||||
| outputs x
|
||||
| trashes z, n
|
||||
| local byte t @ 1024
|
||||
| {
|
||||
| st x, t
|
||||
| inc t
|
||||
| ld x, t
|
||||
| }
|
||||
|
|
||||
| define main routine
|
||||
| trashes a, x, z, n
|
||||
| local byte t @ 1025
|
||||
| {
|
||||
| ld x, 0
|
||||
| st x, t
|
||||
| call foo
|
||||
| }
|
||||
= $080D LDX #$00
|
||||
= $080F STX $0401
|
||||
= $0812 JSR $0816
|
||||
= $0815 RTS
|
||||
= $0816 STX $0400
|
||||
= $0819 INC $0400
|
||||
= $081C LDX $0400
|
||||
= $081F RTS
|
||||
|
1769
tests/SixtyPical Control Flow.md
Normal file
1769
tests/SixtyPical Control Flow.md
Normal file
File diff suppressed because it is too large
Load Diff
1922
tests/SixtyPical Storage.md
Normal file
1922
tests/SixtyPical Storage.md
Normal file
File diff suppressed because it is too large
Load Diff
@ -176,6 +176,7 @@ Other blocks.
|
||||
| ld a, 0
|
||||
| }
|
||||
| point ptr into tab {
|
||||
| reset ptr 0
|
||||
| ld a, [ptr] + y
|
||||
| }
|
||||
| }
|
||||
@ -681,6 +682,7 @@ Tables and pointers.
|
||||
|
|
||||
| define main routine {
|
||||
| point ptr into buf {
|
||||
| reset ptr 0
|
||||
| copy 123, [ptr] + y
|
||||
| copy [ptr] + y, foo
|
||||
| copy [ptr] + y, [ptrb] + y
|
||||
@ -784,6 +786,20 @@ Local static memory locations must always be given an initial value.
|
||||
| }
|
||||
? SyntaxError
|
||||
|
||||
Local static memory locations may not be given an address.
|
||||
|
||||
| define main routine
|
||||
| inputs x
|
||||
| outputs x
|
||||
| trashes z, n
|
||||
| static byte t @ 1024
|
||||
| {
|
||||
| st x, t
|
||||
| inc t
|
||||
| ld x, t
|
||||
| }
|
||||
? SyntaxError
|
||||
|
||||
Local dynamic memory locations may not be given an initial value.
|
||||
|
||||
| define main routine
|
||||
@ -798,6 +814,20 @@ Local dynamic memory locations may not be given an initial value.
|
||||
| }
|
||||
? SyntaxError
|
||||
|
||||
Local dynamic memory locations may be given an address.
|
||||
|
||||
| define main routine
|
||||
| inputs x
|
||||
| outputs x
|
||||
| trashes z, n
|
||||
| local byte t @ 1024
|
||||
| {
|
||||
| st x, t
|
||||
| inc t
|
||||
| ld x, t
|
||||
| }
|
||||
= ok
|
||||
|
||||
Name of a local cannot shadow an existing global or local.
|
||||
|
||||
| byte t
|
||||
|
Loading…
Reference in New Issue
Block a user