1
0
mirror of https://github.com/catseye/SixtyPical.git synced 2024-11-26 14:49:15 +00:00
SixtyPical/tests/SixtyPical Analysis.md

782 lines
12 KiB
Markdown
Raw Normal View History

SixtyPical Analysis
===================
This is a test suite, written in [Falderal][] format, for the SixtyPical
static analysis rules.
[Falderal]: http://catseye.tc/node/Falderal
-> Functionality "Analyze SixtyPical program" is implemented by
-> shell command "bin/sixtypical --analyze %(test-body-file) && echo ok"
-> Tests for functionality "Analyze SixtyPical program"
### Rudiments ###
Routines must declare their inputs, outputs, and memory locations they trash.
| routine up
| inputs a
| outputs a
| trashes c, z, v, n
| {
| st off, c
| add a, 1
| }
= ok
Routines may not declare a memory location to be both an output and trashed.
| routine main
| outputs a
| trashes a
| {
| ld a, 0
| }
? UsageClashError: a
If a routine declares it outputs a location, that location should be initialized.
| routine main
| outputs a, x, z, n
| {
| ld x, 0
| }
? UninitializedOutputError: a
| routine main
| inputs a
| outputs a
| {
| }
= ok
If a routine declares it outputs a location, that location may or may not have
been initialized. Trashing is mainly a signal to the caller.
| routine main
| trashes x, z, n
| {
| ld x, 0
| }
= ok
| routine main
| trashes x, z, n
| {
| }
= ok
If a routine modifies a location, it needs to either output it or trash it.
| routine main
| {
| ld x, 0
| }
? IllegalWriteError: x
| routine main
| outputs x, z, n
| {
| ld x, 0
| }
= ok
| routine main
| trashes x, z, n
| {
| ld x, 0
| }
= ok
### ld ###
Can't `ld` from a memory location that isn't initialized.
| routine main
| inputs a, x
| trashes a, z, n
| {
| ld a, x
| }
= ok
| routine main
| inputs a
| trashes a
| {
| ld a, x
| }
? UninitializedAccessError: x
Can't `ld` to a memory location that doesn't appear in (outputs trashes).
| routine main
| trashes a, z, n
| {
| ld a, 0
| }
= ok
| routine main
| outputs a
| trashes z, n
| {
| ld a, 0
| }
= ok
| routine main
| outputs z, n
| trashes a
| {
| ld a, 0
| }
= ok
| routine main
| trashes z, n
| {
| ld a, 0
| }
? IllegalWriteError: a
| routine main
| trashes a, n
| {
| ld a, 0
| }
? IllegalWriteError: z
### st ###
Can't `st` from a memory location that isn't initialized.
| byte lives
| routine main
| inputs x
| trashes lives
| {
| st x, lives
| }
= ok
| byte lives
| routine main
| trashes x, lives
| {
| st x, lives
| }
? UninitializedAccessError: x
Can't `st` to a memory location that doesn't appear in (outputs trashes).
| byte lives
| routine main
| trashes lives
| {
| st 0, lives
| }
= ok
| byte lives
| routine main
| outputs lives
| {
| st 0, lives
| }
= ok
| byte lives
| routine main
| inputs lives
| {
| st 0, lives
| }
? IllegalWriteError: lives
### add ###
Can't `add` from or to a memory location that isn't initialized.
| routine main
| inputs a
| outputs a
| trashes c, z, v, n
| {
| st off, c
| add a, 0
| }
= ok
| byte lives
| routine main
| inputs a
| outputs a
| trashes c, z, v, n
| {
| st off, c
| add a, lives
| }
? UninitializedAccessError: lives
| byte lives
| routine main
| inputs lives
| outputs a
| trashes c, z, v, n
| {
| st off, c
| add a, lives
| }
? UninitializedAccessError: a
Can't `add` to a memory location that isn't writeable.
| routine main
| inputs a
| trashes c
| {
| st off, c
| add a, 0
| }
? IllegalWriteError: a
### sub ###
Can't `sub` from or to a memory location that isn't initialized.
| routine main
| inputs a
| outputs a
| trashes c, z, v, n
| {
| st off, c
| sub a, 0
| }
= ok
| byte lives
| routine main
| inputs a
| outputs a
| trashes c, z, v, n
| {
| st off, c
| sub a, lives
| }
? UninitializedAccessError: lives
| byte lives
| routine main
| inputs lives
| outputs a
| trashes c, z, v, n
| {
| st off, c
| sub a, lives
| }
? UninitializedAccessError: a
Can't `sub` to a memory location that isn't writeable.
| routine main
| inputs a
| trashes c
| {
| st off, c
| sub a, 0
| }
? IllegalWriteError: a
### inc ###
Location must be initialized and writeable.
| routine main
| outputs x
| trashes z, n
| {
| inc x
| }
? UninitializedAccessError: x
| routine main
| inputs x
| trashes z, n
| {
| inc x
| }
? IllegalWriteError: x
| routine main
| inputs x
| outputs x
| trashes z, n
| {
| inc x
| }
= ok
### dec ###
Location must be initialized and writeable.
| routine main
| outputs x
| trashes z, n
| {
| dec x
| }
? UninitializedAccessError: x
| routine main
| inputs x
| trashes z, n
| {
| dec x
| }
? IllegalWriteError: x
| routine main
| inputs x
| outputs x
| trashes z, n
| {
| dec x
| }
= ok
### cmp ###
Some rudimentary tests for cmp.
| routine main
| inputs a
| trashes z, c, n
| {
| cmp a, 4
| }
= ok
| routine main
| inputs a
| trashes z, n
| {
| cmp a, 4
| }
? IllegalWriteError: c
| routine main
| trashes z, c, n
| {
| cmp a, 4
| }
? UninitializedAccessError: a
### and ###
Some rudimentary tests for and.
| routine main
| inputs a
| outputs a, z, n
| {
| and a, 4
| }
= ok
| routine main
| inputs a
| trashes z, n
| {
| and a, 4
| }
? IllegalWriteError: a
| routine main
| trashes z, n
| {
| and a, 4
| }
? UninitializedAccessError: a
### or ###
Writing unit tests on a train. Wow.
| routine main
| inputs a
| outputs a, z, n
| {
| or a, 4
| }
= ok
| routine main
| inputs a
| trashes z, n
| {
| or a, 4
| }
? IllegalWriteError: a
| routine main
| trashes z, n
| {
| or a, 4
| }
? UninitializedAccessError: a
### xor ###
Writing unit tests on a train. Wow.
| routine main
| inputs a
| outputs a, z, n
| {
| xor a, 4
| }
= ok
| routine main
| inputs a
| trashes z, n
| {
| xor a, 4
| }
? IllegalWriteError: a
| routine main
| trashes z, n
| {
| xor a, 4
| }
? UninitializedAccessError: a
### shl ###
Some rudimentary tests for shl.
| routine main
| inputs a, c
| outputs a, c, z, n
| {
| shl a
| }
= ok
| routine main
| inputs a, c
| outputs c, z, n
| {
| shl a
| }
? IllegalWriteError: a
| routine main
| inputs a
| outputs a, c, z, n
| {
| shl a
| }
? UninitializedAccessError: c
### shr ###
Some rudimentary tests for shr.
| routine main
| inputs a, c
| outputs a, c, z, n
| {
| shr a
| }
= ok
| routine main
| inputs a, c
| outputs c, z, n
| {
| shr a
| }
? IllegalWriteError: a
| routine main
| inputs a
| outputs a, c, z, n
| {
| shr a
| }
? UninitializedAccessError: c
### call ###
When calling a routine, all of the locations it lists as inputs must be
initialized.
| byte lives
|
| routine foo
| inputs x
| trashes lives
| {
| st x, lives
| }
|
| routine main
| {
| call foo
| }
? UninitializedAccessError: x
Note that if you call a routine that trashes a location, you also trash it.
| byte lives
|
| routine foo
| inputs x
| trashes lives
| {
| st x, lives
| }
|
| routine main
| outputs x, z, n
| {
| ld x, 0
| call foo
| }
? IllegalWriteError: lives
| byte lives
|
| routine foo
| inputs x
| trashes lives
| {
| st x, lives
| }
|
| routine main
| outputs x, z, n
| trashes lives
| {
| ld x, 0
| call foo
| }
= ok
You can't output a value that the thing you called trashed.
| byte lives
|
| routine foo
| inputs x
| trashes lives
| {
| st x, lives
| }
|
| routine main
| outputs x, z, n, lives
| {
| ld x, 0
| call foo
| }
? UninitializedOutputError: lives
...unless you write to it yourself afterwards.
| byte lives
|
| routine foo
| inputs x
| trashes lives
| {
| st x, lives
| }
|
| routine main
| outputs x, z, n, lives
| {
| ld x, 0
| call foo
| st x, lives
| }
= ok
If a routine declares outputs, they are initialized in the caller after
calling it.
| routine foo
| outputs x, z, n
| {
| ld x, 0
| }
|
| routine main
| outputs a
| trashes x, z, n
| {
| call foo
| ld a, x
| }
= ok
| routine foo
| {
| }
|
| routine main
| outputs a
| trashes x
| {
| call foo
| ld a, x
| }
? UninitializedAccessError: x
If a routine trashes locations, they are uninitialized in the caller after
calling it.
| routine foo
| trashes x, z, n
| {
| ld x, 0
| }
= ok
| routine foo
| trashes x, z, n
| {
| ld x, 0
| }
|
| routine main
| outputs a
| trashes x, z, n
| {
| call foo
| ld a, x
| }
? UninitializedAccessError: x
2015-10-17 13:54:28 +00:00
Calling an extern is just the same as calling a defined routine with the
same constraints.
| routine chrout
| inputs a
| trashes a
| @ 65490
|
| routine main
| trashes a, z, n
| {
| ld a, 65
| call chrout
| }
= ok
| routine chrout
| inputs a
| trashes a
| @ 65490
|
| routine main
| trashes a, z, n
| {
| call chrout
| }
? UninitializedAccessError: a
| routine chrout
| inputs a
| trashes a
| @ 65490
|
| routine main
| trashes a, x, z, n
| {
| ld a, 65
| call chrout
| ld x, a
| }
? UninitializedAccessError: a
### if ###
Both blocks of an `if` are analyzed.
| routine foo
| inputs a
| outputs x
| trashes a, z, n, c
| {
| cmp a, 42
| if z {
| ld x, 7
| } else {
| ld x, 23
| }
| }
= ok
If a location is initialized in one block, is must be initialized in the other as well.
| routine foo
| inputs a
| outputs x
| trashes a, z, n, c
| {
| cmp a, 42
| if z {
| ld x, 7
| } else {
| ld a, 23
| }
| }
? InconsistentInitializationError: x
| routine foo
| inputs a
| outputs x
| trashes a, z, n, c
| {
| cmp a, 42
| if z {
| ld a, 6
| } else {
| ld x, 7
| }
| }
? InconsistentInitializationError: x
An `if` with a single block is analyzed as if it had an empty `else` block.
| routine foo
| inputs a
| outputs x
| trashes a, z, n, c
| {
| cmp a, 42
| if z {
| ld x, 7
| }
| }
? InconsistentInitializationError: x
| routine foo
| inputs a
| outputs x
| trashes a, z, n, c
| {
| ld x, 0
| cmp a, 42
| if z {
| ld x, 7
| }
| }
= ok