1
0
mirror of https://github.com/catseye/SixtyPical.git synced 2025-01-10 02:29:23 +00:00

Merge pull request #8 from catseye/develop-0.12

Develop 0.12
This commit is contained in:
Chris Pressey 2018-02-14 11:41:51 +00:00 committed by GitHub
commit c10e73bdcb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 824 additions and 323 deletions

View File

@ -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
----

View File

@ -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!

View File

@ -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.

View File

@ -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
View 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
}

View File

@ -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

View File

@ -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 {

View File

@ -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
}

View File

@ -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

View File

@ -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)

View File

@ -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))))

View File

@ -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):

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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