1
0
mirror of https://github.com/catseye/SixtyPical.git synced 2024-06-02 03:41:28 +00:00

Checkpoint import of changes for version 0.19.

This commit is contained in:
Chris Pressey 2019-04-08 11:50:54 +01:00
parent c7e1b69845
commit b19267d3ba
14 changed files with 1189 additions and 444 deletions

View File

@ -1,6 +1,24 @@
History of SixtyPical
=====================
0.19
----
* A `table` may be defined with more than 256 entries, even
though the conventional index syntax can only refer to the
first 256 entries.
* A `pointer` may point inside values of type `byte table`,
allowing access to entries beyond the 256th.
* `buffer` types have been eliminated from the language,
as the above two improvements allow `byte table`s to
do everything `buffer`s previously did.
* When accessing a table with an index, a constant offset
can also be given.
* Accessing a `table` through a `pointer` must be done in
the context of a `point ... into` block. This allows the
analyzer to check *which* table is being modified.
* Added `--dump-exit-contexts` option to `sixtypical`.
0.18
----

69
TODO.md
View File

@ -12,37 +12,66 @@ Allow
Which uses some other storage location instead of the stack. A local static
would be a good candidate for such.
### Associate each pointer with the buffer it points into
### Analyze `call` within blocks?
Check that the buffer being read or written to through pointer, appears in appropriate
inputs or outputs set.
What happens if you call another routine from inside a `with interrupts off` block?
In the analysis, when we obtain a pointer, we need to record, in context, what buffer
that pointer came from.
What happens if you call another routine from inside a `save` block?
When we write through that pointer, we need to set that buffer as written.
What happens if you call another routine from inside a `point into` block?
When we read through the pointer, we need to check that the buffer is readable.
What happens if you call another routine from inside a `for` block?
### Table overlays
Remember that any of these may have a `goto` ... and they may have a second
instance of the same block (e.g. `with interrupts off` nested within
`with interrupts off` shouldn't be allowed to turn them back on after the
inner block has finished -- even if there is no `call`.)
They are uninitialized, but the twist is, the address is a buffer that is
an input to and/or output of the routine. So, they are defined (insofar
as the buffer is defined.)
These holes need to be plugged.
They are therefore a "view" of a section of a buffer.
### Pointers associated globally with a table
This is slightly dangerous since it does permit aliases: the buffer and the
table refer to the same memory.
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.
Although, if they are `static`, you could say, in the routine in which they
are `static`, as soon as you've established one, you can no longer use the
buffer; and the ones you establish must be disjoint.
We could associate every pointer variable with a specific table variable, in its
declaration. This makes some things simple, and would allow us to know what table a
pointer is supposed to point into, even if that pointer was passed into our routine.
(That seems to be the most compelling case for restricting them to `static`.)
One drawback is that it would limit each pointer to be used only on one table. Since a
pointer basically represents a zero-page location, and since those are a relatively scarce
resource, we would prefer if a single pointer could be used to point into different tables
at different times.
An alternative would be `static` pointers, which are currently not possible because
pointers must be zero-page, thus `@`, thus uninitialized.
These can co-exist with general, non-specific-table-linked `pointer` variables.
### Local non-statics
Somewhat related to the above, it should be possible to declare a local storage
location which is not static.
In this case, it would be considered uninitialized each time the routine was
entered.
So, you do not have a guarantee that it has a valid value. But you are guaranteed
that no other routine can read or modify it.
It also enables a trick: if there are two routines A and B, and A never calls B
(even indirectly), and B never calls A (even indirectly), then their locals can
be allocated at the same space.
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.
You have to `ld a`, `st a`. I think maybe we should have one.
### Tail-call optimization

View File

