mirror of
https://github.com/catseye/SixtyPical.git
synced 2025-01-25 08:30:07 +00:00
commit
642ca138a3
17
HISTORY.md
17
HISTORY.md
@ -1,6 +1,23 @@
|
|||||||
History of SixtyPical
|
History of SixtyPical
|
||||||
=====================
|
=====================
|
||||||
|
|
||||||
|
0.16
|
||||||
|
----
|
||||||
|
|
||||||
|
* Added `save` block, which allows the named locations to be modified
|
||||||
|
arbitrarily inside the block, and automatically restored at the end.
|
||||||
|
* More thorough tests and justifications written for the case of
|
||||||
|
assigning a routine to a vector with a "wider" type.
|
||||||
|
* Support for `copy [ptra]+y, [ptrb]+y` to indirect LDA indirect STA.
|
||||||
|
* Support for `shl foo` and `shr foo` where `foo` is a byte storage.
|
||||||
|
* Support for `I a, btable + x` where `I` is `add`, `sub`, `cmp`,
|
||||||
|
`and`, `or`, or `xor`
|
||||||
|
* Support for `I btable + x` where `I` is `shl`, `shr`, `inc`, `dec`
|
||||||
|
* `or a, z`, `and a, z`, and `eor a, z` compile to zero-page operations
|
||||||
|
if the address of z < 256.
|
||||||
|
* Removed `--prelude` in favour of specifying both format and prelude
|
||||||
|
with a single option, `--output-format`. Documentation for same.
|
||||||
|
|
||||||
0.15
|
0.15
|
||||||
----
|
----
|
||||||
|
|
||||||
|
124
README.md
124
README.md
@ -1,7 +1,7 @@
|
|||||||
SixtyPical
|
SixtyPical
|
||||||
==========
|
==========
|
||||||
|
|
||||||
_Version 0.15. Work-in-progress, everything is subject to change._
|
_Version 0.16. Work-in-progress, everything is subject to change._
|
||||||
|
|
||||||
**SixtyPical** is a 6502-like programming language with advanced
|
**SixtyPical** is a 6502-like programming language with advanced
|
||||||
static analysis.
|
static analysis.
|
||||||
@ -54,6 +54,8 @@ You can try the `loadngo.sh` script on other sources in the `eg` directory
|
|||||||
tree, which contains more extensive examples, including an entire
|
tree, which contains more extensive examples, including an entire
|
||||||
game(-like program); see [eg/README.md](eg/README.md) for a listing.
|
game(-like program); see [eg/README.md](eg/README.md) for a listing.
|
||||||
|
|
||||||
|
[VICE]: http://vice-emu.sourceforge.net/
|
||||||
|
|
||||||
Documentation
|
Documentation
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
@ -65,30 +67,112 @@ Documentation
|
|||||||
* [Literate test suite for SixtyPical compilation](tests/SixtyPical%20Compilation.md)
|
* [Literate test suite for SixtyPical compilation](tests/SixtyPical%20Compilation.md)
|
||||||
* [Literate test suite for SixtyPical fallthru optimization](tests/SixtyPical%20Fallthru.md)
|
* [Literate test suite for SixtyPical fallthru optimization](tests/SixtyPical%20Fallthru.md)
|
||||||
* [6502 Opcodes used/not used in SixtyPical](doc/6502%20Opcodes.md)
|
* [6502 Opcodes used/not used in SixtyPical](doc/6502%20Opcodes.md)
|
||||||
|
* [Output formats supported by `sixtypical`](doc/Output%20Formats.md)
|
||||||
|
|
||||||
TODO
|
TODO
|
||||||
----
|
----
|
||||||
|
|
||||||
### Save registers on stack
|
### `low` and `high` address operators
|
||||||
|
|
||||||
This preserves them, so that, semantically, they can be used later even though they
|
To turn `word` type into `byte`.
|
||||||
are trashed inside the block.
|
|
||||||
|
|
||||||
### And at some point...
|
Trying to remember if we have a compelling case for this or now. The best I can think
|
||||||
|
of is for implementing 16-bit `cmp` in an efficient way. Maybe we should see if we
|
||||||
|
can get by with 16-bit `cmp` instead though.
|
||||||
|
|
||||||
* `low` and `high` address operators - to turn `word` type into `byte`.
|
The problem is that once a byte is extracted, putting it back into a word is awkward.
|
||||||
* Tests, and implementation, ensuring a routine can be assigned to a vector of "wider" type
|
The address operators have to modify a destination in a special way. That is, when
|
||||||
* Related: can we simply view a (small) part of a buffer as a byte table? If not, why not?
|
you say `st a, >word`, you are updating `word` to be `word & $ff | a << 8`, somelike.
|
||||||
* Related: add constant to buffer to get new buffer. (Or to table, but... well, maybe.)
|
Is that consistent with `st`? Well, probably it is, but we have to explain it.
|
||||||
* Check that the buffer being read or written to through pointer, appears in appropriate inputs or outputs set.
|
It might make more sense, then, for it to be "part of the operation" instead of "part of
|
||||||
(Associate each pointer with the buffer it points into.)
|
the reference"; something like `st.hi x, word`; `st.lo y, word`. Dunno.
|
||||||
* `static` pointers -- currently not possible because pointers must be zero-page, thus `@`, thus uninitialized.
|
|
||||||
* Question the value of the "consistent initialization" principle for `if` statement analysis.
|
|
||||||
* `interrupt` routines -- to indicate that "the supervisor" has stored values on the stack, so we can trash them.
|
|
||||||
* Add absolute addressing in shl/shr, absolute-indexed for add, sub, etc.
|
|
||||||
* Automatic tail-call optimization (could be tricky, w/constraints?)
|
|
||||||
* Possibly `ld x, [ptr] + y`, possibly `st x, [ptr] + y`.
|
|
||||||
* Maybe even `copy [ptra] + y, [ptrb] + y`, which can be compiled to indirect LDA then indirect STA!
|
|
||||||
* Optimize `or|and|eor a, z` to zero-page operations if address of z < 256.
|
|
||||||
|
|
||||||
[VICE]: http://vice-emu.sourceforge.net/
|
### Save multiple values in single block
|
||||||
|
|
||||||
|
As a shortcut for the idiom
|
||||||
|
|
||||||
|
save a { save var {
|
||||||
|
...
|
||||||
|
} }
|
||||||
|
|
||||||
|
allow
|
||||||
|
|
||||||
|
save a, var {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
### Save values to other-than-the-stack
|
||||||
|
|
||||||
|
Allow
|
||||||
|
|
||||||
|
save a to temp_a {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
Which uses some other storage location instead of the stack. A local static
|
||||||
|
would be a good candidate for such.
|
||||||
|
|
||||||
|
### Make all symbols forward-referencable
|
||||||
|
|
||||||
|
Basically, don't do symbol-table lookups when parsing, but do have a more formal
|
||||||
|
"symbol resolution" (linking) phase right after parsing.
|
||||||
|
|
||||||
|
### Associate each pointer with the buffer it points into
|
||||||
|
|
||||||
|
Check that the buffer being read or written to through pointer, appears in appropriate
|
||||||
|
inputs or outputs set.
|
||||||
|
|
||||||
|
In the analysis, when we obtain a pointer, we need to record, in contect, what buffer
|
||||||
|
that pointer came from.
|
||||||
|
|
||||||
|
When we write through that pointer, we need to set that buffer as written.
|
||||||
|
|
||||||
|
When we read through the pointer, we need to check that the buffer is readable.
|
||||||
|
|
||||||
|
### Table overlays
|
||||||
|
|
||||||
|
They are uninitialized, but the twist is, the address is a buffer that is
|
||||||
|
an input to and/or output of the routine. So, they are defined (insofar
|
||||||
|
as the buffer is defined.)
|
||||||
|
|
||||||
|
They are therefore a "view" of a section of a buffer.
|
||||||
|
|
||||||
|
This is slightly dangerous since it does permit aliases: the buffer and the
|
||||||
|
table refer to the same memory.
|
||||||
|
|
||||||
|
Although, if they are `static`, you could say, in the routine in which they
|
||||||
|
are `static`, as soon as you've established one, you can no longer use the
|
||||||
|
buffer; and the ones you establish must be disjoint.
|
||||||
|
|
||||||
|
(That seems to be the most compelling case for restricting them to `static`.)
|
||||||
|
|
||||||
|
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.
|
||||||
|
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
|
||||||
|
|
||||||
|
Search a searchlist of include paths. And use them to make libraries of routines.
|
||||||
|
|
||||||
|
One such library routine might be an `interrupt routine` type for various architectures.
|
||||||
|
Since "the supervisor" has stored values on the stack, we should be able to trash them
|
||||||
|
with impunity, in such a routine.
|
||||||
|
@ -81,36 +81,36 @@ def process_input_files(filenames, options):
|
|||||||
|
|
||||||
fh = sys.stdout
|
fh = sys.stdout
|
||||||
|
|
||||||
if options.origin.startswith('0x'):
|
if options.output_format == 'raw':
|
||||||
start_addr = int(options.origin, 16)
|
start_addr = 0x0000
|
||||||
else:
|
prelude = []
|
||||||
start_addr = int(options.origin, 10)
|
elif options.output_format == 'prg':
|
||||||
|
start_addr = 0xc000
|
||||||
output_format = options.output_format
|
prelude = []
|
||||||
|
elif options.output_format == 'c64-basic-prg':
|
||||||
prelude = []
|
|
||||||
if options.prelude == 'c64':
|
|
||||||
output_format = 'prg'
|
|
||||||
start_addr = 0x0801
|
start_addr = 0x0801
|
||||||
prelude = [0x10, 0x08, 0xc9, 0x07, 0x9e, 0x32,
|
prelude = [0x10, 0x08, 0xc9, 0x07, 0x9e, 0x32,
|
||||||
0x30, 0x36, 0x31, 0x00, 0x00, 0x00]
|
0x30, 0x36, 0x31, 0x00, 0x00, 0x00]
|
||||||
elif options.prelude == 'vic20':
|
elif options.output_format == 'vic20-basic-prg':
|
||||||
output_format = 'prg'
|
|
||||||
start_addr = 0x1001
|
start_addr = 0x1001
|
||||||
prelude = [0x0b, 0x10, 0xc9, 0x07, 0x9e, 0x34,
|
prelude = [0x0b, 0x10, 0xc9, 0x07, 0x9e, 0x34,
|
||||||
0x31, 0x30, 0x39, 0x00, 0x00, 0x00]
|
0x31, 0x30, 0x39, 0x00, 0x00, 0x00]
|
||||||
elif options.prelude == 'atari2600':
|
elif options.output_format == 'atari2600-cart':
|
||||||
output_format = 'crtbb'
|
|
||||||
start_addr = 0xf000
|
start_addr = 0xf000
|
||||||
prelude = [0x78, 0xd8, 0xa2, 0xff, 0x9a, 0xa9,
|
prelude = [0x78, 0xd8, 0xa2, 0xff, 0x9a, 0xa9,
|
||||||
0x00,0x95, 0x00, 0xca, 0xd0, 0xfb]
|
0x00, 0x95, 0x00, 0xca, 0xd0, 0xfb]
|
||||||
|
else:
|
||||||
|
raise NotImplementedError("Unknown output format: {}".format(options.output_format))
|
||||||
|
|
||||||
elif options.prelude:
|
if options.origin is not None:
|
||||||
raise NotImplementedError("Unknown prelude: {}".format(options.prelude))
|
if options.origin.startswith('0x'):
|
||||||
|
start_addr = int(options.origin, 16)
|
||||||
|
else:
|
||||||
|
start_addr = int(options.origin, 10)
|
||||||
|
|
||||||
# If we are outputting a .PRG, we output the load address first.
|
# If we are outputting a .PRG, we output the load address first.
|
||||||
# We don't use the Emitter for this b/c not part of addr space.
|
# We don't use the Emitter for this b/c not part of addr space.
|
||||||
if output_format == 'prg':
|
if options.output_format in ('prg', 'c64-basic-prg', 'vic20-basic-prg'):
|
||||||
fh.write(Word(start_addr).serialize(0))
|
fh.write(Word(start_addr).serialize(0))
|
||||||
|
|
||||||
emitter = Emitter(start_addr)
|
emitter = Emitter(start_addr)
|
||||||
@ -121,7 +121,7 @@ def process_input_files(filenames, options):
|
|||||||
|
|
||||||
# If we are outputting a cartridge with boot and BRK address
|
# If we are outputting a cartridge with boot and BRK address
|
||||||
# at the end, pad to ROM size minus 4 bytes, and emit addresses.
|
# at the end, pad to ROM size minus 4 bytes, and emit addresses.
|
||||||
if output_format == 'crtbb':
|
if options.output_format == 'atari2600-cart':
|
||||||
emitter.pad_to_size(4096 - 4)
|
emitter.pad_to_size(4096 - 4)
|
||||||
emitter.emit(Word(start_addr))
|
emitter.emit(Word(start_addr))
|
||||||
emitter.emit(Word(start_addr))
|
emitter.emit(Word(start_addr))
|
||||||
@ -141,21 +141,15 @@ if __name__ == '__main__':
|
|||||||
)
|
)
|
||||||
|
|
||||||
argparser.add_argument(
|
argparser.add_argument(
|
||||||
"--origin", type=str, default='0xc000',
|
"--origin", type=str, default=None,
|
||||||
help="Location in memory where the `main` routine will be "
|
help="Location in memory where the `main` routine will be "
|
||||||
"located. Default: 0xc000."
|
"located. Default: depends on output format."
|
||||||
)
|
)
|
||||||
argparser.add_argument(
|
argparser.add_argument(
|
||||||
"--output-format", type=str, default='prg',
|
"--output-format", type=str, default='raw',
|
||||||
help="Executable format to produce. Options are: prg, crtbb. "
|
help="Executable format to produce; also sets a default origin. "
|
||||||
"Default: prg."
|
"Options are: raw, prg, c64-basic-prg, vic20-basic-prg, atari2600-cart."
|
||||||
)
|
"Default: raw."
|
||||||
argparser.add_argument(
|
|
||||||
"--prelude", type=str,
|
|
||||||
help="Insert a snippet of code before the compiled program so that "
|
|
||||||
"it can be booted automatically on a particular platform. "
|
|
||||||
"Also sets the origin and format. "
|
|
||||||
"Options are: c64, vic20, atari2600."
|
|
||||||
)
|
)
|
||||||
|
|
||||||
argparser.add_argument(
|
argparser.add_argument(
|
||||||
|
67
doc/Output Formats.md
Normal file
67
doc/Output Formats.md
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
Output Formats
|
||||||
|
==============
|
||||||
|
|
||||||
|
`sixtypical` can generate an output file in a number of formats.
|
||||||
|
|
||||||
|
### `raw`
|
||||||
|
|
||||||
|
The file contains only the emitted bytes of the compiled SixtyPical
|
||||||
|
program.
|
||||||
|
|
||||||
|
The default origin is $0000; it is not unlikely you will want to
|
||||||
|
override this.
|
||||||
|
|
||||||
|
Note that the origin is not stored in the output file in this format;
|
||||||
|
that information must be recorded separately.
|
||||||
|
|
||||||
|
### `prg`
|
||||||
|
|
||||||
|
The first two bytes of the file contain the origin address in
|
||||||
|
little-endian format. The remainder of the file is the emitted bytes
|
||||||
|
of the compiled SixtyPical program, starting at that origin.
|
||||||
|
|
||||||
|
The default origin is $C000; it is likely you will want to
|
||||||
|
override this.
|
||||||
|
|
||||||
|
This format coincides with Commodore's PRG format for disk files,
|
||||||
|
thus its name.
|
||||||
|
|
||||||
|
### `c64-basic-prg`
|
||||||
|
|
||||||
|
The first few bytes of the file contain a short Commodore 2.0 BASIC
|
||||||
|
program. Directly after this is the emitted bytes of the compiled
|
||||||
|
SixtyPical program. The BASIC program contains a `SYS` to that code.
|
||||||
|
|
||||||
|
The default origin is $0801; it is unlikely that you will want to
|
||||||
|
override this.
|
||||||
|
|
||||||
|
This format allows the PRG file to be loaded and run on a Commodore 64
|
||||||
|
with
|
||||||
|
|
||||||
|
LOAD"FOO.PRG",8:RUN
|
||||||
|
|
||||||
|
### `vic20-basic-prg`
|
||||||
|
|
||||||
|
Exactly like `--c64-basic-prg` except intended for the Commodore VIC-20.
|
||||||
|
|
||||||
|
The default origin is $1001; it is unlikely that you will want to
|
||||||
|
override this.
|
||||||
|
|
||||||
|
This format allows the PRG file to be loaded and run on a VIC-20 with
|
||||||
|
|
||||||
|
LOAD"FOO.PRG",8:RUN
|
||||||
|
|
||||||
|
### `atari2600-cart`
|
||||||
|
|
||||||
|
The file starts with a short machine-language prelude which is intended
|
||||||
|
to initialize an Atari 2600 system, followed by the emitted bytes of the
|
||||||
|
compiled SixtyPical program.
|
||||||
|
|
||||||
|
The file is padded to 4096 bytes in length. The padding is mostly
|
||||||
|
zeroes, except for the final 4 bytes of the file, which consist of
|
||||||
|
two addresses in little-endian format; both are the origin address.
|
||||||
|
|
||||||
|
The default origin is $F000; it is unlikely you will want to
|
||||||
|
override this.
|
||||||
|
|
||||||
|
This is the format used by Atari 2600 cartridges.
|
@ -406,33 +406,31 @@ define game_state_title_screen game_state_routine
|
|||||||
}
|
}
|
||||||
|
|
||||||
define game_state_play game_state_routine
|
define game_state_play game_state_routine
|
||||||
static byte save_x : 0
|
|
||||||
{
|
{
|
||||||
ld x, 0
|
ld x, 0
|
||||||
repeat {
|
for x up to 15 {
|
||||||
copy actor_pos + x, pos
|
copy actor_pos + x, pos
|
||||||
copy actor_delta + x, delta
|
copy actor_delta + x, delta
|
||||||
|
|
||||||
st x, save_x
|
//
|
||||||
|
// Save our loop counter on the stack temporarily. This means that routines
|
||||||
|
// like `dispatch_logic` and `clear_screen` are allowed to do whatever they
|
||||||
|
// want with the `x` register; we will restore it at the end of this block.
|
||||||
|
//
|
||||||
|
save x {
|
||||||
|
copy actor_logic + x, dispatch_logic
|
||||||
|
call dispatch_logic
|
||||||
|
|
||||||
copy actor_logic + x, dispatch_logic
|
if c {
|
||||||
call dispatch_logic
|
// Player died! Want no dead!
|
||||||
|
call clear_screen
|
||||||
if c {
|
copy game_state_game_over, dispatch_game_state
|
||||||
// Player died! Want no dead! Break out of the loop (this is a bit awkward.)
|
}
|
||||||
call clear_screen
|
|
||||||
copy game_state_game_over, dispatch_game_state
|
|
||||||
ld x, 15
|
|
||||||
} else {
|
|
||||||
ld x, save_x
|
|
||||||
}
|
}
|
||||||
|
|
||||||
copy pos, actor_pos + x
|
copy pos, actor_pos + x
|
||||||
copy delta, actor_delta + x
|
copy delta, actor_delta + x
|
||||||
|
}
|
||||||
inc x
|
|
||||||
cmp x, 16
|
|
||||||
} until z
|
|
||||||
|
|
||||||
goto save_cinv
|
goto save_cinv
|
||||||
}
|
}
|
||||||
|
@ -5,21 +5,21 @@ usage="Usage: loadngo.sh (c64|vic20|atari2600) [--dry-run] <source.60p>"
|
|||||||
arch="$1"
|
arch="$1"
|
||||||
shift 1
|
shift 1
|
||||||
if [ "X$arch" = "Xc64" ]; then
|
if [ "X$arch" = "Xc64" ]; then
|
||||||
prelude='c64'
|
output_format='c64-basic-prg'
|
||||||
if [ -e vicerc ]; then
|
if [ -e vicerc ]; then
|
||||||
emu="x64 -config vicerc"
|
emu="x64 -config vicerc"
|
||||||
else
|
else
|
||||||
emu="x64"
|
emu="x64"
|
||||||
fi
|
fi
|
||||||
elif [ "X$arch" = "Xvic20" ]; then
|
elif [ "X$arch" = "Xvic20" ]; then
|
||||||
prelude='vic20'
|
output_format='vic20-basic-prg'
|
||||||
if [ -e vicerc ]; then
|
if [ -e vicerc ]; then
|
||||||
emu="xvic -config vicerc"
|
emu="xvic -config vicerc"
|
||||||
else
|
else
|
||||||
emu="xvic"
|
emu="xvic"
|
||||||
fi
|
fi
|
||||||
elif [ "X$arch" = "Xatari2600" ]; then
|
elif [ "X$arch" = "Xatari2600" ]; then
|
||||||
prelude='atari2600'
|
output_format='atari2600-cart'
|
||||||
emu='stella'
|
emu='stella'
|
||||||
else
|
else
|
||||||
echo $usage && exit 1
|
echo $usage && exit 1
|
||||||
@ -38,7 +38,7 @@ fi
|
|||||||
### do it ###
|
### do it ###
|
||||||
|
|
||||||
out=/tmp/a-out.prg
|
out=/tmp/a-out.prg
|
||||||
bin/sixtypical --traceback --prelude=$prelude $src > $out || exit 1
|
bin/sixtypical --traceback --output-format=$output_format $src > $out || exit 1
|
||||||
ls -la $out
|
ls -la $out
|
||||||
$emu $out
|
$emu $out
|
||||||
rm -f $out
|
rm -f $out
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# encoding: UTF-8
|
# encoding: UTF-8
|
||||||
|
|
||||||
from sixtypical.ast import Program, Routine, Block, Instr, SingleOp, If, Repeat, For, WithInterruptsOff
|
from sixtypical.ast import Program, Routine, Block, Instr, SingleOp, If, Repeat, For, WithInterruptsOff, Save
|
||||||
from sixtypical.model import (
|
from sixtypical.model import (
|
||||||
TYPE_BYTE, TYPE_WORD,
|
TYPE_BYTE, TYPE_WORD,
|
||||||
TableType, BufferType, PointerType, VectorType, RoutineType,
|
TableType, BufferType, PointerType, VectorType, RoutineType,
|
||||||
@ -269,7 +269,7 @@ class Context(object):
|
|||||||
self._writeable.remove(ref)
|
self._writeable.remove(ref)
|
||||||
|
|
||||||
def set_writeable(self, *refs):
|
def set_writeable(self, *refs):
|
||||||
"""Intended to be used for implementing analyzing `for`."""
|
"""Intended to be used for implementing analyzing `for`, but also used in `save`."""
|
||||||
for ref in refs:
|
for ref in refs:
|
||||||
self._writeable.add(ref)
|
self._writeable.add(ref)
|
||||||
|
|
||||||
@ -279,6 +279,54 @@ class Context(object):
|
|||||||
def encountered_gotos(self):
|
def encountered_gotos(self):
|
||||||
return self._gotos_encountered
|
return self._gotos_encountered
|
||||||
|
|
||||||
|
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))
|
||||||
|
self.assert_meaningful(src, src.index)
|
||||||
|
self.assert_in_range(src.index, src.ref)
|
||||||
|
|
||||||
|
def assert_types_for_update_table(self, instr, dest, type_):
|
||||||
|
if not TableType.is_a_table_type(dest.ref.type, type_):
|
||||||
|
raise TypeMismatchError(instr, '{}'.format(dest.ref.name))
|
||||||
|
self.assert_meaningful(dest.index)
|
||||||
|
self.assert_in_range(dest.index, dest.ref)
|
||||||
|
self.set_written(dest.ref)
|
||||||
|
|
||||||
|
def extract(self, location):
|
||||||
|
"""Sets the given location as writeable in the context, and returns a 'baton' representing
|
||||||
|
the previous state of context for that location. This 'baton' can be used to later restore
|
||||||
|
this state of context."""
|
||||||
|
# Used in `save`.
|
||||||
|
baton = (
|
||||||
|
location,
|
||||||
|
location in self._touched,
|
||||||
|
self._range.get(location, None),
|
||||||
|
location in self._writeable,
|
||||||
|
)
|
||||||
|
self.set_writeable(location)
|
||||||
|
return baton
|
||||||
|
|
||||||
|
def re_introduce(self, baton):
|
||||||
|
"""Given a 'baton' produced by `extract()`, restores the context for that saved location
|
||||||
|
to what it was before `extract()` was called."""
|
||||||
|
# Used in `save`.
|
||||||
|
location, was_touched, was_range, was_writeable = baton
|
||||||
|
|
||||||
|
if was_touched:
|
||||||
|
self._touched.add(location)
|
||||||
|
elif location in self._touched:
|
||||||
|
self._touched.remove(location)
|
||||||
|
|
||||||
|
if was_range is not None:
|
||||||
|
self._range[location] = was_range
|
||||||
|
elif location in self._range:
|
||||||
|
del self._range[location]
|
||||||
|
|
||||||
|
if was_writeable:
|
||||||
|
self._writeable.add(location)
|
||||||
|
elif location in self._writeable:
|
||||||
|
self._writeable.remove(location)
|
||||||
|
|
||||||
|
|
||||||
class Analyzer(object):
|
class Analyzer(object):
|
||||||
|
|
||||||
@ -287,9 +335,9 @@ class Analyzer(object):
|
|||||||
self.routines = {}
|
self.routines = {}
|
||||||
self.debug = debug
|
self.debug = debug
|
||||||
|
|
||||||
def assert_type(self, type, *locations):
|
def assert_type(self, type_, *locations):
|
||||||
for location in locations:
|
for location in locations:
|
||||||
if location.type != type:
|
if location.type != type_:
|
||||||
raise TypeMismatchError(self.current_routine, location.name)
|
raise TypeMismatchError(self.current_routine, location.name)
|
||||||
|
|
||||||
def assert_affected_within(self, name, affecting_type, limiting_type):
|
def assert_affected_within(self, name, affecting_type, limiting_type):
|
||||||
@ -372,6 +420,10 @@ class Analyzer(object):
|
|||||||
self.analyze_for(instr, context)
|
self.analyze_for(instr, context)
|
||||||
elif isinstance(instr, WithInterruptsOff):
|
elif isinstance(instr, WithInterruptsOff):
|
||||||
self.analyze_block(instr.block, context)
|
self.analyze_block(instr.block, context)
|
||||||
|
if context.encountered_gotos():
|
||||||
|
raise IllegalJumpError(instr, instr)
|
||||||
|
elif isinstance(instr, Save):
|
||||||
|
self.analyze_save(instr, context)
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@ -386,12 +438,7 @@ class Analyzer(object):
|
|||||||
|
|
||||||
if opcode == 'ld':
|
if opcode == 'ld':
|
||||||
if isinstance(src, IndexedRef):
|
if isinstance(src, IndexedRef):
|
||||||
if TableType.is_a_table_type(src.ref.type, TYPE_BYTE) and dest.type == TYPE_BYTE:
|
context.assert_types_for_read_table(instr, src, dest, TYPE_BYTE)
|
||||||
pass
|
|
||||||
else:
|
|
||||||
raise TypeMismatchError(instr, '{} and {}'.format(src.ref.name, dest.name))
|
|
||||||
context.assert_meaningful(src, src.index)
|
|
||||||
context.assert_in_range(src.index, src.ref)
|
|
||||||
elif isinstance(src, IndirectRef):
|
elif isinstance(src, IndirectRef):
|
||||||
# copying this analysis from the matching branch in `copy`, below
|
# copying this analysis from the matching branch in `copy`, below
|
||||||
if isinstance(src.ref.type, PointerType) and dest.type == TYPE_BYTE:
|
if isinstance(src.ref.type, PointerType) and dest.type == TYPE_BYTE:
|
||||||
@ -407,13 +454,9 @@ class Analyzer(object):
|
|||||||
context.set_written(dest, FLAG_Z, FLAG_N)
|
context.set_written(dest, FLAG_Z, FLAG_N)
|
||||||
elif opcode == 'st':
|
elif opcode == 'st':
|
||||||
if isinstance(dest, IndexedRef):
|
if isinstance(dest, IndexedRef):
|
||||||
if src.type == TYPE_BYTE and TableType.is_a_table_type(dest.ref.type, TYPE_BYTE):
|
if src.type != TYPE_BYTE:
|
||||||
pass
|
|
||||||
else:
|
|
||||||
raise TypeMismatchError(instr, (src, dest))
|
raise TypeMismatchError(instr, (src, dest))
|
||||||
context.assert_meaningful(dest.index)
|
context.assert_types_for_update_table(instr, dest, TYPE_BYTE)
|
||||||
context.assert_in_range(dest.index, dest.ref)
|
|
||||||
context.set_written(dest.ref)
|
|
||||||
elif isinstance(dest, IndirectRef):
|
elif isinstance(dest, IndirectRef):
|
||||||
# copying this analysis from the matching branch in `copy`, below
|
# copying this analysis from the matching branch in `copy`, below
|
||||||
if isinstance(dest.ref.type, PointerType) and src.type == TYPE_BYTE:
|
if isinstance(dest.ref.type, PointerType) and src.type == TYPE_BYTE:
|
||||||
@ -423,67 +466,88 @@ class Analyzer(object):
|
|||||||
context.assert_meaningful(dest.ref, REG_Y)
|
context.assert_meaningful(dest.ref, REG_Y)
|
||||||
context.set_written(dest.ref)
|
context.set_written(dest.ref)
|
||||||
elif src.type != dest.type:
|
elif src.type != dest.type:
|
||||||
raise TypeMismatchError(instr, '{} and {}'.format(src, name))
|
raise TypeMismatchError(instr, '{} and {}'.format(src, dest))
|
||||||
else:
|
else:
|
||||||
context.set_written(dest)
|
context.set_written(dest)
|
||||||
# FIXME: context.copy_range(src, dest) ?
|
# FIXME: context.copy_range(src, dest) ?
|
||||||
context.assert_meaningful(src)
|
context.assert_meaningful(src)
|
||||||
elif opcode == 'add':
|
elif opcode == 'add':
|
||||||
context.assert_meaningful(src, dest, FLAG_C)
|
context.assert_meaningful(src, dest, FLAG_C)
|
||||||
if src.type == TYPE_BYTE:
|
if isinstance(src, IndexedRef):
|
||||||
|
context.assert_types_for_read_table(instr, src, dest, TYPE_BYTE)
|
||||||
|
elif src.type == TYPE_BYTE:
|
||||||
self.assert_type(TYPE_BYTE, src, dest)
|
self.assert_type(TYPE_BYTE, src, dest)
|
||||||
context.set_written(dest, FLAG_Z, FLAG_N, FLAG_C, FLAG_V)
|
|
||||||
else:
|
else:
|
||||||
self.assert_type(TYPE_WORD, src)
|
self.assert_type(TYPE_WORD, src)
|
||||||
if dest.type == TYPE_WORD:
|
if dest.type == TYPE_WORD:
|
||||||
context.set_written(dest, FLAG_Z, FLAG_N, FLAG_C, FLAG_V)
|
|
||||||
context.set_touched(REG_A)
|
context.set_touched(REG_A)
|
||||||
context.set_unmeaningful(REG_A)
|
context.set_unmeaningful(REG_A)
|
||||||
elif isinstance(dest.type, PointerType):
|
elif isinstance(dest.type, PointerType):
|
||||||
context.set_written(dest, FLAG_Z, FLAG_N, FLAG_C, FLAG_V)
|
|
||||||
context.set_touched(REG_A)
|
context.set_touched(REG_A)
|
||||||
context.set_unmeaningful(REG_A)
|
context.set_unmeaningful(REG_A)
|
||||||
else:
|
else:
|
||||||
self.assert_type(TYPE_WORD, dest)
|
self.assert_type(TYPE_WORD, dest)
|
||||||
|
context.set_written(dest, FLAG_Z, FLAG_N, FLAG_C, FLAG_V)
|
||||||
context.invalidate_range(dest)
|
context.invalidate_range(dest)
|
||||||
elif opcode == 'sub':
|
elif opcode == 'sub':
|
||||||
context.assert_meaningful(src, dest, FLAG_C)
|
context.assert_meaningful(src, dest, FLAG_C)
|
||||||
if src.type == TYPE_BYTE:
|
if isinstance(src, IndexedRef):
|
||||||
|
context.assert_types_for_read_table(instr, src, dest, TYPE_BYTE)
|
||||||
|
elif src.type == TYPE_BYTE:
|
||||||
self.assert_type(TYPE_BYTE, src, dest)
|
self.assert_type(TYPE_BYTE, src, dest)
|
||||||
context.set_written(dest, FLAG_Z, FLAG_N, FLAG_C, FLAG_V)
|
|
||||||
else:
|
else:
|
||||||
self.assert_type(TYPE_WORD, src, dest)
|
self.assert_type(TYPE_WORD, src, dest)
|
||||||
context.set_written(dest, FLAG_Z, FLAG_N, FLAG_C, FLAG_V)
|
|
||||||
context.set_touched(REG_A)
|
context.set_touched(REG_A)
|
||||||
context.set_unmeaningful(REG_A)
|
context.set_unmeaningful(REG_A)
|
||||||
context.invalidate_range(dest)
|
context.set_written(dest, FLAG_Z, FLAG_N, FLAG_C, FLAG_V)
|
||||||
elif opcode in ('inc', 'dec'):
|
|
||||||
self.assert_type(TYPE_BYTE, dest)
|
|
||||||
context.assert_meaningful(dest)
|
|
||||||
context.set_written(dest, FLAG_Z, FLAG_N)
|
|
||||||
context.invalidate_range(dest)
|
context.invalidate_range(dest)
|
||||||
elif opcode == 'cmp':
|
elif opcode == 'cmp':
|
||||||
self.assert_type(TYPE_BYTE, src, dest)
|
|
||||||
context.assert_meaningful(src, dest)
|
context.assert_meaningful(src, dest)
|
||||||
|
if isinstance(src, IndexedRef):
|
||||||
|
context.assert_types_for_read_table(instr, src, dest, TYPE_BYTE)
|
||||||
|
else:
|
||||||
|
self.assert_type(TYPE_BYTE, src, dest)
|
||||||
context.set_written(FLAG_Z, FLAG_N, FLAG_C)
|
context.set_written(FLAG_Z, FLAG_N, FLAG_C)
|
||||||
elif opcode == 'and':
|
elif opcode == 'and':
|
||||||
self.assert_type(TYPE_BYTE, src, dest)
|
if isinstance(src, IndexedRef):
|
||||||
|
context.assert_types_for_read_table(instr, src, dest, TYPE_BYTE)
|
||||||
|
else:
|
||||||
|
self.assert_type(TYPE_BYTE, src, dest)
|
||||||
context.assert_meaningful(src, dest)
|
context.assert_meaningful(src, dest)
|
||||||
context.set_written(dest, FLAG_Z, FLAG_N)
|
context.set_written(dest, FLAG_Z, FLAG_N)
|
||||||
# If you AND the A register with a value V, the resulting value of A
|
# If you AND the A register with a value V, the resulting value of A
|
||||||
# cannot exceed the value of V; i.e. the maximum value of A becomes
|
# cannot exceed the value of V; i.e. the maximum value of A becomes
|
||||||
# the maximum value of V.
|
# the maximum value of V.
|
||||||
context.set_top_of_range(dest, context.get_top_of_range(src))
|
if not isinstance(src, IndexedRef):
|
||||||
|
context.set_top_of_range(dest, context.get_top_of_range(src))
|
||||||
elif opcode in ('or', 'xor'):
|
elif opcode in ('or', 'xor'):
|
||||||
self.assert_type(TYPE_BYTE, src, dest)
|
if isinstance(src, IndexedRef):
|
||||||
|
context.assert_types_for_read_table(instr, src, dest, TYPE_BYTE)
|
||||||
|
else:
|
||||||
|
self.assert_type(TYPE_BYTE, src, dest)
|
||||||
context.assert_meaningful(src, dest)
|
context.assert_meaningful(src, dest)
|
||||||
context.set_written(dest, FLAG_Z, FLAG_N)
|
context.set_written(dest, FLAG_Z, FLAG_N)
|
||||||
context.invalidate_range(dest)
|
context.invalidate_range(dest)
|
||||||
|
elif opcode in ('inc', 'dec'):
|
||||||
|
context.assert_meaningful(dest)
|
||||||
|
if isinstance(dest, IndexedRef):
|
||||||
|
context.assert_types_for_update_table(instr, dest, TYPE_BYTE)
|
||||||
|
context.set_written(dest.ref, FLAG_Z, FLAG_N)
|
||||||
|
#context.invalidate_range(dest)
|
||||||
|
else:
|
||||||
|
self.assert_type(TYPE_BYTE, dest)
|
||||||
|
context.set_written(dest, FLAG_Z, FLAG_N)
|
||||||
|
context.invalidate_range(dest)
|
||||||
elif opcode in ('shl', 'shr'):
|
elif opcode in ('shl', 'shr'):
|
||||||
self.assert_type(TYPE_BYTE, dest)
|
|
||||||
context.assert_meaningful(dest, FLAG_C)
|
context.assert_meaningful(dest, FLAG_C)
|
||||||
context.set_written(dest, FLAG_Z, FLAG_N, FLAG_C)
|
if isinstance(dest, IndexedRef):
|
||||||
context.invalidate_range(dest)
|
context.assert_types_for_update_table(instr, dest, TYPE_BYTE)
|
||||||
|
context.set_written(dest.ref, FLAG_Z, FLAG_N, FLAG_C)
|
||||||
|
#context.invalidate_range(dest)
|
||||||
|
else:
|
||||||
|
self.assert_type(TYPE_BYTE, dest)
|
||||||
|
context.set_written(dest, FLAG_Z, FLAG_N, FLAG_C)
|
||||||
|
context.invalidate_range(dest)
|
||||||
elif opcode == 'call':
|
elif opcode == 'call':
|
||||||
type = instr.location.type
|
type = instr.location.type
|
||||||
if isinstance(type, VectorType):
|
if isinstance(type, VectorType):
|
||||||
@ -517,6 +581,11 @@ class Analyzer(object):
|
|||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
raise TypeMismatchError(instr, (src, dest))
|
raise TypeMismatchError(instr, (src, dest))
|
||||||
|
elif isinstance(src, IndirectRef) and isinstance(dest, IndirectRef):
|
||||||
|
if isinstance(src.ref.type, PointerType) and isinstance(dest.ref.type, PointerType):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
raise TypeMismatchError(instr, (src, dest))
|
||||||
|
|
||||||
elif isinstance(src, (LocationRef, ConstantRef)) and isinstance(dest, IndexedRef):
|
elif isinstance(src, (LocationRef, ConstantRef)) and isinstance(dest, IndexedRef):
|
||||||
if src.type == TYPE_WORD and TableType.is_a_table_type(dest.ref.type, TYPE_WORD):
|
if src.type == TYPE_WORD and TableType.is_a_table_type(dest.ref.type, TYPE_WORD):
|
||||||
@ -561,7 +630,12 @@ class Analyzer(object):
|
|||||||
context.set_written(dest.ref)
|
context.set_written(dest.ref)
|
||||||
elif isinstance(src, IndirectRef) and isinstance(dest, LocationRef):
|
elif isinstance(src, IndirectRef) and isinstance(dest, LocationRef):
|
||||||
context.assert_meaningful(src.ref, REG_Y)
|
context.assert_meaningful(src.ref, REG_Y)
|
||||||
|
# TODO more sophisticated?
|
||||||
context.set_written(dest)
|
context.set_written(dest)
|
||||||
|
elif isinstance(src, IndirectRef) and isinstance(dest, IndirectRef):
|
||||||
|
context.assert_meaningful(src.ref, REG_Y)
|
||||||
|
# TODO more sophisticated?
|
||||||
|
context.set_written(dest.ref)
|
||||||
elif isinstance(src, LocationRef) and isinstance(dest, IndexedRef):
|
elif isinstance(src, LocationRef) and isinstance(dest, IndexedRef):
|
||||||
context.assert_meaningful(src, dest.ref, dest.index)
|
context.assert_meaningful(src, dest.ref, dest.index)
|
||||||
context.set_written(dest.ref)
|
context.set_written(dest.ref)
|
||||||
@ -695,3 +769,21 @@ class Analyzer(object):
|
|||||||
# after it is executed, we know the range of the loop variable.
|
# after it is executed, we know the range of the loop variable.
|
||||||
context.set_range(instr.dest, instr.final, instr.final)
|
context.set_range(instr.dest, instr.final, instr.final)
|
||||||
context.set_writeable(instr.dest)
|
context.set_writeable(instr.dest)
|
||||||
|
|
||||||
|
def analyze_save(self, instr, context):
|
||||||
|
if len(instr.locations) != 1:
|
||||||
|
raise NotImplementedError("Only 1 location in save is supported right now")
|
||||||
|
location = instr.locations[0]
|
||||||
|
self.assert_type(TYPE_BYTE, location)
|
||||||
|
|
||||||
|
baton = context.extract(location)
|
||||||
|
self.analyze_block(instr.block, context)
|
||||||
|
if context.encountered_gotos():
|
||||||
|
raise IllegalJumpError(instr, instr)
|
||||||
|
context.re_introduce(baton)
|
||||||
|
|
||||||
|
if location == REG_A:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
context.set_touched(REG_A)
|
||||||
|
context.set_unmeaningful(REG_A)
|
||||||
|
@ -90,3 +90,8 @@ class For(Instr):
|
|||||||
|
|
||||||
class WithInterruptsOff(Instr):
|
class WithInterruptsOff(Instr):
|
||||||
child_attrs = ('block',)
|
child_attrs = ('block',)
|
||||||
|
|
||||||
|
|
||||||
|
class Save(Instr):
|
||||||
|
value_attrs = ('locations',)
|
||||||
|
child_attrs = ('block',)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# encoding: UTF-8
|
# encoding: UTF-8
|
||||||
|
|
||||||
from sixtypical.ast import Program, Routine, Block, Instr, SingleOp, If, Repeat, For, WithInterruptsOff
|
from sixtypical.ast import Program, Routine, Block, Instr, SingleOp, If, Repeat, For, WithInterruptsOff, Save
|
||||||
from sixtypical.model import (
|
from sixtypical.model import (
|
||||||
ConstantRef, LocationRef, IndexedRef, IndirectRef, AddressRef,
|
ConstantRef, LocationRef, IndexedRef, IndirectRef, AddressRef,
|
||||||
TYPE_BIT, TYPE_BYTE, TYPE_WORD,
|
TYPE_BIT, TYPE_BYTE, TYPE_WORD,
|
||||||
@ -12,6 +12,7 @@ from sixtypical.gen6502 import (
|
|||||||
Immediate, Absolute, AbsoluteX, AbsoluteY, ZeroPage, Indirect, IndirectY, Relative,
|
Immediate, Absolute, AbsoluteX, AbsoluteY, ZeroPage, Indirect, IndirectY, Relative,
|
||||||
LDA, LDX, LDY, STA, STX, STY,
|
LDA, LDX, LDY, STA, STX, STY,
|
||||||
TAX, TAY, TXA, TYA,
|
TAX, TAY, TXA, TYA,
|
||||||
|
PHA, PLA,
|
||||||
CLC, SEC, ADC, SBC, ROL, ROR,
|
CLC, SEC, ADC, SBC, ROL, ROR,
|
||||||
INC, INX, INY, DEC, DEX, DEY,
|
INC, INX, INY, DEC, DEX, DEY,
|
||||||
CMP, CPX, CPY, AND, ORA, EOR,
|
CMP, CPX, CPY, AND, ORA, EOR,
|
||||||
@ -169,6 +170,8 @@ class Compiler(object):
|
|||||||
return self.compile_for(instr)
|
return self.compile_for(instr)
|
||||||
elif isinstance(instr, WithInterruptsOff):
|
elif isinstance(instr, WithInterruptsOff):
|
||||||
return self.compile_with_interrupts_off(instr)
|
return self.compile_with_interrupts_off(instr)
|
||||||
|
elif isinstance(instr, Save):
|
||||||
|
return self.compile_save(instr)
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@ -244,6 +247,8 @@ class Compiler(object):
|
|||||||
if dest == REG_A:
|
if dest == REG_A:
|
||||||
if isinstance(src, ConstantRef):
|
if isinstance(src, ConstantRef):
|
||||||
self.emitter.emit(ADC(Immediate(Byte(src.value))))
|
self.emitter.emit(ADC(Immediate(Byte(src.value))))
|
||||||
|
elif isinstance(src, IndexedRef):
|
||||||
|
self.emitter.emit(ADC(self.addressing_mode_for_index(src.index)(self.get_label(src.ref.name))))
|
||||||
else:
|
else:
|
||||||
self.emitter.emit(ADC(Absolute(self.get_label(src.name))))
|
self.emitter.emit(ADC(Absolute(self.get_label(src.name))))
|
||||||
elif isinstance(dest, LocationRef) and src.type == TYPE_WORD and dest.type == TYPE_WORD:
|
elif isinstance(dest, LocationRef) and src.type == TYPE_WORD and dest.type == TYPE_WORD:
|
||||||
@ -292,6 +297,8 @@ class Compiler(object):
|
|||||||
if dest == REG_A:
|
if dest == REG_A:
|
||||||
if isinstance(src, ConstantRef):
|
if isinstance(src, ConstantRef):
|
||||||
self.emitter.emit(SBC(Immediate(Byte(src.value))))
|
self.emitter.emit(SBC(Immediate(Byte(src.value))))
|
||||||
|
elif isinstance(src, IndexedRef):
|
||||||
|
self.emitter.emit(SBC(self.addressing_mode_for_index(src.index)(self.get_label(src.ref.name))))
|
||||||
else:
|
else:
|
||||||
self.emitter.emit(SBC(Absolute(self.get_label(src.name))))
|
self.emitter.emit(SBC(Absolute(self.get_label(src.name))))
|
||||||
elif isinstance(dest, LocationRef) and src.type == TYPE_WORD and dest.type == TYPE_WORD:
|
elif isinstance(dest, LocationRef) and src.type == TYPE_WORD and dest.type == TYPE_WORD:
|
||||||
@ -316,10 +323,6 @@ class Compiler(object):
|
|||||||
raise UnsupportedOpcodeError(instr)
|
raise UnsupportedOpcodeError(instr)
|
||||||
else:
|
else:
|
||||||
raise UnsupportedOpcodeError(instr)
|
raise UnsupportedOpcodeError(instr)
|
||||||
elif opcode == 'inc':
|
|
||||||
self.compile_inc(instr, instr.dest)
|
|
||||||
elif opcode == 'dec':
|
|
||||||
self.compile_dec(instr, instr.dest)
|
|
||||||
elif opcode == 'cmp':
|
elif opcode == 'cmp':
|
||||||
self.compile_cmp(instr, instr.src, instr.dest)
|
self.compile_cmp(instr, instr.src, instr.dest)
|
||||||
elif opcode in ('and', 'or', 'xor',):
|
elif opcode in ('and', 'or', 'xor',):
|
||||||
@ -331,10 +334,16 @@ class Compiler(object):
|
|||||||
if dest == REG_A:
|
if dest == REG_A:
|
||||||
if isinstance(src, ConstantRef):
|
if isinstance(src, ConstantRef):
|
||||||
self.emitter.emit(cls(Immediate(Byte(src.value))))
|
self.emitter.emit(cls(Immediate(Byte(src.value))))
|
||||||
|
elif isinstance(src, IndexedRef):
|
||||||
|
self.emitter.emit(cls(self.addressing_mode_for_index(src.index)(self.get_label(src.ref.name))))
|
||||||
else:
|
else:
|
||||||
self.emitter.emit(cls(Absolute(self.get_label(src.name))))
|
self.emitter.emit(cls(self.absolute_or_zero_page(self.get_label(src.name))))
|
||||||
else:
|
else:
|
||||||
raise UnsupportedOpcodeError(instr)
|
raise UnsupportedOpcodeError(instr)
|
||||||
|
elif opcode == 'inc':
|
||||||
|
self.compile_inc(instr, instr.dest)
|
||||||
|
elif opcode == 'dec':
|
||||||
|
self.compile_dec(instr, instr.dest)
|
||||||
elif opcode in ('shl', 'shr'):
|
elif opcode in ('shl', 'shr'):
|
||||||
cls = {
|
cls = {
|
||||||
'shl': ROL,
|
'shl': ROL,
|
||||||
@ -342,8 +351,10 @@ class Compiler(object):
|
|||||||
}[opcode]
|
}[opcode]
|
||||||
if dest == REG_A:
|
if dest == REG_A:
|
||||||
self.emitter.emit(cls())
|
self.emitter.emit(cls())
|
||||||
|
elif isinstance(dest, IndexedRef):
|
||||||
|
self.emitter.emit(cls(self.addressing_mode_for_index(dest.index)(self.get_label(dest.ref.name))))
|
||||||
else:
|
else:
|
||||||
raise UnsupportedOpcodeError(instr)
|
self.emitter.emit(cls(self.absolute_or_zero_page(self.get_label(dest.name))))
|
||||||
elif opcode == 'call':
|
elif opcode == 'call':
|
||||||
location = instr.location
|
location = instr.location
|
||||||
label = self.get_label(instr.location.name)
|
label = self.get_label(instr.location.name)
|
||||||
@ -389,6 +400,9 @@ class Compiler(object):
|
|||||||
raise UnsupportedOpcodeError(instr)
|
raise UnsupportedOpcodeError(instr)
|
||||||
if isinstance(src, ConstantRef):
|
if isinstance(src, ConstantRef):
|
||||||
self.emitter.emit(cls(Immediate(Byte(src.value))))
|
self.emitter.emit(cls(Immediate(Byte(src.value))))
|
||||||
|
elif isinstance(src, IndexedRef):
|
||||||
|
# FIXME might not work for some dest's (that is, cls's)
|
||||||
|
self.emitter.emit(cls(self.addressing_mode_for_index(src.index)(self.get_label(src.ref.name))))
|
||||||
else:
|
else:
|
||||||
self.emitter.emit(cls(Absolute(self.get_label(src.name))))
|
self.emitter.emit(cls(Absolute(self.get_label(src.name))))
|
||||||
|
|
||||||
@ -398,6 +412,8 @@ class Compiler(object):
|
|||||||
self.emitter.emit(INX())
|
self.emitter.emit(INX())
|
||||||
elif dest == REG_Y:
|
elif dest == REG_Y:
|
||||||
self.emitter.emit(INY())
|
self.emitter.emit(INY())
|
||||||
|
elif isinstance(dest, IndexedRef):
|
||||||
|
self.emitter.emit(INC(self.addressing_mode_for_index(dest.index)(self.get_label(dest.ref.name))))
|
||||||
else:
|
else:
|
||||||
self.emitter.emit(INC(Absolute(self.get_label(dest.name))))
|
self.emitter.emit(INC(Absolute(self.get_label(dest.name))))
|
||||||
|
|
||||||
@ -407,6 +423,8 @@ class Compiler(object):
|
|||||||
self.emitter.emit(DEX())
|
self.emitter.emit(DEX())
|
||||||
elif dest == REG_Y:
|
elif dest == REG_Y:
|
||||||
self.emitter.emit(DEY())
|
self.emitter.emit(DEY())
|
||||||
|
elif isinstance(dest, IndexedRef):
|
||||||
|
self.emitter.emit(DEC(self.addressing_mode_for_index(dest.index)(self.get_label(dest.ref.name))))
|
||||||
else:
|
else:
|
||||||
self.emitter.emit(DEC(Absolute(self.get_label(dest.name))))
|
self.emitter.emit(DEC(Absolute(self.get_label(dest.name))))
|
||||||
|
|
||||||
@ -428,6 +446,12 @@ class Compiler(object):
|
|||||||
dest_label = self.get_label(dest.name)
|
dest_label = self.get_label(dest.name)
|
||||||
self.emitter.emit(LDA(IndirectY(src_label)))
|
self.emitter.emit(LDA(IndirectY(src_label)))
|
||||||
self.emitter.emit(STA(Absolute(dest_label)))
|
self.emitter.emit(STA(Absolute(dest_label)))
|
||||||
|
elif isinstance(src, IndirectRef) and isinstance(dest, IndirectRef) and isinstance(src.ref.type, PointerType) and isinstance(dest.ref.type, PointerType):
|
||||||
|
### copy [ptra] + y, [ptrb] + y
|
||||||
|
src_label = self.get_label(src.ref.name)
|
||||||
|
dest_label = self.get_label(dest.ref.name)
|
||||||
|
self.emitter.emit(LDA(IndirectY(src_label)))
|
||||||
|
self.emitter.emit(STA(IndirectY(dest_label)))
|
||||||
elif isinstance(src, AddressRef) and isinstance(dest, LocationRef) and isinstance(src.ref.type, BufferType) and isinstance(dest.type, PointerType):
|
elif isinstance(src, AddressRef) and isinstance(dest, LocationRef) and isinstance(src.ref.type, BufferType) and isinstance(dest.type, PointerType):
|
||||||
### copy ^buf, ptr
|
### copy ^buf, ptr
|
||||||
src_label = self.get_label(src.ref.name)
|
src_label = self.get_label(src.ref.name)
|
||||||
@ -592,3 +616,29 @@ class Compiler(object):
|
|||||||
self.emitter.emit(SEI())
|
self.emitter.emit(SEI())
|
||||||
self.compile_block(instr.block)
|
self.compile_block(instr.block)
|
||||||
self.emitter.emit(CLI())
|
self.emitter.emit(CLI())
|
||||||
|
|
||||||
|
def compile_save(self, instr):
|
||||||
|
location = instr.locations[0]
|
||||||
|
if location == REG_A:
|
||||||
|
self.emitter.emit(PHA())
|
||||||
|
self.compile_block(instr.block)
|
||||||
|
self.emitter.emit(PLA())
|
||||||
|
elif location == REG_X:
|
||||||
|
self.emitter.emit(TXA())
|
||||||
|
self.emitter.emit(PHA())
|
||||||
|
self.compile_block(instr.block)
|
||||||
|
self.emitter.emit(PLA())
|
||||||
|
self.emitter.emit(TAX())
|
||||||
|
elif location == REG_Y:
|
||||||
|
self.emitter.emit(TYA())
|
||||||
|
self.emitter.emit(PHA())
|
||||||
|
self.compile_block(instr.block)
|
||||||
|
self.emitter.emit(PLA())
|
||||||
|
self.emitter.emit(TAY())
|
||||||
|
else:
|
||||||
|
src_label = self.get_label(location.name)
|
||||||
|
self.emitter.emit(LDA(Absolute(src_label)))
|
||||||
|
self.emitter.emit(PHA())
|
||||||
|
self.compile_block(instr.block)
|
||||||
|
self.emitter.emit(PLA())
|
||||||
|
self.emitter.emit(STA(Absolute(src_label)))
|
||||||
|
@ -133,6 +133,7 @@ class AND(Instruction):
|
|||||||
Absolute: 0x2d,
|
Absolute: 0x2d,
|
||||||
AbsoluteX: 0x3d,
|
AbsoluteX: 0x3d,
|
||||||
AbsoluteY: 0x39,
|
AbsoluteY: 0x39,
|
||||||
|
ZeroPage: 0x25,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -210,6 +211,7 @@ class CPY(Instruction):
|
|||||||
class DEC(Instruction):
|
class DEC(Instruction):
|
||||||
opcodes = {
|
opcodes = {
|
||||||
Absolute: 0xce,
|
Absolute: 0xce,
|
||||||
|
AbsoluteX: 0xde,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -231,6 +233,7 @@ class EOR(Instruction):
|
|||||||
Absolute: 0x4d,
|
Absolute: 0x4d,
|
||||||
AbsoluteX: 0x5d,
|
AbsoluteX: 0x5d,
|
||||||
AbsoluteY: 0x59,
|
AbsoluteY: 0x59,
|
||||||
|
ZeroPage: 0x45,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -299,6 +302,19 @@ class ORA(Instruction):
|
|||||||
Absolute: 0x0d,
|
Absolute: 0x0d,
|
||||||
AbsoluteX: 0x1d,
|
AbsoluteX: 0x1d,
|
||||||
AbsoluteY: 0x19,
|
AbsoluteY: 0x19,
|
||||||
|
ZeroPage: 0x05,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class PHA(Instruction):
|
||||||
|
opcodes = {
|
||||||
|
Implied: 0x48,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class PLA(Instruction):
|
||||||
|
opcodes = {
|
||||||
|
Implied: 0x68,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# encoding: UTF-8
|
# encoding: UTF-8
|
||||||
|
|
||||||
from sixtypical.ast import Program, Defn, Routine, Block, SingleOp, If, Repeat, For, WithInterruptsOff
|
from sixtypical.ast import Program, Defn, Routine, Block, SingleOp, If, Repeat, For, WithInterruptsOff, Save
|
||||||
from sixtypical.model import (
|
from sixtypical.model import (
|
||||||
TYPE_BIT, TYPE_BYTE, TYPE_WORD,
|
TYPE_BIT, TYPE_BYTE, TYPE_WORD,
|
||||||
RoutineType, VectorType, TableType, BufferType, PointerType,
|
RoutineType, VectorType, TableType, BufferType, PointerType,
|
||||||
@ -442,7 +442,7 @@ class Parser(object):
|
|||||||
elif self.scanner.token in ("shl", "shr", "inc", "dec"):
|
elif self.scanner.token in ("shl", "shr", "inc", "dec"):
|
||||||
opcode = self.scanner.token
|
opcode = self.scanner.token
|
||||||
self.scanner.scan()
|
self.scanner.scan()
|
||||||
dest = self.locexpr()
|
dest = self.indexed_locexpr()
|
||||||
return SingleOp(self.scanner.line_number, opcode=opcode, dest=dest, src=None)
|
return SingleOp(self.scanner.line_number, opcode=opcode, dest=dest, src=None)
|
||||||
elif self.scanner.token in ("nop",):
|
elif self.scanner.token in ("nop",):
|
||||||
opcode = self.scanner.token
|
opcode = self.scanner.token
|
||||||
@ -470,6 +470,10 @@ class Parser(object):
|
|||||||
self.scanner.expect("off")
|
self.scanner.expect("off")
|
||||||
block = self.block()
|
block = self.block()
|
||||||
return WithInterruptsOff(self.scanner.line_number, block=block)
|
return WithInterruptsOff(self.scanner.line_number, block=block)
|
||||||
|
elif self.scanner.consume("save"):
|
||||||
|
locations = self.locexprs()
|
||||||
|
block = self.block()
|
||||||
|
return Save(self.scanner.line_number, locations=locations, block=block)
|
||||||
elif self.scanner.consume("trash"):
|
elif self.scanner.consume("trash"):
|
||||||
dest = self.locexpr()
|
dest = self.locexpr()
|
||||||
return SingleOp(self.scanner.line_number, opcode='trash', src=None, dest=dest)
|
return SingleOp(self.scanner.line_number, opcode='trash', src=None, dest=dest)
|
||||||
|
@ -474,6 +474,60 @@ The index must be initialized.
|
|||||||
| }
|
| }
|
||||||
? UnmeaningfulReadError: x
|
? UnmeaningfulReadError: x
|
||||||
|
|
||||||
|
There are other operations you can do on tables. (1/3)
|
||||||
|
|
||||||
|
| byte table[256] many
|
||||||
|
|
|
||||||
|
| routine main
|
||||||
|
| inputs many
|
||||||
|
| outputs many
|
||||||
|
| trashes a, x, c, n, z, v
|
||||||
|
| {
|
||||||
|
| ld x, 0
|
||||||
|
| ld a, 0
|
||||||
|
| st off, c
|
||||||
|
| add a, many + x
|
||||||
|
| sub a, many + x
|
||||||
|
| cmp a, many + x
|
||||||
|
| }
|
||||||
|
= ok
|
||||||
|
|
||||||
|
There are other operations you can do on tables. (2/3)
|
||||||
|
|
||||||
|
| byte table[256] many
|
||||||
|
|
|
||||||
|
| routine main
|
||||||
|
| inputs many
|
||||||
|
| outputs many
|
||||||
|
| trashes a, x, c, n, z
|
||||||
|
| {
|
||||||
|
| ld x, 0
|
||||||
|
| ld a, 0
|
||||||
|
| and a, many + x
|
||||||
|
| or a, many + x
|
||||||
|
| xor a, many + x
|
||||||
|
| }
|
||||||
|
= ok
|
||||||
|
|
||||||
|
There are other operations you can do on tables. (3/3)
|
||||||
|
|
||||||
|
| byte table[256] many
|
||||||
|
|
|
||||||
|
| routine main
|
||||||
|
| inputs many
|
||||||
|
| outputs many
|
||||||
|
| trashes a, x, c, n, z
|
||||||
|
| {
|
||||||
|
| ld x, 0
|
||||||
|
| ld a, 0
|
||||||
|
| st off, c
|
||||||
|
| shl many + x
|
||||||
|
| shr many + x
|
||||||
|
| inc many + x
|
||||||
|
| dec many + x
|
||||||
|
| }
|
||||||
|
= ok
|
||||||
|
|
||||||
Copying to and from a word table.
|
Copying to and from a word table.
|
||||||
|
|
||||||
| word one
|
| word one
|
||||||
@ -1120,11 +1174,13 @@ Some rudimentary tests for `xor`.
|
|||||||
|
|
||||||
Some rudimentary tests for `shl`.
|
Some rudimentary tests for `shl`.
|
||||||
|
|
||||||
|
| byte foo
|
||||||
| routine main
|
| routine main
|
||||||
| inputs a, c
|
| inputs foo, a, c
|
||||||
| outputs a, c, z, n
|
| outputs foo, a, c, z, n
|
||||||
| {
|
| {
|
||||||
| shl a
|
| shl a
|
||||||
|
| shl foo
|
||||||
| }
|
| }
|
||||||
= ok
|
= ok
|
||||||
|
|
||||||
@ -1148,11 +1204,13 @@ Some rudimentary tests for `shl`.
|
|||||||
|
|
||||||
Some rudimentary tests for `shr`.
|
Some rudimentary tests for `shr`.
|
||||||
|
|
||||||
|
| byte foo
|
||||||
| routine main
|
| routine main
|
||||||
| inputs a, c
|
| inputs foo, a, c
|
||||||
| outputs a, c, z, n
|
| outputs foo, a, c, z, n
|
||||||
| {
|
| {
|
||||||
| shr a
|
| shr a
|
||||||
|
| shr foo
|
||||||
| }
|
| }
|
||||||
= ok
|
= ok
|
||||||
|
|
||||||
@ -1879,6 +1937,290 @@ initialized at the start of that loop.
|
|||||||
| }
|
| }
|
||||||
? UnmeaningfulReadError: y
|
? UnmeaningfulReadError: y
|
||||||
|
|
||||||
|
### save ###
|
||||||
|
|
||||||
|
Basic neutral test, where the `save` makes no difference.
|
||||||
|
|
||||||
|
| routine main
|
||||||
|
| inputs a, x
|
||||||
|
| outputs a, x
|
||||||
|
| trashes z, n
|
||||||
|
| {
|
||||||
|
| ld a, 1
|
||||||
|
| save x {
|
||||||
|
| ld a, 2
|
||||||
|
| }
|
||||||
|
| ld a, 3
|
||||||
|
| }
|
||||||
|
= ok
|
||||||
|
|
||||||
|
Saving any location (other than `a`) will trash `a`.
|
||||||
|
|
||||||
|
| routine main
|
||||||
|
| inputs a, x
|
||||||
|
| outputs a, x
|
||||||
|
| trashes z, n
|
||||||
|
| {
|
||||||
|
| ld a, 1
|
||||||
|
| save x {
|
||||||
|
| ld a, 2
|
||||||
|
| }
|
||||||
|
| }
|
||||||
|
? UnmeaningfulOutputError
|
||||||
|
|
||||||
|
Saving `a` does not trash anything.
|
||||||
|
|
||||||
|
| routine main
|
||||||
|
| inputs a, x
|
||||||
|
| outputs a, x
|
||||||
|
| trashes z, n
|
||||||
|
| {
|
||||||
|
| ld x, 1
|
||||||
|
| save a {
|
||||||
|
| ld x, 2
|
||||||
|
| }
|
||||||
|
| ld x, 3
|
||||||
|
| }
|
||||||
|
= ok
|
||||||
|
|
||||||
|
A defined value that has been saved can be trashed inside the block.
|
||||||
|
It will continue to be defined outside the block.
|
||||||
|
|
||||||
|
| routine main
|
||||||
|
| outputs x, y
|
||||||
|
| trashes a, z, n
|
||||||
|
| {
|
||||||
|
| ld x, 0
|
||||||
|
| save x {
|
||||||
|
| ld y, 0
|
||||||
|
| trash x
|
||||||
|
| }
|
||||||
|
| }
|
||||||
|
= ok
|
||||||
|
|
||||||
|
A trashed value that has been saved can be used inside the block.
|
||||||
|
It will continue to be trashed outside the block.
|
||||||
|
|
||||||
|
| routine main
|
||||||
|
| inputs a
|
||||||
|
| outputs a, x
|
||||||
|
| trashes z, n
|
||||||
|
| {
|
||||||
|
| ld x, 0
|
||||||
|
| trash x
|
||||||
|
| save x {
|
||||||
|
| ld a, 0
|
||||||
|
| ld x, 1
|
||||||
|
| }
|
||||||
|
| }
|
||||||
|
? UnmeaningfulOutputError: x
|
||||||
|
|
||||||
|
The known range of a value will be preserved outside the block as well.
|
||||||
|
|
||||||
|
| word one: 77
|
||||||
|
| word table[32] many
|
||||||
|
|
|
||||||
|
| routine main
|
||||||
|
| inputs a, many, one
|
||||||
|
| outputs many, one
|
||||||
|
| trashes a, x, n, z
|
||||||
|
| {
|
||||||
|
| and a, 31
|
||||||
|
| ld x, a
|
||||||
|
| save x {
|
||||||
|
| ld x, 255
|
||||||
|
| }
|
||||||
|
| copy one, many + x
|
||||||
|
| copy many + x, one
|
||||||
|
| }
|
||||||
|
= ok
|
||||||
|
|
||||||
|
| word one: 77
|
||||||
|
| word table[32] many
|
||||||
|
|
|
||||||
|
| routine main
|
||||||
|
| inputs a, many, one
|
||||||
|
| outputs many, one
|
||||||
|
| trashes a, x, n, z
|
||||||
|
| {
|
||||||
|
| and a, 63
|
||||||
|
| ld x, a
|
||||||
|
| save x {
|
||||||
|
| ld x, 1
|
||||||
|
| }
|
||||||
|
| copy one, many + x
|
||||||
|
| copy many + x, one
|
||||||
|
| }
|
||||||
|
? RangeExceededError
|
||||||
|
|
||||||
|
The known properties of a value are preserved inside the block, too.
|
||||||
|
|
||||||
|
| word one: 77
|
||||||
|
| word table[32] many
|
||||||
|
|
|
||||||
|
| routine main
|
||||||
|
| inputs a, many, one
|
||||||
|
| outputs many, one
|
||||||
|
| trashes a, x, n, z
|
||||||
|
| {
|
||||||
|
| and a, 31
|
||||||
|
| ld x, a
|
||||||
|
| save x {
|
||||||
|
| copy one, many + x
|
||||||
|
| copy many + x, one
|
||||||
|
| }
|
||||||
|
| copy one, many + x
|
||||||
|
| copy many + x, one
|
||||||
|
| }
|
||||||
|
= ok
|
||||||
|
|
||||||
|
A value which is not output from the routine, is preserved by the
|
||||||
|
routine; and can appear in a `save` exactly because a `save` preserves it.
|
||||||
|
|
||||||
|
| routine main
|
||||||
|
| outputs y
|
||||||
|
| trashes a, z, n
|
||||||
|
| {
|
||||||
|
| save x {
|
||||||
|
| ld y, 0
|
||||||
|
| ld x, 1
|
||||||
|
| }
|
||||||
|
| }
|
||||||
|
= ok
|
||||||
|
|
||||||
|
Because saving anything except `a` trashes `a`, a common idiom is to save `a`
|
||||||
|
first in a nested series of `save`s.
|
||||||
|
|
||||||
|
| routine main
|
||||||
|
| inputs a
|
||||||
|
| outputs a
|
||||||
|
| trashes z, n
|
||||||
|
| {
|
||||||
|
| save a {
|
||||||
|
| save x {
|
||||||
|
| ld a, 0
|
||||||
|
| ld x, 1
|
||||||
|
| }
|
||||||
|
| }
|
||||||
|
| }
|
||||||
|
= ok
|
||||||
|
|
||||||
|
Not just registers, but also user-defined locations can be saved.
|
||||||
|
|
||||||
|
| byte foo
|
||||||
|
|
|
||||||
|
| routine main
|
||||||
|
| trashes a, z, n
|
||||||
|
| {
|
||||||
|
| save foo {
|
||||||
|
| st 5, foo
|
||||||
|
| }
|
||||||
|
| }
|
||||||
|
= ok
|
||||||
|
|
||||||
|
But only if they are bytes.
|
||||||
|
|
||||||
|
| word foo
|
||||||
|
|
|
||||||
|
| routine main
|
||||||
|
| trashes a, z, n
|
||||||
|
| {
|
||||||
|
| save foo {
|
||||||
|
| copy 555, foo
|
||||||
|
| }
|
||||||
|
| }
|
||||||
|
? TypeMismatchError
|
||||||
|
|
||||||
|
| byte table[16] tab
|
||||||
|
|
|
||||||
|
| routine main
|
||||||
|
| trashes a, y, z, n
|
||||||
|
| {
|
||||||
|
| save tab {
|
||||||
|
| ld y, 0
|
||||||
|
| st 5, tab + y
|
||||||
|
| }
|
||||||
|
| }
|
||||||
|
? TypeMismatchError
|
||||||
|
|
||||||
|
A `goto` cannot appear within a `save` block, even if it is otherwise in tail position.
|
||||||
|
|
||||||
|
| routine other
|
||||||
|
| trashes a, z, n
|
||||||
|
| {
|
||||||
|
| ld a, 0
|
||||||
|
| }
|
||||||
|
|
|
||||||
|
| routine main
|
||||||
|
| trashes a, z, n
|
||||||
|
| {
|
||||||
|
| ld a, 1
|
||||||
|
| save x {
|
||||||
|
| ld x, 2
|
||||||
|
| goto other
|
||||||
|
| }
|
||||||
|
| }
|
||||||
|
? IllegalJumpError
|
||||||
|
|
||||||
|
### with interrupts ###
|
||||||
|
|
||||||
|
| vector routine
|
||||||
|
| inputs x
|
||||||
|
| outputs x
|
||||||
|
| trashes z, n
|
||||||
|
| bar
|
||||||
|
|
|
||||||
|
| routine foo
|
||||||
|
| inputs x
|
||||||
|
| outputs x
|
||||||
|
| trashes z, n
|
||||||
|
| {
|
||||||
|
| inc x
|
||||||
|
| }
|
||||||
|
|
|
||||||
|
| routine main
|
||||||
|
| outputs bar
|
||||||
|
| trashes a, n, z
|
||||||
|
| {
|
||||||
|
| with interrupts off {
|
||||||
|
| copy foo, bar
|
||||||
|
| }
|
||||||
|
| }
|
||||||
|
= ok
|
||||||
|
|
||||||
|
A `goto` cannot appear within a `with interrupts` block, even if it is
|
||||||
|
otherwise in tail position.
|
||||||
|
|
||||||
|
| vector routine
|
||||||
|
| inputs x
|
||||||
|
| outputs x
|
||||||
|
| trashes z, n
|
||||||
|
| bar
|
||||||
|
|
|
||||||
|
| routine foo
|
||||||
|
| inputs x
|
||||||
|
| outputs x
|
||||||
|
| trashes z, n
|
||||||
|
| {
|
||||||
|
| inc x
|
||||||
|
| }
|
||||||
|
|
|
||||||
|
| routine other
|
||||||
|
| trashes bar, a, n, z
|
||||||
|
| {
|
||||||
|
| ld a, 0
|
||||||
|
| }
|
||||||
|
|
|
||||||
|
| routine main
|
||||||
|
| trashes bar, a, n, z
|
||||||
|
| {
|
||||||
|
| with interrupts off {
|
||||||
|
| copy foo, bar
|
||||||
|
| goto other
|
||||||
|
| }
|
||||||
|
| }
|
||||||
|
? IllegalJumpError
|
||||||
|
|
||||||
### copy ###
|
### copy ###
|
||||||
|
|
||||||
Can't `copy` from a memory location that isn't initialized.
|
Can't `copy` from a memory location that isn't initialized.
|
||||||
@ -2114,6 +2456,24 @@ Read through a pointer.
|
|||||||
| }
|
| }
|
||||||
= ok
|
= ok
|
||||||
|
|
||||||
|
Read and write through two pointers.
|
||||||
|
|
||||||
|
| buffer[2048] buf
|
||||||
|
| pointer ptra
|
||||||
|
| pointer ptrb
|
||||||
|
|
|
||||||
|
| routine main
|
||||||
|
| inputs buf
|
||||||
|
| outputs buf
|
||||||
|
| trashes a, y, z, n, ptra, ptrb
|
||||||
|
| {
|
||||||
|
| ld y, 0
|
||||||
|
| copy ^buf, ptra
|
||||||
|
| copy ^buf, ptrb
|
||||||
|
| copy [ptra] + y, [ptrb] + y
|
||||||
|
| }
|
||||||
|
= ok
|
||||||
|
|
||||||
Read through a pointer to the `a` register. Note that this is done with `ld`,
|
Read through a pointer to the `a` register. Note that this is done with `ld`,
|
||||||
not `copy`.
|
not `copy`.
|
||||||
|
|
||||||
@ -2223,21 +2583,30 @@ as an input to, an output of, or as a trashed value of a routine.
|
|||||||
| }
|
| }
|
||||||
? ConstantConstraintError: foo
|
? ConstantConstraintError: foo
|
||||||
|
|
||||||
You can copy the address of a routine into a vector, if that vector is
|
#### routine-vector type compatibility
|
||||||
declared appropriately.
|
|
||||||
|
You can copy the address of a routine into a vector, if that vector type
|
||||||
|
is at least as "wide" as the type of the routine. More specifically,
|
||||||
|
|
||||||
|
- the vector must take _at least_ the inputs that the routine takes
|
||||||
|
- the vector must produce _at least_ the outputs that the routine produces
|
||||||
|
- the vector must trash _at least_ what the routine trashes
|
||||||
|
|
||||||
|
If the vector and the routine have the very same signature, that's not an error.
|
||||||
|
|
||||||
| vector routine
|
| vector routine
|
||||||
| inputs x
|
| inputs x, y
|
||||||
| outputs x
|
| outputs x, y
|
||||||
| trashes z, n
|
| trashes z, n
|
||||||
| vec
|
| vec
|
||||||
|
|
|
|
||||||
| routine foo
|
| routine foo
|
||||||
| inputs x
|
| inputs x, y
|
||||||
| outputs x
|
| outputs x, y
|
||||||
| trashes z, n
|
| trashes z, n
|
||||||
| {
|
| {
|
||||||
| inc x
|
| inc x
|
||||||
|
| inc y
|
||||||
| }
|
| }
|
||||||
|
|
|
|
||||||
| routine main
|
| routine main
|
||||||
@ -2248,20 +2617,48 @@ declared appropriately.
|
|||||||
| }
|
| }
|
||||||
= ok
|
= ok
|
||||||
|
|
||||||
But not if the vector is declared inappropriately.
|
If the vector takes an input that the routine doesn't take, that's not an error.
|
||||||
|
(The interface requires that a parameter be specified before calling, but the
|
||||||
|
implementation doesn't actually read it.)
|
||||||
|
|
||||||
| vector routine
|
| vector routine
|
||||||
| inputs y
|
| inputs x, y, a
|
||||||
| outputs y
|
| outputs x, y
|
||||||
| trashes z, n
|
| trashes z, n
|
||||||
| vec
|
| vec
|
||||||
|
|
|
|
||||||
| routine foo
|
| routine foo
|
||||||
| inputs x
|
| inputs x, y
|
||||||
| outputs x
|
| outputs x, y
|
||||||
| trashes z, n
|
| trashes z, n
|
||||||
| {
|
| {
|
||||||
| inc x
|
| inc x
|
||||||
|
| inc y
|
||||||
|
| }
|
||||||
|
|
|
||||||
|
| routine main
|
||||||
|
| outputs vec
|
||||||
|
| trashes a, z, n
|
||||||
|
| {
|
||||||
|
| copy foo, vec
|
||||||
|
| }
|
||||||
|
= ok
|
||||||
|
|
||||||
|
If the vector fails to take an input that the routine takes, that's an error.
|
||||||
|
|
||||||
|
| vector routine
|
||||||
|
| inputs x
|
||||||
|
| outputs x, y
|
||||||
|
| trashes z, n
|
||||||
|
| vec
|
||||||
|
|
|
||||||
|
| routine foo
|
||||||
|
| inputs x, y
|
||||||
|
| outputs x, y
|
||||||
|
| trashes z, n
|
||||||
|
| {
|
||||||
|
| inc x
|
||||||
|
| inc y
|
||||||
| }
|
| }
|
||||||
|
|
|
|
||||||
| routine main
|
| routine main
|
||||||
@ -2272,21 +2669,24 @@ But not if the vector is declared inappropriately.
|
|||||||
| }
|
| }
|
||||||
? IncompatibleConstraintsError
|
? IncompatibleConstraintsError
|
||||||
|
|
||||||
"Appropriately" means, if the routine affects no more than what is named
|
If the vector produces an output that the routine doesn't produce, that's not an error.
|
||||||
in the input/output sets of the vector.
|
(The interface claims the result of calling the routine is defined, but the implementation
|
||||||
|
actually preserves it instead of changing it; the caller can still treat it as a defined
|
||||||
|
output.)
|
||||||
|
|
||||||
| vector routine
|
| vector routine
|
||||||
| inputs a, x
|
| inputs x, y
|
||||||
| outputs x
|
| outputs x, y, a
|
||||||
| trashes a, z, n
|
| trashes z, n
|
||||||
| vec
|
| vec
|
||||||
|
|
|
|
||||||
| routine foo
|
| routine foo
|
||||||
| inputs x
|
| inputs x, y
|
||||||
| outputs x
|
| outputs x, y
|
||||||
| trashes z, n
|
| trashes z, n
|
||||||
| {
|
| {
|
||||||
| inc x
|
| inc x
|
||||||
|
| inc y
|
||||||
| }
|
| }
|
||||||
|
|
|
|
||||||
| routine main
|
| routine main
|
||||||
@ -2297,6 +2697,86 @@ in the input/output sets of the vector.
|
|||||||
| }
|
| }
|
||||||
= ok
|
= ok
|
||||||
|
|
||||||
|
If the vector fails to produce an output that the routine produces, that's an error.
|
||||||
|
|
||||||
|
| vector routine
|
||||||
|
| inputs x, y
|
||||||
|
| outputs x
|
||||||
|
| trashes z, n
|
||||||
|
| vec
|
||||||
|
|
|
||||||
|
| routine foo
|
||||||
|
| inputs x, y
|
||||||
|
| outputs x, y
|
||||||
|
| trashes z, n
|
||||||
|
| {
|
||||||
|
| inc x
|
||||||
|
| inc y
|
||||||
|
| }
|
||||||
|
|
|
||||||
|
| routine main
|
||||||
|
| outputs vec
|
||||||
|
| trashes a, z, n
|
||||||
|
| {
|
||||||
|
| copy foo, vec
|
||||||
|
| }
|
||||||
|
? IncompatibleConstraintsError
|
||||||
|
|
||||||
|
If the vector fails to trash something the routine trashes, that's an error.
|
||||||
|
|
||||||
|
| vector routine
|
||||||
|
| inputs x, y
|
||||||
|
| outputs x, y
|
||||||
|
| trashes z
|
||||||
|
| vec
|
||||||
|
|
|
||||||
|
| routine foo
|
||||||
|
| inputs x, y
|
||||||
|
| outputs x, y
|
||||||
|
| trashes z, n
|
||||||
|
| {
|
||||||
|
| inc x
|
||||||
|
| inc y
|
||||||
|
| }
|
||||||
|
|
|
||||||
|
| routine main
|
||||||
|
| outputs vec
|
||||||
|
| trashes a, z, n
|
||||||
|
| {
|
||||||
|
| copy foo, vec
|
||||||
|
| }
|
||||||
|
? IncompatibleConstraintsError
|
||||||
|
|
||||||
|
If the vector trashes something the routine doesn't trash, that's not an error.
|
||||||
|
(The implementation preserves something the interface doesn't guarantee is
|
||||||
|
preserved. The caller gets no guarantee that it's preserved. It actually is,
|
||||||
|
but it doesn't know that.)
|
||||||
|
|
||||||
|
| vector routine
|
||||||
|
| inputs x, y
|
||||||
|
| outputs x, y
|
||||||
|
| trashes a, z, n
|
||||||
|
| vec
|
||||||
|
|
|
||||||
|
| routine foo
|
||||||
|
| inputs x, y
|
||||||
|
| outputs x, y
|
||||||
|
| trashes z, n
|
||||||
|
| {
|
||||||
|
| inc x
|
||||||
|
| inc y
|
||||||
|
| }
|
||||||
|
|
|
||||||
|
| routine main
|
||||||
|
| outputs vec
|
||||||
|
| trashes a, z, n
|
||||||
|
| {
|
||||||
|
| copy foo, vec
|
||||||
|
| }
|
||||||
|
= ok
|
||||||
|
|
||||||
|
#### other properties of routines
|
||||||
|
|
||||||
Routines are read-only.
|
Routines are read-only.
|
||||||
|
|
||||||
| vector routine
|
| vector routine
|
||||||
|
@ -7,7 +7,7 @@ SixtyPical to 6502 machine code.
|
|||||||
[Falderal]: http://catseye.tc/node/Falderal
|
[Falderal]: http://catseye.tc/node/Falderal
|
||||||
|
|
||||||
-> Functionality "Compile SixtyPical program" is implemented by
|
-> Functionality "Compile SixtyPical program" is implemented by
|
||||||
-> shell command "bin/sixtypical --prelude=c64 --traceback %(test-body-file) >/tmp/foo && tests/appliances/bin/dcc6502-adapter </tmp/foo"
|
-> shell command "bin/sixtypical --output-format=c64-basic-prg --traceback %(test-body-file) >/tmp/foo && tests/appliances/bin/dcc6502-adapter </tmp/foo"
|
||||||
|
|
||||||
-> Tests for functionality "Compile SixtyPical program"
|
-> Tests for functionality "Compile SixtyPical program"
|
||||||
|
|
||||||
@ -112,7 +112,8 @@ Memory location with explicit address.
|
|||||||
= $080F STA $0400
|
= $080F STA $0400
|
||||||
= $0812 RTS
|
= $0812 RTS
|
||||||
|
|
||||||
Accesses to memory locations in zero-page with `ld` and `st` use zero-page addressing.
|
Accesses to memory locations in zero-page with `ld` and `st`
|
||||||
|
and `and`, `or`, and `xor` use zero-page addressing.
|
||||||
|
|
||||||
| byte zp @ $00
|
| byte zp @ $00
|
||||||
| byte screen @ 100
|
| byte screen @ 100
|
||||||
@ -126,12 +127,18 @@ Accesses to memory locations in zero-page with `ld` and `st` use zero-page addre
|
|||||||
| st a, screen
|
| st a, screen
|
||||||
| ld a, zp
|
| ld a, zp
|
||||||
| st a, zp
|
| st a, zp
|
||||||
|
| and a, zp
|
||||||
|
| or a, zp
|
||||||
|
| xor a, zp
|
||||||
| }
|
| }
|
||||||
= $080D LDA $64
|
= $080D LDA $64
|
||||||
= $080F STA $64
|
= $080F STA $64
|
||||||
= $0811 LDA $00
|
= $0811 LDA $00
|
||||||
= $0813 STA $00
|
= $0813 STA $00
|
||||||
= $0815 RTS
|
= $0815 AND $00
|
||||||
|
= $0817 ORA $00
|
||||||
|
= $0819 EOR $00
|
||||||
|
= $081B RTS
|
||||||
|
|
||||||
Memory location with initial value.
|
Memory location with initial value.
|
||||||
|
|
||||||
@ -213,7 +220,7 @@ Initialized byte table, initialized with list of byte values.
|
|||||||
|
|
||||||
Initialized word table, initialized with list of word values.
|
Initialized word table, initialized with list of word values.
|
||||||
|
|
||||||
| word table[8] message : 65535, 0, 127
|
| word table[4] message : 65535, 0, 127, 127
|
||||||
|
|
|
|
||||||
| routine main
|
| routine main
|
||||||
| {
|
| {
|
||||||
@ -225,7 +232,7 @@ Initialized word table, initialized with list of word values.
|
|||||||
= $0811 BRK
|
= $0811 BRK
|
||||||
= $0812 .byte $7F
|
= $0812 .byte $7F
|
||||||
= $0813 BRK
|
= $0813 BRK
|
||||||
= $0814 BRK
|
= $0814 .byte $7F
|
||||||
= $0815 BRK
|
= $0815 BRK
|
||||||
|
|
||||||
Some instructions.
|
Some instructions.
|
||||||
@ -267,40 +274,116 @@ Some instructions.
|
|||||||
| cmp y, foo
|
| cmp y, foo
|
||||||
| shl a
|
| shl a
|
||||||
| shr a
|
| shr a
|
||||||
|
| shl foo
|
||||||
|
| shr foo
|
||||||
| }
|
| }
|
||||||
= $080D LDA #$00
|
= $080D LDA #$00
|
||||||
= $080F LDX #$00
|
= $080F LDX #$00
|
||||||
= $0811 LDY #$00
|
= $0811 LDY #$00
|
||||||
= $0813 STA $0853
|
= $0813 STA $0859
|
||||||
= $0816 STX $0853
|
= $0816 STX $0859
|
||||||
= $0819 STY $0853
|
= $0819 STY $0859
|
||||||
= $081C SEC
|
= $081C SEC
|
||||||
= $081D CLC
|
= $081D CLC
|
||||||
= $081E ADC #$01
|
= $081E ADC #$01
|
||||||
= $0820 ADC $0853
|
= $0820 ADC $0859
|
||||||
= $0823 SBC #$01
|
= $0823 SBC #$01
|
||||||
= $0825 SBC $0853
|
= $0825 SBC $0859
|
||||||
= $0828 INC $0853
|
= $0828 INC $0859
|
||||||
= $082B INX
|
= $082B INX
|
||||||
= $082C INY
|
= $082C INY
|
||||||
= $082D DEC $0853
|
= $082D DEC $0859
|
||||||
= $0830 DEX
|
= $0830 DEX
|
||||||
= $0831 DEY
|
= $0831 DEY
|
||||||
= $0832 AND #$FF
|
= $0832 AND #$FF
|
||||||
= $0834 AND $0853
|
= $0834 AND $0859
|
||||||
= $0837 ORA #$FF
|
= $0837 ORA #$FF
|
||||||
= $0839 ORA $0853
|
= $0839 ORA $0859
|
||||||
= $083C EOR #$FF
|
= $083C EOR #$FF
|
||||||
= $083E EOR $0853
|
= $083E EOR $0859
|
||||||
= $0841 CMP #$01
|
= $0841 CMP #$01
|
||||||
= $0843 CMP $0853
|
= $0843 CMP $0859
|
||||||
= $0846 CPX #$01
|
= $0846 CPX #$01
|
||||||
= $0848 CPX $0853
|
= $0848 CPX $0859
|
||||||
= $084B CPY #$01
|
= $084B CPY #$01
|
||||||
= $084D CPY $0853
|
= $084D CPY $0859
|
||||||
= $0850 ROL A
|
= $0850 ROL A
|
||||||
= $0851 ROR A
|
= $0851 ROR A
|
||||||
= $0852 RTS
|
= $0852 ROL $0859
|
||||||
|
= $0855 ROR $0859
|
||||||
|
= $0858 RTS
|
||||||
|
|
||||||
|
Some instructions on tables. (1/3)
|
||||||
|
|
||||||
|
| byte table[256] many
|
||||||
|
|
|
||||||
|
| routine main
|
||||||
|
| inputs many
|
||||||
|
| outputs many
|
||||||
|
| trashes a, x, c, n, z, v
|
||||||
|
| {
|
||||||
|
| ld x, 0
|
||||||
|
| ld a, 0
|
||||||
|
| st off, c
|
||||||
|
| add a, many + x
|
||||||
|
| sub a, many + x
|
||||||
|
| cmp a, many + x
|
||||||
|
| }
|
||||||
|
= $080D LDX #$00
|
||||||
|
= $080F LDA #$00
|
||||||
|
= $0811 CLC
|
||||||
|
= $0812 ADC $081C,X
|
||||||
|
= $0815 SBC $081C,X
|
||||||
|
= $0818 CMP $081C,X
|
||||||
|
= $081B RTS
|
||||||
|
|
||||||
|
Some instructions on tables. (2/3)
|
||||||
|
|
||||||
|
| byte table[256] many
|
||||||
|
|
|
||||||
|
| routine main
|
||||||
|
| inputs many
|
||||||
|
| outputs many
|
||||||
|
| trashes a, x, c, n, z
|
||||||
|
| {
|
||||||
|
| ld x, 0
|
||||||
|
| ld a, 0
|
||||||
|
| and a, many + x
|
||||||
|
| or a, many + x
|
||||||
|
| xor a, many + x
|
||||||
|
| }
|
||||||
|
= $080D LDX #$00
|
||||||
|
= $080F LDA #$00
|
||||||
|
= $0811 AND $081B,X
|
||||||
|
= $0814 ORA $081B,X
|
||||||
|
= $0817 EOR $081B,X
|
||||||
|
= $081A RTS
|
||||||
|
|
||||||
|
Some instructions on tables. (3/3)
|
||||||
|
|
||||||
|
| byte table[256] many
|
||||||
|
|
|
||||||
|
| routine main
|
||||||
|
| inputs many
|
||||||
|
| outputs many
|
||||||
|
| trashes a, x, c, n, z
|
||||||
|
| {
|
||||||
|
| ld x, 0
|
||||||
|
| ld a, 0
|
||||||
|
| st off, c
|
||||||
|
| shl many + x
|
||||||
|
| shr many + x
|
||||||
|
| inc many + x
|
||||||
|
| dec many + x
|
||||||
|
| }
|
||||||
|
= $080D LDX #$00
|
||||||
|
= $080F LDA #$00
|
||||||
|
= $0811 CLC
|
||||||
|
= $0812 ROL $081F,X
|
||||||
|
= $0815 ROR $081F,X
|
||||||
|
= $0818 INC $081F,X
|
||||||
|
= $081B DEC $081F,X
|
||||||
|
= $081E RTS
|
||||||
|
|
||||||
Compiling `if`.
|
Compiling `if`.
|
||||||
|
|
||||||
@ -494,6 +577,49 @@ Compiling `for ... down to`.
|
|||||||
= $0815 BNE $080F
|
= $0815 BNE $080F
|
||||||
= $0817 RTS
|
= $0817 RTS
|
||||||
|
|
||||||
|
Compiling `save`.
|
||||||
|
|
||||||
|
| routine main
|
||||||
|
| inputs a
|
||||||
|
| outputs a
|
||||||
|
| trashes z, n
|
||||||
|
| {
|
||||||
|
| save a {
|
||||||
|
| save x {
|
||||||
|
| ld a, 0
|
||||||
|
| ld x, 1
|
||||||
|
| }
|
||||||
|
| }
|
||||||
|
| }
|
||||||
|
= $080D PHA
|
||||||
|
= $080E TXA
|
||||||
|
= $080F PHA
|
||||||
|
= $0810 LDA #$00
|
||||||
|
= $0812 LDX #$01
|
||||||
|
= $0814 PLA
|
||||||
|
= $0815 TAX
|
||||||
|
= $0816 PLA
|
||||||
|
= $0817 RTS
|
||||||
|
|
||||||
|
Compiling `save` on a user-defined location.
|
||||||
|
|
||||||
|
| byte foo
|
||||||
|
| routine main
|
||||||
|
| trashes a, z, n
|
||||||
|
| {
|
||||||
|
| save foo {
|
||||||
|
| ld a, 0
|
||||||
|
| st a, foo
|
||||||
|
| }
|
||||||
|
| }
|
||||||
|
= $080D LDA $081B
|
||||||
|
= $0810 PHA
|
||||||
|
= $0811 LDA #$00
|
||||||
|
= $0813 STA $081B
|
||||||
|
= $0816 PLA
|
||||||
|
= $0817 STA $081B
|
||||||
|
= $081A RTS
|
||||||
|
|
||||||
Indexed access.
|
Indexed access.
|
||||||
|
|
||||||
| byte one
|
| byte one
|
||||||
@ -514,7 +640,7 @@ Indexed access.
|
|||||||
= $0814 LDA $0819,X
|
= $0814 LDA $0819,X
|
||||||
= $0817 RTS
|
= $0817 RTS
|
||||||
|
|
||||||
Byte tables take up 256 bytes in memory.
|
Byte tables take up, at most, 256 bytes in memory.
|
||||||
|
|
||||||
| byte table[256] tab1
|
| byte table[256] tab1
|
||||||
| byte table[256] tab2
|
| byte table[256] tab2
|
||||||
@ -1030,6 +1156,35 @@ Read through a pointer, into a byte storage location, or the `a` register.
|
|||||||
= $081C LDA ($FE),Y
|
= $081C LDA ($FE),Y
|
||||||
= $081E RTS
|
= $081E RTS
|
||||||
|
|
||||||
|
Read and write through two pointers.
|
||||||
|
|
||||||
|
| buffer[2048] buf
|
||||||
|
| pointer ptra @ 252
|
||||||
|
| pointer ptrb @ 254
|
||||||
|
|
|
||||||
|
| routine main
|
||||||
|
| inputs buf
|
||||||
|
| outputs buf
|
||||||
|
| trashes a, y, z, n, ptra, ptrb
|
||||||
|
| {
|
||||||
|
| ld y, 0
|
||||||
|
| copy ^buf, ptra
|
||||||
|
| copy ^buf, ptrb
|
||||||
|
| copy [ptra] + y, [ptrb] + y
|
||||||
|
| }
|
||||||
|
= $080D LDY #$00
|
||||||
|
= $080F LDA #$24
|
||||||
|
= $0811 STA $FC
|
||||||
|
= $0813 LDA #$08
|
||||||
|
= $0815 STA $FD
|
||||||
|
= $0817 LDA #$24
|
||||||
|
= $0819 STA $FE
|
||||||
|
= $081B LDA #$08
|
||||||
|
= $081D STA $FF
|
||||||
|
= $081F LDA ($FC),Y
|
||||||
|
= $0821 STA ($FE),Y
|
||||||
|
= $0823 RTS
|
||||||
|
|
||||||
Write the `a` register through a pointer.
|
Write the `a` register through a pointer.
|
||||||
|
|
||||||
| buffer[2048] buf
|
| buffer[2048] buf
|
||||||
|
@ -65,7 +65,7 @@ to pass these tests to be considered an implementation of SixtyPical.
|
|||||||
-> shell command "bin/sixtypical --optimize-fallthru --dump-fallthru-info --analyze-only --traceback %(test-body-file)"
|
-> shell command "bin/sixtypical --optimize-fallthru --dump-fallthru-info --analyze-only --traceback %(test-body-file)"
|
||||||
|
|
||||||
-> Functionality "Compile SixtyPical program with fallthru optimization" is implemented by
|
-> Functionality "Compile SixtyPical program with fallthru optimization" is implemented by
|
||||||
-> shell command "bin/sixtypical --prelude=c64 --optimize-fallthru --traceback %(test-body-file) >/tmp/foo && tests/appliances/bin/dcc6502-adapter </tmp/foo"
|
-> shell command "bin/sixtypical --output-format=c64-basic-prg --optimize-fallthru --traceback %(test-body-file) >/tmp/foo && tests/appliances/bin/dcc6502-adapter </tmp/foo"
|
||||||
|
|
||||||
-> Tests for functionality "Dump fallthru info for SixtyPical program"
|
-> Tests for functionality "Dump fallthru info for SixtyPical program"
|
||||||
|
|
||||||
|
@ -29,6 +29,12 @@ Program with comments.
|
|||||||
| routine main {
|
| routine main {
|
||||||
| ld a, 0
|
| ld a, 0
|
||||||
| add a, 1 // We are adding the thing.
|
| add a, 1 // We are adding the thing.
|
||||||
|
| sub a, 1
|
||||||
|
| shl a
|
||||||
|
| shr a
|
||||||
|
| and a, 1
|
||||||
|
| or a, 1
|
||||||
|
| xor a, 1
|
||||||
| }
|
| }
|
||||||
= ok
|
= ok
|
||||||
|
|
||||||
@ -155,6 +161,20 @@ Basic "open-faced for" loops, up and down.
|
|||||||
| }
|
| }
|
||||||
= ok
|
= ok
|
||||||
|
|
||||||
|
Other blocks.
|
||||||
|
|
||||||
|
| routine main trashes a, x, c, z, v {
|
||||||
|
| with interrupts off {
|
||||||
|
| save a, x, c {
|
||||||
|
| ld a, 0
|
||||||
|
| }
|
||||||
|
| }
|
||||||
|
| save a, x, c {
|
||||||
|
| ld a, 0
|
||||||
|
| }
|
||||||
|
| }
|
||||||
|
= ok
|
||||||
|
|
||||||
User-defined memory addresses of different types.
|
User-defined memory addresses of different types.
|
||||||
|
|
||||||
| byte byt
|
| byte byt
|
||||||
@ -167,13 +187,26 @@ User-defined memory addresses of different types.
|
|||||||
| }
|
| }
|
||||||
= ok
|
= ok
|
||||||
|
|
||||||
Tables of different types.
|
Tables of different types and some operations on them.
|
||||||
|
|
||||||
| byte table[256] tab
|
| byte table[256] many
|
||||||
| word table[256] wtab
|
| word table[256] wmany
|
||||||
| vector (routine trashes a) table[256] vtab
|
| vector (routine trashes a) table[256] vmany
|
||||||
|
|
|
|
||||||
| routine main {
|
| routine main {
|
||||||
|
| ld x, 0
|
||||||
|
| ld a, 0
|
||||||
|
| st off, c
|
||||||
|
| add a, many + x
|
||||||
|
| sub a, many + x
|
||||||
|
| cmp a, many + x
|
||||||
|
| and a, many + x
|
||||||
|
| or a, many + x
|
||||||
|
| xor a, many + x
|
||||||
|
| shl many + x
|
||||||
|
| shr many + x
|
||||||
|
| inc many + x
|
||||||
|
| dec many + x
|
||||||
| }
|
| }
|
||||||
= ok
|
= ok
|
||||||
|
|
||||||
@ -268,6 +301,8 @@ Explicit memory address.
|
|||||||
| routine main {
|
| routine main {
|
||||||
| ld a, 100
|
| ld a, 100
|
||||||
| st a, screen
|
| st a, screen
|
||||||
|
| shl screen
|
||||||
|
| shr screen
|
||||||
| }
|
| }
|
||||||
= ok
|
= ok
|
||||||
|
|
||||||
@ -565,12 +600,14 @@ Buffers and pointers.
|
|||||||
|
|
||||||
| buffer[2048] buf
|
| buffer[2048] buf
|
||||||
| pointer ptr
|
| pointer ptr
|
||||||
|
| pointer ptrb
|
||||||
| byte foo
|
| byte foo
|
||||||
|
|
|
|
||||||
| routine main {
|
| routine main {
|
||||||
| copy ^buf, ptr
|
| copy ^buf, ptr
|
||||||
| copy 123, [ptr] + y
|
| copy 123, [ptr] + y
|
||||||
| copy [ptr] + y, foo
|
| copy [ptr] + y, foo
|
||||||
|
| copy [ptr] + y, [ptrb] + y
|
||||||
| }
|
| }
|
||||||
= ok
|
= ok
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
# script that allows the binary output of sixtypical --prelude=c64 --compile to be
|
# script that allows the binary output of sixtypical --output-format=c64-basic-prg --compile to be
|
||||||
# disassembled by https://github.com/tcarmelveilleux/dcc6502
|
# disassembled by https://github.com/tcarmelveilleux/dcc6502
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
Loading…
x
Reference in New Issue
Block a user