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

Merge pull request #19 from catseye/develop-0.18

Develop 0.18
This commit is contained in:
Chris Pressey 2019-01-04 11:21:37 +00:00 committed by GitHub
commit c7e1b69845
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 1429 additions and 346 deletions

View File

@ -1,6 +1,27 @@
History of SixtyPical
=====================
0.18
----
* The "consistent initialization" check inside `if` blocks has
been dropped. If a location is initialized inside one block
but not the other, it is treated as uninitialized afterwards.
* Syntactically, `goto` may only appear at the end of a block.
It need no longer be the final instruction in a routine,
as long as the type context is consistent at every exit.
* When the range of a location is known, `inc` and `dec`
on it will usually shift the known instead of invalidating it.
* `cmp` instruction can now perform a 16-bit unsigned comparison
of `word` memory locations and `word` literals (at the cost of
trashing the `a` register.)
* `add` (resp. `sub`) now support adding (resp. subtracting) a
byte location or a byte literal from a byte location.
* Fixed pathological memory use in the lexical scanner - should
be much less inefficient now when parsing large source files.
* Reorganized the examples in `eg/rudiments/` to make them
officially platform-agnostic and to state the expected output.
0.17
----

View File

@ -1,7 +1,7 @@
SixtyPical
==========
_Version 0.17. Work-in-progress, everything is subject to change._
_Version 0.18. Work-in-progress, everything is subject to change._
**SixtyPical** is a low-level programming language with advanced
static analysis. Many of its primitive instructions resemble

24
TODO.md
View File

@ -1,17 +1,6 @@
TODO for SixtyPical
===================
### 16-bit `cmp`
This is because we don't actually want `low` and `high` address operators
that turn `word` type into `byte`.
This is because this immediately makes things harder (that is, effectively
impossible) to analyze.
16-bit `cmp` also benefits from some special differences between `cmp`
and `sub` on 6502, so it would be nice to capture them.
### Save values to other-than-the-stack
Allow
@ -55,24 +44,11 @@ buffer; and the ones you establish must be disjoint.
An alternative would be `static` pointers, which are currently not possible because
pointers must be zero-page, thus `@`, thus uninitialized.
### Question "consistent initialization"
Question the value of the "consistent initialization" principle for `if` statement analysis.
Part of this is the trashes at the end; I think what it should be is that the trashes
after the `if` is the union of the trashes in each of the branches; this would obviate the
need to `trash` values explicitly, but if you tried to access them afterwards, it would still
error.
### Tail-call optimization
More generally, define a block as having zero or one `goto`s at the end. (and `goto`s cannot
appear elsewhere.)
If a block ends in a `call` can that be converted to end in a `goto`? Why not? I think it can,
if the block is in tail position. The constraints should iron out the same both ways.
And - once we have this - why do we need `goto` to be in tail position, strictly?
As long as the routine has consistent type context every place it exits, that should be fine.
### "Include" directives

View File

