mirror of
https://github.com/catseye/SixtyPical.git
synced 2025-04-07 23:37:23 +00:00
commit
c7e1b69845
21
HISTORY.md
21
HISTORY.md
@ -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
|
||||
----
|
||||
|
||||
|
@ -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
24
TODO.md
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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.)
|
||||
|
@ -1,8 +0,0 @@
|
||||
define main routine
|
||||
inputs a
|
||||
outputs a
|
||||
trashes c, z, n, v
|
||||
{
|
||||
st off, c
|
||||
add a, 4
|
||||
}
|
@ -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
36
eg/rudiments/add.60p
Normal 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
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
70
eg/rudiments/cmp-byte.60p
Normal 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
|
||||
}
|
||||
}
|
64
eg/rudiments/cmp-litword.60p
Normal file
64
eg/rudiments/cmp-litword.60p
Normal 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
68
eg/rudiments/cmp-word.60p
Normal 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
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -1,10 +0,0 @@
|
||||
byte bar
|
||||
byte baz
|
||||
|
||||
define main routine
|
||||
inputs baz
|
||||
outputs bar
|
||||
trashes a, n, z
|
||||
{
|
||||
copy baz, bar
|
||||
}
|
4
eg/rudiments/errorful/README.md
Normal file
4
eg/rudiments/errorful/README.md
Normal 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.
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -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
36
eg/rudiments/loadngo.sh
Executable 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
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
@ -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
|
||||
|
6
eg/rudiments/support/c64.60p
Normal file
6
eg/rudiments/support/c64.60p
Normal file
@ -0,0 +1,6 @@
|
||||
// Implementation of `chrout` for the Commodore 64 platform.
|
||||
|
||||
define chrout routine
|
||||
inputs a
|
||||
trashes a
|
||||
@ 65490
|
26
eg/rudiments/support/stdlib.60p
Normal file
26
eg/rudiments/support/stdlib.60p
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
6
eg/rudiments/support/vic20.60p
Normal file
6
eg/rudiments/support/vic20.60p
Normal file
@ -0,0 +1,6 @@
|
||||
// Implementation of `chrout` for the Commodore VIC-20 platform.
|
||||
|
||||
define chrout routine
|
||||
inputs a
|
||||
trashes a
|
||||
@ 65490
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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 {
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user