@ -12,8 +12,9 @@ sys.path.insert(0, join(dirname(realpath(sys.argv[0])), '..', 'src'))
# ----------------------------------------------------------------- #
import codecs
from argparse import ArgumentParser
import codecs
import json
from pprint import pprint
import sys
import traceback
@ -43,14 +44,19 @@ def process_input_files(filenames, options):
program = merge_programs(programs)
analyzer = Analyzer(debug=options.debug)
analyzer.analyze_program(program)
try:
analyzer.analyze_program(program)
finally:
if options.dump_exit_contexts:
sys.stdout.write(json.dumps(analyzer.exit_contexts_map, indent=4, sort_keys=True, separators=(',', ':')))
sys.stdout.write("\n")
compilation_roster = None
if options.optimize_fallthru:
from sixtypical.fallthru import FallthruAnalyzer
def dump(data, label=None):
import json
if not options.dump_fallthru_info:
return
if label:
@ -114,6 +120,12 @@ if __name__ == '__main__':
action="store_true",
help="Only parse and analyze the program; do not compile it."
)
argparser.add_argument(
"--dump-exit-contexts",
action="store_true",
help="Dump a map, in JSON, of the analysis context at each exit of each routine "
"after analyzing the program."
)
argparser.add_argument(
"--optimize-fallthru",
action="store_true",
@ -123,7 +135,7 @@ if __name__ == '__main__':
argparser.add_argument(
"--dump-fallthru-info",
action="store_true",
help="Dump the fallthru map and ordering to stdout after analyzing the program."
help="Dump the ordered fallthru map, in JSON, to stdout after analyzing the program."
)
argparser.add_argument(
"--parse-only",

View File

@ -1,7 +1,7 @@
SixtyPical
==========
This document describes the SixtyPical programming language version 0.15,
This document describes the SixtyPical programming language version 0.19,
both its static semantics (the capabilities and limits of the static
analyses it defines) and its runtime semantics (with reference to the
semantics of 6502 machine code.)
@ -12,34 +12,55 @@ are even more normative.
Refer to the bottom of this document for an EBNF grammar of the syntax of
the language.
Types
-----
Data Model
----------
There are five *primitive types* in SixtyPical:
SixtyPical defines a data model where every value has some type
information associated with it. The values include those that are
directly manipulable by a SixtyPical program, but are not limited to them.
Type information includes not only what kind of structure the data has,
but other properties as well (sometimes called "type annotations".)
### Basic types ###
SixtyPical defines a handful of basic types. There are three types that
are "primitive" in that they are not parameterized in any way:
* bit (2 possible values)
* byte (256 possible values)
* word (65536 possible values)
* routine (code stored somewhere in memory, read-only)
* pointer (address of a byte in a buffer)
There are also three *type constructors*:
Types can also be parameterized and constructed from other types
(which is a kind of parameterization). One such type constructor is
* T table[N] (N entries, 1 ≤ N ≤ 256; each entry holds a value
of type T, where T is `byte`, `word`, or `vector`)
* buffer[N] (N entries; each entry is a byte; 1 ≤ N ≤ 65536)
* pointer (16-bit address of a byte inside a byte table)
* vector T (address of a value of type T; T must be a routine type)
### User-defined ###
Values of the above-listed types are directly manipulable by a SixtyPical
program. Other types describe values which can only be indirectly
manipulated by a program:
* routine (code stored somewhere in memory, read-only)
* T table[N] (series of 1 ≤ N ≤ 65536 values of type T)
There are some restrictions here; for example, a table may only
consist of `byte`, `word`, or `vector` types. A pointer may only
point to a byte inside a `table` of `byte` type.
Each routine is associated with a rich set of type information,
which is basically the types and statuses of memory locations that
have been declared as being relevant to that routine.
#### User-defined ####
A program may define its own types using the `typedef` feature. Typedefs
must occur before everything else in the program. A typedef takes a
type expression and an identifier which has not previously been used in
the program. It associates that identifer with that type. This is merely
a type alias; two types with different names will compare as equal.
a type alias; if two types have identical structure but different names,
they will compare as equal.
Memory locations
----------------
### Memory locations ###
A primary concept in SixtyPical is the *memory location*. At any given point
in time during execution, each memory location is either *uninitialized* or
@ -51,7 +72,7 @@ the program text; thus, it is a static property.
There are four general kinds of memory location. The first three are
pre-defined and built-in.
### Registers ###
#### Registers ####
Each of these hold a byte. They are initially uninitialized.
@ -59,7 +80,7 @@ Each of these hold a byte. They are initially uninitialized.
x
y
### Flags ###
#### Flags ####
Each of these hold a bit. They are initially uninitialized.
@ -68,7 +89,7 @@ Each of these hold a bit. They are initially uninitialized.
v (overflow)
n (negative)
### Constants ###
#### Constants ####
It may be strange to think of constants as memory locations, but keep in mind
that a memory location in SixtyPical need not map to a memory location in the
@ -97,7 +118,7 @@ and sixty-five thousand five hundred and thirty-six word constants,
Note that if a word constant is between 256 and 65535, the leading `word`
token can be omitted.
### User-defined ###
#### User-defined ####
There may be any number of user-defined memory locations. They are defined
by giving the type (which may be any type except `bit` and `routine`) and the
@ -137,13 +158,37 @@ This is actually useful, at least at this point, as you can rely on the fact
that literal integers in the code are always immediate values. (But this
may change at some point.)
### Buffers and Pointers ###
### Tables and Pointers ###
Roughly speaking, a `buffer` is a table that can be longer than 256 bytes,
and a `pointer` is an address within a buffer.
A table is a collection of memory locations that can be indexed in a number
of ways.
The simplest way is to use another memory location as an index. There
are restrictions on which memory locations can be used as indexes;
only the `x` and `y` locations can be used this way. Since those can
only hold a byte, this method, by itself, only allows access to the first
256 entries of the table.
byte table[1024] tab
...
ld a, tab + x
st a, tab + y
However, by combining indexing with a constant _offset_, entries beyond the
256th entry can be accessed.
byte table[1024] tab
...
ld a, tab + 512 + x
st a, tab + 512 + y
Even with an offset, the range of indexing still cannot exceed 256 entries.
Accessing entries at an arbitrary address inside a table can be done with
a `pointer`. Pointers can only be point inside `byte` tables. When a
pointer is used, indexing with `x` or `y` will also take place.
A `pointer` is implemented as a zero-page memory location, and accessing the
buffer pointed to is implemented with "indirect indexed" addressing, as in
table pointed to is implemented with "indirect indexed" addressing, as in
LDA ($02), Y
STA ($02), Y
@ -151,14 +196,15 @@ buffer pointed to is implemented with "indirect indexed" addressing, as in
There are extended instruction modes for using these types of memory location.
See `copy` below, but here is some illustrative example code:
copy ^buf, ptr // this is the only way to initialize a pointer
add ptr, 4 // ok, but only if it does not exceed buffer's size
ld y, 0 // you must set this to something yourself
copy [ptr] + y, byt // read memory through pointer, into byte
copy 100, [ptr] + y // write memory through pointer (still trashes a)
point ptr into buf { // this is the only way to initialize a pointer
add ptr, 4 // note, this is unchecked against table's size!
ld y, 0 // you must set this to something yourself
copy [ptr] + y, byt // read memory through pointer, into byte
copy 100, [ptr] + y // write memory through pointer (still trashes a)
} // after this block, ptr can no longer be used
where `ptr` is a user-defined storage location of `pointer` type, and the
`+ y` part is mandatory.
where `ptr` is a user-defined storage location of `pointer` type, `buf`
is a `table` of `byte` type, and the `+ y` part is mandatory.
Routines
--------
@ -300,17 +346,7 @@ and it trashes the `z` and `n` flags and the `a` register.
After execution, dest is considered initialized, and `z` and `n`, and
`a` are considered uninitialized.
There are two extra modes that this instruction can be used in. The first is
to load an address into a pointer:
copy ^<src-memory-location>, <dest-memory-location>
This copies the address of src into dest. In this case, src must be
of type buffer, and dest must be of type pointer. src will not be
considered a memory location that is read, since it is only its address
that is being retrieved.
The second is to read or write indirectly through a pointer.
There is an extra mode that this instruction can be used in:
copy [<src-memory-location>] + y, <dest-memory-location>
copy <src-memory-location>, [<dest-memory-location>] + y
@ -350,7 +386,7 @@ In fact, this instruction trashes the `a` register in all cases except
when the dest is `a`.
NOTE: If dest is a pointer, the addition does not check if the result of
the pointer arithmetic continues to be valid (within a buffer) or not.
the pointer arithmetic continues to be valid (within a table) or not.
### inc ###
@ -581,11 +617,10 @@ Grammar
Program ::= {ConstDefn | TypeDefn} {Defn} {Routine}.
ConstDefn::= "const" Ident<new> Const.
TypeDefn::= "typedef" Type Ident<new>.
Defn ::= Type Ident<new> [Constraints] (":" Const | "@" LitWord).
Defn ::= Type Ident<new> (":" Const | "@" LitWord).
Type ::= TypeTerm ["table" TypeSize].
TypeExpr::= "byte"
| "word"
| "buffer" TypeSize
| "pointer"
| "vector" TypeTerm
| "routine" Constraints
@ -594,10 +629,8 @@ Grammar
TypeSize::= "[" LitWord "]".
Constrnt::= ["inputs" LocExprs] ["outputs" LocExprs] ["trashes" LocExprs].
Routine ::= "define" Ident<new> Type (Block | "@" LitWord).
| "routine" Ident<new> Constraints (Block | "@" LitWord)
.
LocExprs::= LocExpr {"," LocExpr}.
LocExpr ::= Register | Flag | Const | Ident.
LocExpr ::= Register | Flag | Const | Ident [["+" Const] "+" Register].
Register::= "a" | "x" | "y".
Flag ::= "c" | "z" | "n" | "v".
Const ::= Literal | Ident<const>.

View File

@ -21,24 +21,16 @@
// and the end of their own routines, so the type needs to be compatible.
// (In a good sense, it is a continuation.)
//
// Further,
//
// It's very arguable that screen1/2/3/4 and colormap1/2/3/4 are not REALLY inputs.
// They're only there to support the fact that game states sometimes clear the
// screen, and sometimes don't. When they don't, they preserve the screen, and
// currently the way to say "we preserve the screen" is to have it as both input
// and output. There is probably a better way to do this, but it needs thought.
//
typedef routine
inputs joy2, press_fire_msg, dispatch_game_state,
actor_pos, actor_delta, actor_logic,
player_died,
screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4
screen, colormap
outputs dispatch_game_state,
actor_pos, actor_delta, actor_logic,
player_died,
screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4
screen, colormap
trashes a, x, y, c, z, n, v, pos, new_pos, delta, ptr, dispatch_logic
game_state_routine
@ -62,18 +54,8 @@ typedef routine
byte vic_border @ 53280
byte vic_bg @ 53281
byte table[256] screen1 @ 1024
byte table[256] screen2 @ 1274
byte table[256] screen3 @ 1524
byte table[256] screen4 @ 1774
byte table[256] colormap1 @ 55296
byte table[256] colormap2 @ 55546
byte table[256] colormap3 @ 55796
byte table[256] colormap4 @ 56046
buffer[2048] screen @ 1024
byte table[2048] screen @ 1024
byte table[2048] colormap @ 55296
byte joy2 @ $dc00
// ----------------------------------------------------------------
@ -187,22 +169,22 @@ define check_button routine
}
define clear_screen routine
outputs screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4
outputs screen, colormap
trashes a, y, c, n, z
{
ld y, 0
repeat {
ld a, 1
st a, colormap1 + y
st a, colormap2 + y
st a, colormap3 + y
st a, colormap4 + y
st a, colormap + y
st a, colormap + 250 + y
st a, colormap + 500 + y
st a, colormap + 750 + y
ld a, 32
st a, screen1 + y
st a, screen2 + y
st a, screen3 + y
st a, screen4 + y
st a, screen + y
st a, screen + 250 + y
st a, screen + 500 + y
st a, screen + 750 + y
inc y
cmp y, 250
@ -282,13 +264,14 @@ define player_logic logic_routine
call check_new_position_in_bounds
if c {
copy ^screen, ptr
st off, c
add ptr, new_pos
ld y, 0
point ptr into screen {
st off, c
add ptr, new_pos
ld y, 0
// check collision.
ld a, [ptr] + y
}
// check collision.
ld a, [ptr] + y
// if "collision" is with your own self, treat it as if it's blank space!
cmp a, 81
if z {
@ -296,17 +279,19 @@ define player_logic logic_routine
}
cmp a, 32
if z {
copy ^screen, ptr
st off, c
add ptr, pos
copy 32, [ptr] + y
point ptr into screen {
st off, c
add ptr, pos
copy 32, [ptr] + y
}
copy new_pos, pos
copy ^screen, ptr
st off, c
add ptr, pos
copy 81, [ptr] + y
point ptr into screen {
st off, c
add ptr, pos
copy 81, [ptr] + y
}
} else {
ld a, 1
st a, player_died
@ -321,13 +306,13 @@ define enemy_logic logic_routine
call check_new_position_in_bounds
if c {
copy ^screen, ptr
st off, c
add ptr, new_pos
ld y, 0
// check collision.
ld a, [ptr] + y
point ptr into screen {
st off, c
add ptr, new_pos
ld y, 0
// check collision.
ld a, [ptr] + y
}
// if "collision" is with your own self, treat it as if it's blank space!
cmp a, 82
if z {
@ -335,17 +320,19 @@ define enemy_logic logic_routine
}
cmp a, 32
if z {
copy ^screen, ptr
st off, c
add ptr, pos
copy 32, [ptr] + y
point ptr into screen {
st off, c
add ptr, pos
copy 32, [ptr] + y
}
copy new_pos, pos
copy ^screen, ptr
st off, c
add ptr, pos
copy 82, [ptr] + y
point ptr into screen {
st off, c
add ptr, pos
copy 82, [ptr] + y
}
}
} else {
copy delta, compare_target
@ -372,7 +359,7 @@ define game_state_title_screen game_state_routine
st on, c
sub a, 64 // yuck. oh well
st a, screen1 + y
st a, screen + y
}
st off, c
@ -444,8 +431,7 @@ define our_cinv game_state_routine
define main routine
inputs cinv
outputs cinv, save_cinv, pos, dispatch_game_state,
screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4
outputs cinv, save_cinv, pos, dispatch_game_state, screen, colormap
trashes a, y, n, c, z, vic_border, vic_bg
{
ld a, 5

View File

@ -1,7 +1,7 @@
// Include `support/${PLATFORM}.60p` before this source
// Should print Y
buffer[2048] buf
byte table[2048] buf
pointer ptr @ 254
byte foo

View File

@ -1,10 +1,12 @@
# encoding: UTF-8
from sixtypical.ast import Program, Routine, Block, SingleOp, If, Repeat, For, WithInterruptsOff, Save
from sixtypical.ast import (
Program, Routine, Block, SingleOp, If, Repeat, For, WithInterruptsOff, Save, PointInto
)
from sixtypical.model import (
TYPE_BYTE, TYPE_WORD,
TableType, BufferType, PointerType, VectorType, RoutineType,
ConstantRef, LocationRef, IndirectRef, IndexedRef, AddressRef,
TableType, PointerType, VectorType, RoutineType,
ConstantRef, LocationRef, IndirectRef, IndexedRef,
REG_A, REG_Y, FLAG_Z, FLAG_N, FLAG_V, FLAG_C
)
@ -107,13 +109,14 @@ class Context(object):
unwriteable in certain contexts, such as `for` loops.
"""
def __init__(self, routines, routine, inputs, outputs, trashes):
self.routines = routines # Location -> AST node
self.routine = routine
self._touched = set()
self._range = dict()
self._writeable = set()
self.routines = routines # LocationRef -> Routine (AST node)
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 ref.is_constant():
@ -137,21 +140,38 @@ class Context(object):
LocationRef.format_set(self._touched), LocationRef.format_set(self._range), LocationRef.format_set(self._writeable)
)
def to_json_data(self):
return {
'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 = Context(self.routines, 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.routines = other.routines
self.routine = other.routine
self._touched = set(other._touched)
self._range = dict(other._range)
self._writeable = set(other._writeable)
self._terminated = other._terminated
self._gotos_encounters = set(other._gotos_encountered)
self._pointer_assoc = dict(other._pointer_assoc)
def each_meaningful(self):
for ref in self._range.keys():
@ -197,8 +217,11 @@ class Context(object):
message += ' (%s)' % kwargs['message']
raise exception_class(self.routine, message)
def assert_in_range(self, inside, outside):
# FIXME there's a bit of I'm-not-sure-the-best-way-to-do-this-ness, here...
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]
@ -208,13 +231,11 @@ class Context(object):
outside_range = self._range[outside]
else:
outside_range = outside.max_range()
if isinstance(outside.type, TableType):
outside_range = (0, outside.type.size-1)
if inside_range[0] < outside_range[0] or inside_range[1] > outside_range[1]:
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, outside, outside_range
"Possible range of {} {} (+{}) exceeds acceptable range of {} {}".format(
inside, inside_range, offset, outside, outside_range
)
)
@ -311,17 +332,17 @@ class Context(object):
def has_terminated(self):
return self._terminated
def assert_types_for_read_table(self, instr, src, dest, type_):
def assert_types_for_read_table(self, instr, src, dest, type_, offset):
if (not TableType.is_a_table_type(src.ref.type, type_)) or (not dest.type == type_):
raise TypeMismatchError(instr, '{} and {}'.format(src.ref.name, dest.name))
self.assert_meaningful(src, src.index)
self.assert_in_range(src.index, src.ref)
self.assert_in_range(src.index, src.ref, offset)
def assert_types_for_update_table(self, instr, dest, type_):
def assert_types_for_update_table(self, instr, dest, type_, offset):
if not TableType.is_a_table_type(dest.ref.type, type_):
raise TypeMismatchError(instr, '{}'.format(dest.ref.name))
self.assert_meaningful(dest.index)
self.assert_in_range(dest.index, dest.ref)
self.assert_in_range(dest.index, dest.ref, offset)
self.set_written(dest.ref)
def extract(self, location):
@ -359,6 +380,12 @@ class Context(object):
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
class Analyzer(object):
@ -366,6 +393,7 @@ class Analyzer(object):
self.current_routine = None
self.routines = {}
self.debug = debug
self.exit_contexts_map = {}
def assert_type(self, type_, *locations):
for location in locations:
@ -398,32 +426,21 @@ class Analyzer(object):
assert isinstance(routine, Routine)
if routine.block is None:
# it's an extern, that's fine
return
return None
self.current_routine = routine
type_ = routine.location.type
context = Context(self.routines, routine, type_.inputs, type_.outputs, type_.trashes)
self.exit_contexts = []
if self.debug:
print("at start of routine `{}`:".format(routine.name))
print(context)
self.analyze_block(routine.block, context)
trashed = set(context.each_touched()) - set(context.each_meaningful())
if self.debug:
print("at end of routine `{}`:".format(routine.name))
print(context)
print("trashed: ", LocationRef.format_set(trashed))
print("outputs: ", LocationRef.format_set(type_.outputs))
trashed_outputs = type_.outputs & trashed
if trashed_outputs:
print("TRASHED OUTPUTS: ", LocationRef.format_set(trashed_outputs))
print('')
print('-' * 79)
print('')
self.exit_contexts_map[routine.name] = {
'end_context': context.to_json_data(),
'exit_contexts': [e.to_json_data() for e in self.exit_contexts]
}
if self.exit_contexts:
# check that they are all consistent
@ -438,6 +455,10 @@ class Analyzer(object):
raise InconsistentExitError("Exit contexts are not consistent")
if set(ex.each_writeable()) != exit_writeable:
raise InconsistentExitError("Exit contexts are not consistent")
# We now set the main context to the (consistent) exit context
# so that this routine is perceived as having the same effect
# that any of the goto'ed routines have.
context.update_from(exit_context)
# these all apply whether we encountered goto(s) in this routine, or not...:
@ -480,6 +501,8 @@ class Analyzer(object):
raise IllegalJumpError(instr, instr)
elif isinstance(instr, Save):
self.analyze_save(instr, context)
elif isinstance(instr, PointInto):
self.analyze_point_into(instr, context)
else:
raise NotImplementedError
@ -494,13 +517,19 @@ class Analyzer(object):
if opcode == 'ld':
if isinstance(src, IndexedRef):
context.assert_types_for_read_table(instr, src, dest, TYPE_BYTE)
context.assert_types_for_read_table(instr, src, dest, TYPE_BYTE, src.offset)
elif isinstance(src, IndirectRef):
# copying this analysis from the matching branch in `copy`, below
if isinstance(src.ref.type, PointerType) and dest.type == TYPE_BYTE:
pass
else:
raise TypeMismatchError(instr, (src, dest))
origin = context.get_assoc(src.ref)
if not origin:
raise UnmeaningfulReadError(instr, src.ref)
context.assert_meaningful(origin)
context.assert_meaningful(src.ref, REG_Y)
elif src.type != dest.type:
raise TypeMismatchError(instr, '{} and {}'.format(src.name, dest.name))
@ -512,15 +541,22 @@ class Analyzer(object):
if isinstance(dest, IndexedRef):
if src.type != TYPE_BYTE:
raise TypeMismatchError(instr, (src, dest))
context.assert_types_for_update_table(instr, dest, TYPE_BYTE)
context.assert_types_for_update_table(instr, dest, TYPE_BYTE, dest.offset)
elif isinstance(dest, IndirectRef):
# copying this analysis from the matching branch in `copy`, below
if isinstance(dest.ref.type, PointerType) and src.type == TYPE_BYTE:
pass
else:
raise TypeMismatchError(instr, (src, dest))
context.assert_meaningful(dest.ref, REG_Y)
context.set_written(dest.ref)
target = context.get_assoc(dest.ref)
if not target:
raise ForbiddenWriteError(instr, dest.ref)
context.set_touched(target)
context.set_written(target)
elif src.type != dest.type:
raise TypeMismatchError(instr, '{} and {}'.format(src, dest))
else:
@ -530,7 +566,7 @@ class Analyzer(object):
elif opcode == 'add':
context.assert_meaningful(src, dest, FLAG_C)
if isinstance(src, IndexedRef):
context.assert_types_for_read_table(instr, src, dest, TYPE_BYTE)
context.assert_types_for_read_table(instr, src, dest, TYPE_BYTE, src.offset)
elif src.type == TYPE_BYTE:
self.assert_type(TYPE_BYTE, src, dest)
if dest != REG_A:
@ -551,7 +587,7 @@ class Analyzer(object):
elif opcode == 'sub':
context.assert_meaningful(src, dest, FLAG_C)
if isinstance(src, IndexedRef):
context.assert_types_for_read_table(instr, src, dest, TYPE_BYTE)
context.assert_types_for_read_table(instr, src, dest, TYPE_BYTE, src.offset)
elif src.type == TYPE_BYTE:
self.assert_type(TYPE_BYTE, src, dest)
if dest != REG_A:
@ -566,7 +602,7 @@ class Analyzer(object):
elif opcode == 'cmp':
context.assert_meaningful(src, dest)
if isinstance(src, IndexedRef):
context.assert_types_for_read_table(instr, src, dest, TYPE_BYTE)
context.assert_types_for_read_table(instr, src, dest, TYPE_BYTE, src.offset)
elif src.type == TYPE_BYTE:
self.assert_type(TYPE_BYTE, src, dest)
else:
@ -576,7 +612,7 @@ class Analyzer(object):
context.set_written(FLAG_Z, FLAG_N, FLAG_C)
elif opcode == 'and':
if isinstance(src, IndexedRef):
context.assert_types_for_read_table(instr, src, dest, TYPE_BYTE)
context.assert_types_for_read_table(instr, src, dest, TYPE_BYTE, src.offset)
else:
self.assert_type(TYPE_BYTE, src, dest)
context.assert_meaningful(src, dest)
@ -588,7 +624,7 @@ class Analyzer(object):
context.set_top_of_range(dest, context.get_top_of_range(src))
elif opcode in ('or', 'xor'):
if isinstance(src, IndexedRef):
context.assert_types_for_read_table(instr, src, dest, TYPE_BYTE)
context.assert_types_for_read_table(instr, src, dest, TYPE_BYTE, src.offset)
else:
self.assert_type(TYPE_BYTE, src, dest)
context.assert_meaningful(src, dest)
@ -597,7 +633,7 @@ class Analyzer(object):
elif opcode in ('inc', 'dec'):
context.assert_meaningful(dest)
if isinstance(dest, IndexedRef):
context.assert_types_for_update_table(instr, dest, TYPE_BYTE)
context.assert_types_for_update_table(instr, dest, TYPE_BYTE, dest.offset)
context.set_written(dest.ref, FLAG_Z, FLAG_N)
#context.invalidate_range(dest)
else:
@ -620,7 +656,7 @@ class Analyzer(object):
elif opcode in ('shl', 'shr'):
context.assert_meaningful(dest, FLAG_C)
if isinstance(dest, IndexedRef):
context.assert_types_for_update_table(instr, dest, TYPE_BYTE)
context.assert_types_for_update_table(instr, dest, TYPE_BYTE, dest.offset)
context.set_written(dest.ref, FLAG_Z, FLAG_N, FLAG_C)
#context.invalidate_range(dest)
else:
@ -647,12 +683,7 @@ class Analyzer(object):
# 1. check that their types are compatible
if isinstance(src, AddressRef) and isinstance(dest, LocationRef):
if isinstance(src.ref.type, BufferType) and isinstance(dest.type, PointerType):
pass
else:
raise TypeMismatchError(instr, (src, dest))
elif isinstance(src, (LocationRef, ConstantRef)) and isinstance(dest, IndirectRef):
if isinstance(src, (LocationRef, ConstantRef)) and isinstance(dest, IndirectRef):
if src.type == TYPE_BYTE and isinstance(dest.ref.type, PointerType):
pass
else:
@ -679,7 +710,7 @@ class Analyzer(object):
pass
else:
raise TypeMismatchError(instr, (src, dest))
context.assert_in_range(dest.index, dest.ref)
context.assert_in_range(dest.index, dest.ref, dest.offset)
elif isinstance(src, IndexedRef) and isinstance(dest, LocationRef):
if TableType.is_a_table_type(src.ref.type, TYPE_WORD) and dest.type == TYPE_WORD:
@ -689,7 +720,7 @@ class Analyzer(object):
pass
else:
raise TypeMismatchError(instr, (src, dest))
context.assert_in_range(src.index, src.ref)
context.assert_in_range(src.index, src.ref, src.offset)
elif isinstance(src, (LocationRef, ConstantRef)) and isinstance(dest, LocationRef):
if src.type == dest.type:
@ -707,16 +738,37 @@ class Analyzer(object):
if isinstance(src, (LocationRef, ConstantRef)) and isinstance(dest, IndirectRef):
context.assert_meaningful(src, REG_Y)
# TODO this will need to be more sophisticated. it's the thing ref points to that is written, not ref itself.
context.set_written(dest.ref)
target = context.get_assoc(dest.ref)
if not target:
raise ForbiddenWriteError(instr, dest.ref)
context.set_touched(target)
context.set_written(target)
elif isinstance(src, IndirectRef) and isinstance(dest, LocationRef):
context.assert_meaningful(src.ref, REG_Y)
# TODO more sophisticated?
origin = context.get_assoc(src.ref)
if not origin:
raise UnmeaningfulReadError(instr, src.ref)
context.assert_meaningful(origin)
context.set_touched(dest)
context.set_written(dest)
elif isinstance(src, IndirectRef) and isinstance(dest, IndirectRef):
context.assert_meaningful(src.ref, REG_Y)
# TODO more sophisticated?
context.set_written(dest.ref)
origin = context.get_assoc(src.ref)
if not origin:
raise UnmeaningfulReadError(instr, src.ref)
context.assert_meaningful(origin)
target = context.get_assoc(dest.ref)
if not target:
raise ForbiddenWriteError(instr, dest.ref)
context.set_touched(target)
context.set_written(target)
elif isinstance(src, LocationRef) and isinstance(dest, IndexedRef):
context.assert_meaningful(src, dest.ref, dest.index)
context.set_written(dest.ref)
@ -905,3 +957,29 @@ class Analyzer(object):
else:
context.set_touched(REG_A)
context.set_unmeaningful(REG_A)
def analyze_point_into(self, instr, context):
if not isinstance(instr.pointer.type, PointerType):
raise TypeMismatchError(instr, instr.pointer)
if not TableType.is_a_table_type(instr.table.type, TYPE_BYTE):
raise TypeMismatchError(instr, instr.table)
# check that pointer is not yet associated with any table.
if context.get_assoc(instr.pointer):
raise ForbiddenWriteError(instr, instr.pointer)
# associate pointer with table, mark it as meaningful.
context.set_assoc(instr.pointer, instr.table)
context.set_meaningful(instr.pointer)
context.set_touched(instr.pointer)
self.analyze_block(instr.block, context)
if context.encountered_gotos():
raise IllegalJumpError(instr, instr)
# unassociate pointer with table, mark as unmeaningful.
context.set_assoc(instr.pointer, None)
context.set_unmeaningful(instr.pointer)

View File

@ -97,3 +97,8 @@ class WithInterruptsOff(Instr):
class Save(Instr):
value_attrs = ('locations',)
child_attrs = ('block',)
class PointInto(Instr):
value_attrs = ('pointer', 'table',)
child_attrs = ('block',)

View File

@ -1,10 +1,12 @@
# encoding: UTF-8
from sixtypical.ast import Program, Routine, Block, SingleOp, If, Repeat, For, WithInterruptsOff, Save
from sixtypical.ast import (
Program, Routine, Block, SingleOp, If, Repeat, For, WithInterruptsOff, Save, PointInto
)
from sixtypical.model import (
ConstantRef, LocationRef, IndexedRef, IndirectRef, AddressRef,
ConstantRef, LocationRef, IndexedRef, IndirectRef,
TYPE_BIT, TYPE_BYTE, TYPE_WORD,
TableType, BufferType, PointerType, RoutineType, VectorType,
TableType, PointerType, RoutineType, VectorType,
REG_A, REG_X, REG_Y, FLAG_C
)
from sixtypical.emitter import Byte, Word, Table, Label, Offset, LowAddressByte, HighAddressByte
@ -55,8 +57,6 @@ class Compiler(object):
length = 2
elif isinstance(type_, TableType):
length = type_.size * (1 if type_.of_type == TYPE_BYTE else 2)
elif isinstance(type_, BufferType):
length = type_.size
if length is None:
raise NotImplementedError("Need size for type {}".format(type_))
return length
@ -172,6 +172,8 @@ class Compiler(object):
return self.compile_with_interrupts_off(instr)
elif isinstance(instr, Save):
return self.compile_save(instr)
elif isinstance(instr, PointInto):
return self.compile_point_into(instr)
else:
raise NotImplementedError
@ -190,9 +192,9 @@ class Compiler(object):
elif isinstance(src, ConstantRef):
self.emitter.emit(LDA(Immediate(Byte(src.value))))
elif isinstance(src, IndexedRef) and src.index == REG_X:
self.emitter.emit(LDA(AbsoluteX(self.get_label(src.ref.name))))
self.emitter.emit(LDA(AbsoluteX(Offset(self.get_label(src.ref.name), src.offset.value))))
elif isinstance(src, IndexedRef) and src.index == REG_Y:
self.emitter.emit(LDA(AbsoluteY(self.get_label(src.ref.name))))
self.emitter.emit(LDA(AbsoluteY(Offset(self.get_label(src.ref.name), src.offset.value))))
elif isinstance(src, IndirectRef) and isinstance(src.ref.type, PointerType):
self.emitter.emit(LDA(IndirectY(self.get_label(src.ref.name))))
else:
@ -203,7 +205,7 @@ class Compiler(object):
elif isinstance(src, ConstantRef):
self.emitter.emit(LDX(Immediate(Byte(src.value))))
elif isinstance(src, IndexedRef) and src.index == REG_Y:
self.emitter.emit(LDX(AbsoluteY(self.get_label(src.ref.name))))
self.emitter.emit(LDX(AbsoluteY(Offset(self.get_label(src.ref.name), src.offset.value))))
else:
self.emitter.emit(LDX(self.absolute_or_zero_page(self.get_label(src.name))))
elif dest == REG_Y:
@ -212,7 +214,7 @@ class Compiler(object):
elif isinstance(src, ConstantRef):
self.emitter.emit(LDY(Immediate(Byte(src.value))))
elif isinstance(src, IndexedRef) and src.index == REG_X:
self.emitter.emit(LDY(AbsoluteX(self.get_label(src.ref.name))))
self.emitter.emit(LDY(AbsoluteX(Offset(self.get_label(src.ref.name), src.offset.value))))
else:
self.emitter.emit(LDY(self.absolute_or_zero_page(self.get_label(src.name))))
else:
@ -234,7 +236,7 @@ class Compiler(object):
REG_X: AbsoluteX,
REG_Y: AbsoluteY,
}[dest.index]
operand = mode_cls(self.get_label(dest.ref.name))
operand = mode_cls(Offset(self.get_label(dest.ref.name), dest.offset.value))
elif isinstance(dest, IndirectRef) and isinstance(dest.ref.type, PointerType):
operand = IndirectY(self.get_label(dest.ref.name))
else:
@ -250,7 +252,8 @@ class Compiler(object):
if isinstance(src, ConstantRef):
self.emitter.emit(ADC(Immediate(Byte(src.value))))
elif isinstance(src, IndexedRef):
self.emitter.emit(ADC(self.addressing_mode_for_index(src.index)(self.get_label(src.ref.name))))
mode = self.addressing_mode_for_index(src.index)
self.emitter.emit(ADC(mode(Offset(self.get_label(src.ref.name), src.offset.value))))
else:
self.emitter.emit(ADC(Absolute(self.get_label(src.name))))
elif isinstance(dest, LocationRef) and src.type == TYPE_BYTE and dest.type == TYPE_BYTE:
@ -316,7 +319,8 @@ class Compiler(object):
if isinstance(src, ConstantRef):
self.emitter.emit(SBC(Immediate(Byte(src.value))))
elif isinstance(src, IndexedRef):
self.emitter.emit(SBC(self.addressing_mode_for_index(src.index)(self.get_label(src.ref.name))))
mode = self.addressing_mode_for_index(src.index)
self.emitter.emit(SBC(mode(Offset(self.get_label(src.ref.name), src.offset.value))))
else:
self.emitter.emit(SBC(Absolute(self.get_label(src.name))))
elif isinstance(dest, LocationRef) and src.type == TYPE_BYTE and dest.type == TYPE_BYTE:
@ -367,7 +371,8 @@ class Compiler(object):
if isinstance(src, ConstantRef):
self.emitter.emit(cls(Immediate(Byte(src.value))))
elif isinstance(src, IndexedRef):
self.emitter.emit(cls(self.addressing_mode_for_index(src.index)(self.get_label(src.ref.name))))
mode = self.addressing_mode_for_index(src.index)
self.emitter.emit(cls(mode(Offset(self.get_label(src.ref.name), src.offset.value))))
else:
self.emitter.emit(cls(self.absolute_or_zero_page(self.get_label(src.name))))
else:
@ -384,7 +389,8 @@ class Compiler(object):
if dest == REG_A:
self.emitter.emit(cls())
elif isinstance(dest, IndexedRef):
self.emitter.emit(cls(self.addressing_mode_for_index(dest.index)(self.get_label(dest.ref.name))))
mode = self.addressing_mode_for_index(dest.index)
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':
@ -455,7 +461,8 @@ class Compiler(object):
self.emitter.emit(cls(Immediate(Byte(src.value))))
elif isinstance(src, IndexedRef):
# FIXME might not work for some dest's (that is, cls's)
self.emitter.emit(cls(self.addressing_mode_for_index(src.index)(self.get_label(src.ref.name))))
mode = self.addressing_mode_for_index(src.index)
self.emitter.emit(cls(mode(Offset(self.get_label(src.ref.name), src.offset.value))))
else:
self.emitter.emit(cls(Absolute(self.get_label(src.name))))
@ -466,7 +473,8 @@ class Compiler(object):
elif dest == REG_Y:
self.emitter.emit(INY())
elif isinstance(dest, IndexedRef):
self.emitter.emit(INC(self.addressing_mode_for_index(dest.index)(self.get_label(dest.ref.name))))
mode = self.addressing_mode_for_index(dest.index)
self.emitter.emit(INC(mode(Offset(self.get_label(dest.ref.name), dest.offset.value))))
else:
self.emitter.emit(INC(Absolute(self.get_label(dest.name))))
@ -477,7 +485,8 @@ class Compiler(object):
elif dest == REG_Y:
self.emitter.emit(DEY())
elif isinstance(dest, IndexedRef):
self.emitter.emit(DEC(self.addressing_mode_for_index(dest.index)(self.get_label(dest.ref.name))))
mode = self.addressing_mode_for_index(dest.index)
self.emitter.emit(DEC(mode(Offset(self.get_label(dest.ref.name), dest.offset.value))))
else:
self.emitter.emit(DEC(Absolute(self.get_label(dest.name))))
@ -505,62 +514,60 @@ class Compiler(object):
dest_label = self.get_label(dest.ref.name)
self.emitter.emit(LDA(IndirectY(src_label)))
self.emitter.emit(STA(IndirectY(dest_label)))
elif isinstance(src, AddressRef) and isinstance(dest, LocationRef) and isinstance(src.ref.type, BufferType) and isinstance(dest.type, PointerType):
### copy ^buf, ptr
src_label = self.get_label(src.ref.name)
dest_label = self.get_label(dest.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))))
elif isinstance(src, LocationRef) and isinstance(dest, IndexedRef) and src.type == TYPE_WORD and TableType.is_a_table_type(dest.ref.type, TYPE_WORD):
### copy w, wtab + y
src_label = self.get_label(src.name)
dest_label = self.get_label(dest.ref.name)
mode = self.addressing_mode_for_index(dest.index)
self.emitter.emit(LDA(Absolute(src_label)))
self.emitter.emit(STA(self.addressing_mode_for_index(dest.index)(dest_label)))
self.emitter.emit(STA(mode(Offset(dest_label, dest.offset.value))))
self.emitter.emit(LDA(Absolute(Offset(src_label, 1))))
self.emitter.emit(STA(self.addressing_mode_for_index(dest.index)(Offset(dest_label, 256))))
self.emitter.emit(STA(mode(Offset(dest_label, dest.offset.value + 256))))
elif isinstance(src, LocationRef) and isinstance(dest, IndexedRef) and isinstance(src.type, VectorType) and isinstance(dest.ref.type, TableType) and isinstance(dest.ref.type.of_type, VectorType):
### copy vec, vtab + y
# FIXME this is the exact same as above - can this be simplified?
src_label = self.get_label(src.name)
dest_label = self.get_label(dest.ref.name)
mode = self.addressing_mode_for_index(dest.index)
self.emitter.emit(LDA(Absolute(src_label)))
self.emitter.emit(STA(self.addressing_mode_for_index(dest.index)(dest_label)))
self.emitter.emit(STA(mode(Offset(dest_label, dest.offset.value))))
self.emitter.emit(LDA(Absolute(Offset(src_label, 1))))
self.emitter.emit(STA(self.addressing_mode_for_index(dest.index)(Offset(dest_label, 256))))
self.emitter.emit(STA(mode(Offset(dest_label, dest.offset.value + 256))))
elif isinstance(src, LocationRef) and isinstance(dest, IndexedRef) and isinstance(src.type, RoutineType) and isinstance(dest.ref.type, TableType) and isinstance(dest.ref.type.of_type, VectorType):
### copy routine, vtab + y
src_label = self.get_label(src.name)
dest_label = self.get_label(dest.ref.name)
mode = self.addressing_mode_for_index(dest.index)
self.emitter.emit(LDA(Immediate(HighAddressByte(src_label))))
self.emitter.emit(STA(self.addressing_mode_for_index(dest.index)(dest_label)))
self.emitter.emit(STA(mode(Offset(dest_label, dest.offset.value))))
self.emitter.emit(LDA(Immediate(LowAddressByte(src_label))))
self.emitter.emit(STA(self.addressing_mode_for_index(dest.index)(Offset(dest_label, 256))))
self.emitter.emit(STA(mode(Offset(dest_label, dest.offset.value + 256))))
elif isinstance(src, ConstantRef) and isinstance(dest, IndexedRef) and src.type == TYPE_WORD and TableType.is_a_table_type(dest.ref.type, TYPE_WORD):
### copy 9999, wtab + y
dest_label = self.get_label(dest.ref.name)
mode = self.addressing_mode_for_index(dest.index)
self.emitter.emit(LDA(Immediate(Byte(src.low_byte()))))
self.emitter.emit(STA(self.addressing_mode_for_index(dest.index)(dest_label)))
self.emitter.emit(STA(mode(Offset(dest_label, dest.offset.value))))
self.emitter.emit(LDA(Immediate(Byte(src.high_byte()))))
self.emitter.emit(STA(self.addressing_mode_for_index(dest.index)(Offset(dest_label, 256))))
self.emitter.emit(STA(mode(Offset(dest_label, dest.offset.value + 256))))
elif isinstance(src, IndexedRef) and isinstance(dest, LocationRef) and TableType.is_a_table_type(src.ref.type, TYPE_WORD) and dest.type == TYPE_WORD:
### copy wtab + y, w
src_label = self.get_label(src.ref.name)
dest_label = self.get_label(dest.name)
self.emitter.emit(LDA(self.addressing_mode_for_index(src.index)(src_label)))
mode = self.addressing_mode_for_index(src.index)
self.emitter.emit(LDA(mode(Offset(src_label, src.offset.value))))
self.emitter.emit(STA(Absolute(dest_label)))
self.emitter.emit(LDA(self.addressing_mode_for_index(src.index)(Offset(src_label, 256))))
self.emitter.emit(LDA(mode(Offset(src_label, src.offset.value + 256))))
self.emitter.emit(STA(Absolute(Offset(dest_label, 1))))
elif isinstance(src, IndexedRef) and isinstance(dest, LocationRef) and isinstance(dest.type, VectorType) and isinstance(src.ref.type, TableType) and isinstance(src.ref.type.of_type, VectorType):
### copy vtab + y, vec
# FIXME this is the exact same as above - can this be simplified?
src_label = self.get_label(src.ref.name)
dest_label = self.get_label(dest.name)
self.emitter.emit(LDA(self.addressing_mode_for_index(src.index)(src_label)))
mode = self.addressing_mode_for_index(src.index)
self.emitter.emit(LDA(mode(Offset(src_label, src.offset.value))))
self.emitter.emit(STA(Absolute(dest_label)))
self.emitter.emit(LDA(self.addressing_mode_for_index(src.index)(Offset(src_label, 256))))
self.emitter.emit(LDA(mode(Offset(src_label, src.offset.value + 256))))
self.emitter.emit(STA(Absolute(Offset(dest_label, 1))))
elif src.type == TYPE_BYTE and dest.type == TYPE_BYTE and not isinstance(src, ConstantRef):
### copy b1, b2
@ -700,3 +707,14 @@ class Compiler(object):
src_label = self.get_label(location.name)
self.emitter.emit(PLA())
self.emitter.emit(STA(Absolute(src_label)))
def compile_point_into(self, instr):
src_label = self.get_label(instr.table.name)
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)

View File

@ -9,14 +9,8 @@ class Type(object):
def __repr__(self):
return 'Type(%r)' % self.name
def __str__(self):
return self.name
def __eq__(self, other):
return isinstance(other, Type) and other.name == self.name
def __hash__(self):
return hash(self.name)
return other.__class__ == self.__class__ and other.name == self.name
TYPE_BIT = Type('bit', max_range=(0, 1))
@ -24,31 +18,25 @@ TYPE_BYTE = Type('byte', max_range=(0, 255))
TYPE_WORD = Type('word', max_range=(0, 65535))
class RoutineType(Type):
"""This memory location contains the code for a routine."""
def __init__(self, inputs, outputs, trashes):
self.name = 'routine'
self.inputs = inputs
self.outputs = outputs
self.trashes = trashes
def __repr__(self):
return '%s(%r, inputs=%r, outputs=%r, trashes=%r)' % (
self.__class__.__name__, self.name, self.inputs, self.outputs, self.trashes
return '%s(inputs=%r, outputs=%r, trashes=%r)' % (
self.__class__.__name__, self.inputs, self.outputs, self.trashes
)
def __eq__(self, other):
return isinstance(other, RoutineType) and (
other.name == self.name and
other.inputs == self.inputs and
other.outputs == self.outputs and
other.trashes == self.trashes
)
def __hash__(self):
return hash(self.name) ^ hash(self.inputs) ^ hash(self.outputs) ^ hash(self.trashes)
@classmethod
def executable_types_compatible(cls_, src, dest):
"""Returns True iff a value of type `src` can be assigned to a storage location of type `dest`."""
@ -70,7 +58,6 @@ class RoutineType(Type):
class VectorType(Type):
"""This memory location contains the address of some other type (currently, only RoutineType)."""
def __init__(self, of_type):
self.name = 'vector'
self.of_type = of_type
def __repr__(self):
@ -79,38 +66,38 @@ class VectorType(Type):
)
def __eq__(self, other):
return self.name == other.name and self.of_type == other.of_type
def __hash__(self):
return hash(self.name) ^ hash(self.of_type)
return isinstance(other, VectorType) and self.of_type == other.of_type
class TableType(Type):
def __init__(self, of_type, size):
self.of_type = of_type
self.size = size
self.name = '{} table[{}]'.format(self.of_type.name, self.size)
def __repr__(self):
return '%s(%r, %r)' % (
self.__class__.__name__, self.of_type, self.size
)
def __eq__(self, other):
return isinstance(other, TableType) and self.of_type == other.of_type and self.size == other.size
@property
def max_range(self):
return (0, self.size - 1)
@classmethod
def is_a_table_type(cls_, x, of_type):
return isinstance(x, TableType) and x.of_type == of_type
class BufferType(Type):
def __init__(self, size):
self.size = size
self.name = 'buffer[%s]' % self.size
class PointerType(Type):
def __init__(self):
self.name = 'pointer'
def __eq__(self, other):
return other.__class__ == self.__class__
class Ref(object):
def is_constant(self):
@ -139,7 +126,7 @@ class LocationRef(Ref):
return equal
def __hash__(self):
return hash(self.name + str(self.type))
return hash(self.name + repr(self.type))
def __repr__(self):
return '%s(%r, %r)' % (self.__class__.__name__, self.type, self.name)
@ -183,77 +170,28 @@ class IndirectRef(Ref):
class IndexedRef(Ref):
def __init__(self, ref, index):
def __init__(self, ref, offset, index):
self.ref = ref
self.offset = offset
self.index = index
def __eq__(self, other):
return isinstance(other, self.__class__) and self.ref == other.ref and self.index == other.index
return isinstance(other, self.__class__) and self.ref == other.ref and self.offset == other.offset and self.index == other.index
def __hash__(self):
return hash(self.__class__.name) ^ hash(self.ref) ^ hash(self.index)
return hash(self.__class__.name) ^ hash(self.ref) ^ hash(self.offset) ^ hash(self.index)
def __repr__(self):
return '%s(%r, %r)' % (self.__class__.__name__, self.ref, self.index)
return '%s(%r, %r, %r)' % (self.__class__.__name__, self.ref, self.offset, self.index)
@property
def name(self):
return '{}+{}'.format(self.ref.name, self.index.name)
return '{}+{}+{}'.format(self.ref.name, self.offset, self.index.name)
def is_constant(self):
return False
class AddressRef(Ref):
def __init__(self, ref):
self.ref = ref
def __eq__(self, other):
return self.ref == other.ref
def __hash__(self):
return hash(self.__class__.name) ^ hash(self.ref)
def __repr__(self):
return '%s(%r)' % (self.__class__.__name__, self.ref)
@property
def name(self):
return '^{}'.format(self.ref.name)
def is_constant(self):
return True
class PartRef(Ref):
"""For 'low byte of' location and 'high byte of' location modifiers.
height=0 = low byte, height=1 = high byte.
NOTE: Not actually used yet. Might require more thought before it's usable.
"""
def __init__(self, ref, height):
assert isinstance(ref, Ref)
assert ref.type == TYPE_WORD
self.ref = ref
self.height = height
self.type = TYPE_BYTE
def __eq__(self, other):
return isinstance(other, PartRef) and (
other.height == self.height and other.ref == self.ref
)
def __hash__(self):
return hash(self.ref) ^ hash(self.height) ^ hash(self.type)
def __repr__(self):
return '%s(%r, %r)' % (self.__class__.__name__, self.ref, self.height)
def is_constant(self):
return self.ref.is_constant()
class ConstantRef(Ref):
def __init__(self, type, value):
self.type = type
@ -296,6 +234,10 @@ class ConstantRef(Ref):
value -= 256
return ConstantRef(self.type, value)
@property
def name(self):
return 'constant({})'.format(self.value)
REG_A = LocationRef(TYPE_BYTE, 'a')
REG_X = LocationRef(TYPE_BYTE, 'x')

View File

@ -1,10 +1,12 @@
# encoding: UTF-8
from sixtypical.ast import Program, Defn, Routine, Block, SingleOp, If, Repeat, For, WithInterruptsOff, Save
from sixtypical.ast import (
Program, Defn, Routine, Block, SingleOp, If, Repeat, For, WithInterruptsOff, Save, PointInto
)
from sixtypical.model import (
TYPE_BIT, TYPE_BYTE, TYPE_WORD,
RoutineType, VectorType, TableType, BufferType, PointerType,
LocationRef, ConstantRef, IndirectRef, IndexedRef, AddressRef,
RoutineType, VectorType, TableType, PointerType,
LocationRef, ConstantRef, IndirectRef, IndexedRef,
)
from sixtypical.scanner import Scanner
@ -81,9 +83,9 @@ class Parser(object):
def backpatch_constraint_labels(type_):
def resolve(w):
if not isinstance(w, ForwardReference):
return w
return self.lookup(w.name)
if not isinstance(w, ForwardReference):
return w
return self.lookup(w.name)
if isinstance(type_, TableType):
backpatch_constraint_labels(type_.of_type)
elif isinstance(type_, VectorType):
@ -122,7 +124,7 @@ class Parser(object):
self.typedef()
if self.scanner.on('const'):
self.defn_const()
typenames = ['byte', 'word', 'table', 'vector', 'buffer', 'pointer'] # 'routine',
typenames = ['byte', 'word', 'table', 'vector', 'pointer'] # 'routine',
typenames.extend(self.context.typedefs.keys())
while self.scanner.on(*typenames):
defn = self.defn()
@ -222,8 +224,8 @@ class Parser(object):
if self.scanner.consume('table'):
size = self.defn_size()
if size <= 0 or size > 256:
self.syntax_error("Table size must be > 0 and <= 256")
if size <= 0 or size > 65536:
self.syntax_error("Table size must be > 0 and <= 65536")
type_ = TableType(type_, size)
return type_
@ -248,9 +250,6 @@ class Parser(object):
elif self.scanner.consume('routine'):
(inputs, outputs, trashes) = self.constraints()
type_ = RoutineType(inputs=inputs, outputs=outputs, trashes=trashes)
elif self.scanner.consume('buffer'):
size = self.defn_size()
type_ = BufferType(size)
elif self.scanner.consume('pointer'):
type_ = PointerType()
else:
@ -351,9 +350,6 @@ class Parser(object):
self.scanner.expect('+')
self.scanner.expect('y')
return IndirectRef(loc)
elif self.scanner.consume('^'):
loc = self.locexpr()
return AddressRef(loc)
else:
return self.indexed_locexpr()
@ -361,9 +357,13 @@ class Parser(object):
loc = self.locexpr()
if not isinstance(loc, str):
index = None
offset = ConstantRef(TYPE_BYTE, 0)
if self.scanner.consume('+'):
if self.scanner.token in self.context.consts or self.scanner.on_type('integer literal'):
offset = self.const()
self.scanner.expect('+')
index = self.locexpr()
loc = IndexedRef(loc, index)
loc = IndexedRef(loc, offset, index)
return loc
def statics(self):
@ -474,6 +474,12 @@ class Parser(object):
locations = self.locexprs()
block = self.block()
return Save(self.scanner.line_number, locations=locations, block=block)
elif self.scanner.consume("point"):
pointer = self.locexpr()
self.scanner.expect("into")
table = self.locexpr()
block = self.block()
return PointInto(self.scanner.line_number, pointer=pointer, table=table, block=block)
elif self.scanner.consume("trash"):
dest = self.locexpr()
return SingleOp(self.scanner.line_number, opcode='trash', src=None, dest=dest)

View File

@ -503,6 +503,52 @@ The index must be initialized.
| }
? UnmeaningfulReadError: x
Storing to a table, you may also include a constant offset.
| byte one
| byte table[256] many
|
| define main routine
| outputs many
| trashes a, x, n, z
| {
| ld x, 0
| ld a, 0
| st a, many + 100 + x
| }
= ok
Reading from a table, you may also include a constant offset.
| byte table[256] many
|
| define main routine
| inputs many
| outputs many
| trashes a, x, n, z
| {
| ld x, 0
| ld a, many + 100 + x
| }
= ok
Using a constant offset, you can read and write entries in
the table beyond the 256th.
| byte one
| byte table[1024] many
|
| define main routine
| inputs many
| outputs many
| trashes a, x, n, z
| {
| ld x, 0
| ld a, many + 999 + x
| st a, many + 1000 + x
| }
= ok
There are other operations you can do on tables. (1/3)
| byte table[256] many
@ -614,13 +660,35 @@ You can also copy a literal word to a word table.
| }
= ok
Copying to and from a word table with a constant offset.
| word one
| word table[256] many
|
| define main routine
| inputs one, many
| outputs one, many
| trashes a, x, n, z
| {
| ld x, 0
| copy one, many + 100 + x
| copy many + 100 + x, one
| copy 9999, many + 1 + x
| }
= ok
#### tables: range checking ####
It is a static analysis error if it cannot be proven that a read or write
to a table falls within the defined size of that table.
(If a table has 256 entries, then there is never a problem, because a byte
cannot index any entry outside of 0..255.)
If a table has 256 entries, then there is never a problem (so long as
no constant offset is supplied), because a byte cannot index any entry
outside of 0..255.
But if the table has fewer than 256 entries, or if a constant offset is
supplied, there is the possibility that the index will refer to an
entry in the table which does not exist.
A SixtyPical implementation must be able to prove that the index is inside
the range of the table in various ways. The simplest is to show that a
@ -664,6 +732,33 @@ constant value falls inside or outside the range of the table.
| }
? RangeExceededError
Any constant offset is taken into account in this check.
| byte table[32] many
|
| define main routine
| inputs many
| outputs many
| trashes a, x, n, z
| {
| ld x, 31
| ld a, many + 1 + x
| }
? RangeExceededError
| byte table[32] many
|
| define main routine
| inputs many
| outputs many
| trashes a, x, n, z
| {
| ld x, 31
| ld a, 0
| st a, many + 1 + x
| }
? RangeExceededError
This applies to `copy` as well.
| word one: 77
@ -706,6 +801,34 @@ This applies to `copy` as well.
| }
? RangeExceededError
Any constant offset is taken into account in this check.
| word one: 77
| word table[32] many
|
| define main routine
| inputs many, one
| outputs many, one
| trashes a, x, n, z
| {
| ld x, 31
| copy many + 1 + x, one
| }
? RangeExceededError
| word one: 77
| word table[32] many
|
| define main routine
| inputs many, one
| outputs many, one
| trashes a, x, n, z
| {
| ld x, 31
| copy one, many + 1 + x
| }
? RangeExceededError
`AND`'ing a register with a value ensures the range of the
register will not exceed the range of the value. This can
be used to "clip" the range of an index so that it fits in
@ -726,7 +849,7 @@ a table.
| }
= ok
Test for "clipping", but not enough.
Tests for "clipping", but not enough.
| word one: 77
| word table[32] many
@ -743,6 +866,21 @@ Test for "clipping", but not enough.
| }
? RangeExceededError
| word one: 77
| word table[32] many
|
| define main routine
| inputs a, many, one
| outputs many, one
| trashes a, x, n, z
| {
| and a, 31
| ld x, a
| copy one, many + 1 + x
| copy many + 1 + x, one
| }
? RangeExceededError
If you alter the value after "clipping" it, the range can
no longer be guaranteed.
@ -2779,140 +2917,338 @@ Can't `copy` from a `word` to a `byte`.
| }
? TypeMismatchError
### Buffers and pointers ###
### point ... into blocks ###
Note that `^buf` is a constant value, so it by itself does not require `buf` to be
listed in any input/output sets.
Pointer must be a pointer type.
However, if the code reads from it through a pointer, it *should* be in `inputs`.
Likewise, if the code writes to it through a pointer, it *should* be in `outputs`.
Of course, unless you write to *all* the bytes in a buffer, some of those bytes
might not be meaningful. So how meaningful is this check?
This is an open problem.
For now, convention says: if it is being read, list it in `inputs`, and if it is
being modified, list it in both `inputs` and `outputs`.
Write literal through a pointer.
| buffer[2048] buf
| pointer ptr
| byte table[256] tab
| word ptr
|
| define main routine
| inputs buf
| outputs y, buf
| inputs tab
| outputs y, tab
| trashes a, z, n, ptr
| {
| ld y, 0
| copy ^buf, ptr
| copy 123, [ptr] + y
| point ptr into tab {
| copy 123, [ptr] + y
| }
| }
= ok
? TypeMismatchError
It does use `y`.
Cannot write through pointer outside a `point ... into` block.
| buffer[2048] buf
| byte table[256] tab
| pointer ptr
|
| define main routine
| inputs buf
| outputs buf
| inputs tab, ptr
| outputs y, tab
| trashes a, z, n, ptr
| {
| copy ^buf, ptr
| ld y, 0
| copy 123, [ptr] + y
| }
? ForbiddenWriteError
| byte table[256] tab
| pointer ptr
|
| define main routine
| inputs tab
| outputs y, tab
| trashes a, z, n, ptr
| {
| ld y, 0
| point ptr into tab {
| copy 123, [ptr] + y
| }
| copy 123, [ptr] + y
| }
? ForbiddenWriteError
Write literal through a pointer into a table.
| byte table[256] tab
| pointer ptr
|
| define main routine
| inputs tab
| outputs y, tab
| trashes a, z, n, ptr
| {
| ld y, 0
| point ptr into tab {
| copy 123, [ptr] + y
| }
| }
= ok
Writing into a table via a pointer does use `y`.
| byte table[256] tab
| pointer ptr
|
| define main routine
| inputs tab
| outputs tab
| trashes a, z, n, ptr
| {
| point ptr into tab {
| copy 123, [ptr] + y
| }
| }
? UnmeaningfulReadError
Write stored value through a pointer.
Write stored value through a pointer into a table.
| buffer[2048] buf
| byte table[256] tab
| pointer ptr
| byte foo
|
| define main routine
| inputs foo, buf
| outputs y, buf
| inputs foo, tab
| outputs y, tab
| trashes a, z, n, ptr
| {
| ld y, 0
| copy ^buf, ptr
| copy foo, [ptr] + y
| point ptr into tab {
| copy foo, [ptr] + y
| }
| }
= ok
Read through a pointer.
Read a table entry via a pointer.
| buffer[2048] buf
| byte table[256] tab
| pointer ptr
| byte foo
|
| define main routine
| inputs buf
| inputs tab
| outputs foo
| trashes a, y, z, n, ptr
| {
| ld y, 0
| copy ^buf, ptr
| point ptr into tab {
| copy [ptr] + y, foo
| }
| }
= ok
Read and write through two pointers.
Read and write through two pointers into a table.
| buffer[2048] buf
| byte table[256] tab
| pointer ptra
| pointer ptrb
|
| define main routine
| inputs buf
| outputs buf
| inputs tab
| outputs tab
| trashes a, y, z, n, ptra, ptrb
| {
| ld y, 0
| copy ^buf, ptra
| copy ^buf, ptrb
| copy [ptra] + y, [ptrb] + y
| point ptra into tab {
| point ptrb into tab {
| copy [ptra] + y, [ptrb] + y
| }
| }
| }
= ok
Read through a pointer to the `a` register. Note that this is done with `ld`,
Read through a pointer into a table, to the `a` register. Note that this is done with `ld`,
not `copy`.
| buffer[2048] buf
| byte table[256] tab
| pointer ptr
| byte foo
|
| define main routine
| inputs buf
| inputs tab
| outputs a
| trashes y, z, n, ptr
| {
| ld y, 0
| copy ^buf, ptr
| ld a, [ptr] + y
| point ptr into tab {
| ld a, [ptr] + y
| }
| }
= ok
Write the `a` register through a pointer. Note that this is done with `st`,
Write the `a` register through a pointer into a table. Note that this is done with `st`,
not `copy`.
| buffer[2048] buf
| byte table[256] tab
| pointer ptr
| byte foo
|
| define main routine
| inputs buf
| outputs buf
| inputs tab
| outputs tab
| trashes a, y, z, n, ptr
| {
| ld y, 0
| copy ^buf, ptr
| ld a, 255
| st a, [ptr] + y
| point ptr into tab {
| ld a, 255
| st a, [ptr] + y
| }
| }
= ok
Cannot get a pointer into a non-byte (for instance, word) table.
| word table[256] tab
| pointer ptr
| byte foo
|
| define main routine
| inputs tab
| outputs foo
| trashes a, y, z, n, ptr
| {
| ld y, 0
| point ptr into tab {
| copy [ptr] + y, foo
| }
| }
? TypeMismatchError
Cannot get a pointer into a non-byte (for instance, vector) table.
| vector (routine trashes a, z, n) table[256] tab
| pointer ptr
| vector (routine trashes a, z, n) foo
|
| define main routine
| inputs tab
| outputs foo
| trashes a, y, z, n, ptr
| {
| ld y, 0
| point ptr into tab {
| copy [ptr] + y, foo
| }
| }
? TypeMismatchError
`point into` by itself only requires `ptr` to be writeable. By itself,
it does not require `tab` to be readable or writeable.
| byte table[256] tab
| pointer ptr
|
| define main routine
| trashes a, z, n, ptr
| {
| point ptr into tab {
| ld a, 0
| }
| }
= ok
| byte table[256] tab
| pointer ptr
|
| define main routine
| trashes a, z, n
| {
| point ptr into tab {
| ld a, 0
| }
| }
? ForbiddenWriteError
After a `point into` block, the pointer is no longer meaningful and cannot
be considered an output of the routine.
| byte table[256] tab
| pointer ptr
|
| define main routine
| inputs tab
| outputs y, tab, ptr
| trashes a, z, n
| {
| ld y, 0
| point ptr into tab {
| copy 123, [ptr] + y
| }
| }
? UnmeaningfulOutputError
If code in a routine reads from a table through a pointer, the table must be in
the `inputs` of that routine.
| byte table[256] tab
| pointer ptr
| byte foo
|
| define main routine
| outputs foo
| trashes a, y, z, n, ptr
| {
| ld y, 0
| point ptr into tab {
| copy [ptr] + y, foo
| }
| }
? UnmeaningfulReadError
Likewise, if code in a routine writes into a table via a pointer, the table must
be in the `outputs` of that routine.
| byte table[256] tab
| pointer ptr
|
| define main routine
| inputs tab
| trashes a, y, z, n, ptr
| {
| ld y, 0
| point ptr into tab {
| copy 123, [ptr] + y
| }
| }
? ForbiddenWriteError
If code in a routine reads from a table through a pointer, the pointer *should*
remain inside the range of the table. This is currently not checked.
| byte table[32] tab
| pointer ptr
| byte foo
|
| define main routine
| inputs tab
| outputs foo
| trashes a, y, c, z, n, v, ptr
| {
| ld y, 0
| point ptr into tab {
| st off, c
| add ptr, word 100
| copy [ptr] + y, foo
| }
| }
= ok
Likewise, if code in a routine writes into a table through a pointer, the pointer
*should* remain inside the range of the table. This is currently not checked.
| byte table[32] tab
| pointer ptr
|
| define main routine
| inputs tab
| outputs tab
| trashes a, y, c, z, n, v, ptr
| {
| ld y, 0
| point ptr into tab {
| st off, c
| add ptr, word 100
| copy 123, [ptr] + y
| }
| }
= ok

View File

@ -385,6 +385,68 @@ Some instructions on tables. (3/3)
= $081B DEC $081F,X
= $081E RTS
Using a constant offset, you can read and write entries in
the table beyond the 256th.
| byte one
| byte table[1024] many
|
| define main routine
| inputs many
| outputs many
| trashes a, x, n, z
| {
| ld x, 0
| ld a, many + x
| st a, many + x
| ld a, many + 999 + x
| st a, many + 1000 + x
| }
= $080D LDX #$00
= $080F LDA $081D,X
= $0812 STA $081D,X
= $0815 LDA $0C04,X
= $0818 STA $0C05,X
= $081B RTS
Instructions on tables, with constant offsets.
| byte table[256] many
|
| define main routine
| inputs many
| outputs many
| trashes a, x, c, n, z, v
| {
| ld x, 0
| ld a, 0
| st off, c
| add a, many + 1 + x
| sub a, many + 2 + x
| cmp a, many + 3 + x
| and a, many + 4 + x
| or a, many + 5 + x
| xor a, many + 6 + x
| shl many + 7 + x
| shr many + 8 + x
| inc many + 9 + x
| dec many + 10 + x
| }
= $080D LDX #$00
= $080F LDA #$00
= $0811 CLC
= $0812 ADC $0832,X
= $0815 SBC $0833,X
= $0818 CMP $0834,X
= $081B AND $0835,X
= $081E ORA $0836,X
= $0821 EOR $0837,X
= $0824 ROL $0838,X
= $0827 ROR $0839,X
= $082A INC $083A,X
= $082D DEC $083B,X
= $0830 RTS
Compiling 16-bit `cmp`.
| word za @ 60001
@ -876,6 +938,42 @@ Copy routine (by forward reference) to vector.
= $0818 INX
= $0819 RTS
Copy byte to byte table and back, with both `x` and `y` as indexes,
plus constant offsets.
| byte one
| byte table[256] many
|
| define main routine
| inputs one, many
| outputs one, many
| trashes a, x, y, n, z
| {
| ld x, 0
| ld y, 0
| ld a, 77
| st a, many + x
| st a, many + y
| st a, many + 1 + x
| st a, many + 1 + y
| ld a, many + x
| ld a, many + y
| ld a, many + 8 + x
| ld a, many + 8 + y
| }
= $080D LDX #$00
= $080F LDY #$00
= $0811 LDA #$4D
= $0813 STA $082D,X
= $0816 STA $082D,Y
= $0819 STA $082E,X
= $081C STA $082E,Y
= $081F LDA $082D,X
= $0822 LDA $082D,Y
= $0825 LDA $0835,X
= $0828 LDA $0835,Y
= $082B RTS
Copy word to word table and back, with both `x` and `y` as indexes.
| word one
@ -918,6 +1016,48 @@ Copy word to word table and back, with both `x` and `y` as indexes.
= $0848 STA $084D
= $084B RTS
Copy word to word table and back, with constant offsets.
| word one
| word table[256] many
|
| define main routine
| inputs one, many
| outputs one, many
| trashes a, x, y, n, z
| {
| ld x, 0
| ld y, 0
| copy 777, one
| copy one, many + 1 + x
| copy one, many + 2 + y
| copy many + 3 + x, one
| copy many + 4 + y, one
| }
= $080D LDX #$00
= $080F LDY #$00
= $0811 LDA #$09
= $0813 STA $084C
= $0816 LDA #$03
= $0818 STA $084D
= $081B LDA $084C
= $081E STA $084F,X
= $0821 LDA $084D
= $0824 STA $094F,X
= $0827 LDA $084C
= $082A STA $0850,Y
= $082D LDA $084D
= $0830 STA $0950,Y
= $0833 LDA $0851,X
= $0836 STA $084C
= $0839 LDA $0951,X
= $083C STA $084D
= $083F LDA $0852,Y
= $0842 STA $084C
= $0845 LDA $0952,Y
= $0848 STA $084D
= $084B RTS
Indirect call.
| vector routine
@ -1025,6 +1165,57 @@ Copying to and from a vector table.
= $0842 JMP ($0846)
= $0845 RTS
Copying to and from a vector table, with constant offsets.
| vector routine
| outputs x
| trashes a, z, n
| one
| vector routine
| outputs x
| trashes a, z, n
| table[256] many
|
| define bar routine outputs x trashes a, z, n {
| ld x, 200
| }
|
| define main routine
| inputs one, many
| outputs one, many
| trashes a, x, n, z
| {
| ld x, 0
| copy bar, one
| copy bar, many + 1 + x
| copy one, many + 2 + x
| copy many + 3 + x, one
| call one
| }
= $080D LDX #$00
= $080F LDA #$3F
= $0811 STA $0846
= $0814 LDA #$08
= $0816 STA $0847
= $0819 LDA #$3F
= $081B STA $0849,X
= $081E LDA #$08
= $0820 STA $0949,X
= $0823 LDA $0846
= $0826 STA $084A,X
= $0829 LDA $0847
= $082C STA $094A,X
= $082F LDA $084B,X
= $0832 STA $0846
= $0835 LDA $094B,X
= $0838 STA $0847
= $083B JSR $0842
= $083E RTS
= $083F LDX #$C8
= $0841 RTS
= $0842 JMP ($0846)
= $0845 RTS
### add, sub
Various modes of `add`.
@ -1207,20 +1398,21 @@ Subtracting a word memory location from another word memory location.
= $081D STA $0822
= $0820 RTS
### Buffers and Pointers
### Tables and Pointers
Load address into pointer.
Load address of table into pointer.
| buffer[2048] buf
| byte table[256] tab
| pointer ptr @ 254
|
| define main routine
| inputs buf
| outputs buf, y
| inputs tab
| outputs tab, y
| trashes a, z, n, ptr
| {
| ld y, 0
| copy ^buf, ptr
| point ptr into tab {
| }
| }
= $080D LDY #$00
= $080F LDA #$18
@ -1231,17 +1423,18 @@ Load address into pointer.
Write literal through a pointer.
| buffer[2048] buf
| byte table[256] tab
| pointer ptr @ 254
|
| define main routine
| inputs buf
| outputs buf, y
| inputs tab
| outputs tab, y
| trashes a, z, n, ptr
| {
| ld y, 0
| copy ^buf, ptr
| copy 123, [ptr] + y
| point ptr into tab {
| copy 123, [ptr] + y
| }
| }
= $080D LDY #$00
= $080F LDA #$1C
@ -1254,43 +1447,45 @@ Write literal through a pointer.
Write stored value through a pointer.
| buffer[2048] buf
| byte table[256] tab
| pointer ptr @ 254
| byte foo
|
| define main routine
| inputs foo, buf
| outputs y, buf
| inputs foo, tab
| outputs y, tab
| trashes a, z, n, ptr
| {
| ld y, 0
| copy ^buf, ptr
| copy foo, [ptr] + y
| point ptr into tab {
| copy foo, [ptr] + y
| }
| }
= $080D LDY #$00
= $080F LDA #$1D
= $0811 STA $FE
= $0813 LDA #$08
= $0815 STA $FF
= $0817 LDA $101D
= $0817 LDA $091D
= $081A STA ($FE),Y
= $081C RTS
Read through a pointer, into a byte storage location, or the `a` register.
| buffer[2048] buf
| byte table[256] tab
| pointer ptr @ 254
| byte foo
|
| define main routine
| inputs buf
| inputs tab
| outputs y, foo
| trashes a, z, n, ptr
| {
| ld y, 0
| copy ^buf, ptr
| copy [ptr] + y, foo
| ld a, [ptr] + y
| point ptr into tab {
| copy [ptr] + y, foo
| ld a, [ptr] + y
| }
| }
= $080D LDY #$00
= $080F LDA #$1F
@ -1298,25 +1493,27 @@ Read through a pointer, into a byte storage location, or the `a` register.
= $0813 LDA #$08
= $0815 STA $FF
= $0817 LDA ($FE),Y
= $0819 STA $101F
= $0819 STA $091F
= $081C LDA ($FE),Y
= $081E RTS
Read and write through two pointers.
| buffer[2048] buf
| byte table[256] tab
| pointer ptra @ 252
| pointer ptrb @ 254
|
| define main routine
| inputs buf
| outputs buf
| inputs tab
| outputs tab
| trashes a, y, z, n, ptra, ptrb
| {
| ld y, 0
| copy ^buf, ptra
| copy ^buf, ptrb
| copy [ptra] + y, [ptrb] + y
| point ptra into tab {
| point ptrb into tab {
| copy [ptra] + y, [ptrb] + y
| }
| }
| }
= $080D LDY #$00
= $080F LDA #$24
@ -1333,19 +1530,20 @@ Read and write through two pointers.
Write the `a` register through a pointer.
| buffer[2048] buf
| byte table[256] tab
| pointer ptr @ 254
| byte foo
|
| define main routine
| inputs buf
| outputs buf
| inputs tab
| outputs tab
| trashes a, y, z, n, ptr
| {
| ld y, 0
| copy ^buf, ptr
| ld a, 255
| st a, [ptr] + y
| point ptr into tab {
| ld a, 255
| st a, [ptr] + y
| }
| }
= $080D LDY #$00
= $080F LDA #$1C
@ -1359,28 +1557,29 @@ Write the `a` register through a pointer.
Add a word memory location, and a literal word, to a pointer, and then read through it.
Note that this is *not* range-checked. (Yet.)
| buffer[2048] buf
| byte table[256] tab
| pointer ptr @ 254
| byte foo
| word delta
|
| define main routine
| inputs buf
| inputs tab
| outputs y, foo, delta
| trashes a, c, v, z, n, ptr
| {
| copy 619, delta
| ld y, 0
| st off, c
| copy ^buf, ptr
| add ptr, delta
| add ptr, word 1
| copy [ptr] + y, foo
| point ptr into tab {
| add ptr, delta
| add ptr, word 1
| copy [ptr] + y, foo
| }
| }
= $080D LDA #$6B
= $080F STA $1043
= $080F STA $0943
= $0812 LDA #$02
= $0814 STA $1044
= $0814 STA $0944
= $0817 LDY #$00
= $0819 CLC
= $081A LDA #$42
@ -1388,10 +1587,10 @@ Note that this is *not* range-checked. (Yet.)
= $081E LDA #$08
= $0820 STA $FF
= $0822 LDA $FE
= $0824 ADC $1043
= $0824 ADC $0943
= $0827 STA $FE
= $0829 LDA $FF
= $082B ADC $1044
= $082B ADC $0944
= $082E STA $FF
= $0830 LDA $FE
= $0832 ADC #$01
@ -1400,7 +1599,7 @@ Note that this is *not* range-checked. (Yet.)
= $0838 ADC #$00
= $083A STA $FF
= $083C LDA ($FE),Y
= $083E STA $1042
= $083E STA $0942
= $0841 RTS
### Trash

View File

@ -163,6 +163,9 @@ Basic "open-faced for" loops, up and down.
Other blocks.
| byte table[256] tab
| pointer ptr
|
| define main routine trashes a, x, c, z, v {
| with interrupts off {
| save a, x, c {
@ -172,6 +175,9 @@ Other blocks.
| save a, x, c {
| ld a, 0
| }
| point ptr into tab {
| ld a, [ptr] + y
| }
| }
= ok
@ -180,7 +186,7 @@ User-defined memory addresses of different types.
| byte byt
| word wor
| vector routine trashes a vec
| buffer[2048] buf
| byte table[2048] buf
| pointer ptr
|
| define main routine {
@ -192,6 +198,8 @@ Tables of different types and some operations on them.
| byte table[256] many
| word table[256] wmany
| vector (routine trashes a) table[256] vmany
| byte bval
| word wval
|
| define main routine {
| ld x, 0
@ -207,11 +215,48 @@ Tables of different types and some operations on them.
| shr many + x
| inc many + x
| dec many + x
| ld a, many + x
| st a, many + x
| copy wval, wmany + x
| copy wmany + x, wval
| }
= ok
Indexing with an offset in some tables.
| byte table[256] many
| word table[256] wmany
| byte bval
| word wval
|
| define main routine {
| ld x, 0
| ld a, 0
| st off, c
| add a, many + 100 + x
| sub a, many + 100 + x
| cmp a, many + 100 + x
| and a, many + 100 + x
| or a, many + 100 + x
| xor a, many + 100 + x
| shl many + 100 + x
| shr many + 100 + x
| inc many + 100 + x
| dec many + 100 + x
| ld a, many + 100 + x
| st a, many + 100 + x
| copy wval, wmany + 100 + x
| copy wmany + 100 + x, wval
| }
= ok
The number of entries in a table must be
greater than 0 and less than or equal to 256.
greater than 0 and less than or equal to 65536.
(In previous versions, a table could have at
most 256 entries. They can now have more, however
the offset-access syntax can only access the
first 256. To access more, a pointer is required.)
| word table[512] many
|
@ -223,6 +268,30 @@ greater than 0 and less than or equal to 256.
| ld x, 0
| copy 9999, many + x
| }
= ok
| byte table[65536] many
|
| define main routine
| inputs many
| outputs many
| trashes a, x, n, z
| {
| ld x, 0
| copy 99, many + x
| }
= ok
| byte table[65537] many
|
| define main routine
| inputs many
| outputs many
| trashes a, x, n, z
| {
| ld x, 0
| copy 99, many + x
| }
? SyntaxError
| word table[0] many
@ -285,6 +354,19 @@ Constants.
| }
= ok
Named constants can be used as offsets.
| const lives 3
| const w1 1000
|
| byte table[w1] those
|
| define main routine {
| ld y, 0
| ld a, those + lives + y
| }
= ok
Can't have two constants with the same name.
| const w1 1000
@ -590,18 +672,19 @@ But you can't `goto` a label that never gets defined.
| }
? Expected '}', but found 'ld'
Buffers and pointers.
Tables and pointers.
| buffer[2048] buf
| byte table[2048] buf
| pointer ptr
| pointer ptrb
| byte foo
|
| define main routine {
| copy ^buf, ptr
| copy 123, [ptr] + y
| copy [ptr] + y, foo
| copy [ptr] + y, [ptrb] + y
| point ptr into buf {
| copy 123, [ptr] + y
| copy [ptr] + y, foo
| copy [ptr] + y, [ptrb] + y
| }
| }
= ok