mirror of
https://github.com/catseye/SixtyPical.git
synced 2025-03-31 23:29:38 +00:00
commit
55ae845345
15
HISTORY.md
15
HISTORY.md
@ -1,6 +1,21 @@
|
||||
History of SixtyPical
|
||||
=====================
|
||||
|
||||
0.10
|
||||
----
|
||||
|
||||
* Can `call` and `goto` routines that are defined further down in the source code.
|
||||
* The `forward` modifier can also be used to indicate that the symbol being copied
|
||||
in a `copy` to a vector is a routine that is defined further down in the source.
|
||||
* Initialized `word` memory locations.
|
||||
* Can `copy` a literal word to a word table.
|
||||
* Subtract word (constant or memory location) from word memory location.
|
||||
* `trash` instruction explicitly indicates a value is no longer considered meaningful.
|
||||
* `copy []+y, a` can indirectly read a byte value into the `a` register.
|
||||
* Fixed bug which was preventing `if` branches to diverge in what they initialized,
|
||||
if it was already initialized when going into the `if`.
|
||||
* Fixed a bug which was making it crash when trying to analyze `repeat forever` loops.
|
||||
|
||||
0.9
|
||||
---
|
||||
|
||||
|
32
README.md
32
README.md
@ -23,7 +23,7 @@ The reference implementation can execute, analyze, and compile SixtyPical
|
||||
programs to 6502 machine code.
|
||||
|
||||
SixtyPical is a work in progress. The current released version of SixtyPical
|
||||
is 0.9.
|
||||
is 0.10.
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
@ -46,16 +46,6 @@ Finish the little demo "game" where you can move a block around the screen with
|
||||
the joystick (i.e. bring it up to par with the original demo game that was written
|
||||
for SixtyPical)
|
||||
|
||||
### `call` routines that are defined further down in the source code
|
||||
|
||||
We might have a graph of states that refer to each other and that want to `goto`
|
||||
each other. Thus we need this. We have it for vectors, but we need it for `call`.
|
||||
|
||||
### Allow branches to diverge in what they touch
|
||||
|
||||
For example, if the routine inputs and outputs `foo`, and one branch of an `if`
|
||||
sets `foo` and the other does not touch it, that should be OK.
|
||||
|
||||
### `vector table` type
|
||||
|
||||
### `low` and `high` address operators
|
||||
@ -75,20 +65,28 @@ 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!)
|
||||
|
||||
### And at some point...
|
||||
### Routine-local static memory locations
|
||||
|
||||
* Compare word (constant or memory location) with memory location or pointer. (Maybe?)
|
||||
These would not need to appear in the inputs/outputs/trashes sets of the routines
|
||||
that call this routine.
|
||||
|
||||
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!
|
||||
|
||||
### And at some point...
|
||||
|
||||
* Check that the buffer being read or written to through pointer, appears in approporiate inputs or outputs set.
|
||||
* `byte table` and `word table` of sizes other than 256
|
||||
* initialized `byte table` memory locations
|
||||
* always analyze before executing or compiling, unless told not to
|
||||
* `trash` instruction.
|
||||
* `interrupt` routines -- to indicate that "the supervisor" has stored values on the stack, so we can trash them.
|
||||
* pre-initialized `word` variables
|
||||
* error messages that include the line number of the source code
|
||||
* have `copy` instruction able to copy a byte to a user-def mem loc, etc.
|
||||
* 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?)
|
||||
|
@ -1,7 +1,7 @@
|
||||
SixtyPical
|
||||
==========
|
||||
|
||||
This document describes the SixtyPical programming language version 0.9,
|
||||
This document describes the SixtyPical programming language version 0.10,
|
||||
both its execution aspect and its static analysis aspect (even though
|
||||
these are, technically speaking, separate concepts.)
|
||||
|
||||
|
@ -27,18 +27,38 @@ byte joy2 @ $dc00
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
pointer ptr @ 254
|
||||
|
||||
word table actor_pos
|
||||
word pos
|
||||
word new_pos
|
||||
|
||||
word table actor_delta
|
||||
word delta
|
||||
|
||||
byte button_down : 0 // effectively static-local to check_button
|
||||
byte table press_fire_msg: "PRESS`FIRE`TO`PLAY"
|
||||
|
||||
byte save_x
|
||||
word compare_target
|
||||
|
||||
//
|
||||
// Points to the routine that implements the current game state.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
vector dispatch_game_state
|
||||
inputs joy2, pos, button_down, dispatch_game_state
|
||||
outputs delta, pos, screen, screen1, button_down, dispatch_game_state
|
||||
trashes a, x, y, c, z, n, v, ptr
|
||||
inputs joy2, button_down, press_fire_msg, dispatch_game_state, save_x,
|
||||
actor_pos, pos, new_pos, actor_delta, delta,
|
||||
screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4
|
||||
outputs button_down, dispatch_game_state,
|
||||
actor_pos, pos, new_pos, actor_delta, delta,
|
||||
screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4
|
||||
trashes a, x, y, c, z, n, v, ptr, save_x, compare_target
|
||||
|
||||
//
|
||||
// The constraints on these 2 vectors are kind-of sort-of big fibs.
|
||||
@ -53,15 +73,23 @@ vector dispatch_game_state
|
||||
//
|
||||
|
||||
vector cinv
|
||||
inputs joy2, pos, button_down, dispatch_game_state
|
||||
outputs delta, pos, screen, screen1, button_down, dispatch_game_state
|
||||
trashes a, x, y, c, z, n, v, ptr
|
||||
inputs joy2, button_down, press_fire_msg, dispatch_game_state, save_x,
|
||||
actor_pos, pos, new_pos, actor_delta, delta,
|
||||
screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4
|
||||
outputs button_down, dispatch_game_state,
|
||||
actor_pos, pos, new_pos, actor_delta, delta,
|
||||
screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4
|
||||
trashes a, x, y, c, z, n, v, ptr, save_x, compare_target
|
||||
@ 788
|
||||
|
||||
vector save_cinv
|
||||
inputs joy2, pos, button_down, dispatch_game_state
|
||||
outputs delta, pos, screen, screen1, button_down, dispatch_game_state
|
||||
trashes a, x, y, c, z, n, v, ptr
|
||||
inputs joy2, button_down, press_fire_msg, dispatch_game_state, save_x,
|
||||
actor_pos, pos, new_pos, actor_delta, delta,
|
||||
screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4
|
||||
outputs button_down, dispatch_game_state,
|
||||
actor_pos, pos, new_pos, actor_delta, delta,
|
||||
screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4
|
||||
trashes a, x, y, c, z, n, v, ptr, save_x, compare_target
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// Utility Routines
|
||||
@ -154,6 +182,197 @@ routine clear_screen
|
||||
} until z
|
||||
}
|
||||
|
||||
routine calculate_new_position
|
||||
inputs pos, delta
|
||||
outputs new_pos
|
||||
trashes a, c, n, z, v
|
||||
{
|
||||
copy pos, new_pos
|
||||
st off, c
|
||||
add new_pos, delta
|
||||
}
|
||||
|
||||
routine check_new_position_in_bounds
|
||||
inputs new_pos
|
||||
outputs c
|
||||
trashes compare_target, a, z, n, v
|
||||
{
|
||||
copy 1000, compare_target
|
||||
st on, c
|
||||
sub compare_target, new_pos
|
||||
|
||||
if not c {
|
||||
copy word 0, compare_target
|
||||
st on, c
|
||||
sub compare_target, new_pos
|
||||
if not c {
|
||||
st off, c
|
||||
} else {
|
||||
st on, c
|
||||
}
|
||||
} else {
|
||||
st on, c
|
||||
}
|
||||
}
|
||||
|
||||
routine init_game
|
||||
inputs actor_pos, actor_delta
|
||||
outputs actor_pos, actor_delta, pos
|
||||
trashes 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
|
||||
|
||||
st off, c
|
||||
add pos, word 7
|
||||
|
||||
inc y
|
||||
cmp y, 16
|
||||
} until z
|
||||
|
||||
ld y, 0
|
||||
copy word 0, actor_pos + y
|
||||
copy word 0, actor_delta + y
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// Actor Logics
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
//
|
||||
// Sets carry if the player perished. Carry clear otherwise.
|
||||
//
|
||||
|
||||
routine player_logic
|
||||
inputs pos, delta, joy2, screen
|
||||
outputs pos, delta, new_pos, screen, c
|
||||
trashes a, x, y, z, n, v, ptr, compare_target
|
||||
{
|
||||
call read_stick
|
||||
|
||||
call calculate_new_position
|
||||
call check_new_position_in_bounds
|
||||
|
||||
if c {
|
||||
copy ^screen, ptr
|
||||
st off, c
|
||||
add ptr, new_pos
|
||||
ld y, 0
|
||||
|
||||
// check collision.
|
||||
copy [ptr] + y, a
|
||||
// if "collision" is with your own self, treat it as if it's blank space!
|
||||
cmp a, 81
|
||||
if z {
|
||||
ld a, 32
|
||||
}
|
||||
cmp a, 32
|
||||
if z {
|
||||
copy ^screen, ptr
|
||||
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
|
||||
|
||||
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
|
||||
// at the end of the routine anyway.
|
||||
trash ptr
|
||||
trash y
|
||||
trash a
|
||||
trash v
|
||||
} else {
|
||||
st off, c
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Sets carry if the player perished. Carry clear otherwise.
|
||||
//
|
||||
|
||||
routine enemy_logic
|
||||
inputs pos, delta, screen
|
||||
outputs pos, delta, new_pos, screen, c
|
||||
trashes a, x, y, z, n, v, ptr, compare_target
|
||||
{
|
||||
call calculate_new_position
|
||||
call check_new_position_in_bounds
|
||||
|
||||
if c {
|
||||
copy ^screen, ptr
|
||||
st off, c
|
||||
add ptr, new_pos
|
||||
ld y, 0
|
||||
|
||||
// check collision.
|
||||
copy [ptr] + y, a
|
||||
// if "collision" is with your own self, treat it as if it's blank space!
|
||||
cmp a, 82
|
||||
if z {
|
||||
ld a, 32
|
||||
}
|
||||
cmp a, 32
|
||||
if z {
|
||||
copy ^screen, ptr
|
||||
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
|
||||
|
||||
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
|
||||
// at the end of the routine anyway.
|
||||
trash ptr
|
||||
trash y
|
||||
trash a
|
||||
} else {
|
||||
copy delta, compare_target
|
||||
st on, c
|
||||
sub compare_target, word 40
|
||||
if not z {
|
||||
copy word 40, delta
|
||||
} else {
|
||||
copy $ffd8, delta
|
||||
}
|
||||
trash compare_target
|
||||
}
|
||||
|
||||
st off, c
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// Game States
|
||||
// ----------------------------------------------------------------
|
||||
@ -162,49 +381,132 @@ routine clear_screen
|
||||
// Because these all `goto save_cinv` at the end, they must have the same signature as that routine.
|
||||
//
|
||||
|
||||
routine game_state_play
|
||||
inputs joy2, pos, button_down, dispatch_game_state
|
||||
outputs delta, pos, screen, screen1, button_down, dispatch_game_state
|
||||
trashes a, x, y, c, z, n, v, ptr
|
||||
{
|
||||
call read_stick
|
||||
|
||||
st off, c
|
||||
add pos, delta
|
||||
|
||||
copy ^screen, ptr
|
||||
st off, c
|
||||
add ptr, pos
|
||||
|
||||
ld y, 0
|
||||
copy 81, [ptr] + y
|
||||
|
||||
goto save_cinv
|
||||
}
|
||||
|
||||
routine game_state_title_screen
|
||||
inputs joy2, pos, button_down, dispatch_game_state
|
||||
outputs delta, pos, screen, screen1, button_down, dispatch_game_state
|
||||
trashes a, x, y, c, z, n, v, ptr
|
||||
inputs joy2, button_down, press_fire_msg, dispatch_game_state, save_x,
|
||||
actor_pos, pos, new_pos, actor_delta, delta,
|
||||
screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4
|
||||
outputs button_down, dispatch_game_state,
|
||||
actor_pos, pos, new_pos, actor_delta, delta,
|
||||
screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4
|
||||
trashes a, x, y, c, z, n, v, ptr, save_x, compare_target
|
||||
{
|
||||
ld y, 10
|
||||
ld y, 0
|
||||
repeat {
|
||||
ld a, 90
|
||||
ld a, press_fire_msg + y
|
||||
|
||||
st on, c
|
||||
sub a, 64 // yuck. oh well
|
||||
|
||||
st a, screen1 + y
|
||||
inc y
|
||||
cmp y, 20
|
||||
cmp y, 18
|
||||
} until z
|
||||
|
||||
st off, c
|
||||
call check_button
|
||||
|
||||
if c {
|
||||
// call clear_screen
|
||||
// call init_game
|
||||
copy game_state_play, dispatch_game_state
|
||||
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 {
|
||||
// This is sort of a hack. FIXME: let `if` branches diverge this much.
|
||||
copy dispatch_game_state, dispatch_game_state
|
||||
trash y
|
||||
trash c
|
||||
trash v
|
||||
}
|
||||
|
||||
goto save_cinv
|
||||
}
|
||||
|
||||
routine game_state_play
|
||||
inputs joy2, button_down, press_fire_msg, dispatch_game_state, save_x,
|
||||
actor_pos, pos, new_pos, actor_delta, delta,
|
||||
screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4
|
||||
outputs button_down, dispatch_game_state,
|
||||
actor_pos, pos, new_pos, actor_delta, delta,
|
||||
screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4
|
||||
trashes a, x, y, c, z, n, v, ptr, save_x, compare_target
|
||||
{
|
||||
ld x, 0
|
||||
repeat {
|
||||
copy actor_pos + x, pos
|
||||
copy actor_delta + x, delta
|
||||
|
||||
st x, save_x
|
||||
|
||||
// FIXME need VECTOR TABLEs to make this happen:
|
||||
// copy actor_logic, x dispatch_logic
|
||||
// call indirect_jsr_logic
|
||||
// For now, just check the actor ID to see what type it is, and go from there.
|
||||
|
||||
cmp x, 0
|
||||
if z {
|
||||
call player_logic
|
||||
} else {
|
||||
call enemy_logic
|
||||
}
|
||||
|
||||
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
|
||||
ld x, 15
|
||||
st x, save_x
|
||||
trash n
|
||||
trash z
|
||||
trash x
|
||||
} else {
|
||||
trash c
|
||||
}
|
||||
|
||||
ld x, save_x
|
||||
|
||||
copy pos, actor_pos + x
|
||||
copy delta, actor_delta + x
|
||||
|
||||
inc x
|
||||
cmp x, 16
|
||||
} until z
|
||||
|
||||
goto save_cinv
|
||||
}
|
||||
|
||||
routine game_state_game_over
|
||||
inputs joy2, button_down, press_fire_msg, dispatch_game_state, save_x,
|
||||
actor_pos, pos, new_pos, actor_delta, delta,
|
||||
screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4
|
||||
outputs button_down, dispatch_game_state,
|
||||
actor_pos, pos, new_pos, actor_delta, delta,
|
||||
screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4
|
||||
trashes a, x, y, c, z, n, v, ptr, save_x, compare_target
|
||||
{
|
||||
st off, c
|
||||
call check_button
|
||||
|
||||
if c {
|
||||
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
|
||||
@ -215,9 +517,13 @@ routine game_state_title_screen
|
||||
// *************************
|
||||
|
||||
routine our_cinv
|
||||
inputs joy2, pos, button_down, dispatch_game_state
|
||||
outputs delta, pos, screen, screen1, button_down, dispatch_game_state
|
||||
trashes a, x, y, c, z, n, v, ptr
|
||||
inputs joy2, button_down, press_fire_msg, dispatch_game_state, save_x,
|
||||
actor_pos, pos, new_pos, actor_delta, delta,
|
||||
screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4
|
||||
outputs button_down, dispatch_game_state,
|
||||
actor_pos, pos, new_pos, actor_delta, delta,
|
||||
screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4
|
||||
trashes a, x, y, c, z, n, v, ptr, save_x, compare_target
|
||||
{
|
||||
goto dispatch_game_state
|
||||
}
|
||||
@ -243,8 +549,6 @@ routine main
|
||||
copy cinv, save_cinv
|
||||
copy our_cinv, cinv
|
||||
}
|
||||
// FIXME: find out why `repeat { } forever` does not analyze OK
|
||||
repeat {
|
||||
ld a, 0
|
||||
} until not z
|
||||
|
||||
repeat { } forever
|
||||
}
|
||||
|
@ -89,17 +89,8 @@ class Context(object):
|
||||
self._writeable.add(ref)
|
||||
|
||||
def __str__(self):
|
||||
def locstr(loc):
|
||||
if isinstance(loc, LocationRef):
|
||||
return "{}:{}".format(loc.name, loc.type)
|
||||
else:
|
||||
return str(loc)
|
||||
|
||||
def locsetstr(s):
|
||||
return '{' + ', '.join([locstr(loc) for loc in list(s)]) + '}'
|
||||
|
||||
return "Context(\n _touched={},\n _meaningful={},\n _writeable={}\n)".format(
|
||||
locsetstr(self._touched), locsetstr(self._meaningful), locsetstr(self._writeable)
|
||||
LocationRef.format_set(self._touched), LocationRef.format_set(self._meaningful), LocationRef.format_set(self._writeable)
|
||||
)
|
||||
|
||||
def clone(self):
|
||||
@ -185,22 +176,12 @@ class Analyzer(object):
|
||||
)
|
||||
|
||||
def assert_affected_within(self, name, affected, limited_to):
|
||||
# We reduce the set of LocationRefs to a set of strings (their labels).
|
||||
# This is necessary because currently, two LocationRefs that refer to the
|
||||
# same location are not considered euqal. (But two LocationRefs with the
|
||||
# same label should always be the same type.)
|
||||
|
||||
affected = set([loc.name for loc in affected])
|
||||
limited_to = set([loc.name for loc in limited_to])
|
||||
|
||||
def loc_list(label_set):
|
||||
return ', '.join(sorted(label_set))
|
||||
|
||||
overage = affected - limited_to
|
||||
if not overage:
|
||||
return
|
||||
message = 'in %s: %s are {%s} but affects {%s} which exceeds by: {%s} ' % (
|
||||
self.current_routine.name, name, loc_list(limited_to), loc_list(affected), loc_list(overage)
|
||||
message = 'in %s: %s are %s but affects %s which exceeds it by: %s ' % (
|
||||
self.current_routine.name, name,
|
||||
LocationRef.format_set(limited_to), LocationRef.format_set(affected), LocationRef.format_set(overage)
|
||||
)
|
||||
raise IncompatibleConstraintsError(message)
|
||||
|
||||
@ -292,9 +273,15 @@ class Analyzer(object):
|
||||
else:
|
||||
self.assert_type(TYPE_WORD, dest)
|
||||
elif opcode == 'sub':
|
||||
self.assert_type(TYPE_BYTE, src, dest)
|
||||
context.assert_meaningful(src, dest, FLAG_C)
|
||||
context.set_written(dest, FLAG_Z, FLAG_N, FLAG_C, FLAG_V)
|
||||
if src.type == TYPE_BYTE:
|
||||
self.assert_type(TYPE_BYTE, src, dest)
|
||||
context.set_written(dest, FLAG_Z, FLAG_N, FLAG_C, FLAG_V)
|
||||
else:
|
||||
self.assert_type(TYPE_WORD, src, dest)
|
||||
context.set_written(dest, FLAG_Z, FLAG_N, FLAG_C, FLAG_V)
|
||||
context.set_touched(REG_A)
|
||||
context.set_unmeaningful(REG_A)
|
||||
elif opcode in ('inc', 'dec'):
|
||||
self.assert_type(TYPE_BYTE, dest)
|
||||
context.assert_meaningful(dest)
|
||||
@ -332,23 +319,27 @@ class Analyzer(object):
|
||||
# doesn't really matter if you modified it or not, coming out.
|
||||
for ref in context1.each_meaningful():
|
||||
context2.assert_meaningful(
|
||||
ref, exception_class=InconsistentInitializationError, message='initialized in block 1 but not in block 2'
|
||||
ref, exception_class=InconsistentInitializationError,
|
||||
message='initialized in block 1 but not in block 2 of `if {}`'.format(src)
|
||||
)
|
||||
for ref in context2.each_meaningful():
|
||||
context1.assert_meaningful(
|
||||
ref, exception_class=InconsistentInitializationError, message='initialized in block 2 but not in block 1'
|
||||
ref, exception_class=InconsistentInitializationError,
|
||||
message='initialized in block 2 but not in block 1 of `if {}`'.format(src)
|
||||
)
|
||||
context.set_from(context1)
|
||||
elif opcode == 'repeat':
|
||||
# it will always be executed at least once, so analyze it having
|
||||
# been executed the first time.
|
||||
self.analyze_block(instr.block, context)
|
||||
context.assert_meaningful(src)
|
||||
if src is not None: # None indicates 'repeat forever'
|
||||
context.assert_meaningful(src)
|
||||
|
||||
# now analyze it having been executed a second time, with the context
|
||||
# of it having already been executed.
|
||||
self.analyze_block(instr.block, context)
|
||||
context.assert_meaningful(src)
|
||||
if src is not None:
|
||||
context.assert_meaningful(src)
|
||||
|
||||
elif opcode == 'copy':
|
||||
# 1. check that their types are compatible
|
||||
@ -369,7 +360,7 @@ class Analyzer(object):
|
||||
else:
|
||||
raise TypeMismatchError((src, dest))
|
||||
|
||||
elif isinstance(src, LocationRef) and isinstance(dest, IndexedRef):
|
||||
elif isinstance(src, (LocationRef, ConstantRef)) and isinstance(dest, IndexedRef):
|
||||
if src.type == TYPE_WORD and dest.ref.type == TYPE_WORD_TABLE:
|
||||
pass
|
||||
else:
|
||||
@ -403,11 +394,15 @@ class Analyzer(object):
|
||||
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_touched(src.ref) # TODO and REG_Y? if not, why not?
|
||||
context.set_touched(dest)
|
||||
context.set_written(dest)
|
||||
elif isinstance(src, LocationRef) and isinstance(dest, IndexedRef):
|
||||
context.assert_meaningful(src, dest.ref, dest.index)
|
||||
context.set_touched(src) # TODO and dest.index?
|
||||
context.set_written(dest.ref)
|
||||
elif isinstance(src, ConstantRef) and isinstance(dest, IndexedRef):
|
||||
context.assert_meaningful(src, dest.ref, dest.index)
|
||||
context.set_written(dest.ref)
|
||||
elif isinstance(src, IndexedRef) and isinstance(dest, LocationRef):
|
||||
context.assert_meaningful(src.ref, src.index, dest)
|
||||
context.set_touched(dest) # TODO and src.index?
|
||||
@ -417,7 +412,15 @@ class Analyzer(object):
|
||||
context.set_written(dest)
|
||||
|
||||
context.set_touched(REG_A, FLAG_Z, FLAG_N)
|
||||
context.set_unmeaningful(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)
|
||||
|
||||
elif opcode == 'with-sei':
|
||||
self.analyze_block(instr.block, context)
|
||||
@ -439,5 +442,7 @@ class Analyzer(object):
|
||||
self.assert_affected_within('trashes', type_.trashes, current_type.trashes)
|
||||
|
||||
self.has_encountered_goto = True
|
||||
elif opcode == 'trash':
|
||||
context.set_unmeaningful(instr.dest)
|
||||
else:
|
||||
raise NotImplementedError(opcode)
|
||||
|
@ -6,7 +6,7 @@ from sixtypical.model import (
|
||||
TYPE_BIT, TYPE_BYTE, TYPE_BYTE_TABLE, TYPE_WORD, TYPE_WORD_TABLE, BufferType, PointerType, RoutineType, VectorType,
|
||||
REG_A, REG_X, REG_Y, FLAG_C
|
||||
)
|
||||
from sixtypical.emitter import Byte, Label, Offset, LowAddressByte, HighAddressByte
|
||||
from sixtypical.emitter import Byte, Word, Table, Label, Offset, LowAddressByte, HighAddressByte
|
||||
from sixtypical.gen6502 import (
|
||||
Immediate, Absolute, AbsoluteX, AbsoluteY, ZeroPage, Indirect, IndirectY, Relative,
|
||||
LDA, LDX, LDY, STA, STX, STY,
|
||||
@ -31,6 +31,18 @@ class Compiler(object):
|
||||
self.labels = {}
|
||||
self.trampolines = {} # Location -> Label
|
||||
|
||||
# helper methods
|
||||
|
||||
def addressing_mode_for_index(self, index):
|
||||
if index == REG_X:
|
||||
return AbsoluteX
|
||||
elif index == REG_Y:
|
||||
return AbsoluteY
|
||||
else:
|
||||
raise NotImplementedError(index)
|
||||
|
||||
# visitor methods
|
||||
|
||||
def compile_program(self, program):
|
||||
assert isinstance(program, Program)
|
||||
|
||||
@ -72,7 +84,16 @@ class Compiler(object):
|
||||
for defn in program.defns:
|
||||
if defn.initial is not None:
|
||||
label = self.labels[defn.name]
|
||||
initial_data = Byte(defn.initial) # TODO: support other types than Byte
|
||||
initial_data = None
|
||||
type_ = defn.location.type
|
||||
if type_ == TYPE_BYTE:
|
||||
initial_data = Byte(defn.initial)
|
||||
elif type_ == TYPE_WORD:
|
||||
initial_data = Word(defn.initial)
|
||||
elif type_ == TYPE_BYTE_TABLE:
|
||||
initial_data = Table(defn.initial)
|
||||
else:
|
||||
raise NotImplementedError(type_)
|
||||
label.set_length(initial_data.size())
|
||||
self.emitter.resolve_label(label)
|
||||
self.emitter.emit(initial_data)
|
||||
@ -209,6 +230,26 @@ class Compiler(object):
|
||||
self.emitter.emit(SBC(Immediate(Byte(src.value))))
|
||||
else:
|
||||
self.emitter.emit(SBC(Absolute(self.labels[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]
|
||||
self.emitter.emit(LDA(Absolute(dest_label)))
|
||||
self.emitter.emit(SBC(Immediate(Byte(src.low_byte()))))
|
||||
self.emitter.emit(STA(Absolute(dest_label)))
|
||||
self.emitter.emit(LDA(Absolute(Offset(dest_label, 1))))
|
||||
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]
|
||||
self.emitter.emit(LDA(Absolute(dest_label)))
|
||||
self.emitter.emit(SBC(Absolute(src_label)))
|
||||
self.emitter.emit(STA(Absolute(dest_label)))
|
||||
self.emitter.emit(LDA(Absolute(Offset(dest_label, 1))))
|
||||
self.emitter.emit(SBC(Absolute(Offset(src_label, 1))))
|
||||
self.emitter.emit(STA(Absolute(Offset(dest_label, 1))))
|
||||
else:
|
||||
raise UnsupportedOpcodeError(instr)
|
||||
else:
|
||||
raise UnsupportedOpcodeError(instr)
|
||||
elif opcode == 'inc':
|
||||
@ -307,7 +348,7 @@ class Compiler(object):
|
||||
elif opcode == 'repeat':
|
||||
top_label = self.emitter.make_label()
|
||||
self.compile_block(instr.block)
|
||||
if src is None:
|
||||
if src is None: # indicates 'repeat forever'
|
||||
self.emitter.emit(JMP(Absolute(top_label)))
|
||||
else:
|
||||
cls = {
|
||||
@ -344,7 +385,10 @@ class Compiler(object):
|
||||
else:
|
||||
raise NotImplementedError((src, dest))
|
||||
elif isinstance(src, IndirectRef) and isinstance(dest, LocationRef):
|
||||
if dest.type == TYPE_BYTE and isinstance(src.ref.type, PointerType):
|
||||
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]
|
||||
self.emitter.emit(LDA(IndirectY(src_label)))
|
||||
@ -363,33 +407,28 @@ class Compiler(object):
|
||||
if src.type == TYPE_WORD and dest.ref.type == TYPE_WORD_TABLE:
|
||||
src_label = self.labels[src.name]
|
||||
dest_label = self.labels[dest.ref.name]
|
||||
addressing_mode = None
|
||||
if dest.index == REG_X:
|
||||
addressing_mode = AbsoluteX
|
||||
elif dest.index == REG_Y:
|
||||
addressing_mode = AbsoluteY
|
||||
else:
|
||||
raise NotImplementedError(dest)
|
||||
self.emitter.emit(LDA(Absolute(src_label)))
|
||||
self.emitter.emit(STA(addressing_mode(dest_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(addressing_mode(Offset(dest_label, 256))))
|
||||
self.emitter.emit(STA(self.addressing_mode_for_index(dest.index)(Offset(dest_label, 256))))
|
||||
else:
|
||||
raise NotImplementedError
|
||||
elif isinstance(src, ConstantRef) and isinstance(dest, IndexedRef):
|
||||
if src.type == TYPE_WORD and dest.ref.type == TYPE_WORD_TABLE:
|
||||
dest_label = self.labels[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()))))
|
||||
self.emitter.emit(STA(self.addressing_mode_for_index(dest.index)(Offset(dest_label, 256))))
|
||||
else:
|
||||
raise NotImplementedError
|
||||
elif isinstance(src, IndexedRef) and isinstance(dest, LocationRef):
|
||||
if src.ref.type == TYPE_WORD_TABLE and dest.type == TYPE_WORD:
|
||||
src_label = self.labels[src.ref.name]
|
||||
dest_label = self.labels[dest.name]
|
||||
addressing_mode = None
|
||||
if src.index == REG_X:
|
||||
addressing_mode = AbsoluteX
|
||||
elif src.index == REG_Y:
|
||||
addressing_mode = AbsoluteY
|
||||
else:
|
||||
raise NotImplementedError(src)
|
||||
self.emitter.emit(LDA(addressing_mode(src_label)))
|
||||
self.emitter.emit(LDA(self.addressing_mode_for_index(src.index)(src_label)))
|
||||
self.emitter.emit(STA(Absolute(dest_label)))
|
||||
self.emitter.emit(LDA(addressing_mode(Offset(src_label, 256))))
|
||||
self.emitter.emit(LDA(self.addressing_mode_for_index(src.index)(Offset(src_label, 256))))
|
||||
self.emitter.emit(STA(Absolute(Offset(dest_label, 1))))
|
||||
else:
|
||||
raise NotImplementedError
|
||||
@ -434,5 +473,7 @@ class Compiler(object):
|
||||
self.emitter.emit(STA(Absolute(Offset(dest_label, 1))))
|
||||
else:
|
||||
raise NotImplementedError(src.type)
|
||||
elif opcode == 'trash':
|
||||
pass
|
||||
else:
|
||||
raise NotImplementedError(opcode)
|
||||
|
@ -48,11 +48,20 @@ class Word(Emittable):
|
||||
|
||||
|
||||
class Table(Emittable):
|
||||
def __init__(self, value):
|
||||
# TODO: range-checking
|
||||
self.value = value
|
||||
|
||||
def size(self):
|
||||
return 256
|
||||
|
||||
def serialize(self, addr=None):
|
||||
return chr(0) * self.size()
|
||||
bytes = []
|
||||
for b in self.value:
|
||||
bytes.append(chr(ord(b)))
|
||||
while len(bytes) < self.size():
|
||||
bytes.append(chr(0))
|
||||
return ''.join(bytes)
|
||||
|
||||
def __repr__(self):
|
||||
return "%s()" % (self.__class__.__name__)
|
||||
|
@ -8,6 +8,9 @@ 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
|
||||
|
||||
@ -86,11 +89,12 @@ class LocationRef(Ref):
|
||||
def __eq__(self, other):
|
||||
# Ordinarily there will only be one ref with a given name,
|
||||
# but because we store the type in here and we want to treat
|
||||
# these objects as immutable, we compare the types, too.
|
||||
# Not sure if very wise.
|
||||
return isinstance(other, self.__class__) and (
|
||||
other.name == self.name and other.type == self.type
|
||||
)
|
||||
# these objects as immutable, we compare the types, too,
|
||||
# just to be sure.
|
||||
equal = isinstance(other, self.__class__) and other.name == self.name
|
||||
if equal:
|
||||
assert other.type == self.type
|
||||
return equal
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.name + str(self.type))
|
||||
@ -98,9 +102,23 @@ class LocationRef(Ref):
|
||||
def __repr__(self):
|
||||
return '%s(%r, %r)' % (self.__class__.__name__, self.type, self.name)
|
||||
|
||||
def __str__(self):
|
||||
return "{}:{}".format(self.name, self.type)
|
||||
|
||||
def is_constant(self):
|
||||
return isinstance(self.type, RoutineType)
|
||||
|
||||
def backpatch_vector_labels(self, resolver):
|
||||
if isinstance(self.type, ExecutableType):
|
||||
t = self.type
|
||||
t.inputs = set([resolver(w) for w in t.inputs])
|
||||
t.outputs = set([resolver(w) for w in t.outputs])
|
||||
t.trashes = set([resolver(w) for w in t.trashes])
|
||||
|
||||
@classmethod
|
||||
def format_set(cls, location_refs):
|
||||
return '{%s}' % ', '.join([str(loc) for loc in sorted(location_refs)])
|
||||
|
||||
|
||||
class IndirectRef(Ref):
|
||||
def __init__(self, ref):
|
||||
|
@ -23,6 +23,7 @@ class Parser(object):
|
||||
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 lookup(self, name):
|
||||
if name not in self.symbols:
|
||||
@ -49,19 +50,28 @@ class Parser(object):
|
||||
self.symbols[name] = SymEntry(routine, routine.location)
|
||||
routines.append(routine)
|
||||
self.scanner.check_type('EOF')
|
||||
|
||||
# now backpatch the executable types.
|
||||
for defn in defns:
|
||||
if isinstance(defn.location.type, VectorType):
|
||||
t = defn.location.type
|
||||
t.inputs = set([self.lookup(w) for w in t.inputs])
|
||||
t.outputs = set([self.lookup(w) for w in t.outputs])
|
||||
t.trashes = set([self.lookup(w) for w in t.trashes])
|
||||
defn.location.backpatch_vector_labels(lambda w: self.lookup(w))
|
||||
for routine in routines:
|
||||
if isinstance(routine.location.type, ExecutableType):
|
||||
t = routine.location.type
|
||||
t.inputs = set([self.lookup(w) for w in t.inputs])
|
||||
t.outputs = set([self.lookup(w) for w in t.outputs])
|
||||
t.trashes = set([self.lookup(w) for w in t.trashes])
|
||||
routine.location.backpatch_vector_labels(lambda w: self.lookup(w))
|
||||
for instr in self.backpatch_instrs:
|
||||
if instr.opcode in ('call', 'goto'):
|
||||
name = instr.location
|
||||
if name not in self.symbols:
|
||||
raise SyntaxError('Undefined routine "%s"' % name)
|
||||
if not isinstance(self.symbols[name].model.type, ExecutableType):
|
||||
raise SyntaxError('Illegal call of non-executable "%s"' % name)
|
||||
instr.location = self.symbols[name].model
|
||||
if instr.opcode in ('copy',) and isinstance(instr.src, basestring):
|
||||
name = instr.src
|
||||
if name not in self.symbols:
|
||||
raise SyntaxError('Undefined routine "%s"' % name)
|
||||
if not isinstance(self.symbols[name].model.type, ExecutableType):
|
||||
raise SyntaxError('Illegal copy of non-executable "%s"' % name)
|
||||
instr.src = self.symbols[name].model
|
||||
|
||||
return Program(defns=defns, routines=routines)
|
||||
|
||||
def defn(self):
|
||||
@ -79,8 +89,11 @@ class Parser(object):
|
||||
|
||||
initial = None
|
||||
if self.scanner.consume(':'):
|
||||
self.scanner.check_type('integer literal')
|
||||
initial = int(self.scanner.token)
|
||||
if type_ == TYPE_BYTE_TABLE and self.scanner.on_type('string literal'):
|
||||
initial = self.scanner.token
|
||||
else:
|
||||
self.scanner.check_type('integer literal')
|
||||
initial = int(self.scanner.token)
|
||||
self.scanner.scan()
|
||||
|
||||
addr = None
|
||||
@ -194,7 +207,9 @@ class Parser(object):
|
||||
return loc
|
||||
|
||||
def indlocexpr(self):
|
||||
if self.scanner.consume('['):
|
||||
if self.scanner.consume('forward'):
|
||||
return self.label()
|
||||
elif self.scanner.consume('['):
|
||||
loc = self.locexpr()
|
||||
self.scanner.expect(']')
|
||||
self.scanner.expect('+')
|
||||
@ -273,22 +288,25 @@ class Parser(object):
|
||||
self.scanner.scan()
|
||||
name = self.scanner.token
|
||||
self.scanner.scan()
|
||||
if name not in self.symbols:
|
||||
raise SyntaxError('Undefined routine "%s"' % name)
|
||||
if not isinstance(self.symbols[name].model.type, ExecutableType):
|
||||
raise SyntaxError('Illegal call of non-executable "%s"' % name)
|
||||
return Instr(opcode=opcode, location=self.symbols[name].model, dest=None, src=None)
|
||||
instr = Instr(opcode=opcode, location=name, dest=None, src=None)
|
||||
self.backpatch_instrs.append(instr)
|
||||
return instr
|
||||
elif self.scanner.token in ("copy",):
|
||||
opcode = self.scanner.token
|
||||
self.scanner.scan()
|
||||
src = self.indlocexpr()
|
||||
self.scanner.expect(',')
|
||||
dest = self.indlocexpr()
|
||||
return Instr(opcode=opcode, dest=dest, src=src)
|
||||
instr = Instr(opcode=opcode, dest=dest, src=src)
|
||||
self.backpatch_instrs.append(instr)
|
||||
return instr
|
||||
elif self.scanner.consume("with"):
|
||||
self.scanner.expect("interrupts")
|
||||
self.scanner.expect("off")
|
||||
block = self.block()
|
||||
return Instr(opcode='with-sei', dest=None, src=None, block=block)
|
||||
elif self.scanner.consume("trash"):
|
||||
dest = self.locexpr()
|
||||
return Instr(opcode='trash', src=None, dest=dest)
|
||||
else:
|
||||
raise ValueError('bad opcode "%s"' % self.scanner.token)
|
||||
|
@ -89,6 +89,24 @@ If a routine modifies a location, it needs to either output it or trash it.
|
||||
| }
|
||||
= ok
|
||||
|
||||
If a routine reads or writes a user-define memory location, it needs to declare that too.
|
||||
|
||||
| byte b1 @ 60000
|
||||
| byte b2 : 3
|
||||
| word w1 @ 60001
|
||||
| word w2 : 2000
|
||||
|
|
||||
| routine main
|
||||
| inputs b1, w1
|
||||
| outputs b2, w2
|
||||
| trashes a, z, n
|
||||
| {
|
||||
| ld a, b1
|
||||
| st a, b2
|
||||
| copy w1, w2
|
||||
| }
|
||||
= ok
|
||||
|
||||
### ld ###
|
||||
|
||||
Can't `ld` from a memory location that isn't initialized.
|
||||
@ -370,6 +388,20 @@ Copying to and from a word table.
|
||||
| }
|
||||
? TypeMismatchError
|
||||
|
||||
You can also copy a literal word to a word table.
|
||||
|
||||
| word table many
|
||||
|
|
||||
| routine main
|
||||
| inputs many
|
||||
| outputs many
|
||||
| trashes a, x, n, z
|
||||
| {
|
||||
| ld x, 0
|
||||
| copy 9999, many + x
|
||||
| }
|
||||
= ok
|
||||
|
||||
### add ###
|
||||
|
||||
Can't `add` from or to a memory location that isn't initialized.
|
||||
@ -561,6 +593,60 @@ Can't `sub` to a memory location that isn't writeable.
|
||||
| }
|
||||
? ForbiddenWriteError: a in main
|
||||
|
||||
You can `sub` a word constant from a word memory location.
|
||||
|
||||
| word score
|
||||
| routine main
|
||||
| inputs a, score
|
||||
| outputs score
|
||||
| trashes a, c, z, v, n
|
||||
| {
|
||||
| st on, c
|
||||
| sub score, 1999
|
||||
| }
|
||||
= ok
|
||||
|
||||
`sub`ing a word constant from a word memory location trashes `a`.
|
||||
|
||||
| word score
|
||||
| routine main
|
||||
| inputs a, score
|
||||
| outputs score, a
|
||||
| trashes c, z, v, n
|
||||
| {
|
||||
| st on, c
|
||||
| sub score, 1999
|
||||
| }
|
||||
? UnmeaningfulOutputError: a in main
|
||||
|
||||
You can `sub` a word memory location from another word memory location.
|
||||
|
||||
| word score
|
||||
| word delta
|
||||
| routine main
|
||||
| inputs score, delta
|
||||
| outputs score
|
||||
| trashes a, c, z, v, n
|
||||
| {
|
||||
| st off, c
|
||||
| sub score, delta
|
||||
| }
|
||||
= ok
|
||||
|
||||
`sub`ing a word memory location from a word memory location trashes `a`.
|
||||
|
||||
| word score
|
||||
| word delta
|
||||
| routine main
|
||||
| inputs score, delta
|
||||
| outputs score
|
||||
| trashes c, z, v, n
|
||||
| {
|
||||
| st off, c
|
||||
| sub score, delta
|
||||
| }
|
||||
? ForbiddenWriteError: a in main
|
||||
|
||||
### inc ###
|
||||
|
||||
Location must be initialized and writeable.
|
||||
@ -1003,6 +1089,43 @@ same constraints.
|
||||
| }
|
||||
? UnmeaningfulReadError: a in main
|
||||
|
||||
### trash ###
|
||||
|
||||
Trash does nothing except indicate that we do not care about the value anymore.
|
||||
|
||||
| routine foo
|
||||
| inputs a
|
||||
| outputs x
|
||||
| trashes a, z, n
|
||||
| {
|
||||
| st a, x
|
||||
| ld a, 0
|
||||
| trash a
|
||||
| }
|
||||
= ok
|
||||
|
||||
| routine foo
|
||||
| inputs a
|
||||
| outputs a, x
|
||||
| trashes z, n
|
||||
| {
|
||||
| st a, x
|
||||
| ld a, 0
|
||||
| trash a
|
||||
| }
|
||||
? UnmeaningfulOutputError: a in foo
|
||||
|
||||
| routine foo
|
||||
| inputs a
|
||||
| outputs x
|
||||
| trashes a, z, n
|
||||
| {
|
||||
| st a, x
|
||||
| trash a
|
||||
| st a, x
|
||||
| }
|
||||
? UnmeaningfulReadError: a in foo
|
||||
|
||||
### if ###
|
||||
|
||||
Both blocks of an `if` are analyzed.
|
||||
@ -1065,6 +1188,26 @@ If a location is initialized in one block, is must be initialized in the other a
|
||||
| }
|
||||
? InconsistentInitializationError: x
|
||||
|
||||
However, this only pertains to initialization. If a value is already
|
||||
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
|
||||
| inputs x
|
||||
| outputs x
|
||||
| trashes a, z, n, c
|
||||
| {
|
||||
| ld a, 0
|
||||
| cmp a, 42
|
||||
| if z {
|
||||
| ld x, 7
|
||||
| } else {
|
||||
| ld a, 23
|
||||
| }
|
||||
| }
|
||||
= ok
|
||||
|
||||
An `if` with a single block is analyzed as if it had an empty `else` block.
|
||||
|
||||
| routine foo
|
||||
@ -1175,6 +1318,15 @@ this is an error too.
|
||||
| }
|
||||
? UnmeaningfulReadError: z in main
|
||||
|
||||
The body of `repeat forever` can be empty.
|
||||
|
||||
| routine main
|
||||
| {
|
||||
| repeat {
|
||||
| } forever
|
||||
| }
|
||||
= ok
|
||||
|
||||
### copy ###
|
||||
|
||||
Can't `copy` from a memory location that isn't initialized.
|
||||
@ -1234,7 +1386,7 @@ a, z, and n are trashed, and must be declared as such
|
||||
| {
|
||||
| copy 0, lives
|
||||
| }
|
||||
? ForbiddenWriteError: a in main
|
||||
? ForbiddenWriteError: n in main
|
||||
|
||||
a, z, and n are trashed, and must not be declared as outputs.
|
||||
|
||||
@ -1244,7 +1396,7 @@ a, z, and n are trashed, and must not be declared as outputs.
|
||||
| {
|
||||
| copy 0, lives
|
||||
| }
|
||||
? UnmeaningfulOutputError: a in main
|
||||
? UnmeaningfulOutputError: n in main
|
||||
|
||||
Unless of course you subsequently initialize them.
|
||||
|
||||
|
@ -117,6 +117,296 @@ Memory location with initial value.
|
||||
= $0810 RTS
|
||||
= $0811 .byte $03
|
||||
|
||||
Word memory locations with explicit address, initial value.
|
||||
|
||||
| word w1 @ 60001
|
||||
| word w2 : 3003
|
||||
|
|
||||
| routine main
|
||||
| inputs w1
|
||||
| outputs w2
|
||||
| trashes a, z, n
|
||||
| {
|
||||
| copy w1, w2
|
||||
| }
|
||||
= $080D LDA $EA61
|
||||
= $0810 STA $081A
|
||||
= $0813 LDA $EA62
|
||||
= $0816 STA $081B
|
||||
= $0819 RTS
|
||||
= $081A .byte $BB
|
||||
= $081B .byte $0B
|
||||
|
||||
Initialized byte table.
|
||||
|
||||
| byte table message : "WHAT?"
|
||||
|
|
||||
| routine main
|
||||
| inputs message
|
||||
| outputs x, a, z, n
|
||||
| {
|
||||
| ld x, 0
|
||||
| ld a, message + x
|
||||
| }
|
||||
= $080D LDX #$00
|
||||
= $080F LDA $0813,X
|
||||
= $0812 RTS
|
||||
= $0813 .byte $57
|
||||
= $0814 PHA
|
||||
= $0815 EOR ($54,X)
|
||||
= $0817 .byte $3F
|
||||
= $0818 BRK
|
||||
= $0819 BRK
|
||||
= $081A BRK
|
||||
= $081B BRK
|
||||
= $081C BRK
|
||||
= $081D BRK
|
||||
= $081E BRK
|
||||
= $081F BRK
|
||||
= $0820 BRK
|
||||
= $0821 BRK
|
||||
= $0822 BRK
|
||||
= $0823 BRK
|
||||
= $0824 BRK
|
||||
= $0825 BRK
|
||||
= $0826 BRK
|
||||
= $0827 BRK
|
||||
= $0828 BRK
|
||||
= $0829 BRK
|
||||
= $082A BRK
|
||||
= $082B BRK
|
||||
= $082C BRK
|
||||
= $082D BRK
|
||||
= $082E BRK
|
||||
= $082F BRK
|
||||
= $0830 BRK
|
||||
= $0831 BRK
|
||||
= $0832 BRK
|
||||
= $0833 BRK
|
||||
= $0834 BRK
|
||||
= $0835 BRK
|
||||
= $0836 BRK
|
||||
= $0837 BRK
|
||||
= $0838 BRK
|
||||
= $0839 BRK
|
||||
= $083A BRK
|
||||
= $083B BRK
|
||||
= $083C BRK
|
||||
= $083D BRK
|
||||
= $083E BRK
|
||||
= $083F BRK
|
||||
= $0840 BRK
|
||||
= $0841 BRK
|
||||
= $0842 BRK
|
||||
= $0843 BRK
|
||||
= $0844 BRK
|
||||
= $0845 BRK
|
||||
= $0846 BRK
|
||||
= $0847 BRK
|
||||
= $0848 BRK
|
||||
= $0849 BRK
|
||||
= $084A BRK
|
||||
= $084B BRK
|
||||
= $084C BRK
|
||||
= $084D BRK
|
||||
= $084E BRK
|
||||
= $084F BRK
|
||||
= $0850 BRK
|
||||
= $0851 BRK
|
||||
= $0852 BRK
|
||||
= $0853 BRK
|
||||
= $0854 BRK
|
||||
= $0855 BRK
|
||||
= $0856 BRK
|
||||
= $0857 BRK
|
||||
= $0858 BRK
|
||||
= $0859 BRK
|
||||
= $085A BRK
|
||||
= $085B BRK
|
||||
= $085C BRK
|
||||
= $085D BRK
|
||||
= $085E BRK
|
||||
= $085F BRK
|
||||
= $0860 BRK
|
||||
= $0861 BRK
|
||||
= $0862 BRK
|
||||
= $0863 BRK
|
||||
= $0864 BRK
|
||||
= $0865 BRK
|
||||
= $0866 BRK
|
||||
= $0867 BRK
|
||||
= $0868 BRK
|
||||
= $0869 BRK
|
||||
= $086A BRK
|
||||
= $086B BRK
|
||||
= $086C BRK
|
||||
= $086D BRK
|
||||
= $086E BRK
|
||||
= $086F BRK
|
||||
= $0870 BRK
|
||||
= $0871 BRK
|
||||
= $0872 BRK
|
||||
= $0873 BRK
|
||||
= $0874 BRK
|
||||
= $0875 BRK
|
||||
= $0876 BRK
|
||||
= $0877 BRK
|
||||
= $0878 BRK
|
||||
= $0879 BRK
|
||||
= $087A BRK
|
||||
= $087B BRK
|
||||
= $087C BRK
|
||||
= $087D BRK
|
||||
= $087E BRK
|
||||
= $087F BRK
|
||||
= $0880 BRK
|
||||
= $0881 BRK
|
||||
= $0882 BRK
|
||||
= $0883 BRK
|
||||
= $0884 BRK
|
||||
= $0885 BRK
|
||||
= $0886 BRK
|
||||
= $0887 BRK
|
||||
= $0888 BRK
|
||||
= $0889 BRK
|
||||
= $088A BRK
|
||||
= $088B BRK
|
||||
= $088C BRK
|
||||
= $088D BRK
|
||||
= $088E BRK
|
||||
= $088F BRK
|
||||
= $0890 BRK
|
||||
= $0891 BRK
|
||||
= $0892 BRK
|
||||
= $0893 BRK
|
||||
= $0894 BRK
|
||||
= $0895 BRK
|
||||
= $0896 BRK
|
||||
= $0897 BRK
|
||||
= $0898 BRK
|
||||
= $0899 BRK
|
||||
= $089A BRK
|
||||
= $089B BRK
|
||||
= $089C BRK
|
||||
= $089D BRK
|
||||
= $089E BRK
|
||||
= $089F BRK
|
||||
= $08A0 BRK
|
||||
= $08A1 BRK
|
||||
= $08A2 BRK
|
||||
= $08A3 BRK
|
||||
= $08A4 BRK
|
||||
= $08A5 BRK
|
||||
= $08A6 BRK
|
||||
= $08A7 BRK
|
||||
= $08A8 BRK
|
||||
= $08A9 BRK
|
||||
= $08AA BRK
|
||||
= $08AB BRK
|
||||
= $08AC BRK
|
||||
= $08AD BRK
|
||||
= $08AE BRK
|
||||
= $08AF BRK
|
||||
= $08B0 BRK
|
||||
= $08B1 BRK
|
||||
= $08B2 BRK
|
||||
= $08B3 BRK
|
||||
= $08B4 BRK
|
||||
= $08B5 BRK
|
||||
= $08B6 BRK
|
||||
= $08B7 BRK
|
||||
= $08B8 BRK
|
||||
= $08B9 BRK
|
||||
= $08BA BRK
|
||||
= $08BB BRK
|
||||
= $08BC BRK
|
||||
= $08BD BRK
|
||||
= $08BE BRK
|
||||
= $08BF BRK
|
||||
= $08C0 BRK
|
||||
= $08C1 BRK
|
||||
= $08C2 BRK
|
||||
= $08C3 BRK
|
||||
= $08C4 BRK
|
||||
= $08C5 BRK
|
||||
= $08C6 BRK
|
||||
= $08C7 BRK
|
||||
= $08C8 BRK
|
||||
= $08C9 BRK
|
||||
= $08CA BRK
|
||||
= $08CB BRK
|
||||
= $08CC BRK
|
||||
= $08CD BRK
|
||||
= $08CE BRK
|
||||
= $08CF BRK
|
||||
= $08D0 BRK
|
||||
= $08D1 BRK
|
||||
= $08D2 BRK
|
||||
= $08D3 BRK
|
||||
= $08D4 BRK
|
||||
= $08D5 BRK
|
||||
= $08D6 BRK
|
||||
= $08D7 BRK
|
||||
= $08D8 BRK
|
||||
= $08D9 BRK
|
||||
= $08DA BRK
|
||||
= $08DB BRK
|
||||
= $08DC BRK
|
||||
= $08DD BRK
|
||||
= $08DE BRK
|
||||
= $08DF BRK
|
||||
= $08E0 BRK
|
||||
= $08E1 BRK
|
||||
= $08E2 BRK
|
||||
= $08E3 BRK
|
||||
= $08E4 BRK
|
||||
= $08E5 BRK
|
||||
= $08E6 BRK
|
||||
= $08E7 BRK
|
||||
= $08E8 BRK
|
||||
= $08E9 BRK
|
||||
= $08EA BRK
|
||||
= $08EB BRK
|
||||
= $08EC BRK
|
||||
= $08ED BRK
|
||||
= $08EE BRK
|
||||
= $08EF BRK
|
||||
= $08F0 BRK
|
||||
= $08F1 BRK
|
||||
= $08F2 BRK
|
||||
= $08F3 BRK
|
||||
= $08F4 BRK
|
||||
= $08F5 BRK
|
||||
= $08F6 BRK
|
||||
= $08F7 BRK
|
||||
= $08F8 BRK
|
||||
= $08F9 BRK
|
||||
= $08FA BRK
|
||||
= $08FB BRK
|
||||
= $08FC BRK
|
||||
= $08FD BRK
|
||||
= $08FE BRK
|
||||
= $08FF BRK
|
||||
= $0900 BRK
|
||||
= $0901 BRK
|
||||
= $0902 BRK
|
||||
= $0903 BRK
|
||||
= $0904 BRK
|
||||
= $0905 BRK
|
||||
= $0906 BRK
|
||||
= $0907 BRK
|
||||
= $0908 BRK
|
||||
= $0909 BRK
|
||||
= $090A BRK
|
||||
= $090B BRK
|
||||
= $090C BRK
|
||||
= $090D BRK
|
||||
= $090E BRK
|
||||
= $090F BRK
|
||||
= $0910 BRK
|
||||
= $0911 BRK
|
||||
= $0912 BRK
|
||||
|
||||
Some instructions.
|
||||
|
||||
| byte foo
|
||||
@ -297,6 +587,16 @@ Compiling `repeat forever`.
|
||||
= $0810 JMP $080F
|
||||
= $0813 RTS
|
||||
|
||||
The body of `repeat forever` can be empty.
|
||||
|
||||
| routine main
|
||||
| {
|
||||
| repeat {
|
||||
| } forever
|
||||
| }
|
||||
= $080D JMP $080D
|
||||
= $0810 RTS
|
||||
|
||||
Indexed access.
|
||||
|
||||
| byte one
|
||||
@ -404,6 +704,25 @@ Copy literal word to word.
|
||||
= $0814 STA $0819
|
||||
= $0817 RTS
|
||||
|
||||
You can also copy a literal word to a word table.
|
||||
|
||||
| word table many
|
||||
|
|
||||
| routine main
|
||||
| inputs many
|
||||
| outputs many
|
||||
| trashes a, x, n, z
|
||||
| {
|
||||
| ld x, 0
|
||||
| copy 9999, many + x
|
||||
| }
|
||||
= $080D LDX #$00
|
||||
= $080F LDA #$0F
|
||||
= $0811 STA $081A,X
|
||||
= $0814 LDA #$27
|
||||
= $0816 STA $091A,X
|
||||
= $0819 RTS
|
||||
|
||||
Copy vector to vector.
|
||||
|
||||
| vector bar
|
||||
@ -576,6 +895,47 @@ Adding a word memory location to another word memory location.
|
||||
= $081D STA $0822
|
||||
= $0820 RTS
|
||||
|
||||
Subtracting a constant word from a word memory location.
|
||||
|
||||
| word score
|
||||
| routine main
|
||||
| inputs score
|
||||
| outputs score
|
||||
| trashes a, c, z, v, n
|
||||
| {
|
||||
| st on, c
|
||||
| sub score, 1999
|
||||
| }
|
||||
= $080D SEC
|
||||
= $080E LDA $081F
|
||||
= $0811 SBC #$CF
|
||||
= $0813 STA $081F
|
||||
= $0816 LDA $0820
|
||||
= $0819 SBC #$07
|
||||
= $081B STA $0820
|
||||
= $081E RTS
|
||||
|
||||
Subtracting a word memory location from another word memory location.
|
||||
|
||||
| word score
|
||||
| word delta
|
||||
| routine main
|
||||
| inputs score, delta
|
||||
| outputs score
|
||||
| trashes a, c, z, v, n
|
||||
| {
|
||||
| st on, c
|
||||
| sub score, delta
|
||||
| }
|
||||
= $080D SEC
|
||||
= $080E LDA $0821
|
||||
= $0811 SBC $0823
|
||||
= $0814 STA $0821
|
||||
= $0817 LDA $0822
|
||||
= $081A SBC $0824
|
||||
= $081D STA $0822
|
||||
= $0820 RTS
|
||||
|
||||
### Buffers and Pointers
|
||||
|
||||
Load address into pointer.
|
||||
@ -645,7 +1005,7 @@ Write stored value through a pointer.
|
||||
= $081A STA ($FE),Y
|
||||
= $081C RTS
|
||||
|
||||
Read through a pointer.
|
||||
Read through a pointer, into a byte storage location, or the `a` register.
|
||||
|
||||
| buffer[2048] buf
|
||||
| pointer ptr @ 254
|
||||
@ -659,15 +1019,17 @@ Read through a pointer.
|
||||
| ld y, 0
|
||||
| copy ^buf, ptr
|
||||
| copy [ptr] + y, foo
|
||||
| copy [ptr] + y, a
|
||||
| }
|
||||
= $080D LDY #$00
|
||||
= $080F LDA #$1D
|
||||
= $080F LDA #$1F
|
||||
= $0811 STA $FE
|
||||
= $0813 LDA #$08
|
||||
= $0815 STA $FF
|
||||
= $0817 LDA ($FE),Y
|
||||
= $0819 STA $101D
|
||||
= $081C RTS
|
||||
= $0819 STA $101F
|
||||
= $081C LDA ($FE),Y
|
||||
= $081E 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.)
|
||||
@ -713,3 +1075,20 @@ Note that this is *not* range-checked. (Yet.)
|
||||
= $083B LDA ($FE),Y
|
||||
= $083D STA $1041
|
||||
= $0840 RTS
|
||||
|
||||
### Trash
|
||||
|
||||
Trash does nothing except indicate that we do not care about the value anymore.
|
||||
|
||||
| routine main
|
||||
| inputs a
|
||||
| outputs x
|
||||
| trashes a, z, n
|
||||
| {
|
||||
| ld x, a
|
||||
| ld a, 0
|
||||
| trash a
|
||||
| }
|
||||
= $080D TAX
|
||||
= $080E LDA #$00
|
||||
= $0810 RTS
|
||||
|
@ -70,6 +70,14 @@ Extern routines
|
||||
| @ 65487
|
||||
= ok
|
||||
|
||||
Trash.
|
||||
|
||||
| routine main {
|
||||
| trash a
|
||||
| trash n
|
||||
| }
|
||||
= ok
|
||||
|
||||
If with not
|
||||
|
||||
| routine foo {
|
||||
@ -178,6 +186,22 @@ User-defined locations of other types.
|
||||
| }
|
||||
= ok
|
||||
|
||||
Initialized byte table.
|
||||
|
||||
| byte table message : "WHAT DO YOU WANT TO DO NEXT?"
|
||||
|
|
||||
| routine main {
|
||||
| }
|
||||
= ok
|
||||
|
||||
Can't initialize anything but a byte table with a string.
|
||||
|
||||
| word message : "WHAT DO YOU WANT TO DO NEXT?"
|
||||
|
|
||||
| routine main {
|
||||
| }
|
||||
? SyntaxError
|
||||
|
||||
Can't access an undeclared memory location.
|
||||
|
||||
| routine main {
|
||||
@ -239,6 +263,19 @@ And you can't call a non-routine.
|
||||
| }
|
||||
? SyntaxError
|
||||
|
||||
But you can call a routine that is yet to be defined, further on.
|
||||
|
||||
| routine main {
|
||||
| ld x, 0
|
||||
| ld y, 1
|
||||
| call up
|
||||
| call up
|
||||
| }
|
||||
| routine up {
|
||||
| ld a, 0
|
||||
| }
|
||||
= ok
|
||||
|
||||
Can't define two routines with the same name.
|
||||
|
||||
| routine main {
|
||||
@ -343,6 +380,33 @@ 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`.
|
||||
|
||||
| vector cinv inputs cinv, a outputs cinv, x trashes a, x, z, n @ 788
|
||||
| routine main {
|
||||
| with interrupts off {
|
||||
| copy foo, cinv
|
||||
| }
|
||||
| call cinv
|
||||
| }
|
||||
| routine foo {
|
||||
| ld a, 0
|
||||
| }
|
||||
? SyntaxError: Undefined symbol
|
||||
|
||||
| vector cinv inputs cinv, a outputs cinv, x trashes a, x, z, n @ 788
|
||||
| routine main {
|
||||
| with interrupts off {
|
||||
| copy forward foo, cinv
|
||||
| }
|
||||
| call cinv
|
||||
| }
|
||||
| routine foo {
|
||||
| ld a, 0
|
||||
| }
|
||||
= ok
|
||||
|
||||
goto.
|
||||
|
||||
| routine foo {
|
||||
@ -353,6 +417,14 @@ goto.
|
||||
| }
|
||||
= ok
|
||||
|
||||
| routine main {
|
||||
| goto foo
|
||||
| }
|
||||
| routine foo {
|
||||
| ld a, 0
|
||||
| }
|
||||
= ok
|
||||
|
||||
| vector foo
|
||||
|
|
||||
| routine main {
|
||||
|
Loading…
x
Reference in New Issue
Block a user