@ -288,9 +288,9 @@ this mode is used.
copy <src-memory-location>, <dest-memory-location>
Reads from src and writes to dest. Differs from `st` in that is able to
copy more general types of data (for example, vectors,) and it trashes the
`z` and `n` flags and the `a` register.
Reads from src and writes to dest. Differs from `ld` and `st` in that
it is able to copy more general types of data (for example, vectors,)
and it trashes the `z` and `n` flags and the `a` register.
* It is illegal if dest is read-only.
* It is illegal if dest does not occur in the WRITES of the current routine.
@ -333,8 +333,9 @@ this mode is used.
Adds the contents of src to dest and stores the result in dest.
* It is illegal if src OR dest OR c is uninitialized.
* It is illegal if src OR dest OR `c` is uninitialized.
* It is illegal if dest is read-only.
* It is illegal if dest is `x` or `y`.
* It is illegal if dest does not occur in the WRITES of the current routine.
Affects n, z, c, and v flags, requiring that they be in the WRITES,
@ -345,6 +346,9 @@ dest and src continue to be initialized afterwards.
In addition, if dest is of `word` type, then src must also be of `word`
type, and in this case this instruction trashes the `a` register.
In fact, this instruction trashes the `a` register in all cases except
when the dest is `a`.
NOTE: If dest is a pointer, the addition does not check if the result of
the pointer arithmetic continues to be valid (within a buffer) or not.
@ -367,8 +371,9 @@ and initializing them afterwards.
Subtracts the contents of src from dest and stores the result in dest.
* It is illegal if src OR dest OR c is uninitialized.
* It is illegal if src OR dest OR `c` is uninitialized.
* It is illegal if dest is read-only.
* It is illegal if dest is `x` or `y`.
* It is illegal if dest does not occur in the WRITES of the current routine.
Affects n, z, c, and v flags, requiring that they be in the WRITES,
@ -376,6 +381,12 @@ and initializing them afterwards.
dest and src continue to be initialized afterwards.
In addition, if dest is of `word` type, then src must also be of `word`
type, and in this case this instruction trashes the `a` register.
In fact, this instruction trashes the `a` register in all cases except
when the dest is `a`.
### dec ###
dec <dest-memory-location>
@ -395,12 +406,24 @@ and initializing them afterwards.
Subtracts the contents of src from dest (without considering carry) but
does not store the result anywhere, only sets the resulting flags.
This means that `z` is set if src and dest are equal,
and `c` is set if dest is greater than or equal to src
(`c` is unset if dest is less than src.)
* It is illegal if src OR dest is uninitialized.
Affects n, z, and c flags, requiring that they be in the WRITES,
and initializing them afterwards.
In addition, if dest is of `word` type, then src must also be of `word`
type, and in this case this instruction trashes the `a` register.
Note that `cmp` is not suitable for making a
signed comparison; this article, which mentions
techniques that a SixtyPical compiler could use to
implement `cmp`, also explains why that is:
[Beyond 8-bit Unsigned Comparisons, by Bruce Clark](http://www.6502.org/tutorials/compare_beyond.html).
### and, or, xor ###
and <dest-memory-location>, <src-memory-location>

View File

@ -311,14 +311,6 @@ define player_logic logic_routine
ld a, 1
st a, player_died
}
// FIXME these trashes, strictly speaking, probably shouldn't be needed,
// 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 v
}
}
@ -355,13 +347,6 @@ define enemy_logic logic_routine
add ptr, pos
copy 82, [ptr] + y
}
// 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
} else {
copy delta, compare_target
st on, c

View File

@ -1,12 +1,25 @@
This directory contains example sources which demonstrate
the rudiments of SixtyPical.
Some are meant to fail and produce an error message.
Examples that are meant to fail and produce an error message
are in the `errorful/` subdirectory.
They are not meant to be specific to any architecture, but
many do assume the existence of a routine at 65490 which
outputs the value of the accumulator as an ASCII character,
These files are intended to be architecture-agnostic.
For the ones that do produce output, an appropriate source
under `platform/`, should be included first, like
sixtypical platform/c64.60p vector-table.60p
so that system entry points such as `chrout` are defined.
There's a `loadngo.sh` script in this directory that does this.
./loadngo.sh c64 vector-table.60p
`chrout` is a routine with outputs the value of the accumulator
as an ASCII character, disturbing none of the other registers,
simply for the purposes of producing some observable output.
(This is an address of a KERNAL routine which does this
on both the Commodore 64 and the Commodore VIC-20, so these
sources should be usable on these architectures.)
(There is a KERNAL routine which does this on both the
Commodore 64 and the Commodore VIC-20. It should not be hard
to find or write such a routine for most other architectures.)

View File

@ -1,8 +0,0 @@
define main routine
inputs a
outputs a
trashes c, z, n, v
{
st off, c
add a, 4
}

View File

@ -1,9 +0,0 @@
word score
define main routine
inputs score
outputs score
trashes a, c, z, v, n
{
st off, c
add score, 1999
}

36
eg/rudiments/add.60p Normal file
View File

@ -0,0 +1,36 @@
// Include `support/${PLATFORM}.60p` before this source
// Should print YY
word score
define main routine
inputs a, score
outputs score
trashes a, c, z, n, v
{
ld a, 3
st off, c
add a, 4
cmp a, 7
if z {
ld a, 89
call chrout
} else {
ld a, 78
call chrout
}
copy 999, score
st off, c
add score, 1999
cmp score, 2998
if z {
ld a, 89
call chrout
} else {
ld a, 78
call chrout
}
}

View File

@ -1,3 +1,6 @@
// Include `support/${PLATFORM}.60p` before this source
// Should print Y
buffer[2048] buf
pointer ptr @ 254
byte foo
@ -5,11 +8,23 @@ byte foo
define main routine
inputs buf
outputs buf, y, foo
trashes a, z, n, ptr
trashes a, z, n, c, ptr
{
ld y, 0
copy ^buf, ptr
copy 123, [ptr] + y
copy [ptr] + y, foo
copy foo, [ptr] + y
// TODO: support saying `cmp foo, 123`, maybe
ld a, foo
cmp a, 123
if z {
ld a, 89
call chrout
} else {
ld a, 78
call chrout
}
}

View File

@ -1,7 +1,5 @@
define chrout routine
inputs a
trashes a
@ 65490
// Include `support/${PLATFORM}.60p` before this source
// Should print AA
define print routine
trashes a, z, n

70
eg/rudiments/cmp-byte.60p Normal file
View File

@ -0,0 +1,70 @@
// Include `support/${PLATFORM}.60p` before this source
// Should print ENGGL
byte b
define main routine
outputs b
trashes a, x, y, z, n, c, v
{
ld a, 40
st a, b
cmp a, b
if z {
ld a, 69 // E
call chrout
} else {
ld a, 78 // N
call chrout
}
ld a, 41
st a, b
ld a, 40
cmp a, b
if z {
ld a, 69 // E
call chrout
} else {
ld a, 78 // N
call chrout
}
ld a, 20
st a, b
ld a, 21
cmp a, b // 21 >= 20
if c {
ld a, 71 // G
call chrout
} else {
ld a, 76 // L
call chrout
}
ld a, 20
cmp a, b // 20 >= 20
if c {
ld a, 71 // G
call chrout
} else {
ld a, 76 // L
call chrout
}
ld a, 19
cmp a, b // 19 < 20
if c {
ld a, 71 // G
call chrout
} else {
ld a, 76 // L
call chrout
}
}

View File

@ -0,0 +1,64 @@
// Include `support/${PLATFORM}.60p` before this source
// Should print ENGGL
word w1
define main routine
outputs w1
trashes a, x, y, z, n, c, v
{
copy 4000, w1
cmp w1, 4000
if z {
ld a, 69 // E
call chrout
} else {
ld a, 78 // N
call chrout
}
copy 4000, w1
cmp w1, 4001
if z {
ld a, 69 // E
call chrout
} else {
ld a, 78 // N
call chrout
}
copy 20002, w1
cmp w1, 20001 // 20002 >= 20001
if c {
ld a, 71 // G
call chrout
} else {
ld a, 76 // L
call chrout
}
copy 20001, w1
cmp w1, 20001 // 20001 >= 20001
if c {
ld a, 71 // G
call chrout
} else {
ld a, 76 // L
call chrout
}
copy 20000, w1
cmp w1, 20001 // 20000 < 20001
if c {
ld a, 71 // G
call chrout
} else {
ld a, 76 // L
call chrout
}
}

68
eg/rudiments/cmp-word.60p Normal file
View File

@ -0,0 +1,68 @@
// Include `support/${PLATFORM}.60p` before this source
// Should print ENGGL
word w1
word w2
define main routine
outputs w1, w2
trashes a, x, y, z, n, c, v
{
copy 4000, w1
copy 4000, w2
cmp w1, w2
if z {
ld a, 69 // E
call chrout
} else {
ld a, 78 // N
call chrout
}
copy 4000, w1
copy 4001, w2
cmp w1, w2
if z {
ld a, 69 // E
call chrout
} else {
ld a, 78 // N
call chrout
}
copy 20002, w1
copy 20001, w2
cmp w1, w2 // 20002 >= 20001
if c {
ld a, 71 // G
call chrout
} else {
ld a, 76 // L
call chrout
}
copy 20001, w1
cmp w1, w2 // 20001 >= 20001
if c {
ld a, 71 // G
call chrout
} else {
ld a, 76 // L
call chrout
}
copy 20000, w1
cmp w1, w2 // 20000 < 20001
if c {
ld a, 71 // G
call chrout
} else {
ld a, 76 // L
call chrout
}
}

View File

@ -1,7 +1,6 @@
define chrout routine
inputs a
trashes a
@ 65490
// Demonstrates vector tables.
// Include `support/${PLATFORM}.60p` before this source
// Should print YN
define main routine
trashes a, x, y, z, n, c, v

View File

@ -1,7 +1,5 @@
define chrout routine
inputs a
trashes a
@ 65490
// Include `support/${PLATFORM}.60p` before this source
// Should print YA
define main routine
trashes a, x, y, z, n, c, v

View File

@ -1,10 +0,0 @@
byte bar
byte baz
define main routine
inputs baz
outputs bar
trashes a, n, z
{
copy baz, bar
}

View File

@ -0,0 +1,4 @@
This directory contains example SixtyPical sources which
are intentionally invalid (for demonstration purposes)
and are expected to elicit an error message from a
SixtyPical implementation.

View File

@ -1,14 +1,19 @@
// Include `support/${PLATFORM}.60p` and `support/stdlib.60p` before this source
// Should print 01
byte lives
define main routine
inputs lives
inputs lives, hexchars
outputs lives
trashes a, x, z, n, c, v
{
ld a, 0
st a, lives
ld x, lives
st off, c
add x, 1
st x, lives
}
{
ld a, 0
st a, lives
ld x, lives
st off, c
inc x
st x, lives
ld a, lives
call prbyte
}

View File

@ -1,3 +1,6 @@
// This program is expected to loop forever.
// Be prepared to forcibly terminate your emulator.
define main routine
trashes a, y, z, n, c
{

View File

@ -1,7 +1,5 @@
define chrout routine
inputs a
trashes a
@ 65490
// Include `support/${PLATFORM}.60p` before this source
// Should print AB
define bar routine trashes a, z, n {
ld a, 66

View File

@ -1,12 +0,0 @@
define main routine
inputs a
outputs a
trashes z, n, c
{
cmp a, 42
if z {
ld a, 7
} else {
ld a, 23
}
}

36
eg/rudiments/loadngo.sh Executable file
View File

@ -0,0 +1,36 @@
#!/bin/sh
usage="Usage: loadngo.sh (c64|vic20) <source.60p>"
arch="$1"
shift 1
if [ "X$arch" = "Xc64" ]; then
output_format='c64-basic-prg'
if [ -e vicerc ]; then
emu="x64 -config vicerc"
else
emu="x64"
fi
elif [ "X$arch" = "Xvic20" ]; then
output_format='vic20-basic-prg'
if [ -e vicerc ]; then
emu="xvic -config vicerc"
else
emu="xvic"
fi
else
echo $usage && exit 1
fi
src="$1"
if [ "X$src" = "X" ]; then
echo $usage && exit 1
fi
### do it ###
out=/tmp/a-out.prg
../../bin/sixtypical --traceback --output-format=$output_format support/$arch.60p support/stdlib.60p $src --output $out || exit 1
ls -la $out
$emu $out
rm -f $out

View File

@ -1,7 +1,5 @@
define chrout routine
inputs a
trashes a
@ 65490
// Include `support/${PLATFORM}.60p` before this source
// Should print ABCDEFGHIJKLMNOPQRSTUVWXYZ
define main routine
trashes a, y, z, n, c

View File

@ -1,9 +1,7 @@
byte foo
// Include `support/${PLATFORM}.60p` before this source
// Should print AB
define chrout routine
inputs a
trashes a
@ 65490
byte foo
define print routine
inputs foo

View File

@ -1,22 +0,0 @@
// 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

@ -1,7 +1,5 @@
define chrout routine
inputs a
trashes a
@ 65490
// Include `support/${PLATFORM}.60p` before this source
// Should print A
define main routine
inputs a

View File

@ -0,0 +1,6 @@
// Implementation of `chrout` for the Commodore 64 platform.
define chrout routine
inputs a
trashes a
@ 65490

View File

@ -0,0 +1,26 @@
byte table[16] hexchars : "0123456789ABCDEF"
define prbyte routine
inputs a, hexchars
trashes a, z, n, c, v
{
save x {
save a {
st off, c
shr a
shr a
shr a
shr a
and a, 15
ld x, a
ld a, hexchars + x
call chrout
}
save a {
and a, 15
ld x, a
ld a, hexchars + x
call chrout
}
}
}

View File

@ -0,0 +1,6 @@
// Implementation of `chrout` for the Commodore VIC-20 platform.
define chrout routine
inputs a
trashes a
@ 65490

View File

@ -1,21 +0,0 @@
// This will not compile on its own, because there is no `main`.
// But this and `vector-main.60p` together will compile.
define chrout routine
inputs a
trashes a
@ 65490
define printa routine
trashes a, z, n
{
ld a, 65
call chrout
}
define printb routine
trashes a, z, n
{
ld a, 66
call chrout
}

View File

@ -1,22 +0,0 @@
// This will not compile on its own, because `printa` and `printb` are not defined.
// But `vector-inc.60p` and this together will compile.
vector routine
trashes a, z, n
print
// routine printb
// trashes a, z, n
// {
// ld a, 66
// call chrout
// }
define main routine
trashes print, a, z, n
{
copy printa, print
call print
copy printb, print
call print
}

View File

@ -1,7 +1,6 @@
//
// Demonstrates vector tables.
// Prints "AABAB".
//
// Include `support/${PLATFORM}.60p` before this source
// Should print AABAB
vector routine
trashes a, z, n
@ -11,11 +10,6 @@ vector (routine
trashes a, z, n)
table[32] vectors
define chrout routine
inputs a
trashes a
@ 65490
define printa routine
trashes a, z, n
{
@ -49,10 +43,8 @@ define main routine
copy printb, vectors + x
ld x, 0
repeat {
for x up to 4 {
copy vectors + x, print
call print
inc x
cmp x, 5
} until z
}
}

View File

@ -1,12 +1,10 @@
// Include `support/${PLATFORM}.60p` before this source
// Should print AB
vector routine
trashes a, z, n
print
define chrout routine
inputs a
trashes a
@ 65490
define printa routine
trashes a, z, n
{

View File

@ -1,16 +1,41 @@
// Include `support/${PLATFORM}.60p` before this source
// Should print YY
word one
word table[256] many
define main routine
inputs one, many
outputs one, many
trashes a, x, y, n, z
trashes a, x, y, c, n, z
{
ld x, 0
ld y, 0
ld y, 1
copy 777, one
copy one, many + x
copy 888, one
copy one, many + y
ld x, 1
ld y, 0
copy many + x, one
cmp one, 888
if z {
ld a, 89
call chrout
} else {
ld a, 78
call chrout
}
copy many + y, one
cmp one, 777
if z {
ld a, 89
call chrout
} else {
ld a, 78
call chrout
}
}

View File

@ -34,6 +34,11 @@ class InconsistentInitializationError(StaticAnalysisError):
pass
class InconsistentExitError(StaticAnalysisError):
"""The type context differs at two different exit points of the routine."""
pass
class ForbiddenWriteError(StaticAnalysisError):
pass
@ -46,6 +51,12 @@ class IllegalJumpError(StaticAnalysisError):
pass
class TerminatedContextError(StaticAnalysisError):
"""What the program is doing here is not valid, due to preceding `goto`s,
which make this dead code."""
pass
class RangeExceededError(StaticAnalysisError):
pass
@ -101,6 +112,7 @@ class Context(object):
self._touched = set()
self._range = dict()
self._writeable = set()
self._terminated = False
self._gotos_encountered = set()
for ref in inputs:
@ -132,6 +144,15 @@ class Context(object):
c._writeable = set(self._writeable)
return c
def update_from(self, other):
self.routines = other.routines
self.routine = other.routine
self._touched = set(other._touched)
self._range = dict(other._range)
self._writeable = set(other._writeable)
self._terminated = other._terminated
self._gotos_encounters = set(other._gotos_encountered)
def each_meaningful(self):
for ref in self._range.keys():
yield ref
@ -140,6 +161,10 @@ class Context(object):
for ref in self._touched:
yield ref
def each_writeable(self):
for ref in self._writeable:
yield ref
def assert_meaningful(self, *refs, **kwargs):
exception_class = kwargs.get('exception_class', UnmeaningfulReadError)
for ref in refs:
@ -279,6 +304,13 @@ class Context(object):
def encountered_gotos(self):
return self._gotos_encountered
def set_terminated(self):
# Having a terminated context and having encountered gotos is not the same thing.
self._terminated = True
def has_terminated(self):
return self._terminated
def assert_types_for_read_table(self, instr, src, dest, type_):
if (not TableType.is_a_table_type(src.ref.type, type_)) or (not dest.type == type_):
raise TypeMismatchError(instr, '{} and {}'.format(src.ref.name, dest.name))
@ -364,18 +396,21 @@ class Analyzer(object):
def analyze_routine(self, routine):
assert isinstance(routine, Routine)
self.current_routine = routine
if routine.block is None:
# it's an extern, that's fine
return
self.current_routine = routine
type_ = routine.location.type
context = Context(self.routines, routine, type_.inputs, type_.outputs, type_.trashes)
self.exit_contexts = []
if self.debug:
print("at start of routine `{}`:".format(routine.name))
print(context)
self.analyze_block(routine.block, context)
trashed = set(context.each_touched()) - set(context.each_meaningful())
if self.debug:
@ -390,17 +425,38 @@ class Analyzer(object):
print('-' * 79)
print('')
# even if we goto another routine, we can't trash an output.
if self.exit_contexts:
# check that they are all consistent
exit_context = self.exit_contexts[0]
exit_meaningful = set(exit_context.each_meaningful())
exit_touched = set(exit_context.each_touched())
exit_writeable = set(exit_context.each_writeable())
for ex in self.exit_contexts[1:]:
if set(ex.each_meaningful()) != exit_meaningful:
raise InconsistentExitError("Exit contexts are not consistent")
if set(ex.each_touched()) != exit_touched:
raise InconsistentExitError("Exit contexts are not consistent")
if set(ex.each_writeable()) != exit_writeable:
raise InconsistentExitError("Exit contexts are not consistent")
context.update_from(exit_context)
# these all apply whether we encountered goto(s) in this routine, or not...:
# can't trash an output.
for ref in trashed:
if ref in type_.outputs:
raise UnmeaningfulOutputError(routine, ref.name)
if not context.encountered_gotos():
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 and not routine_has_static(routine, ref):
raise ForbiddenWriteError(routine, ref.name)
# all outputs are meaningful.
for ref in type_.outputs:
context.assert_meaningful(ref, exception_class=UnmeaningfulOutputError)
# if something was touched, then it should have been declared to be writable.
for ref in context.each_touched():
if ref not in type_.outputs and ref not in type_.trashes and not routine_has_static(routine, ref):
raise ForbiddenWriteError(routine, ref.name)
self.exit_contexts = None
self.current_routine = None
return context
@ -433,8 +489,8 @@ class Analyzer(object):
dest = instr.dest
src = instr.src
if context.encountered_gotos():
raise IllegalJumpError(instr, instr)
if context.has_terminated():
raise TerminatedContextError(instr, instr)
if opcode == 'ld':
if isinstance(src, IndexedRef):
@ -477,6 +533,9 @@ class Analyzer(object):
context.assert_types_for_read_table(instr, src, dest, TYPE_BYTE)
elif src.type == TYPE_BYTE:
self.assert_type(TYPE_BYTE, src, dest)
if dest != REG_A:
context.set_touched(REG_A)
context.set_unmeaningful(REG_A)
else:
self.assert_type(TYPE_WORD, src)
if dest.type == TYPE_WORD:
@ -495,6 +554,9 @@ class Analyzer(object):
context.assert_types_for_read_table(instr, src, dest, TYPE_BYTE)
elif src.type == TYPE_BYTE:
self.assert_type(TYPE_BYTE, src, dest)
if dest != REG_A:
context.set_touched(REG_A)
context.set_unmeaningful(REG_A)
else:
self.assert_type(TYPE_WORD, src, dest)
context.set_touched(REG_A)
@ -505,8 +567,12 @@ class Analyzer(object):
context.assert_meaningful(src, dest)
if isinstance(src, IndexedRef):
context.assert_types_for_read_table(instr, src, dest, TYPE_BYTE)
else:
elif src.type == TYPE_BYTE:
self.assert_type(TYPE_BYTE, src, dest)
else:
self.assert_type(TYPE_WORD, src, dest)
context.set_touched(REG_A)
context.set_unmeaningful(REG_A)
context.set_written(FLAG_Z, FLAG_N, FLAG_C)
elif opcode == 'and':
if isinstance(src, IndexedRef):
@ -537,7 +603,20 @@ class Analyzer(object):
else:
self.assert_type(TYPE_BYTE, dest)
context.set_written(dest, FLAG_Z, FLAG_N)
context.invalidate_range(dest)
bottom = context.get_bottom_of_range(dest)
top = context.get_top_of_range(dest)
if opcode == 'inc':
if bottom == top and top < 255:
context.set_range(dest, bottom + 1, top + 1)
else:
context.invalidate_range(dest)
elif opcode == 'dec':
if bottom == top and bottom > 0:
context.set_range(dest, bottom - 1, top - 1)
else:
context.invalidate_range(dest)
else:
raise NotImplementedError
elif opcode in ('shl', 'shr'):
context.assert_meaningful(dest, FLAG_C)
if isinstance(dest, IndexedRef):
@ -674,6 +753,40 @@ class Analyzer(object):
self.assert_affected_within('trashes', type_, current_type)
context.encounter_gotos(set([instr.location]))
# Now that we have encountered a goto, we update the
# context here to match what someone calling the goto'ed
# function directly, would expect. (which makes sense
# when you think about it; if this goto's F, then calling
# this is like calling F, from the perspective of what is
# returned.)
#
# However, this isn't the current context anymore. This
# is an exit context of this routine.
exit_context = context.clone()
for ref in type_.outputs:
exit_context.set_touched(ref) # ?
exit_context.set_written(ref)
for ref in type_.trashes:
exit_context.assert_writeable(ref)
exit_context.set_touched(ref)
exit_context.set_unmeaningful(ref)
self.exit_contexts.append(exit_context)
# When we get to the end, we'll check that all the
# exit contexts are consistent with each other.
# We set the current context as having terminated.
# If we are in a branch, the merge will deal with
# having terminated. If we are at the end of the
# routine, the routine end will deal with that.
context.set_terminated()
elif opcode == 'trash':
context.set_touched(instr.dest)
context.set_unmeaningful(instr.dest)
@ -694,28 +807,23 @@ class Analyzer(object):
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(instr.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(instr.src)
)
# merge the contexts.
# merge the contexts. this used to be a method called `set_from`
context._touched = set(context1._touched) | set(context2._touched)
context.set_meaningful(*list(outgoing_meaningful))
context._writeable = set(context1._writeable) | set(context2._writeable)
# first, the easy case: if one of the contexts has terminated, just use the other one.
# if both have terminated, we return a terminated context, and that's OK.
if context1.has_terminated():
context.update_from(context2)
elif context2.has_terminated():
context.update_from(context1)
else:
# the more complicated case: merge the contents of the contexts.
context._touched = set(context1._touched) | set(context2._touched)
context.set_meaningful(*list(outgoing_meaningful))
context._writeable = set(context1._writeable) | set(context2._writeable)
# in both cases, we need to merge the encountered gotos, in order that
# fallthru optimization continues to work correctly.
context.encounter_gotos(context1.encountered_gotos() | context2.encountered_gotos())
for ref in outgoing_trashes:
@ -729,6 +837,9 @@ class Analyzer(object):
if instr.src is not None: # None indicates 'repeat forever'
context.assert_meaningful(instr.src)
if context.encountered_gotos():
raise IllegalJumpError(instr, instr)
# now analyze it having been executed a second time, with the context
# of it having already been executed.
self.analyze_block(instr.block, context)

View File

@ -244,6 +244,8 @@ class Compiler(object):
raise UnsupportedOpcodeError(instr)
self.emitter.emit(op_cls(operand))
elif opcode == 'add':
if dest == REG_X or dest == REG_Y:
raise UnsupportedOpcodeError(instr)
if dest == REG_A:
if isinstance(src, ConstantRef):
self.emitter.emit(ADC(Immediate(Byte(src.value))))
@ -251,6 +253,20 @@ class Compiler(object):
self.emitter.emit(ADC(self.addressing_mode_for_index(src.index)(self.get_label(src.ref.name))))
else:
self.emitter.emit(ADC(Absolute(self.get_label(src.name))))
elif isinstance(dest, LocationRef) and src.type == TYPE_BYTE and dest.type == TYPE_BYTE:
if isinstance(src, ConstantRef):
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)))
elif isinstance(src, LocationRef):
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)))
else:
raise UnsupportedOpcodeError(instr)
elif isinstance(dest, LocationRef) and src.type == TYPE_WORD and dest.type == TYPE_WORD:
if isinstance(src, ConstantRef):
dest_label = self.get_label(dest.name)
@ -294,6 +310,8 @@ class Compiler(object):
else:
raise UnsupportedOpcodeError(instr)
elif opcode == 'sub':
if dest == REG_X or dest == REG_Y:
raise UnsupportedOpcodeError(instr)
if dest == REG_A:
if isinstance(src, ConstantRef):
self.emitter.emit(SBC(Immediate(Byte(src.value))))
@ -301,6 +319,20 @@ class Compiler(object):
self.emitter.emit(SBC(self.addressing_mode_for_index(src.index)(self.get_label(src.ref.name))))
else:
self.emitter.emit(SBC(Absolute(self.get_label(src.name))))
elif isinstance(dest, LocationRef) and src.type == TYPE_BYTE and dest.type == TYPE_BYTE:
if isinstance(src, ConstantRef):
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)))
elif isinstance(src, LocationRef):
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)))
else:
raise UnsupportedOpcodeError(instr)
elif isinstance(dest, LocationRef) and src.type == TYPE_WORD and dest.type == TYPE_WORD:
if isinstance(src, ConstantRef):
dest_label = self.get_label(dest.name)
@ -391,6 +423,27 @@ class Compiler(object):
def compile_cmp(self, instr, src, dest):
"""`instr` is only for reporting purposes"""
if isinstance(src, LocationRef) and src.type == TYPE_WORD:
src_label = self.get_label(src.name)
dest_label = self.get_label(dest.name)
self.emitter.emit(LDA(Absolute(dest_label)))
self.emitter.emit(CMP(Absolute(src_label)))
end_label = Label('end_label')
self.emitter.emit(BNE(Relative(end_label)))
self.emitter.emit(LDA(Absolute(Offset(dest_label, 1))))
self.emitter.emit(CMP(Absolute(Offset(src_label, 1))))
self.emitter.resolve_label(end_label)
return
if isinstance(src, ConstantRef) and src.type == TYPE_WORD:
dest_label = self.get_label(dest.name)
self.emitter.emit(LDA(Absolute(dest_label)))
self.emitter.emit(CMP(Immediate(Byte(src.low_byte()))))
end_label = Label('end_label')
self.emitter.emit(BNE(Relative(end_label)))
self.emitter.emit(LDA(Absolute(Offset(dest_label, 1))))
self.emitter.emit(CMP(Immediate(Byte(src.high_byte()))))
self.emitter.resolve_label(end_label)
return
cls = {
'a': CMP,
'x': CPX,

View File

@ -380,6 +380,8 @@ class Parser(object):
self.scanner.expect('{')
while not self.scanner.on('}'):
instrs.append(self.instr())
if isinstance(instrs[-1], SingleOp) and instrs[-1].opcode == 'goto':
break
self.scanner.expect('}')
return Block(self.scanner.line_number, instrs=instrs)

View File

@ -17,18 +17,20 @@ class Scanner(object):
self.filename = filename
self.token = None
self.type = None
self.pos = 0
self.line_number = 1
self.scan()
def scan_pattern(self, pattern, type, token_group=1, rest_group=2):
pattern = r'^(' + pattern + r')(.*?)$'
match = re.match(pattern, self.text, re.DOTALL)
def scan_pattern(self, pattern, type, token_group=1):
pattern = r'(' + pattern + r')'
regexp = re.compile(pattern, flags=re.DOTALL)
match = regexp.match(self.text, pos=self.pos)
if not match:
return False
else:
self.type = type
self.token = match.group(token_group)
self.text = match.group(rest_group)
self.pos += len(match.group(0))
self.line_number += self.token.count('\n')
return True
@ -36,7 +38,7 @@ class Scanner(object):
self.scan_pattern(r'[ \t\n\r]*', 'whitespace')
while self.scan_pattern(r'\/\/.*?[\n\r]', 'comment'):
self.scan_pattern(r'[ \t\n\r]*', 'whitespace')
if not self.text:
if self.pos >= len(self.text):
self.token = None
self.type = 'EOF'
return
@ -44,20 +46,18 @@ class Scanner(object):
return
if self.scan_pattern(r'\d+', 'integer literal'):
return
if self.scan_pattern(r'\$([0-9a-fA-F]+)', 'integer literal',
token_group=2, rest_group=3):
if self.scan_pattern(r'\$([0-9a-fA-F]+)', 'integer literal', token_group=2):
# ecch
self.token = str(eval('0x' + self.token))
return
if self.scan_pattern(r'\"(.*?)\"', 'string literal',
token_group=2, rest_group=3):
if self.scan_pattern(r'\"(.*?)\"', 'string literal', token_group=2):
return
if self.scan_pattern(r'\w+', 'identifier'):
return
if self.scan_pattern(r'.', 'unknown character'):
return
else:
raise AssertionError("this should never happen, self.text=({})".format(self.text))
raise AssertionError("this should never happen, self.text=({}), self.pos=({})".format(self.text, self.pos))
def expect(self, token):
if self.token == token:

View File

@ -762,6 +762,49 @@ no longer be guaranteed.
| }
? RangeExceededError
When the range of a location is known, incrementing or
decrementing that location's value will shift the known
range. It will not invalidate it unless the known range
is at the limits of the possible ranges for the type.
| vector routine
| trashes a, z, n
| print
|
| vector (routine
| trashes a, z, n)
| table[32] vectors
|
| define main routine
| inputs vectors, print
| outputs vectors
| trashes print, a, x, z, n, c
| {
| ld x, 0
| inc x
| copy print, vectors + x
| }
= ok
| vector routine
| trashes a, z, n
| print
|
| vector (routine
| trashes a, z, n)
| table[32] vectors
|
| define main routine
| inputs vectors, print
| outputs vectors
| trashes print, a, x, z, n, c
| {
| ld x, 32
| dec x
| copy print, vectors + x
| }
= ok
### add ###
Can't `add` from or to a memory location that isn't initialized.
@ -809,6 +852,59 @@ Can't `add` to a memory location that isn't writeable.
| }
? ForbiddenWriteError: a
You can `add` a byte constant to a byte memory location.
| byte lives
| define main routine
| inputs a, lives
| outputs lives
| trashes a, c, z, v, n
| {
| st off, c
| add lives, 3
| }
= ok
`add`ing a byte constant to a byte memory location trashes `a`.
| byte lives
| define main routine
| inputs a, lives
| outputs a, lives
| trashes c, z, v, n
| {
| st off, c
| add lives, 3
| }
? UnmeaningfulOutputError: a
You can `add` a byte memory location to another byte memory location.
This trashes `a`.
| byte lives
| byte extra
| define main routine
| inputs a, lives, extra
| outputs lives
| trashes a, c, z, v, n
| {
| st off, c
| add lives, extra
| }
= ok
| byte lives
| byte extra
| define main routine
| inputs a, lives, extra
| outputs a, lives
| trashes c, z, v, n
| {
| st off, c
| add lives, extra
| }
? UnmeaningfulOutputError: a
You can `add` a word constant to a word memory location.
| word score
@ -953,6 +1049,59 @@ Can't `sub` to a memory location that isn't writeable.
| }
? ForbiddenWriteError: a
You can `sub` a byte constant from a byte memory location.
| byte lives
| define main routine
| inputs a, lives
| outputs lives
| trashes a, c, z, v, n
| {
| st on, c
| sub lives, 3
| }
= ok
`sub`ing a byte constant from a byte memory location trashes `a`.
| byte lives
| define main routine
| inputs a, lives
| outputs a, lives
| trashes c, z, v, n
| {
| st on, c
| sub lives, 3
| }
? UnmeaningfulOutputError: a
You can `sub` a byte memory location from another byte memory location.
This trashes `a`.
| byte lives
| byte extra
| define main routine
| inputs a, lives, extra
| outputs lives
| trashes a, c, z, v, n
| {
| st on, c
| sub lives, extra
| }
= ok
| byte lives
| byte extra
| define main routine
| inputs a, lives, extra
| outputs a, lives
| trashes c, z, v, n
| {
| st on, c
| sub lives, extra
| }
? UnmeaningfulOutputError: a
You can `sub` a word constant from a word memory location.
| word score
@ -1118,6 +1267,74 @@ Some rudimentary tests for `cmp`.
| }
? UnmeaningfulReadError: a
`cmp` can work on words. In this case, it trashes `a`.
| word za
| word zb
|
| define main routine
| inputs za, zb
| trashes a, z, c, n
| {
| cmp za, zb
| }
= ok
| word za
| word zb
|
| define main routine
| inputs za, zb
| trashes a, z, n
| {
| cmp za, zb
| }
? ForbiddenWriteError: c
| word za
| word zb
|
| define main routine
| inputs za, zb
| trashes z, c, n
| {
| cmp za, zb
| }
? ForbiddenWriteError: a
| word za
| word zb
|
| define main routine
| inputs za
| trashes z, c, n
| {
| cmp za, zb
| }
? UnmeaningfulReadError: zb
`cmp` can compare against a literal word.
| word za
|
| define main routine
| inputs za
| trashes a, z, c, n
| {
| cmp za, 4000
| }
= ok
| word za
|
| define main routine
| inputs za
| trashes z, c, n
| {
| cmp za, 4000
| }
? ForbiddenWriteError: a
### and ###
Some rudimentary tests for `and`.
@ -1518,7 +1735,9 @@ Both blocks of an `if` are analyzed.
| }
= ok
If a location is initialized in one block, is must be initialized in the other as well.
If a location is initialized in one block, it must be initialized in the other as well
in order to be considered to be initialized after the block. If it is not consistent,
it will be considered uninitialized.
| define foo routine
| inputs a
@ -1532,7 +1751,7 @@ If a location is initialized in one block, is must be initialized in the other a
| ld a, 23
| }
| }
? InconsistentInitializationError: x
? UnmeaningfulOutputError: x
| define foo routine
| inputs a
@ -1546,7 +1765,7 @@ If a location is initialized in one block, is must be initialized in the other a
| ld x, 7
| }
| }
? InconsistentInitializationError: x
? UnmeaningfulOutputError: x
| define foo routine
| inputs a
@ -1560,7 +1779,53 @@ If a location is initialized in one block, is must be initialized in the other a
| ld x, 7
| }
| }
? InconsistentInitializationError: x
? UnmeaningfulOutputError: x
| define foo routine
| inputs a
| trashes a, x, z, n, c
| {
| cmp a, 42
| if not z {
| ld a, 6
| } else {
| ld x, 7
| }
| ld a, x
| }
? UnmeaningfulReadError: x
If we don't care if it's uninitialized after the `if`, that's okay then.
| define foo routine
| inputs a
| trashes a, x, z, n, c
| {
| cmp a, 42
| if not z {
| ld a, 6
| } else {
| ld x, 7
| }
| }
= ok
Or, if it does get initialized on both branches, that's okay then.
| define foo routine
| inputs a
| outputs x
| trashes a, z, n, c
| {
| cmp a, 42
| if not z {
| ld x, 0
| ld a, 6
| } else {
| ld x, 7
| }
| }
= ok
However, this only pertains to initialization. If a value is already
initialized, either because it was set previous to the `if`, or is an
@ -1609,7 +1874,7 @@ An `if` with a single block is analyzed as if it had an empty `else` block.
| ld x, 7
| }
| }
? InconsistentInitializationError: x
? UnmeaningfulOutputError: x
| define foo routine
| inputs a
@ -2280,7 +2545,7 @@ But only if they are bytes.
| }
? TypeMismatchError
A `goto` cannot appear within a `save` block, even if it is otherwise in tail position.
A `goto` cannot appear within a `save` block.
| define other routine
| trashes a, z, n
@ -2325,8 +2590,7 @@ A `goto` cannot appear within a `save` block, even if it is otherwise in tail po
| }
= ok
A `goto` cannot appear within a `with interrupts` block, even if it is
otherwise in tail position.
A `goto` cannot appear within a `with interrupts` block.
| vector routine
| inputs x
@ -2973,87 +3237,7 @@ Calling the vector does indeed trash the things the vector says it does.
| }
? UnmeaningfulOutputError: x
`goto`, if present, must be in tail position (the final instruction in a routine.)
| define bar routine trashes x, z, n {
| ld x, 200
| }
|
| define main routine trashes x, z, n {
| ld x, 0
| goto bar
| }
= ok
| define bar routine trashes x, z, n {
| ld x, 200
| }
|
| define main routine trashes x, z, n {
| goto bar
| ld x, 0
| }
? IllegalJumpError
| define bar routine trashes x, z, n {
| ld x, 200
| }
|
| define main routine trashes x, z, n {
| ld x, 0
| if z {
| ld x, 1
| goto bar
| }
| }
= ok
| define bar routine trashes x, z, n {
| ld x, 200
| }
|
| define main routine trashes x, z, n {
| ld x, 0
| if z {
| ld x, 1
| goto bar
| }
| ld x, 0
| }
? IllegalJumpError
| define bar routine trashes x, z, n {
| ld x, 200
| }
|
| define main routine trashes x, z, n {
| ld x, 0
| if z {
| ld x, 1
| goto bar
| } else {
| ld x, 0
| goto bar
| }
| }
= ok
| define bar routine trashes x, z, n {
| ld x, 200
| }
|
| define main routine trashes x, z, n {
| ld x, 0
| if z {
| ld x, 1
| goto bar
| } else {
| ld x, 0
| }
| }
= ok
For the purposes of `goto`, the end of a loop is never tail position.
For now at least, you cannot have a `goto` inside a `repeat` loop.
| define bar routine trashes x, z, n {
| ld x, 200
@ -3068,6 +3252,341 @@ For the purposes of `goto`, the end of a loop is never tail position.
| }
? IllegalJumpError
`goto`, as a matter of syntax, can only appear at the end
of a block; but it need not be the final instruction in a
routine.
| define bar routine trashes x, z, n {
| ld x, 200
| }
|
| define main routine trashes x, z, n {
| ld x, 0
| goto bar
| }
= ok
| define bar routine trashes x, z, n {
| ld x, 200
| }
|
| define main routine trashes x, z, n {
| ld x, 0
| if z {
| ld x, 1
| goto bar
| }
| }
= ok
| define bar routine trashes x, z, n {
| ld x, 200
| }
|
| define main routine trashes x, z, n {
| ld x, 0
| if z {
| ld x, 1
| goto bar
| }
| goto bar
| }
= ok
| define bar routine trashes x, z, n {
| ld x, 200
| }
|
| define main routine trashes x, z, n {
| ld x, 0
| if z {
| ld x, 1
| goto bar
| }
| ld x, 0
| }
= ok
| define bar routine trashes x, z, n {
| ld x, 200
| }
|
| define main routine trashes x, z, n {
| ld x, 0
| if z {
| ld x, 1
| goto bar
| } else {
| ld x, 0
| goto bar
| }
| }
= ok
| define bar routine trashes x, z, n {
| ld x, 200
| }
|
| define main routine trashes x, z, n {
| ld x, 0
| if z {
| ld x, 1
| goto bar
| } else {
| ld x, 0
| }
| }
= ok
| define bar routine trashes x, z, n {
| ld x, 200
| }
|
| define main routine trashes x, z, n {
| ld x, 0
| if z {
| ld x, 1
| goto bar
| } else {
| ld x, 0
| }
| ld x, 0
| }
= ok
| define bar routine trashes x, z, n {
| ld x, 200
| }
|
| define main routine trashes x, z, n {
| ld x, 0
| if z {
| ld x, 1
| goto bar
| } else {
| ld x, 0
| }
| goto bar
| }
= ok
Even though `goto` can only appear at the end of a block,
you can still wind up with dead code; the analysis detects
this.
| define bar routine trashes x, z, n {
| ld x, 200
| }
|
| define main routine trashes x, z, n {
| ld x, 0
| if z {
| ld x, 1
| goto bar
| } else {
| ld x, 0
| goto bar
| }
| ld x, 100
| }
? TerminatedContextError
It is important that the type context at every
`goto` is compatible with the type context at the end of
the routine.
| define bar routine
| inputs x
| trashes x, z, n
| {
| ld x, 200
| }
|
| define main routine trashes x, z, n {
| ld x, 0
| if z {
| ld x, 1
| goto bar
| } else {
| ld x, 0
| }
| ld x, 1
| }
= ok
Here, we try to trash `x` before `goto`ing a routine that inputs `x`.
| define bar routine
| inputs x
| trashes x, z, n
| {
| ld x, 200
| }
|
| define main routine
| outputs a
| trashes x, z, n
| {
| ld x, 0
| if z {
| trash x
| goto bar
| } else {
| trash x
| }
| ld a, 1
| }
? UnmeaningfulReadError: x
Here, we declare that main outputs `a`, but we `goto` a routine that does not output `a`.
| define bar routine
| inputs x
| trashes x, z, n
| {
| ld x, 200
| }
|
| define main routine
| outputs a
| trashes x, z, n
| {
| ld x, 0
| if z {
| ld x, 1
| goto bar
| } else {
| ld x, 2
| }
| ld a, 1
| }
? UnmeaningfulOutputError: a
Here, we declare that main outputs a, and we goto a routine that outputs a so that's OK.
| define bar routine
| inputs x
| outputs a
| trashes x, z, n
| {
| ld x, 200
| ld a, 1
| }
|
| define main routine
| outputs a
| trashes x, z, n
| {
| ld x, 0
| if z {
| ld x, 1
| goto bar
| } else {
| ld x, 2
| }
| ld a, 1
| }
= ok
Here, we declare that main outputs `a`, and we `goto` two routines, and they both output `a`.
| define bar0 routine
| inputs x
| outputs a
| trashes x, z, n
| {
| ld a, x
| }
|
| define bar1 routine
| inputs x
| outputs a
| trashes x, z, n
| {
| ld a, 200
| }
|
| define main routine
| outputs a
| trashes x, z, n
| {
| ld x, 0
| if z {
| ld x, 1
| goto bar0
| } else {
| ld x, 2
| goto bar1
| }
| }
= ok
Here is like just above, but one routine doesn't output `a`.
| define bar0 routine
| inputs x
| outputs a
| trashes x, z, n
| {
| ld a, x
| }
|
| define bar1 routine
| inputs x
| trashes x, z, n
| {
| ld x, 200
| }
|
| define main routine
| outputs a
| trashes x, z, n
| {
| ld x, 0
| if z {
| ld x, 1
| goto bar0
| } else {
| ld x, 2
| goto bar1
| }
| }
? InconsistentExitError
Here is like the above, but the two routines have different inputs, and that's OK.
| define bar0 routine
| inputs x
| outputs a
| trashes x, z, n
| {
| ld a, x
| }
|
| define bar1 routine
| outputs a
| trashes x, z, n
| {
| ld a, 200
| }
|
| define main routine
| outputs a
| trashes x, z, n
| {
| ld x, 0
| if z {
| ld x, 1
| goto bar0
| } else {
| ld x, 2
| goto bar1
| }
| }
= ok
TODO: we should have a lot more test cases for the above, here.
Can't `goto` a routine that outputs or trashes more than the current routine.
| define bar routine trashes x, y, z, n {

View File

@ -385,6 +385,32 @@ Some instructions on tables. (3/3)
= $081B DEC $081F,X
= $081E RTS
Compiling 16-bit `cmp`.
| word za @ 60001
| word zb : 3003
|
| define main routine
| inputs za, zb
| trashes a, z, c, n
| {
| cmp za, zb
| cmp za, 4000
| }
= $080D LDA $EA61
= $0810 CMP $0828
= $0813 BNE $081B
= $0815 LDA $EA62
= $0818 CMP $0829
= $081B LDA $EA61
= $081E CMP #$A0
= $0820 BNE $0827
= $0822 LDA $EA62
= $0825 CMP #$0F
= $0827 RTS
= $0828 .byte $BB
= $0829 .byte $0B
Compiling `if`.
| define main routine
@ -999,6 +1025,104 @@ Copying to and from a vector table.
= $0842 JMP ($0846)
= $0845 RTS
### add, sub
Various modes of `add`.
| byte lives
| byte extra
| word score
| word bonus
| define main routine
| inputs lives, score, extra, bonus
| outputs lives, score
| trashes a, x, y, c, z, v, n
| {
| ld a, 0
| ld x, 0
| ld y, 0
| st off, c
| add a, 7
| add a, lives
| add lives, 2
| add lives, extra
| add score, 1999
| add score, bonus
| }
= $080D LDA #$00
= $080F LDX #$00
= $0811 LDY #$00
= $0813 CLC
= $0814 ADC #$07
= $0816 ADC $084D
= $0819 LDA $084D
= $081C ADC #$02
= $081E STA $084D
= $0821 LDA $084D
= $0824 ADC $084E
= $0827 STA $084D
= $082A LDA $084F
= $082D ADC #$CF
= $082F STA $084F
= $0832 LDA $0850
= $0835 ADC #$07
= $0837 STA $0850
= $083A LDA $084F
= $083D ADC $0851
= $0840 STA $084F
= $0843 LDA $0850
= $0846 ADC $0852
= $0849 STA $0850
= $084C RTS
Various modes of `sub`.
| byte lives
| byte extra
| word score
| word bonus
| define main routine
| inputs lives, score, extra, bonus
| outputs lives, score
| trashes a, x, y, c, z, v, n
| {
| ld a, 0
| ld x, 0
| ld y, 0
| st on, c
| sub a, 7
| sub a, lives
| sub lives, 2
| sub lives, extra
| sub score, 1999
| sub score, bonus
| }
= $080D LDA #$00
= $080F LDX #$00
= $0811 LDY #$00
= $0813 SEC
= $0814 SBC #$07
= $0816 SBC $084D
= $0819 LDA $084D
= $081C SBC #$02
= $081E STA $084D
= $0821 LDA $084D
= $0824 SBC $084E
= $0827 STA $084D
= $082A LDA $084F
= $082D SBC #$CF
= $082F STA $084F
= $0832 LDA $0850
= $0835 SBC #$07
= $0837 STA $0850
= $083A LDA $084F
= $083D SBC $0851
= $0840 STA $084F
= $0843 LDA $0850
= $0846 SBC $0852
= $0849 STA $0850
= $084C RTS
### word operations
Adding a constant word to a word memory location.

View File

@ -551,6 +551,9 @@ goto.
| }
= ok
The label doesn't have to be defined yet at the point
in the program text where it is `goto`d.
| define main routine {
| goto foo
| }
@ -559,6 +562,8 @@ goto.
| }
= ok
Syntactically, you can `goto` a vector.
| vector routine foo
|
| define main routine {
@ -566,11 +571,25 @@ goto.
| }
= ok
But you can't `goto` a label that never gets defined.
| define main routine {
| goto foo
| }
? SyntaxError
`goto` may only be the final instruction in a block.
| define bar routine trashes x, z, n {
| ld x, 200
| }
|
| define main routine trashes x, z, n {
| goto bar
| ld x, 0
| }
? Expected '}', but found 'ld'
Buffers and pointers.
| buffer[2048] buf