mirror of
https://github.com/catseye/SixtyPical.git
synced 2025-01-10 02:29:23 +00:00
commit
c10e73bdcb
14
HISTORY.md
14
HISTORY.md
@ -1,6 +1,20 @@
|
||||
History of SixtyPical
|
||||
=====================
|
||||
|
||||
0.12
|
||||
----
|
||||
|
||||
* `copy` is now understood to trash `a`, thus it is not valid to use `a` in `copy`.
|
||||
To compensate, indirect addressing is supported in `ld` and `st`, for example,
|
||||
as `ld a, [ptr] + y` and `st a, [ptr] + y`.
|
||||
* Implements the "union rule for trashes" when analyzing `if` blocks.
|
||||
* Even if we `goto` another routine, we can't trash an output.
|
||||
* `static` storage locations local to routines can now be defined within routines.
|
||||
* Small grammar changes that obviate the need for:
|
||||
* the parentheses in type expressions like `vector (routine ...) table[256]`
|
||||
* the `forward` keyword in forward references in source of `copy` instruction
|
||||
* Fixed bug where `trash` was not marking the location as being virtually altered.
|
||||
|
||||
0.11
|
||||
----
|
||||
|
||||
|
45
README.md
45
README.md
@ -1,7 +1,7 @@
|
||||
SixtyPical
|
||||
==========
|
||||
|
||||
_Version 0.11. Work-in-progress, everything is subject to change._
|
||||
_Version 0.12. Work-in-progress, everything is subject to change._
|
||||
|
||||
SixtyPical is a very low-level programming language, similar to 6502 assembly,
|
||||
with static analysis through abstract interpretation.
|
||||
@ -39,10 +39,6 @@ Documentation
|
||||
TODO
|
||||
----
|
||||
|
||||
### `low` and `high` address operators
|
||||
|
||||
To turn `word` type into `byte`.
|
||||
|
||||
### Save registers on stack
|
||||
|
||||
This preserves them, so that, semantically, they can be used later even though they
|
||||
@ -56,32 +52,27 @@ But if you add a value ≥ N to it, it becomes invalid.
|
||||
This should be tracked in the abstract interpretation.
|
||||
(If only because abstract interpretation is the major point of this project!)
|
||||
|
||||
### Routine-local static memory locations
|
||||
Range-checking buffers might be too difficult. Range checking tables will be easier.
|
||||
If a value is ANDed with 15, its range must be 0-15, etc.
|
||||
|
||||
These would not need to appear in the inputs/outputs/trashes sets of the routines
|
||||
that call this routine.
|
||||
### Re-order routines and optimize tail-calls to fallthroughs
|
||||
|
||||
These might be forced to specify an initial value so that they can always be
|
||||
assumed to be meaningful.
|
||||
|
||||
### More modes for `copy`
|
||||
|
||||
* don't allow `copy foo, a` probably. insist on `ld a, foo` for this.
|
||||
* have `copy` instruction able to copy a byte to a user-def mem loc, etc.
|
||||
* `copy x, [ptr] + y`
|
||||
* Maybe even `copy [ptra] + y, [ptrb] + y`, which can be compiled to indirect LDA then indirect STA!
|
||||
|
||||
### Union rule for trashes in `if`
|
||||
|
||||
If one branch trashes {`a`} and the other branch trashes {`b`} then the whole
|
||||
`if` statement trashes {`a`, `b`}.
|
||||
Not because it saves 3 bytes, but because it's a neat trick. Doing it optimally
|
||||
is probably NP-complete. But doing it adeuqately is probably not that hard.
|
||||
|
||||
### And at some point...
|
||||
|
||||
* `low` and `high` address operators - to turn `word` type into `byte`.
|
||||
* `const`s that can be used in defining the size of tables, etc.
|
||||
* Tests, and implementation, ensuring a routine can be assigned to a vector of "wider" type
|
||||
* Related: can we simply view a (small) part of a buffer as a byte table? If not, why not?
|
||||
* Check that the buffer being read or written to through pointer, appears in approporiate inputs or outputs set.
|
||||
(Associate each pointer with the buffer it points into.)
|
||||
* `static` pointers -- currently not possible because pointers must be zero-page, thus `@`, thus uninitialized.
|
||||
* Question the value of the "consistent initialization" principle for `if` statement analysis.
|
||||
* `interrupt` routines -- to indicate that "the supervisor" has stored values on the stack, so we can trash them.
|
||||
* error messages that include the line number of the source code
|
||||
* add absolute addressing in shl/shr, absolute-indexed for add, sub, etc.
|
||||
* check and disallow recursion.
|
||||
* automatic tail-call optimization (could be tricky, w/constraints?)
|
||||
* re-order routines and optimize tail-calls to fallthroughs
|
||||
* Error messages that include the line number of the source code.
|
||||
* Add absolute addressing in shl/shr, absolute-indexed for add, sub, etc.
|
||||
* Automatic tail-call optimization (could be tricky, w/constraints?)
|
||||
* Possibly `ld x, [ptr] + y`, possibly `st x, [ptr] + y`.
|
||||
* Maybe even `copy [ptra] + y, [ptrb] + y`, which can be compiled to indirect LDA then indirect STA!
|
||||
|
@ -26,6 +26,31 @@ to spend a lot of time debugging.
|
||||
The intent is not to make it absolutely impossible to make such errors,
|
||||
just harder.
|
||||
|
||||
### Things it will Not Do ###
|
||||
|
||||
To emphasize the point, the intent is not to make it impossible to make
|
||||
data-usage (and other) errors, just harder.
|
||||
|
||||
Here are some things SixtyPical will not try to detect or prevent you
|
||||
from doing:
|
||||
|
||||
* Check that a vector is initialized before it's called.
|
||||
* Check that the stack has enough room on it.
|
||||
* Prevent bad things happening (e.g. clobbering a static storage
|
||||
location) because of a recursive call. (You can always recursively
|
||||
call yourself through a vector.)
|
||||
* Check that reads and writes to a buffer are in bounds. (This may
|
||||
happen someday, but it's difficult. It's more likely that this
|
||||
will happen for tables, than for buffers.)
|
||||
|
||||
At one point I wanted to do a call-tree analysis to find sets of
|
||||
routines that would never be called together (i.e. would never be on
|
||||
the call stack at the same time) and allow any static storage locations
|
||||
defined within them to occupy the same addresses, i.e. allow storage
|
||||
to be re-used across these routines. But, in the presence of vectors,
|
||||
this becomes difficult (see "Prevent bad things happening", above.)
|
||||
Also, it would usually only save a few bytes of storage space.
|
||||
|
||||
### Some Background ###
|
||||
|
||||
The ideas in SixtyPical came from a couple of places.
|
||||
|
@ -148,7 +148,7 @@ buffer pointed to is implemented with "indirect indexed" addressing, as in
|
||||
LDA ($02), Y
|
||||
STA ($02), Y
|
||||
|
||||
There are extended modes of `copy` for using these types of memory location.
|
||||
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
|
||||
@ -238,6 +238,18 @@ If and only if src is a byte table, the index-memory-location must be given.
|
||||
Some combinations, such as `ld x, y`, are illegal because they do not map to
|
||||
underlying opcodes.
|
||||
|
||||
There is another mode of `ld` which reads into `a` indirectly through a pointer.
|
||||
|
||||
ld a, [<src-memory-location>] + y
|
||||
|
||||
The memory location in this syntax must be a pointer.
|
||||
|
||||
This syntax copies the contents of memory at the pointer (offset by the `y`
|
||||
register) into a register (which must be the `a` register.)
|
||||
|
||||
In addition to the constraints above, `y` must be initialized before
|
||||
this mode is used.
|
||||
|
||||
### st ###
|
||||
|
||||
st <src-memory-location>, <dest-memory-location> [+ <index-memory-location>]
|
||||
@ -254,6 +266,19 @@ changed by this instruction (unless of course dest is a flag.)
|
||||
|
||||
If and only if dest is a byte table, the index-memory-location must be given.
|
||||
|
||||
There is another mode of `st` which write `a` into memory, indirectly through
|
||||
a pointer.
|
||||
|
||||
st a, [<dest-memory-location>] + y
|
||||
|
||||
The memory location in this syntax must be a pointer.
|
||||
|
||||
This syntax copies the constents of the `a` register into
|
||||
the contents of memory at the pointer (offset by the `y` register).
|
||||
|
||||
In addition to the constraints above, `y` must be initialized before
|
||||
this mode is used.
|
||||
|
||||
### copy ###
|
||||
|
||||
copy <src-memory-location>, <dest-memory-location>
|
||||
@ -512,13 +537,14 @@ Grammar
|
||||
Program ::= {TypeDefn} {Defn} {Routine}.
|
||||
TypeDefn::= "typedef" Type Ident<new>.
|
||||
Defn ::= Type Ident<new> [Constraints] (":" Literal | "@" LitWord).
|
||||
Type ::= "(" Type ")" | TypeExpr ["table" TypeSize].
|
||||
Type ::= TypeTerm ["table" TypeSize].
|
||||
TypeExpr::= "byte"
|
||||
| "word"
|
||||
| "buffer" TypeSize
|
||||
| "pointer"
|
||||
| "vector" Type
|
||||
| "vector" TypeTerm
|
||||
| "routine" Constraints
|
||||
| "(" Type ")"
|
||||
.
|
||||
TypeSize::= "[" LitWord "]".
|
||||
Constrnt::= ["inputs" LocExprs] ["outputs" LocExprs] ["trashes" LocExprs].
|
||||
|
22
eg/new-style-routine.60p
Normal file
22
eg/new-style-routine.60p
Normal file
@ -0,0 +1,22 @@
|
||||
// SixtyPical 0.11 introduced a new syntax for defining routines
|
||||
// (routine was made into a type, and you can now say: define name type.)
|
||||
|
||||
typedef routine
|
||||
inputs x
|
||||
outputs x
|
||||
trashes z, n
|
||||
routine_type
|
||||
|
||||
vector routine_type vec
|
||||
|
||||
define foo routine_type
|
||||
{
|
||||
inc x
|
||||
}
|
||||
|
||||
define main routine
|
||||
outputs vec
|
||||
trashes a, z, n
|
||||
{
|
||||
copy foo, vec
|
||||
}
|
@ -31,13 +31,13 @@
|
||||
//
|
||||
|
||||
typedef routine
|
||||
inputs joy2, button_down, press_fire_msg, dispatch_game_state, save_x,
|
||||
actor_pos, pos, new_pos, actor_delta, delta, actor_logic,
|
||||
inputs joy2, press_fire_msg, dispatch_game_state,
|
||||
actor_pos, actor_delta, actor_logic,
|
||||
screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4
|
||||
outputs button_down, dispatch_game_state,
|
||||
actor_pos, pos, new_pos, actor_delta, delta, actor_logic,
|
||||
outputs dispatch_game_state,
|
||||
actor_pos, actor_delta, actor_logic,
|
||||
screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4
|
||||
trashes a, x, y, c, z, n, v, ptr, save_x, compare_target, dispatch_logic
|
||||
trashes a, x, y, c, z, n, v, pos, new_pos, delta, ptr, dispatch_logic
|
||||
game_state_routine
|
||||
|
||||
//
|
||||
@ -51,7 +51,7 @@ typedef routine
|
||||
typedef routine
|
||||
inputs pos, delta, joy2, screen
|
||||
outputs pos, delta, new_pos, screen, c
|
||||
trashes a, x, y, z, n, v, ptr, compare_target
|
||||
trashes a, x, y, z, n, v, ptr
|
||||
logic_routine
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
@ -87,15 +87,11 @@ word new_pos
|
||||
word table[256] actor_delta
|
||||
word delta
|
||||
|
||||
vector (logic_routine) table[256] actor_logic
|
||||
vector logic_routine table[256] actor_logic
|
||||
vector logic_routine dispatch_logic
|
||||
|
||||
byte button_down : 0 // effectively static-local to check_button
|
||||
byte table[32] press_fire_msg: "PRESS`FIRE`TO`PLAY"
|
||||
|
||||
byte save_x
|
||||
word compare_target
|
||||
|
||||
//
|
||||
// Points to the routine that implements the current game state.
|
||||
//
|
||||
@ -158,10 +154,11 @@ routine read_stick
|
||||
// call this routine.
|
||||
// Upon return, if carry is set, the button was pressed then released.
|
||||
|
||||
routine check_button
|
||||
inputs joy2, button_down
|
||||
outputs c, button_down
|
||||
define check_button routine
|
||||
inputs joy2
|
||||
outputs c
|
||||
trashes a, z, n
|
||||
static byte button_down : 0
|
||||
{
|
||||
ld a, button_down
|
||||
if z {
|
||||
@ -218,10 +215,11 @@ routine calculate_new_position
|
||||
add new_pos, delta
|
||||
}
|
||||
|
||||
routine check_new_position_in_bounds
|
||||
define check_new_position_in_bounds routine
|
||||
inputs new_pos
|
||||
outputs c
|
||||
trashes compare_target, a, z, n, v
|
||||
trashes a, z, n, v
|
||||
static word compare_target : 0
|
||||
{
|
||||
copy 1000, compare_target
|
||||
st on, c
|
||||
@ -243,15 +241,15 @@ routine check_new_position_in_bounds
|
||||
|
||||
routine init_game
|
||||
inputs actor_pos, actor_delta, actor_logic
|
||||
outputs actor_pos, actor_delta, pos, actor_logic
|
||||
trashes a, y, z, n, c, v
|
||||
outputs actor_pos, actor_delta, actor_logic
|
||||
trashes pos, a, y, z, n, c, v
|
||||
{
|
||||
ld y, 0
|
||||
copy word 0, pos
|
||||
repeat {
|
||||
copy pos, actor_pos + y
|
||||
copy word 40, actor_delta + y
|
||||
copy forward enemy_logic, actor_logic + y
|
||||
copy enemy_logic, actor_logic + y
|
||||
|
||||
st off, c
|
||||
add pos, word 7
|
||||
@ -263,7 +261,7 @@ routine init_game
|
||||
ld y, 0
|
||||
copy word 0, actor_pos + y
|
||||
copy word 0, actor_delta + y
|
||||
copy forward player_logic, actor_logic + y
|
||||
copy player_logic, actor_logic + y
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
@ -284,7 +282,7 @@ define player_logic logic_routine
|
||||
ld y, 0
|
||||
|
||||
// check collision.
|
||||
copy [ptr] + y, a
|
||||
ld a, [ptr] + y
|
||||
// if "collision" is with your own self, treat it as if it's blank space!
|
||||
cmp a, 81
|
||||
if z {
|
||||
@ -307,18 +305,14 @@ define player_logic logic_routine
|
||||
st off, c
|
||||
} else {
|
||||
st on, c
|
||||
trash n
|
||||
trash a
|
||||
trash z
|
||||
}
|
||||
|
||||
// FIXME these trashes, strictly speaking, probably shouldn't be needed,
|
||||
// but currently the compiler cares too much about values that are
|
||||
// initialized in one branch of an `if`, but not the other, but trashed
|
||||
// but currently the compiler cares a little too much about values that are
|
||||
// initialized in one branch of an `if`, but not the other, but are trashed
|
||||
// at the end of the routine anyway.
|
||||
trash ptr
|
||||
trash y
|
||||
trash a
|
||||
trash v
|
||||
} else {
|
||||
st off, c
|
||||
@ -326,6 +320,7 @@ define player_logic logic_routine
|
||||
}
|
||||
|
||||
define enemy_logic logic_routine
|
||||
static word compare_target : 0
|
||||
{
|
||||
call calculate_new_position
|
||||
call check_new_position_in_bounds
|
||||
@ -337,7 +332,7 @@ define enemy_logic logic_routine
|
||||
ld y, 0
|
||||
|
||||
// check collision.
|
||||
copy [ptr] + y, a
|
||||
ld a, [ptr] + y
|
||||
// if "collision" is with your own self, treat it as if it's blank space!
|
||||
cmp a, 82
|
||||
if z {
|
||||
@ -360,9 +355,6 @@ define enemy_logic logic_routine
|
||||
st off, c
|
||||
} else {
|
||||
st on, c
|
||||
trash n
|
||||
trash a
|
||||
trash z
|
||||
}
|
||||
|
||||
// FIXME these trashes, strictly speaking, probably shouldn't be needed,
|
||||
@ -371,7 +363,6 @@ define enemy_logic logic_routine
|
||||
// at the end of the routine anyway.
|
||||
trash ptr
|
||||
trash y
|
||||
trash a
|
||||
} else {
|
||||
copy delta, compare_target
|
||||
st on, c
|
||||
@ -381,7 +372,6 @@ define enemy_logic logic_routine
|
||||
} else {
|
||||
copy $ffd8, delta
|
||||
}
|
||||
trash compare_target
|
||||
}
|
||||
|
||||
st off, c
|
||||
@ -411,25 +401,14 @@ define game_state_title_screen game_state_routine
|
||||
if c {
|
||||
call clear_screen
|
||||
call init_game
|
||||
copy forward game_state_play, dispatch_game_state
|
||||
|
||||
// FIXME these trashes, strictly speaking, probably shouldn't be needed,
|
||||
// but currently the compiler cares too much about values that are
|
||||
// initialized in one branch of an `if`, but not the other, but trashed
|
||||
// at the end of the routine anyway.
|
||||
trash a
|
||||
trash n
|
||||
trash z
|
||||
} else {
|
||||
trash y
|
||||
trash c
|
||||
trash v
|
||||
copy game_state_play, dispatch_game_state
|
||||
}
|
||||
|
||||
goto save_cinv
|
||||
}
|
||||
|
||||
define game_state_play game_state_routine
|
||||
static byte save_x : 0
|
||||
{
|
||||
ld x, 0
|
||||
repeat {
|
||||
@ -444,11 +423,10 @@ define game_state_play game_state_routine
|
||||
if c {
|
||||
// Player died! Want no dead! Break out of the loop (this is a bit awkward.)
|
||||
call clear_screen
|
||||
copy forward game_state_game_over, dispatch_game_state
|
||||
copy game_state_game_over, dispatch_game_state
|
||||
ld x, 15
|
||||
} else {
|
||||
ld x, save_x
|
||||
trash c
|
||||
}
|
||||
|
||||
copy pos, actor_pos + x
|
||||
@ -470,18 +448,6 @@ define game_state_game_over game_state_routine
|
||||
call clear_screen
|
||||
call init_game
|
||||
copy game_state_title_screen, dispatch_game_state
|
||||
|
||||
// FIXME these trashes, strictly speaking, probably shouldn't be needed,
|
||||
// but currently the compiler cares too much about values that are
|
||||
// initialized in one branch of an `if`, but not the other, but trashed
|
||||
// at the end of the routine anyway.
|
||||
trash a
|
||||
trash n
|
||||
trash z
|
||||
} else {
|
||||
trash y
|
||||
trash c
|
||||
trash v
|
||||
}
|
||||
|
||||
goto save_cinv
|
||||
|
@ -1,3 +1,8 @@
|
||||
//
|
||||
// Demonstrates vector tables.
|
||||
// Prints "AABAB".
|
||||
//
|
||||
|
||||
vector routine
|
||||
trashes a, z, n
|
||||
print
|
||||
@ -31,8 +36,7 @@ routine main
|
||||
trashes print, a, x, z, n, c
|
||||
{
|
||||
ld x, 0
|
||||
copy printa, print
|
||||
copy print, vectors + x
|
||||
copy printa, vectors + x
|
||||
inc x
|
||||
copy printa, print
|
||||
copy print, vectors + x
|
||||
@ -40,13 +44,9 @@ routine main
|
||||
copy printb, print
|
||||
copy print, vectors + x
|
||||
inc x
|
||||
copy printa, print
|
||||
copy print, vectors + x
|
||||
copy printa, vectors + x
|
||||
inc x
|
||||
copy printb, print
|
||||
copy print, vectors + x
|
||||
|
||||
copy printa, print
|
||||
copy printb, vectors + x
|
||||
|
||||
ld x, 0
|
||||
repeat {
|
||||
|
@ -1,51 +0,0 @@
|
||||
vector routine
|
||||
trashes a, z, n
|
||||
print
|
||||
|
||||
vector (routine
|
||||
trashes a, z, n)
|
||||
table[32] vectors
|
||||
|
||||
routine chrout
|
||||
inputs a
|
||||
trashes a
|
||||
@ 65490
|
||||
|
||||
routine printa
|
||||
trashes a, z, n
|
||||
{
|
||||
ld a, 65
|
||||
call chrout
|
||||
}
|
||||
|
||||
routine printb
|
||||
trashes a, z, n
|
||||
{
|
||||
ld a, 66
|
||||
call chrout
|
||||
}
|
||||
|
||||
routine main
|
||||
inputs vectors
|
||||
outputs vectors
|
||||
trashes print, a, x, z, n, c
|
||||
{
|
||||
ld x, 0
|
||||
copy printa, vectors + x
|
||||
inc x
|
||||
copy printa, vectors + x
|
||||
inc x
|
||||
copy printb, vectors + x
|
||||
inc x
|
||||
copy printa, vectors + x
|
||||
inc x
|
||||
copy printb, vectors + x
|
||||
|
||||
ld x, 0
|
||||
repeat {
|
||||
copy vectors + x, print
|
||||
call print
|
||||
inc x
|
||||
cmp x, 5
|
||||
} until z
|
||||
}
|
@ -1,5 +1,8 @@
|
||||
#!/bin/sh
|
||||
|
||||
if [ "X$X64" = "X" ]; then
|
||||
X64=x64
|
||||
fi
|
||||
SRC=$1
|
||||
if [ "X$1" = "X" ]; then
|
||||
echo "Usage: ./loadngo.sh <source.60p>"
|
||||
@ -7,9 +10,10 @@ if [ "X$1" = "X" ]; then
|
||||
fi
|
||||
OUT=/tmp/a-out.prg
|
||||
bin/sixtypical --traceback --basic-prelude $SRC > $OUT || exit 1
|
||||
ls -la $OUT
|
||||
if [ -e vicerc ]; then
|
||||
x64 -config vicerc $OUT
|
||||
$X64 -config vicerc $OUT
|
||||
else
|
||||
x64 $OUT
|
||||
$X64 $OUT
|
||||
fi
|
||||
rm -f $OUT
|
||||
|
@ -53,6 +53,15 @@ class IncompatibleConstraintsError(ConstraintsError):
|
||||
pass
|
||||
|
||||
|
||||
def routine_has_static(routine, ref):
|
||||
if not hasattr(routine, 'statics'):
|
||||
return False
|
||||
for static in routine.statics:
|
||||
if static.location == ref:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class Context(object):
|
||||
"""
|
||||
A location is touched if it was changed (or even potentially
|
||||
@ -101,13 +110,6 @@ class Context(object):
|
||||
c._writeable = set(self._writeable)
|
||||
return c
|
||||
|
||||
def set_from(self, c):
|
||||
assert c.routines == self.routines
|
||||
assert c.routine == self.routine
|
||||
self._touched = set(c._touched)
|
||||
self._meaningful = set(c._meaningful)
|
||||
self._writeable = set(c._writeable)
|
||||
|
||||
def each_meaningful(self):
|
||||
for ref in self._meaningful:
|
||||
yield ref
|
||||
@ -119,6 +121,9 @@ class Context(object):
|
||||
def assert_meaningful(self, *refs, **kwargs):
|
||||
exception_class = kwargs.get('exception_class', UnmeaningfulReadError)
|
||||
for ref in refs:
|
||||
# statics are always meaningful
|
||||
if routine_has_static(self.routine, ref):
|
||||
continue
|
||||
if ref.is_constant() or ref in self.routines:
|
||||
pass
|
||||
elif isinstance(ref, LocationRef):
|
||||
@ -127,12 +132,18 @@ class Context(object):
|
||||
if kwargs.get('message'):
|
||||
message += ' (%s)' % kwargs['message']
|
||||
raise exception_class(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:
|
||||
# statics are always writeable
|
||||
if routine_has_static(self.routine, ref):
|
||||
continue
|
||||
if ref not in self._writeable:
|
||||
message = '%s in %s' % (ref.name, self.routine.name)
|
||||
if kwargs.get('message'):
|
||||
@ -204,20 +215,38 @@ class Analyzer(object):
|
||||
if routine.block is None:
|
||||
# it's an extern, that's fine
|
||||
return
|
||||
type = routine.location.type
|
||||
context = Context(self.routines, routine, type.inputs, type.outputs, type.trashes)
|
||||
type_ = routine.location.type
|
||||
context = Context(self.routines, routine, type_.inputs, type_.outputs, type_.trashes)
|
||||
|
||||
if self.debug:
|
||||
print "at start of routine `{}`:".format(routine.name)
|
||||
print context
|
||||
|
||||
self.analyze_block(routine.block, context)
|
||||
trashed = set(context.each_touched()) - context._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 ''
|
||||
|
||||
# even if we goto another routine, we can't trash an output.
|
||||
for ref in trashed:
|
||||
if ref in type_.outputs:
|
||||
raise UnmeaningfulOutputError('%s in %s' % (ref.name, routine.name))
|
||||
|
||||
if not self.has_encountered_goto:
|
||||
for ref in type.outputs:
|
||||
for ref in type_.outputs:
|
||||
context.assert_meaningful(ref, exception_class=UnmeaningfulOutputError)
|
||||
for ref in context.each_touched():
|
||||
if ref not in type.outputs and ref not in type.trashes:
|
||||
if ref not in type_.outputs and ref not in type_.trashes and not routine_has_static(routine, ref):
|
||||
message = '%s in %s' % (ref.name, routine.name)
|
||||
raise ForbiddenWriteError(message)
|
||||
self.current_routine = None
|
||||
@ -236,33 +265,51 @@ class Analyzer(object):
|
||||
src = instr.src
|
||||
|
||||
if opcode == 'ld':
|
||||
if instr.index:
|
||||
if TableType.is_a_table_type(src.type, TYPE_BYTE) and dest.type == TYPE_BYTE:
|
||||
if isinstance(src, IndexedRef):
|
||||
if TableType.is_a_table_type(src.ref.type, TYPE_BYTE) and dest.type == TYPE_BYTE:
|
||||
pass
|
||||
else:
|
||||
raise TypeMismatchError('%s and %s in %s' %
|
||||
(src.name, dest.name, self.current_routine.name)
|
||||
(src.ref.name, dest.name, self.current_routine.name)
|
||||
)
|
||||
context.assert_meaningful(instr.index)
|
||||
context.assert_meaningful(src, src.index)
|
||||
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((src, dest))
|
||||
context.assert_meaningful(src.ref, REG_Y)
|
||||
elif src.type != dest.type:
|
||||
raise TypeMismatchError('%s and %s in %s' %
|
||||
(src.name, dest.name, self.current_routine.name)
|
||||
)
|
||||
context.assert_meaningful(src)
|
||||
else:
|
||||
context.assert_meaningful(src)
|
||||
context.set_written(dest, FLAG_Z, FLAG_N)
|
||||
elif opcode == 'st':
|
||||
if instr.index:
|
||||
if src.type == TYPE_BYTE and TableType.is_a_table_type(dest.type, TYPE_BYTE):
|
||||
if isinstance(dest, IndexedRef):
|
||||
if src.type == TYPE_BYTE and TableType.is_a_table_type(dest.ref.type, TYPE_BYTE):
|
||||
pass
|
||||
else:
|
||||
raise TypeMismatchError((src, dest))
|
||||
context.assert_meaningful(instr.index)
|
||||
context.assert_meaningful(dest.index)
|
||||
context.set_written(dest.ref)
|
||||
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((src, dest))
|
||||
context.assert_meaningful(dest.ref, REG_Y)
|
||||
context.set_written(dest.ref)
|
||||
elif src.type != dest.type:
|
||||
raise TypeMismatchError('%r and %r in %s' %
|
||||
(src, dest, self.current_routine.name)
|
||||
)
|
||||
else:
|
||||
context.set_written(dest)
|
||||
context.assert_meaningful(src)
|
||||
context.set_written(dest)
|
||||
elif opcode == 'add':
|
||||
context.assert_meaningful(src, dest, FLAG_C)
|
||||
if src.type == TYPE_BYTE:
|
||||
@ -319,25 +366,44 @@ class Analyzer(object):
|
||||
context.set_touched(ref)
|
||||
context.set_unmeaningful(ref)
|
||||
elif opcode == 'if':
|
||||
incoming_meaningful = set(context.each_meaningful())
|
||||
|
||||
context1 = context.clone()
|
||||
context2 = context.clone()
|
||||
self.analyze_block(instr.block1, context1)
|
||||
if instr.block2 is not None:
|
||||
self.analyze_block(instr.block2, context2)
|
||||
|
||||
outgoing_meaningful = set(context1.each_meaningful()) & set(context2.each_meaningful())
|
||||
outgoing_trashes = incoming_meaningful - outgoing_meaningful
|
||||
|
||||
# TODO may we need to deal with touched separately here too?
|
||||
# probably not; if it wasn't meaningful in the first place, it
|
||||
# doesn't really matter if you modified it or not, coming out.
|
||||
for ref in context1.each_meaningful():
|
||||
if ref in outgoing_trashes:
|
||||
continue
|
||||
context2.assert_meaningful(
|
||||
ref, exception_class=InconsistentInitializationError,
|
||||
message='initialized in block 1 but not in block 2 of `if {}`'.format(src)
|
||||
)
|
||||
for ref in context2.each_meaningful():
|
||||
if ref in outgoing_trashes:
|
||||
continue
|
||||
context1.assert_meaningful(
|
||||
ref, exception_class=InconsistentInitializationError,
|
||||
message='initialized in block 2 but not in block 1 of `if {}`'.format(src)
|
||||
)
|
||||
context.set_from(context1)
|
||||
|
||||
# merge the contexts. this used to be a method called `set_from`
|
||||
context._touched = set(context1._touched) | set(context2._touched)
|
||||
context._meaningful = outgoing_meaningful
|
||||
context._writeable = set(context1._writeable) | set(context2._writeable)
|
||||
|
||||
for ref in outgoing_trashes:
|
||||
context.set_touched(ref)
|
||||
context.set_unmeaningful(ref)
|
||||
|
||||
elif opcode == 'repeat':
|
||||
# it will always be executed at least once, so analyze it having
|
||||
# been executed the first time.
|
||||
@ -352,6 +418,9 @@ class Analyzer(object):
|
||||
context.assert_meaningful(src)
|
||||
|
||||
elif opcode == 'copy':
|
||||
if dest == REG_A:
|
||||
raise ForbiddenWriteError("{} cannot be used as destination for copy".format(dest))
|
||||
|
||||
# 1. check that their types are compatible
|
||||
|
||||
if isinstance(src, AddressRef) and isinstance(dest, LocationRef):
|
||||
@ -411,7 +480,6 @@ class Analyzer(object):
|
||||
context.set_written(dest.ref)
|
||||
elif isinstance(src, IndirectRef) and isinstance(dest, LocationRef):
|
||||
context.assert_meaningful(src.ref, REG_Y)
|
||||
# TODO this will need to be more sophisticated. the thing ref points to is touched, as well.
|
||||
context.set_written(dest)
|
||||
elif isinstance(src, LocationRef) and isinstance(dest, IndexedRef):
|
||||
context.assert_meaningful(src, dest.ref, dest.index)
|
||||
@ -428,15 +496,7 @@ class Analyzer(object):
|
||||
context.set_written(dest)
|
||||
|
||||
context.set_touched(REG_A, FLAG_Z, FLAG_N)
|
||||
context.set_unmeaningful(FLAG_Z, FLAG_N)
|
||||
|
||||
# FIXME: this is just to support "copy [foo] + y, a". consider disallowing `a` as something
|
||||
# that can be used in `copy`. should be using `st` or `ld` instead, probably.
|
||||
if dest == REG_A:
|
||||
context.set_touched(REG_A)
|
||||
context.set_written(REG_A)
|
||||
else:
|
||||
context.set_unmeaningful(REG_A)
|
||||
context.set_unmeaningful(REG_A, FLAG_Z, FLAG_N)
|
||||
|
||||
elif opcode == 'with-sei':
|
||||
self.analyze_block(instr.block, context)
|
||||
@ -461,6 +521,7 @@ class Analyzer(object):
|
||||
|
||||
self.has_encountered_goto = True
|
||||
elif opcode == 'trash':
|
||||
context.set_touched(instr.dest)
|
||||
context.set_unmeaningful(instr.dest)
|
||||
else:
|
||||
raise NotImplementedError(opcode)
|
||||
|
@ -28,9 +28,11 @@ class UnsupportedOpcodeError(KeyError):
|
||||
class Compiler(object):
|
||||
def __init__(self, emitter):
|
||||
self.emitter = emitter
|
||||
self.routines = {}
|
||||
self.labels = {}
|
||||
self.trampolines = {} # Location -> Label
|
||||
self.routines = {} # routine.name -> Routine
|
||||
self.routine_statics = {} # routine.name -> { static.name -> Label }
|
||||
self.labels = {} # global.name -> Label ("global" includes routines)
|
||||
self.trampolines = {} # Location -> Label
|
||||
self.current_routine = None
|
||||
|
||||
# helper methods
|
||||
|
||||
@ -42,26 +44,40 @@ class Compiler(object):
|
||||
else:
|
||||
raise NotImplementedError(index)
|
||||
|
||||
def compute_length_of_defn(self, defn):
|
||||
length = None
|
||||
type_ = defn.location.type
|
||||
if type_ == TYPE_BYTE:
|
||||
length = 1
|
||||
elif type_ == TYPE_WORD or isinstance(type_, (PointerType, VectorType)):
|
||||
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
|
||||
|
||||
def get_label(self, name):
|
||||
if self.current_routine:
|
||||
static_label = self.routine_statics.get(self.current_routine.name, {}).get(name)
|
||||
if static_label:
|
||||
return static_label
|
||||
return self.labels[name]
|
||||
|
||||
# visitor methods
|
||||
|
||||
def compile_program(self, program):
|
||||
assert isinstance(program, Program)
|
||||
|
||||
defn_labels = []
|
||||
|
||||
for defn in program.defns:
|
||||
# compute length of memory pointed to. this is awful.
|
||||
length = None
|
||||
type_ = defn.location.type
|
||||
if type_ == TYPE_BYTE:
|
||||
length = 1
|
||||
elif type_ == TYPE_WORD or isinstance(type_, (PointerType, VectorType)):
|
||||
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_))
|
||||
self.labels[defn.name] = Label(defn.name, addr=defn.addr, length=length)
|
||||
length = self.compute_length_of_defn(defn)
|
||||
label = Label(defn.name, addr=defn.addr, length=length)
|
||||
self.labels[defn.name] = label
|
||||
defn_labels.append((defn, label))
|
||||
|
||||
for routine in program.routines:
|
||||
self.routines[routine.name] = routine
|
||||
@ -70,6 +86,15 @@ class Compiler(object):
|
||||
label.set_addr(routine.addr)
|
||||
self.labels[routine.name] = label
|
||||
|
||||
if hasattr(routine, 'statics'):
|
||||
static_labels = {}
|
||||
for defn in routine.statics:
|
||||
length = self.compute_length_of_defn(defn)
|
||||
label = Label(defn.name, addr=defn.addr, length=length)
|
||||
static_labels[defn.name] = label
|
||||
defn_labels.append((defn, label))
|
||||
self.routine_statics[routine.name] = static_labels
|
||||
|
||||
self.compile_routine(self.routines['main'])
|
||||
for routine in program.routines:
|
||||
if routine.name != 'main':
|
||||
@ -77,13 +102,12 @@ class Compiler(object):
|
||||
|
||||
for location, label in self.trampolines.iteritems():
|
||||
self.emitter.resolve_label(label)
|
||||
self.emitter.emit(JMP(Indirect(self.labels[location.name])))
|
||||
self.emitter.emit(JMP(Indirect(self.get_label(location.name))))
|
||||
self.emitter.emit(RTS())
|
||||
|
||||
# initialized data
|
||||
for defn in program.defns:
|
||||
for defn, label in defn_labels:
|
||||
if defn.initial is not None:
|
||||
label = self.labels[defn.name]
|
||||
initial_data = None
|
||||
type_ = defn.location.type
|
||||
if type_ == TYPE_BYTE:
|
||||
@ -99,18 +123,18 @@ class Compiler(object):
|
||||
self.emitter.emit(initial_data)
|
||||
|
||||
# uninitialized, "BSS" data
|
||||
for defn in program.defns:
|
||||
for defn, label in defn_labels:
|
||||
if defn.initial is None and defn.addr is None:
|
||||
label = self.labels[defn.name]
|
||||
self.emitter.resolve_bss_label(label)
|
||||
|
||||
|
||||
def compile_routine(self, routine):
|
||||
self.current_routine = routine
|
||||
assert isinstance(routine, Routine)
|
||||
if routine.block:
|
||||
self.emitter.resolve_label(self.labels[routine.name])
|
||||
self.emitter.resolve_label(self.get_label(routine.name))
|
||||
self.compile_block(routine.block)
|
||||
self.emitter.emit(RTS())
|
||||
self.current_routine = None
|
||||
|
||||
def compile_block(self, block):
|
||||
assert isinstance(block, Block)
|
||||
@ -131,30 +155,32 @@ class Compiler(object):
|
||||
self.emitter.emit(TYA())
|
||||
elif isinstance(src, ConstantRef):
|
||||
self.emitter.emit(LDA(Immediate(Byte(src.value))))
|
||||
elif instr.index == REG_X:
|
||||
self.emitter.emit(LDA(AbsoluteX(self.labels[src.name])))
|
||||
elif instr.index == REG_Y:
|
||||
self.emitter.emit(LDA(AbsoluteY(self.labels[src.name])))
|
||||
elif isinstance(src, IndexedRef) and src.index == REG_X:
|
||||
self.emitter.emit(LDA(AbsoluteX(self.get_label(src.ref.name))))
|
||||
elif isinstance(src, IndexedRef) and src.index == REG_Y:
|
||||
self.emitter.emit(LDA(AbsoluteY(self.get_label(src.ref.name))))
|
||||
elif isinstance(src, IndirectRef) and isinstance(src.ref.type, PointerType):
|
||||
self.emitter.emit(LDA(IndirectY(self.get_label(src.ref.name))))
|
||||
else:
|
||||
self.emitter.emit(LDA(Absolute(self.labels[src.name])))
|
||||
self.emitter.emit(LDA(Absolute(self.get_label(src.name))))
|
||||
elif dest == REG_X:
|
||||
if src == REG_A:
|
||||
self.emitter.emit(TAX())
|
||||
elif isinstance(src, ConstantRef):
|
||||
self.emitter.emit(LDX(Immediate(Byte(src.value))))
|
||||
elif instr.index == REG_Y:
|
||||
self.emitter.emit(LDX(AbsoluteY(self.labels[src.name])))
|
||||
elif isinstance(src, IndexedRef) and src.index == REG_Y:
|
||||
self.emitter.emit(LDX(AbsoluteY(self.get_label(src.ref.name))))
|
||||
else:
|
||||
self.emitter.emit(LDX(Absolute(self.labels[src.name])))
|
||||
self.emitter.emit(LDX(Absolute(self.get_label(src.name))))
|
||||
elif dest == REG_Y:
|
||||
if src == REG_A:
|
||||
self.emitter.emit(TAY())
|
||||
elif isinstance(src, ConstantRef):
|
||||
self.emitter.emit(LDY(Immediate(Byte(src.value))))
|
||||
elif instr.index == REG_X:
|
||||
self.emitter.emit(LDY(AbsoluteX(self.labels[src.name])))
|
||||
elif isinstance(src, IndexedRef) and src.index == REG_X:
|
||||
self.emitter.emit(LDY(AbsoluteX(self.get_label(src.ref.name))))
|
||||
else:
|
||||
self.emitter.emit(LDY(Absolute(self.labels[src.name])))
|
||||
self.emitter.emit(LDY(Absolute(self.get_label(src.name))))
|
||||
else:
|
||||
raise UnsupportedOpcodeError(instr)
|
||||
elif opcode == 'st':
|
||||
@ -168,23 +194,32 @@ class Compiler(object):
|
||||
REG_X: STX,
|
||||
REG_Y: STY
|
||||
}.get(src, None)
|
||||
mode_cls = {
|
||||
REG_X: AbsoluteX,
|
||||
REG_Y: AbsoluteY,
|
||||
None: Absolute
|
||||
}.get(instr.index, None)
|
||||
|
||||
if isinstance(dest, IndexedRef):
|
||||
mode_cls = {
|
||||
REG_X: AbsoluteX,
|
||||
REG_Y: AbsoluteY,
|
||||
}[dest.index]
|
||||
label = self.get_label(dest.ref.name)
|
||||
elif isinstance(dest, IndirectRef) and isinstance(dest.ref.type, PointerType):
|
||||
mode_cls = IndirectY
|
||||
label = self.get_label(dest.ref.name)
|
||||
else:
|
||||
mode_cls = Absolute
|
||||
label = self.get_label(dest.name)
|
||||
|
||||
if op_cls is None or mode_cls is None:
|
||||
raise UnsupportedOpcodeError(instr)
|
||||
self.emitter.emit(op_cls(mode_cls(self.labels[dest.name])))
|
||||
self.emitter.emit(op_cls(mode_cls(label)))
|
||||
elif opcode == 'add':
|
||||
if dest == REG_A:
|
||||
if isinstance(src, ConstantRef):
|
||||
self.emitter.emit(ADC(Immediate(Byte(src.value))))
|
||||
else:
|
||||
self.emitter.emit(ADC(Absolute(self.labels[src.name])))
|
||||
self.emitter.emit(ADC(Absolute(self.get_label(src.name))))
|
||||
elif isinstance(dest, LocationRef) and src.type == TYPE_WORD and dest.type == TYPE_WORD:
|
||||
if isinstance(src, ConstantRef):
|
||||
dest_label = self.labels[dest.name]
|
||||
dest_label = self.get_label(dest.name)
|
||||
self.emitter.emit(LDA(Absolute(dest_label)))
|
||||
self.emitter.emit(ADC(Immediate(Byte(src.low_byte()))))
|
||||
self.emitter.emit(STA(Absolute(dest_label)))
|
||||
@ -192,8 +227,8 @@ class Compiler(object):
|
||||
self.emitter.emit(ADC(Immediate(Byte(src.high_byte()))))
|
||||
self.emitter.emit(STA(Absolute(Offset(dest_label, 1))))
|
||||
elif isinstance(src, LocationRef):
|
||||
src_label = self.labels[src.name]
|
||||
dest_label = self.labels[dest.name]
|
||||
src_label = self.get_label(src.name)
|
||||
dest_label = self.get_label(dest.name)
|
||||
self.emitter.emit(LDA(Absolute(dest_label)))
|
||||
self.emitter.emit(ADC(Absolute(src_label)))
|
||||
self.emitter.emit(STA(Absolute(dest_label)))
|
||||
@ -204,7 +239,7 @@ class Compiler(object):
|
||||
raise UnsupportedOpcodeError(instr)
|
||||
elif isinstance(dest, LocationRef) and src.type == TYPE_WORD and isinstance(dest.type, PointerType):
|
||||
if isinstance(src, ConstantRef):
|
||||
dest_label = self.labels[dest.name]
|
||||
dest_label = self.get_label(dest.name)
|
||||
self.emitter.emit(LDA(ZeroPage(dest_label)))
|
||||
self.emitter.emit(ADC(Immediate(Byte(src.low_byte()))))
|
||||
self.emitter.emit(STA(ZeroPage(dest_label)))
|
||||
@ -212,8 +247,8 @@ class Compiler(object):
|
||||
self.emitter.emit(ADC(Immediate(Byte(src.high_byte()))))
|
||||
self.emitter.emit(STA(ZeroPage(Offset(dest_label, 1))))
|
||||
elif isinstance(src, LocationRef):
|
||||
src_label = self.labels[src.name]
|
||||
dest_label = self.labels[dest.name]
|
||||
src_label = self.get_label(src.name)
|
||||
dest_label = self.get_label(dest.name)
|
||||
self.emitter.emit(LDA(ZeroPage(dest_label)))
|
||||
self.emitter.emit(ADC(Absolute(src_label)))
|
||||
self.emitter.emit(STA(ZeroPage(dest_label)))
|
||||
@ -229,10 +264,10 @@ class Compiler(object):
|
||||
if isinstance(src, ConstantRef):
|
||||
self.emitter.emit(SBC(Immediate(Byte(src.value))))
|
||||
else:
|
||||
self.emitter.emit(SBC(Absolute(self.labels[src.name])))
|
||||
self.emitter.emit(SBC(Absolute(self.get_label(src.name))))
|
||||
elif isinstance(dest, LocationRef) and src.type == TYPE_WORD and dest.type == TYPE_WORD:
|
||||
if isinstance(src, ConstantRef):
|
||||
dest_label = self.labels[dest.name]
|
||||
dest_label = self.get_label(dest.name)
|
||||
self.emitter.emit(LDA(Absolute(dest_label)))
|
||||
self.emitter.emit(SBC(Immediate(Byte(src.low_byte()))))
|
||||
self.emitter.emit(STA(Absolute(dest_label)))
|
||||
@ -240,8 +275,8 @@ class Compiler(object):
|
||||
self.emitter.emit(SBC(Immediate(Byte(src.high_byte()))))
|
||||
self.emitter.emit(STA(Absolute(Offset(dest_label, 1))))
|
||||
elif isinstance(src, LocationRef):
|
||||
src_label = self.labels[src.name]
|
||||
dest_label = self.labels[dest.name]
|
||||
src_label = self.get_label(src.name)
|
||||
dest_label = self.get_label(dest.name)
|
||||
self.emitter.emit(LDA(Absolute(dest_label)))
|
||||
self.emitter.emit(SBC(Absolute(src_label)))
|
||||
self.emitter.emit(STA(Absolute(dest_label)))
|
||||
@ -258,14 +293,14 @@ class Compiler(object):
|
||||
elif dest == REG_Y:
|
||||
self.emitter.emit(INY())
|
||||
else:
|
||||
self.emitter.emit(INC(Absolute(self.labels[dest.name])))
|
||||
self.emitter.emit(INC(Absolute(self.get_label(dest.name))))
|
||||
elif opcode == 'dec':
|
||||
if dest == REG_X:
|
||||
self.emitter.emit(DEX())
|
||||
elif dest == REG_Y:
|
||||
self.emitter.emit(DEY())
|
||||
else:
|
||||
self.emitter.emit(DEC(Absolute(self.labels[dest.name])))
|
||||
self.emitter.emit(DEC(Absolute(self.get_label(dest.name))))
|
||||
elif opcode == 'cmp':
|
||||
cls = {
|
||||
'a': CMP,
|
||||
@ -277,7 +312,7 @@ class Compiler(object):
|
||||
if isinstance(src, ConstantRef):
|
||||
self.emitter.emit(cls(Immediate(Byte(src.value))))
|
||||
else:
|
||||
self.emitter.emit(cls(Absolute(self.labels[src.name])))
|
||||
self.emitter.emit(cls(Absolute(self.get_label(src.name))))
|
||||
elif opcode in ('and', 'or', 'xor',):
|
||||
cls = {
|
||||
'and': AND,
|
||||
@ -288,7 +323,7 @@ class Compiler(object):
|
||||
if isinstance(src, ConstantRef):
|
||||
self.emitter.emit(cls(Immediate(Byte(src.value))))
|
||||
else:
|
||||
self.emitter.emit(cls(Absolute(self.labels[src.name])))
|
||||
self.emitter.emit(cls(Absolute(self.get_label(src.name))))
|
||||
else:
|
||||
raise UnsupportedOpcodeError(instr)
|
||||
elif opcode in ('shl', 'shr'):
|
||||
@ -302,7 +337,7 @@ class Compiler(object):
|
||||
raise UnsupportedOpcodeError(instr)
|
||||
elif opcode == 'call':
|
||||
location = instr.location
|
||||
label = self.labels[instr.location.name]
|
||||
label = self.get_label(instr.location.name)
|
||||
if isinstance(location.type, RoutineType):
|
||||
self.emitter.emit(JSR(Absolute(label)))
|
||||
elif isinstance(location.type, VectorType):
|
||||
@ -314,7 +349,7 @@ class Compiler(object):
|
||||
raise NotImplementedError
|
||||
elif opcode == 'goto':
|
||||
location = instr.location
|
||||
label = self.labels[instr.location.name]
|
||||
label = self.get_label(instr.location.name)
|
||||
if isinstance(location.type, RoutineType):
|
||||
self.emitter.emit(JMP(Absolute(label)))
|
||||
elif isinstance(location.type, VectorType):
|
||||
@ -372,12 +407,12 @@ class Compiler(object):
|
||||
if isinstance(src, (LocationRef, ConstantRef)) and isinstance(dest, IndirectRef):
|
||||
if src.type == TYPE_BYTE and isinstance(dest.ref.type, PointerType):
|
||||
if isinstance(src, ConstantRef):
|
||||
dest_label = self.labels[dest.ref.name]
|
||||
dest_label = self.get_label(dest.ref.name)
|
||||
self.emitter.emit(LDA(Immediate(Byte(src.value))))
|
||||
self.emitter.emit(STA(IndirectY(dest_label)))
|
||||
elif isinstance(src, LocationRef):
|
||||
src_label = self.labels[src.name]
|
||||
dest_label = self.labels[dest.ref.name]
|
||||
src_label = self.get_label(src.name)
|
||||
dest_label = self.get_label(dest.ref.name)
|
||||
self.emitter.emit(LDA(Absolute(src_label)))
|
||||
self.emitter.emit(STA(IndirectY(dest_label)))
|
||||
else:
|
||||
@ -385,43 +420,40 @@ class Compiler(object):
|
||||
else:
|
||||
raise NotImplementedError((src, dest))
|
||||
elif isinstance(src, IndirectRef) and isinstance(dest, LocationRef):
|
||||
if dest == REG_A and isinstance(src.ref.type, PointerType):
|
||||
src_label = self.labels[src.ref.name]
|
||||
self.emitter.emit(LDA(IndirectY(src_label)))
|
||||
elif dest.type == TYPE_BYTE and isinstance(src.ref.type, PointerType):
|
||||
src_label = self.labels[src.ref.name]
|
||||
dest_label = self.labels[dest.name]
|
||||
if dest.type == TYPE_BYTE and isinstance(src.ref.type, PointerType):
|
||||
src_label = self.get_label(src.ref.name)
|
||||
dest_label = self.get_label(dest.name)
|
||||
self.emitter.emit(LDA(IndirectY(src_label)))
|
||||
self.emitter.emit(STA(Absolute(dest_label)))
|
||||
else:
|
||||
raise NotImplementedError((src, dest))
|
||||
elif isinstance(src, AddressRef) and isinstance(dest, LocationRef) and \
|
||||
isinstance(src.ref.type, BufferType) and isinstance(dest.type, PointerType):
|
||||
src_label = self.labels[src.ref.name]
|
||||
dest_label = self.labels[dest.name]
|
||||
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):
|
||||
if src.type == TYPE_WORD and TableType.is_a_table_type(dest.ref.type, TYPE_WORD):
|
||||
src_label = self.labels[src.name]
|
||||
dest_label = self.labels[dest.ref.name]
|
||||
src_label = self.get_label(src.name)
|
||||
dest_label = self.get_label(dest.ref.name)
|
||||
self.emitter.emit(LDA(Absolute(src_label)))
|
||||
self.emitter.emit(STA(self.addressing_mode_for_index(dest.index)(dest_label)))
|
||||
self.emitter.emit(LDA(Absolute(Offset(src_label, 1))))
|
||||
self.emitter.emit(STA(self.addressing_mode_for_index(dest.index)(Offset(dest_label, 256))))
|
||||
elif isinstance(src.type, VectorType) and isinstance(dest.ref.type, TableType) and isinstance(dest.ref.type.of_type, VectorType):
|
||||
# FIXME this is the exact same as above - can this be simplified?
|
||||
src_label = self.labels[src.name]
|
||||
dest_label = self.labels[dest.ref.name]
|
||||
src_label = self.get_label(src.name)
|
||||
dest_label = self.get_label(dest.ref.name)
|
||||
self.emitter.emit(LDA(Absolute(src_label)))
|
||||
self.emitter.emit(STA(self.addressing_mode_for_index(dest.index)(dest_label)))
|
||||
self.emitter.emit(LDA(Absolute(Offset(src_label, 1))))
|
||||
self.emitter.emit(STA(self.addressing_mode_for_index(dest.index)(Offset(dest_label, 256))))
|
||||
elif isinstance(src.type, RoutineType) and isinstance(dest.ref.type, TableType) and isinstance(dest.ref.type.of_type, VectorType):
|
||||
src_label = self.labels[src.name]
|
||||
dest_label = self.labels[dest.ref.name]
|
||||
src_label = self.get_label(src.name)
|
||||
dest_label = self.get_label(dest.ref.name)
|
||||
self.emitter.emit(LDA(Immediate(HighAddressByte(src_label))))
|
||||
self.emitter.emit(STA(self.addressing_mode_for_index(dest.index)(dest_label)))
|
||||
self.emitter.emit(LDA(Immediate(LowAddressByte(src_label))))
|
||||
@ -430,7 +462,7 @@ class Compiler(object):
|
||||
raise NotImplementedError
|
||||
elif isinstance(src, ConstantRef) and isinstance(dest, IndexedRef):
|
||||
if src.type == TYPE_WORD and TableType.is_a_table_type(dest.ref.type, TYPE_WORD):
|
||||
dest_label = self.labels[dest.ref.name]
|
||||
dest_label = self.get_label(dest.ref.name)
|
||||
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(LDA(Immediate(Byte(src.high_byte()))))
|
||||
@ -439,16 +471,16 @@ class Compiler(object):
|
||||
raise NotImplementedError
|
||||
elif isinstance(src, IndexedRef) and isinstance(dest, LocationRef):
|
||||
if TableType.is_a_table_type(src.ref.type, TYPE_WORD) and dest.type == TYPE_WORD:
|
||||
src_label = self.labels[src.ref.name]
|
||||
dest_label = self.labels[dest.name]
|
||||
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)))
|
||||
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(STA(Absolute(Offset(dest_label, 1))))
|
||||
elif isinstance(dest.type, VectorType) and isinstance(src.ref.type, TableType) and isinstance(src.ref.type.of_type, VectorType):
|
||||
# FIXME this is the exact same as above - can this be simplified?
|
||||
src_label = self.labels[src.ref.name]
|
||||
dest_label = self.labels[dest.name]
|
||||
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)))
|
||||
self.emitter.emit(STA(Absolute(dest_label)))
|
||||
self.emitter.emit(LDA(self.addressing_mode_for_index(src.index)(Offset(src_label, 256))))
|
||||
@ -462,34 +494,34 @@ class Compiler(object):
|
||||
if isinstance(src, ConstantRef):
|
||||
raise NotImplementedError
|
||||
else:
|
||||
src_label = self.labels[src.name]
|
||||
dest_label = self.labels[dest.name]
|
||||
src_label = self.get_label(src.name)
|
||||
dest_label = self.get_label(dest.name)
|
||||
self.emitter.emit(LDA(Absolute(src_label)))
|
||||
self.emitter.emit(STA(Absolute(dest_label)))
|
||||
elif src.type == TYPE_WORD and dest.type == TYPE_WORD:
|
||||
if isinstance(src, ConstantRef):
|
||||
dest_label = self.labels[dest.name]
|
||||
dest_label = self.get_label(dest.name)
|
||||
self.emitter.emit(LDA(Immediate(Byte(src.low_byte()))))
|
||||
self.emitter.emit(STA(Absolute(dest_label)))
|
||||
self.emitter.emit(LDA(Immediate(Byte(src.high_byte()))))
|
||||
self.emitter.emit(STA(Absolute(Offset(dest_label, 1))))
|
||||
else:
|
||||
src_label = self.labels[src.name]
|
||||
dest_label = self.labels[dest.name]
|
||||
src_label = self.get_label(src.name)
|
||||
dest_label = self.get_label(dest.name)
|
||||
self.emitter.emit(LDA(Absolute(src_label)))
|
||||
self.emitter.emit(STA(Absolute(dest_label)))
|
||||
self.emitter.emit(LDA(Absolute(Offset(src_label, 1))))
|
||||
self.emitter.emit(STA(Absolute(Offset(dest_label, 1))))
|
||||
elif isinstance(src.type, VectorType) and isinstance(dest.type, VectorType):
|
||||
src_label = self.labels[src.name]
|
||||
dest_label = self.labels[dest.name]
|
||||
src_label = self.get_label(src.name)
|
||||
dest_label = self.get_label(dest.name)
|
||||
self.emitter.emit(LDA(Absolute(src_label)))
|
||||
self.emitter.emit(STA(Absolute(dest_label)))
|
||||
self.emitter.emit(LDA(Absolute(Offset(src_label, 1))))
|
||||
self.emitter.emit(STA(Absolute(Offset(dest_label, 1))))
|
||||
elif isinstance(src.type, RoutineType) and isinstance(dest.type, VectorType):
|
||||
src_label = self.labels[src.name]
|
||||
dest_label = self.labels[dest.name]
|
||||
src_label = self.get_label(src.name)
|
||||
dest_label = self.get_label(dest.name)
|
||||
self.emitter.emit(LDA(Immediate(HighAddressByte(src_label))))
|
||||
self.emitter.emit(STA(Absolute(dest_label)))
|
||||
self.emitter.emit(LDA(Immediate(LowAddressByte(src_label))))
|
||||
|
@ -145,7 +145,7 @@ class LocationRef(Ref):
|
||||
# just to be sure.
|
||||
equal = isinstance(other, self.__class__) and other.name == self.name
|
||||
if equal:
|
||||
assert other.type == self.type
|
||||
assert other.type == self.type, repr((self, other))
|
||||
return equal
|
||||
|
||||
def __hash__(self):
|
||||
|
@ -14,22 +14,34 @@ class SymEntry(object):
|
||||
self.ast_node = ast_node
|
||||
self.model = model
|
||||
|
||||
def __repr__(self):
|
||||
return "%s(%r, %r)" % (self.__class__.__name__, self.ast_node, self.model)
|
||||
|
||||
|
||||
class Parser(object):
|
||||
def __init__(self, text):
|
||||
self.scanner = Scanner(text)
|
||||
self.symbols = {} # token -> SymEntry
|
||||
self.typedefs = {} # token -> Type AST
|
||||
self.symbols = {} # token -> SymEntry
|
||||
self.current_statics = {} # token -> SymEntry
|
||||
self.typedefs = {} # token -> Type AST
|
||||
for token in ('a', 'x', 'y'):
|
||||
self.symbols[token] = SymEntry(None, LocationRef(TYPE_BYTE, token))
|
||||
for token in ('c', 'z', 'n', 'v'):
|
||||
self.symbols[token] = SymEntry(None, LocationRef(TYPE_BIT, token))
|
||||
self.backpatch_instrs = []
|
||||
|
||||
def soft_lookup(self, name):
|
||||
if name in self.current_statics:
|
||||
return self.current_statics[name].model
|
||||
if name in self.symbols:
|
||||
return self.symbols[name].model
|
||||
return None
|
||||
|
||||
def lookup(self, name):
|
||||
if name not in self.symbols:
|
||||
model = self.soft_lookup(name)
|
||||
if model is None:
|
||||
raise SyntaxError('Undefined symbol "%s"' % name)
|
||||
return self.symbols[name].model
|
||||
return model
|
||||
|
||||
# --- grammar productions
|
||||
|
||||
@ -130,6 +142,15 @@ class Parser(object):
|
||||
return size
|
||||
|
||||
def defn_type(self):
|
||||
type_ = self.defn_type_term()
|
||||
|
||||
if self.scanner.consume('table'):
|
||||
size = self.defn_size()
|
||||
type_ = TableType(type_, size)
|
||||
|
||||
return type_
|
||||
|
||||
def defn_type_term(self):
|
||||
type_ = None
|
||||
|
||||
if self.scanner.consume('('):
|
||||
@ -142,7 +163,7 @@ class Parser(object):
|
||||
elif self.scanner.consume('word'):
|
||||
type_ = TYPE_WORD
|
||||
elif self.scanner.consume('vector'):
|
||||
type_ = self.defn_type()
|
||||
type_ = self.defn_type_term()
|
||||
if not isinstance(type_, RoutineType):
|
||||
raise SyntaxError("Vectors can only be of a routine, not %r" % type_)
|
||||
type_ = VectorType(type_)
|
||||
@ -161,10 +182,6 @@ class Parser(object):
|
||||
raise SyntaxError("Undefined type '%s'" % type_name)
|
||||
type_ = self.typedefs[type_name]
|
||||
|
||||
if self.scanner.consume('table'):
|
||||
size = self.defn_size()
|
||||
type_ = TableType(type_, size)
|
||||
|
||||
return type_
|
||||
|
||||
def defn_name(self):
|
||||
@ -209,20 +226,35 @@ class Parser(object):
|
||||
type_ = self.defn_type()
|
||||
if not isinstance(type_, RoutineType):
|
||||
raise SyntaxError("Can only define a routine, not %r" % type_)
|
||||
statics = []
|
||||
if self.scanner.consume('@'):
|
||||
self.scanner.check_type('integer literal')
|
||||
block = None
|
||||
addr = int(self.scanner.token)
|
||||
self.scanner.scan()
|
||||
else:
|
||||
statics = self.statics()
|
||||
|
||||
self.current_statics = self.compose_statics_dict(statics)
|
||||
block = self.block()
|
||||
self.current_statics = {}
|
||||
|
||||
addr = None
|
||||
location = LocationRef(type_, name)
|
||||
return Routine(
|
||||
name=name, block=block, addr=addr,
|
||||
location=location
|
||||
location=location, statics=statics
|
||||
)
|
||||
|
||||
def compose_statics_dict(self, statics):
|
||||
c = {}
|
||||
for defn in statics:
|
||||
name = defn.name
|
||||
if name in self.symbols or name in self.current_statics:
|
||||
raise SyntaxError('Symbol "%s" already declared' % name)
|
||||
c[name] = SymEntry(defn, defn.location)
|
||||
return c
|
||||
|
||||
def labels(self):
|
||||
accum = []
|
||||
accum.append(self.label())
|
||||
@ -244,7 +276,7 @@ class Parser(object):
|
||||
accum.append(self.locexpr())
|
||||
return accum
|
||||
|
||||
def locexpr(self):
|
||||
def locexpr(self, forward=False):
|
||||
if self.scanner.token in ('on', 'off'):
|
||||
loc = ConstantRef(TYPE_BIT, 1 if self.scanner.token == 'on' else 0)
|
||||
self.scanner.scan()
|
||||
@ -259,15 +291,21 @@ class Parser(object):
|
||||
loc = ConstantRef(TYPE_WORD, int(self.scanner.token))
|
||||
self.scanner.scan()
|
||||
return loc
|
||||
elif forward:
|
||||
name = self.scanner.token
|
||||
self.scanner.scan()
|
||||
loc = self.soft_lookup(name)
|
||||
if loc is not None:
|
||||
return loc
|
||||
else:
|
||||
return name
|
||||
else:
|
||||
loc = self.lookup(self.scanner.token)
|
||||
self.scanner.scan()
|
||||
return loc
|
||||
|
||||
def indlocexpr(self):
|
||||
if self.scanner.consume('forward'):
|
||||
return self.label()
|
||||
elif self.scanner.consume('['):
|
||||
def indlocexpr(self, forward=False):
|
||||
if self.scanner.consume('['):
|
||||
loc = self.locexpr()
|
||||
self.scanner.expect(']')
|
||||
self.scanner.expect('+')
|
||||
@ -277,13 +315,23 @@ class Parser(object):
|
||||
loc = self.locexpr()
|
||||
return AddressRef(loc)
|
||||
else:
|
||||
loc = self.locexpr()
|
||||
index = None
|
||||
if self.scanner.consume('+'):
|
||||
index = self.locexpr()
|
||||
loc = IndexedRef(loc, index)
|
||||
loc = self.locexpr(forward=forward)
|
||||
if not isinstance(loc, basestring):
|
||||
index = None
|
||||
if self.scanner.consume('+'):
|
||||
index = self.locexpr()
|
||||
loc = IndexedRef(loc, index)
|
||||
return loc
|
||||
|
||||
def statics(self):
|
||||
defns = []
|
||||
while self.scanner.consume('static'):
|
||||
defn = self.defn()
|
||||
if defn.initial is None:
|
||||
raise SyntaxError("Static definition {} must have initial value".format(defn))
|
||||
defns.append(defn)
|
||||
return defns
|
||||
|
||||
def block(self):
|
||||
instrs = []
|
||||
self.scanner.expect('{')
|
||||
@ -316,7 +364,15 @@ class Parser(object):
|
||||
self.scanner.expect('forever')
|
||||
return Instr(opcode='repeat', dest=None, src=src,
|
||||
block=block, inverted=inverted)
|
||||
elif self.scanner.token in ("ld", "add", "sub", "cmp", "and", "or", "xor"):
|
||||
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
|
||||
self.scanner.scan()
|
||||
dest = self.locexpr()
|
||||
self.scanner.expect(',')
|
||||
src = self.indlocexpr()
|
||||
return Instr(opcode=opcode, dest=dest, src=src, index=None)
|
||||
elif self.scanner.token in ("add", "sub", "cmp", "and", "or", "xor"):
|
||||
opcode = self.scanner.token
|
||||
self.scanner.scan()
|
||||
dest = self.locexpr()
|
||||
@ -331,11 +387,8 @@ class Parser(object):
|
||||
self.scanner.scan()
|
||||
src = self.locexpr()
|
||||
self.scanner.expect(',')
|
||||
dest = self.locexpr()
|
||||
index = None
|
||||
if self.scanner.consume('+'):
|
||||
index = self.locexpr()
|
||||
return Instr(opcode=opcode, dest=dest, src=src, index=index)
|
||||
dest = self.indlocexpr()
|
||||
return Instr(opcode=opcode, dest=dest, src=src, index=None)
|
||||
elif self.scanner.token in ("shl", "shr", "inc", "dec"):
|
||||
opcode = self.scanner.token
|
||||
self.scanner.scan()
|
||||
@ -352,7 +405,7 @@ class Parser(object):
|
||||
elif self.scanner.token in ("copy",):
|
||||
opcode = self.scanner.token
|
||||
self.scanner.scan()
|
||||
src = self.indlocexpr()
|
||||
src = self.indlocexpr(forward=True)
|
||||
self.scanner.expect(',')
|
||||
dest = self.indlocexpr()
|
||||
instr = Instr(opcode=opcode, dest=dest, src=src)
|
||||
|
@ -89,6 +89,95 @@ If a routine modifies a location, it needs to either output it or trash it.
|
||||
| }
|
||||
= ok
|
||||
|
||||
This is true regardless of whether it's an input or not.
|
||||
|
||||
| routine main
|
||||
| inputs x
|
||||
| {
|
||||
| ld x, 0
|
||||
| }
|
||||
? ForbiddenWriteError: x in main
|
||||
|
||||
| routine main
|
||||
| inputs x
|
||||
| outputs x, z, n
|
||||
| {
|
||||
| ld x, 0
|
||||
| }
|
||||
= ok
|
||||
|
||||
| routine main
|
||||
| inputs x
|
||||
| trashes x, z, n
|
||||
| {
|
||||
| ld x, 0
|
||||
| }
|
||||
= ok
|
||||
|
||||
If a routine trashes a location, this must be declared.
|
||||
|
||||
| routine foo
|
||||
| trashes x
|
||||
| {
|
||||
| trash x
|
||||
| }
|
||||
= ok
|
||||
|
||||
| routine foo
|
||||
| {
|
||||
| trash x
|
||||
| }
|
||||
? ForbiddenWriteError: x in foo
|
||||
|
||||
| routine foo
|
||||
| outputs x
|
||||
| {
|
||||
| trash x
|
||||
| }
|
||||
? UnmeaningfulOutputError: x in foo
|
||||
|
||||
If a routine causes a location to be trashed, this must be declared in the caller.
|
||||
|
||||
| routine trash_x
|
||||
| trashes x, z, n
|
||||
| {
|
||||
| ld x, 0
|
||||
| }
|
||||
|
|
||||
| routine foo
|
||||
| trashes x, z, n
|
||||
| {
|
||||
| call trash_x
|
||||
| }
|
||||
= ok
|
||||
|
||||
| routine trash_x
|
||||
| trashes x, z, n
|
||||
| {
|
||||
| ld x, 0
|
||||
| }
|
||||
|
|
||||
| routine foo
|
||||
| trashes z, n
|
||||
| {
|
||||
| call trash_x
|
||||
| }
|
||||
? ForbiddenWriteError: x in foo
|
||||
|
||||
| routine trash_x
|
||||
| trashes x, z, n
|
||||
| {
|
||||
| ld x, 0
|
||||
| }
|
||||
|
|
||||
| routine foo
|
||||
| outputs x
|
||||
| trashes z, n
|
||||
| {
|
||||
| call trash_x
|
||||
| }
|
||||
? UnmeaningfulOutputError: x in foo
|
||||
|
||||
If a routine reads or writes a user-define memory location, it needs to declare that too.
|
||||
|
||||
| byte b1 @ 60000
|
||||
@ -1232,6 +1321,21 @@ initialized, either because it was set previous to the `if`, or is an
|
||||
input to the routine, and it is initialized in one branch, it need not
|
||||
be initialized in the other.
|
||||
|
||||
| routine foo
|
||||
| outputs x
|
||||
| trashes a, z, n, c
|
||||
| {
|
||||
| ld x, 0
|
||||
| ld a, 0
|
||||
| cmp a, 42
|
||||
| if z {
|
||||
| ld x, 7
|
||||
| } else {
|
||||
| ld a, 23
|
||||
| }
|
||||
| }
|
||||
= ok
|
||||
|
||||
| routine foo
|
||||
| inputs x
|
||||
| outputs x
|
||||
@ -1287,6 +1391,46 @@ An `if` with a single block is analyzed as if it had an empty `else` block.
|
||||
| }
|
||||
= ok
|
||||
|
||||
The cardinal rule for trashes in an `if` is the "union rule": if one branch
|
||||
trashes {`a`} and the other branch trashes {`b`} then the whole `if` statement
|
||||
trashes {`a`, `b`}.
|
||||
|
||||
| routine foo
|
||||
| inputs a, x, z
|
||||
| trashes a, x
|
||||
| {
|
||||
| if z {
|
||||
| trash a
|
||||
| } else {
|
||||
| trash x
|
||||
| }
|
||||
| }
|
||||
= ok
|
||||
|
||||
| routine foo
|
||||
| inputs a, x, z
|
||||
| trashes a
|
||||
| {
|
||||
| if z {
|
||||
| trash a
|
||||
| } else {
|
||||
| trash x
|
||||
| }
|
||||
| }
|
||||
? ForbiddenWriteError: x in foo
|
||||
|
||||
| routine foo
|
||||
| inputs a, x, z
|
||||
| trashes x
|
||||
| {
|
||||
| if z {
|
||||
| trash a
|
||||
| } else {
|
||||
| trash x
|
||||
| }
|
||||
| }
|
||||
? ForbiddenWriteError: a in foo
|
||||
|
||||
### repeat ###
|
||||
|
||||
Repeat loop.
|
||||
@ -1462,6 +1606,21 @@ Can `copy` from a `byte` to a `byte`.
|
||||
| }
|
||||
= ok
|
||||
|
||||
The understanding is that, because `copy` trashes `a`, `a` cannot be used
|
||||
as the destination of a `copy`.
|
||||
|
||||
| byte source : 0
|
||||
| byte dest
|
||||
|
|
||||
| routine main
|
||||
| inputs source
|
||||
| outputs dest
|
||||
| trashes a, z, n
|
||||
| {
|
||||
| copy source, a
|
||||
| }
|
||||
? ForbiddenWriteError
|
||||
|
||||
Can `copy` from a `word` to a `word`.
|
||||
|
||||
| word source : 0
|
||||
@ -1504,9 +1663,7 @@ Can't `copy` from a `word` to a `byte`.
|
||||
| }
|
||||
? TypeMismatchError
|
||||
|
||||
### copy[] ###
|
||||
|
||||
Buffers and pointers.
|
||||
### Buffers and pointers ###
|
||||
|
||||
Note that `^buf` is a constant value, so it by itself does not require `buf` to be
|
||||
listed in any input/output sets.
|
||||
@ -1588,6 +1745,43 @@ Read through a pointer.
|
||||
| }
|
||||
= ok
|
||||
|
||||
Read through a pointer to the `a` register. Note that this is done with `ld`,
|
||||
not `copy`.
|
||||
|
||||
| buffer[2048] buf
|
||||
| pointer ptr
|
||||
| byte foo
|
||||
|
|
||||
| routine main
|
||||
| inputs buf
|
||||
| outputs a
|
||||
| trashes y, z, n, ptr
|
||||
| {
|
||||
| ld y, 0
|
||||
| copy ^buf, ptr
|
||||
| ld a, [ptr] + y
|
||||
| }
|
||||
= ok
|
||||
|
||||
Write the `a` register through a pointer. Note that this is done with `st`,
|
||||
not `copy`.
|
||||
|
||||
| buffer[2048] buf
|
||||
| pointer ptr
|
||||
| byte foo
|
||||
|
|
||||
| routine main
|
||||
| inputs buf
|
||||
| outputs buf
|
||||
| trashes a, y, z, n, ptr
|
||||
| {
|
||||
| ld y, 0
|
||||
| copy ^buf, ptr
|
||||
| ld a, 255
|
||||
| st a, [ptr] + y
|
||||
| }
|
||||
= ok
|
||||
|
||||
### routines ###
|
||||
|
||||
Routines are constants. You need not, and in fact cannot, specify a constant
|
||||
@ -2089,3 +2283,29 @@ The new style routine definitions support typedefs.
|
||||
| copy foo, vec
|
||||
| }
|
||||
= ok
|
||||
|
||||
### static ###
|
||||
|
||||
When memory locations are defined static to a routine, they cannot be
|
||||
directly input, nor directly output; and since they are always initialized,
|
||||
they cannot be trashed. Thus, they really don't participate in the analysis.
|
||||
|
||||
| define foo routine
|
||||
| inputs x
|
||||
| outputs x
|
||||
| trashes z, n
|
||||
| static byte t : 0
|
||||
| {
|
||||
| st x, t
|
||||
| inc t
|
||||
| ld x, t
|
||||
| }
|
||||
|
|
||||
| define main routine
|
||||
| trashes a, x, z, n
|
||||
| static byte t : 0
|
||||
| {
|
||||
| ld x, t
|
||||
| call foo
|
||||
| }
|
||||
= ok
|
||||
|
@ -529,6 +529,36 @@ Copy routine to vector, inside an `interrupts off` block.
|
||||
= $081A INX
|
||||
= $081B RTS
|
||||
|
||||
Copy routine (by forward reference) to vector.
|
||||
|
||||
| vector routine
|
||||
| inputs x
|
||||
| outputs x
|
||||
| trashes z, n
|
||||
| bar
|
||||
|
|
||||
| routine main
|
||||
| outputs bar
|
||||
| trashes a, n, z
|
||||
| {
|
||||
| copy foo, bar
|
||||
| }
|
||||
|
|
||||
| routine foo
|
||||
| inputs x
|
||||
| outputs x
|
||||
| trashes z, n
|
||||
| {
|
||||
| inc x
|
||||
| }
|
||||
= $080D LDA #$18
|
||||
= $080F STA $081A
|
||||
= $0812 LDA #$08
|
||||
= $0814 STA $081B
|
||||
= $0817 RTS
|
||||
= $0818 INX
|
||||
= $0819 RTS
|
||||
|
||||
Copy word to word table and back, with both `x` and `y` as indexes.
|
||||
|
||||
| word one
|
||||
@ -634,9 +664,9 @@ Copying to and from a vector table.
|
||||
| outputs x
|
||||
| trashes a, z, n
|
||||
| one
|
||||
| vector (routine
|
||||
| vector routine
|
||||
| outputs x
|
||||
| trashes a, z, n)
|
||||
| trashes a, z, n
|
||||
| table[256] many
|
||||
|
|
||||
| routine bar outputs x trashes a, z, n {
|
||||
@ -846,7 +876,7 @@ Read through a pointer, into a byte storage location, or the `a` register.
|
||||
| ld y, 0
|
||||
| copy ^buf, ptr
|
||||
| copy [ptr] + y, foo
|
||||
| copy [ptr] + y, a
|
||||
| ld a, [ptr] + y
|
||||
| }
|
||||
= $080D LDY #$00
|
||||
= $080F LDA #$1F
|
||||
@ -858,6 +888,31 @@ Read through a pointer, into a byte storage location, or the `a` register.
|
||||
= $081C LDA ($FE),Y
|
||||
= $081E RTS
|
||||
|
||||
Write the `a` register through a pointer.
|
||||
|
||||
| buffer[2048] buf
|
||||
| pointer ptr @ 254
|
||||
| byte foo
|
||||
|
|
||||
| routine main
|
||||
| inputs buf
|
||||
| outputs buf
|
||||
| trashes a, y, z, n, ptr
|
||||
| {
|
||||
| ld y, 0
|
||||
| copy ^buf, ptr
|
||||
| ld a, 255
|
||||
| st a, [ptr] + y
|
||||
| }
|
||||
= $080D LDY #$00
|
||||
= $080F LDA #$1C
|
||||
= $0811 STA $FE
|
||||
= $0813 LDA #$08
|
||||
= $0815 STA $FF
|
||||
= $0817 LDA #$FF
|
||||
= $0819 STA ($FE),Y
|
||||
= $081B RTS
|
||||
|
||||
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.)
|
||||
|
||||
@ -921,3 +976,36 @@ Trash does nothing except indicate that we do not care about the value anymore.
|
||||
= $080D TAX
|
||||
= $080E LDA #$00
|
||||
= $0810 RTS
|
||||
|
||||
### static ###
|
||||
|
||||
Memory locations defined static to a routine are allocated
|
||||
just the same as initialized global storage locations are.
|
||||
|
||||
| define foo routine
|
||||
| inputs x
|
||||
| outputs x
|
||||
| trashes z, n
|
||||
| static byte t : 255
|
||||
| {
|
||||
| st x, t
|
||||
| inc t
|
||||
| ld x, t
|
||||
| }
|
||||
|
|
||||
| define main routine
|
||||
| trashes a, x, z, n
|
||||
| static byte t : 7
|
||||
| {
|
||||
| ld x, t
|
||||
| call foo
|
||||
| }
|
||||
= $080D LDX $081F
|
||||
= $0810 JSR $0814
|
||||
= $0813 RTS
|
||||
= $0814 STX $081E
|
||||
= $0817 INC $081E
|
||||
= $081A LDX $081E
|
||||
= $081D RTS
|
||||
= $081E .byte $FF
|
||||
= $081F .byte $07
|
||||
|
@ -408,8 +408,9 @@ A vector can name itself in its inputs, outputs, and trashes.
|
||||
| }
|
||||
= ok
|
||||
|
||||
A routine can be copied into a vector before the routine appears in the program,
|
||||
*however*, it must be marked as such with the keyword `forward`.
|
||||
A routine can be copied into a vector before the routine appears in the program.
|
||||
This is known as a "forward reference". You are only allowed to make forward
|
||||
references in the source of a `copy` instruction.
|
||||
|
||||
| vector routine
|
||||
| inputs cinv, a
|
||||
@ -425,22 +426,6 @@ A routine can be copied into a vector before the routine appears in the program,
|
||||
| routine foo {
|
||||
| ld a, 0
|
||||
| }
|
||||
? SyntaxError: Undefined symbol
|
||||
|
||||
| vector routine
|
||||
| inputs cinv, a
|
||||
| outputs cinv, x
|
||||
| trashes a, x, z, n
|
||||
| cinv @ 788
|
||||
| routine main {
|
||||
| with interrupts off {
|
||||
| copy forward foo, cinv
|
||||
| }
|
||||
| call cinv
|
||||
| }
|
||||
| routine foo {
|
||||
| ld a, 0
|
||||
| }
|
||||
= ok
|
||||
|
||||
goto.
|
||||
@ -529,3 +514,68 @@ Only routines can be defined in the new style.
|
||||
| ld a, 0
|
||||
| }
|
||||
? SyntaxError
|
||||
|
||||
Memory locations can be defined static to a routine.
|
||||
|
||||
| define foo routine
|
||||
| inputs x
|
||||
| outputs x
|
||||
| trashes z, n
|
||||
| static byte t : 0
|
||||
| {
|
||||
| st x, t
|
||||
| inc t
|
||||
| ld x, t
|
||||
| }
|
||||
|
|
||||
| define main routine
|
||||
| trashes a, x, z, n
|
||||
| static byte t : 0
|
||||
| {
|
||||
| ld x, t
|
||||
| call foo
|
||||
| }
|
||||
= ok
|
||||
|
||||
Static memory locations must always be given an initial value.
|
||||
|
||||
| define main routine
|
||||
| inputs x
|
||||
| outputs x
|
||||
| trashes z, n
|
||||
| static byte t
|
||||
| {
|
||||
| st x, t
|
||||
| inc t
|
||||
| ld x, t
|
||||
| }
|
||||
? SyntaxError
|
||||
|
||||
Name of a static cannot shadow an existing global or static.
|
||||
|
||||
| byte t
|
||||
|
|
||||
| define main routine
|
||||
| inputs x
|
||||
| outputs x
|
||||
| trashes z, n
|
||||
| static byte t
|
||||
| {
|
||||
| st x, t
|
||||
| inc t
|
||||
| ld x, t
|
||||
| }
|
||||
? SyntaxError
|
||||
|
||||
| define main routine
|
||||
| inputs x
|
||||
| outputs x
|
||||
| trashes z, n
|
||||
| static byte t
|
||||
| static byte t
|
||||
| {
|
||||
| st x, t
|
||||
| inc t
|
||||
| ld x, t
|
||||
| }
|
||||
? SyntaxError
|
||||
|
Loading…
x
Reference in New Issue
Block a user