mirror of
https://github.com/catseye/SixtyPical.git
synced 2025-01-10 02:29:23 +00:00
commit
642ca138a3
17
HISTORY.md
17
HISTORY.md
@ -1,6 +1,23 @@
|
||||
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
|
||||
----
|
||||
|
||||
|
124
README.md
124
README.md
@ -1,7 +1,7 @@
|
||||
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
|
||||
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
|
||||
game(-like program); see [eg/README.md](eg/README.md) for a listing.
|
||||
|
||||
[VICE]: http://vice-emu.sourceforge.net/
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
@ -65,30 +67,112 @@ Documentation
|
||||
* [Literate test suite for SixtyPical compilation](tests/SixtyPical%20Compilation.md)
|
||||
* [Literate test suite for SixtyPical fallthru optimization](tests/SixtyPical%20Fallthru.md)
|
||||
* [6502 Opcodes used/not used in SixtyPical](doc/6502%20Opcodes.md)
|
||||
* [Output formats supported by `sixtypical`](doc/Output%20Formats.md)
|
||||
|
||||
TODO
|
||||
----
|
||||
|
||||
### Save registers on stack
|
||||
### `low` and `high` address operators
|
||||
|
||||
This preserves them, so that, semantically, they can be used later even though they
|
||||
are trashed inside the block.
|
||||
To turn `word` type into `byte`.
|
||||
|
||||
### 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`.
|
||||
* Tests, and implementation, ensuring a routine can be assigned to a vector of "wider" type
|
||||
* Related: can we simply view a (small) part of a buffer as a byte table? If not, why not?
|
||||
* Related: add constant to buffer to get new buffer. (Or to table, but... well, maybe.)
|
||||
* Check that the buffer being read or written to through pointer, appears in appropriate inputs or outputs set.
|
||||
(Associate each pointer with the buffer it points into.)
|
||||
* `static` pointers -- currently not possible because pointers must be zero-page, thus `@`, thus uninitialized.
|
||||
* Question the value of the "consistent initialization" principle for `if` statement analysis.
|
||||
* `interrupt` routines -- to indicate that "the supervisor" has stored values on the stack, so we can trash them.
|
||||
* 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.
|
||||
The problem is that once a byte is extracted, putting it back into a word is awkward.
|
||||
The address operators have to modify a destination in a special way. That is, when
|
||||
you say `st a, >word`, you are updating `word` to be `word & $ff | a << 8`, somelike.
|
||||
Is that consistent with `st`? Well, probably it is, but we have to explain it.
|
||||
It might make more sense, then, for it to be "part of the operation" instead of "part of
|
||||
the reference"; something like `st.hi x, word`; `st.lo y, word`. Dunno.
|
||||
|
||||
[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
|
||||
|
||||
if options.origin.startswith('0x'):
|
||||
start_addr = int(options.origin, 16)
|
||||
else:
|
||||
start_addr = int(options.origin, 10)
|
||||
|
||||
output_format = options.output_format
|
||||
|
||||
prelude = []
|
||||
if options.prelude == 'c64':
|
||||
output_format = 'prg'
|
||||
if options.output_format == 'raw':
|
||||
start_addr = 0x0000
|
||||
prelude = []
|
||||
elif options.output_format == 'prg':
|
||||
start_addr = 0xc000
|
||||
prelude = []
|
||||
elif options.output_format == 'c64-basic-prg':
|
||||
start_addr = 0x0801
|
||||
prelude = [0x10, 0x08, 0xc9, 0x07, 0x9e, 0x32,
|
||||
0x30, 0x36, 0x31, 0x00, 0x00, 0x00]
|
||||
elif options.prelude == 'vic20':
|
||||
output_format = 'prg'
|
||||
elif options.output_format == 'vic20-basic-prg':
|
||||
start_addr = 0x1001
|
||||
prelude = [0x0b, 0x10, 0xc9, 0x07, 0x9e, 0x34,
|
||||
0x31, 0x30, 0x39, 0x00, 0x00, 0x00]
|
||||
elif options.prelude == 'atari2600':
|
||||
output_format = 'crtbb'
|
||||
elif options.output_format == 'atari2600-cart':
|
||||
start_addr = 0xf000
|
||||
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:
|
||||
raise NotImplementedError("Unknown prelude: {}".format(options.prelude))
|
||||
if options.origin is not None:
|
||||
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.
|
||||
# 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))
|
||||
|
||||
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
|
||||
# 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.emit(Word(start_addr))
|
||||
emitter.emit(Word(start_addr))
|
||||
@ -141,21 +141,15 @@ if __name__ == '__main__':
|
||||
)
|
||||
|
||||
argparser.add_argument(
|
||||
"--origin", type=str, default='0xc000',
|
||||
"--origin", type=str, default=None,
|
||||
help="Location in memory where the `main` routine will be "
|
||||
"located. Default: 0xc000."
|
||||
"located. Default: depends on output format."
|
||||
)
|
||||
argparser.add_argument(
|
||||
"--output-format", type=str, default='prg',
|
||||
help="Executable format to produce. Options are: prg, crtbb. "
|
||||
"Default: prg."
|
||||
)
|
||||
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."
|
||||
"--output-format", type=str, default='raw',
|
||||
help="Executable format to produce; also sets a default origin. "
|
||||
"Options are: raw, prg, c64-basic-prg, vic20-basic-prg, atari2600-cart."
|
||||
"Default: raw."
|
||||
)
|
||||
|
||||
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
|
||||
static byte save_x : 0
|
||||
{
|
||||
ld x, 0
|
||||
repeat {
|
||||
for x up to 15 {
|
||||
copy actor_pos + x, pos
|
||||
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
|
||||
call dispatch_logic
|
||||
|
||||
if c {
|
||||
// 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
|
||||
if c {
|
||||
// Player died! Want no dead!
|
||||
call clear_screen
|
||||
copy game_state_game_over, dispatch_game_state
|
||||
}
|
||||
}
|
||||
|
||||
copy pos, actor_pos + x
|
||||
copy delta, actor_delta + x
|
||||
|
||||
inc x
|
||||
cmp x, 16
|
||||
} until z
|
||||
}
|
||||
|
||||
goto save_cinv
|
||||
}
|
||||
|
@ -5,21 +5,21 @@ usage="Usage: loadngo.sh (c64|vic20|atari2600) [--dry-run] <source.60p>"
|
||||
arch="$1"
|
||||
shift 1
|
||||
if [ "X$arch" = "Xc64" ]; then
|
||||
prelude='c64'
|
||||
output_format='c64-basic-prg'
|
||||
if [ -e vicerc ]; then
|
||||
emu="x64 -config vicerc"
|
||||
else
|
||||
emu="x64"
|
||||
fi
|
||||
elif [ "X$arch" = "Xvic20" ]; then
|
||||
prelude='vic20'
|
||||
output_format='vic20-basic-prg'
|
||||
if [ -e vicerc ]; then
|
||||
emu="xvic -config vicerc"
|
||||
else
|
||||
emu="xvic"
|
||||
fi
|
||||
elif [ "X$arch" = "Xatari2600" ]; then
|
||||
prelude='atari2600'
|
||||
output_format='atari2600-cart'
|
||||
emu='stella'
|
||||
else
|
||||
echo $usage && exit 1
|
||||
@ -38,7 +38,7 @@ fi
|
||||
### do it ###
|
||||
|
||||
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
|
||||
$emu $out
|
||||
rm -f $out
|
||||
|
@ -1,6 +1,6 @@
|
||||
# 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 (
|
||||
TYPE_BYTE, TYPE_WORD,
|
||||
TableType, BufferType, PointerType, VectorType, RoutineType,
|
||||
@ -269,7 +269,7 @@ class Context(object):
|
||||
self._writeable.remove(ref)
|
||||
|
||||
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:
|
||||
self._writeable.add(ref)
|
||||
|
||||
@ -279,6 +279,54 @@ class Context(object):
|
||||
def encountered_gotos(self):
|
||||
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):
|
||||
|
||||
@ -287,9 +335,9 @@ class Analyzer(object):
|
||||
self.routines = {}
|
||||
self.debug = debug
|
||||
|
||||
def assert_type(self, type, *locations):
|
||||
def assert_type(self, type_, *locations):
|
||||
for location in locations:
|
||||
if location.type != type:
|
||||
if location.type != type_:
|
||||
raise TypeMismatchError(self.current_routine, location.name)
|
||||
|
||||
def assert_affected_within(self, name, affecting_type, limiting_type):
|
||||
@ -372,6 +420,10 @@ class Analyzer(object):
|
||||
self.analyze_for(instr, context)
|
||||
elif isinstance(instr, WithInterruptsOff):
|
||||
self.analyze_block(instr.block, context)
|
||||
if context.encountered_gotos():
|
||||
raise IllegalJumpError(instr, instr)
|
||||
elif isinstance(instr, Save):
|
||||
self.analyze_save(instr, context)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
@ -386,12 +438,7 @@ class Analyzer(object):
|
||||
|
||||
if opcode == 'ld':
|
||||
if isinstance(src, IndexedRef):
|
||||
if TableType.is_a_table_type(src.ref.type, TYPE_BYTE) and dest.type == 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)
|
||||
context.assert_types_for_read_table(instr, src, dest, TYPE_BYTE)
|
||||
elif isinstance(src, IndirectRef):
|
||||
# copying this analysis from the matching branch in `copy`, below
|
||||
if isinstance(src.ref.type, PointerType) and dest.type == TYPE_BYTE:
|
||||
@ -407,13 +454,9 @@ class Analyzer(object):
|
||||
context.set_written(dest, FLAG_Z, FLAG_N)
|
||||
elif opcode == 'st':
|
||||
if isinstance(dest, IndexedRef):
|
||||
if src.type == TYPE_BYTE and TableType.is_a_table_type(dest.ref.type, TYPE_BYTE):
|
||||
pass
|
||||
else:
|
||||
if src.type != TYPE_BYTE:
|
||||
raise TypeMismatchError(instr, (src, dest))
|
||||
context.assert_meaningful(dest.index)
|
||||
context.assert_in_range(dest.index, dest.ref)
|
||||
context.set_written(dest.ref)
|
||||
context.assert_types_for_update_table(instr, dest, TYPE_BYTE)
|
||||
elif isinstance(dest, IndirectRef):
|
||||
# copying this analysis from the matching branch in `copy`, below
|
||||
if isinstance(dest.ref.type, PointerType) and src.type == TYPE_BYTE:
|
||||
@ -423,67 +466,88 @@ class Analyzer(object):
|
||||
context.assert_meaningful(dest.ref, REG_Y)
|
||||
context.set_written(dest.ref)
|
||||
elif src.type != dest.type:
|
||||
raise TypeMismatchError(instr, '{} and {}'.format(src, name))
|
||||
raise TypeMismatchError(instr, '{} and {}'.format(src, dest))
|
||||
else:
|
||||
context.set_written(dest)
|
||||
# FIXME: context.copy_range(src, dest) ?
|
||||
context.assert_meaningful(src)
|
||||
elif opcode == 'add':
|
||||
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)
|
||||
context.set_written(dest, FLAG_Z, FLAG_N, FLAG_C, FLAG_V)
|
||||
else:
|
||||
self.assert_type(TYPE_WORD, src)
|
||||
if dest.type == TYPE_WORD:
|
||||
context.set_written(dest, FLAG_Z, FLAG_N, FLAG_C, FLAG_V)
|
||||
context.set_touched(REG_A)
|
||||
context.set_unmeaningful(REG_A)
|
||||
elif isinstance(dest.type, PointerType):
|
||||
context.set_written(dest, FLAG_Z, FLAG_N, FLAG_C, FLAG_V)
|
||||
context.set_touched(REG_A)
|
||||
context.set_unmeaningful(REG_A)
|
||||
else:
|
||||
self.assert_type(TYPE_WORD, dest)
|
||||
context.set_written(dest, FLAG_Z, FLAG_N, FLAG_C, FLAG_V)
|
||||
context.invalidate_range(dest)
|
||||
elif opcode == 'sub':
|
||||
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)
|
||||
context.set_written(dest, FLAG_Z, FLAG_N, FLAG_C, FLAG_V)
|
||||
else:
|
||||
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_unmeaningful(REG_A)
|
||||
context.invalidate_range(dest)
|
||||
elif opcode in ('inc', 'dec'):
|
||||
self.assert_type(TYPE_BYTE, dest)
|
||||
context.assert_meaningful(dest)
|
||||
context.set_written(dest, FLAG_Z, FLAG_N)
|
||||
context.set_written(dest, FLAG_Z, FLAG_N, FLAG_C, FLAG_V)
|
||||
context.invalidate_range(dest)
|
||||
elif opcode == 'cmp':
|
||||
self.assert_type(TYPE_BYTE, 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)
|
||||
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.set_written(dest, FLAG_Z, FLAG_N)
|
||||
# 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
|
||||
# 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'):
|
||||
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.set_written(dest, FLAG_Z, FLAG_N)
|
||||
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'):
|
||||
self.assert_type(TYPE_BYTE, dest)
|
||||
context.assert_meaningful(dest, FLAG_C)
|
||||
context.set_written(dest, FLAG_Z, FLAG_N, FLAG_C)
|
||||
context.invalidate_range(dest)
|
||||
if isinstance(dest, IndexedRef):
|
||||
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':
|
||||
type = instr.location.type
|
||||
if isinstance(type, VectorType):
|
||||
@ -517,6 +581,11 @@ class Analyzer(object):
|
||||
pass
|
||||
else:
|
||||
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):
|
||||
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)
|
||||
elif isinstance(src, IndirectRef) and isinstance(dest, LocationRef):
|
||||
context.assert_meaningful(src.ref, REG_Y)
|
||||
# TODO more sophisticated?
|
||||
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):
|
||||
context.assert_meaningful(src, dest.ref, dest.index)
|
||||
context.set_written(dest.ref)
|
||||
@ -695,3 +769,21 @@ class Analyzer(object):
|
||||
# after it is executed, we know the range of the loop variable.
|
||||
context.set_range(instr.dest, instr.final, instr.final)
|
||||
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):
|
||||
child_attrs = ('block',)
|
||||
|
||||
|
||||
class Save(Instr):
|
||||
value_attrs = ('locations',)
|
||||
child_attrs = ('block',)
|
||||
|
@ -1,6 +1,6 @@
|
||||
# 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 (
|
||||
ConstantRef, LocationRef, IndexedRef, IndirectRef, AddressRef,
|
||||
TYPE_BIT, TYPE_BYTE, TYPE_WORD,
|
||||
@ -12,6 +12,7 @@ from sixtypical.gen6502 import (
|
||||
Immediate, Absolute, AbsoluteX, AbsoluteY, ZeroPage, Indirect, IndirectY, Relative,
|
||||
LDA, LDX, LDY, STA, STX, STY,
|
||||
TAX, TAY, TXA, TYA,
|
||||
PHA, PLA,
|
||||
CLC, SEC, ADC, SBC, ROL, ROR,
|
||||
INC, INX, INY, DEC, DEX, DEY,
|
||||
CMP, CPX, CPY, AND, ORA, EOR,
|
||||
@ -169,6 +170,8 @@ class Compiler(object):
|
||||
return self.compile_for(instr)
|
||||
elif isinstance(instr, WithInterruptsOff):
|
||||
return self.compile_with_interrupts_off(instr)
|
||||
elif isinstance(instr, Save):
|
||||
return self.compile_save(instr)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
@ -244,6 +247,8 @@ class Compiler(object):
|
||||
if dest == REG_A:
|
||||
if isinstance(src, ConstantRef):
|
||||
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:
|
||||
self.emitter.emit(ADC(Absolute(self.get_label(src.name))))
|
||||
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 isinstance(src, ConstantRef):
|
||||
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:
|
||||
self.emitter.emit(SBC(Absolute(self.get_label(src.name))))
|
||||
elif isinstance(dest, LocationRef) and src.type == TYPE_WORD and dest.type == TYPE_WORD:
|
||||
@ -316,10 +323,6 @@ class Compiler(object):
|
||||
raise UnsupportedOpcodeError(instr)
|
||||
else:
|
||||
raise UnsupportedOpcodeError(instr)
|
||||
elif opcode == 'inc':
|
||||
self.compile_inc(instr, instr.dest)
|
||||
elif opcode == 'dec':
|
||||
self.compile_dec(instr, instr.dest)
|
||||
elif opcode == 'cmp':
|
||||
self.compile_cmp(instr, instr.src, instr.dest)
|
||||
elif opcode in ('and', 'or', 'xor',):
|
||||
@ -331,10 +334,16 @@ class Compiler(object):
|
||||
if dest == REG_A:
|
||||
if isinstance(src, ConstantRef):
|
||||
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:
|
||||
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:
|
||||
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'):
|
||||
cls = {
|
||||
'shl': ROL,
|
||||
@ -342,8 +351,10 @@ class Compiler(object):
|
||||
}[opcode]
|
||||
if dest == REG_A:
|
||||
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:
|
||||
raise UnsupportedOpcodeError(instr)
|
||||
self.emitter.emit(cls(self.absolute_or_zero_page(self.get_label(dest.name))))
|
||||
elif opcode == 'call':
|
||||
location = instr.location
|
||||
label = self.get_label(instr.location.name)
|
||||
@ -389,6 +400,9 @@ class Compiler(object):
|
||||
raise UnsupportedOpcodeError(instr)
|
||||
if isinstance(src, ConstantRef):
|
||||
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:
|
||||
self.emitter.emit(cls(Absolute(self.get_label(src.name))))
|
||||
|
||||
@ -398,6 +412,8 @@ class Compiler(object):
|
||||
self.emitter.emit(INX())
|
||||
elif dest == REG_Y:
|
||||
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:
|
||||
self.emitter.emit(INC(Absolute(self.get_label(dest.name))))
|
||||
|
||||
@ -407,6 +423,8 @@ class Compiler(object):
|
||||
self.emitter.emit(DEX())
|
||||
elif dest == REG_Y:
|
||||
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:
|
||||
self.emitter.emit(DEC(Absolute(self.get_label(dest.name))))
|
||||
|
||||
@ -428,6 +446,12 @@ class Compiler(object):
|
||||
dest_label = self.get_label(dest.name)
|
||||
self.emitter.emit(LDA(IndirectY(src_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):
|
||||
### copy ^buf, ptr
|
||||
src_label = self.get_label(src.ref.name)
|
||||
@ -592,3 +616,29 @@ class Compiler(object):
|
||||
self.emitter.emit(SEI())
|
||||
self.compile_block(instr.block)
|
||||
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,
|
||||
AbsoluteX: 0x3d,
|
||||
AbsoluteY: 0x39,
|
||||
ZeroPage: 0x25,
|
||||
}
|
||||
|
||||
|
||||
@ -210,6 +211,7 @@ class CPY(Instruction):
|
||||
class DEC(Instruction):
|
||||
opcodes = {
|
||||
Absolute: 0xce,
|
||||
AbsoluteX: 0xde,
|
||||
}
|
||||
|
||||
|
||||
@ -231,6 +233,7 @@ class EOR(Instruction):
|
||||
Absolute: 0x4d,
|
||||
AbsoluteX: 0x5d,
|
||||
AbsoluteY: 0x59,
|
||||
ZeroPage: 0x45,
|
||||
}
|
||||
|
||||
|
||||
@ -299,6 +302,19 @@ class ORA(Instruction):
|
||||
Absolute: 0x0d,
|
||||
AbsoluteX: 0x1d,
|
||||
AbsoluteY: 0x19,
|
||||
ZeroPage: 0x05,
|
||||
}
|
||||
|
||||
|
||||
class PHA(Instruction):
|
||||
opcodes = {
|
||||
Implied: 0x48,
|
||||
}
|
||||
|
||||
|
||||
class PLA(Instruction):
|
||||
opcodes = {
|
||||
Implied: 0x68,
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
# 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 (
|
||||
TYPE_BIT, TYPE_BYTE, TYPE_WORD,
|
||||
RoutineType, VectorType, TableType, BufferType, PointerType,
|
||||
@ -442,7 +442,7 @@ class Parser(object):
|
||||
elif self.scanner.token in ("shl", "shr", "inc", "dec"):
|
||||
opcode = self.scanner.token
|
||||
self.scanner.scan()
|
||||
dest = self.locexpr()
|
||||
dest = self.indexed_locexpr()
|
||||
return SingleOp(self.scanner.line_number, opcode=opcode, dest=dest, src=None)
|
||||
elif self.scanner.token in ("nop",):
|
||||
opcode = self.scanner.token
|
||||
@ -470,6 +470,10 @@ class Parser(object):
|
||||
self.scanner.expect("off")
|
||||
block = self.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"):
|
||||
dest = self.locexpr()
|
||||
return SingleOp(self.scanner.line_number, opcode='trash', src=None, dest=dest)
|
||||
|
@ -474,6 +474,60 @@ The index must be initialized.
|
||||
| }
|
||||
? 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.
|
||||
|
||||
| word one
|
||||
@ -1120,11 +1174,13 @@ Some rudimentary tests for `xor`.
|
||||
|
||||
Some rudimentary tests for `shl`.
|
||||
|
||||
| byte foo
|
||||
| routine main
|
||||
| inputs a, c
|
||||
| outputs a, c, z, n
|
||||
| inputs foo, a, c
|
||||
| outputs foo, a, c, z, n
|
||||
| {
|
||||
| shl a
|
||||
| shl foo
|
||||
| }
|
||||
= ok
|
||||
|
||||
@ -1148,11 +1204,13 @@ Some rudimentary tests for `shl`.
|
||||
|
||||
Some rudimentary tests for `shr`.
|
||||
|
||||
| byte foo
|
||||
| routine main
|
||||
| inputs a, c
|
||||
| outputs a, c, z, n
|
||||
| inputs foo, a, c
|
||||
| outputs foo, a, c, z, n
|
||||
| {
|
||||
| shr a
|
||||
| shr foo
|
||||
| }
|
||||
= ok
|
||||
|
||||
@ -1879,6 +1937,290 @@ initialized at the start of that loop.
|
||||
| }
|
||||
? 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 ###
|
||||
|
||||
Can't `copy` from a memory location that isn't initialized.
|
||||
@ -2114,6 +2456,24 @@ Read through a pointer.
|
||||
| }
|
||||
= 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`,
|
||||
not `copy`.
|
||||
|
||||
@ -2223,21 +2583,30 @@ as an input to, an output of, or as a trashed value of a routine.
|
||||
| }
|
||||
? ConstantConstraintError: foo
|
||||
|
||||
You can copy the address of a routine into a vector, if that vector is
|
||||
declared appropriately.
|
||||
#### routine-vector type compatibility
|
||||
|
||||
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
|
||||
| inputs x
|
||||
| outputs x
|
||||
| inputs x, y
|
||||
| outputs x, y
|
||||
| trashes z, n
|
||||
| vec
|
||||
|
|
||||
| routine foo
|
||||
| inputs x
|
||||
| outputs x
|
||||
| inputs x, y
|
||||
| outputs x, y
|
||||
| trashes z, n
|
||||
| {
|
||||
| inc x
|
||||
| inc y
|
||||
| }
|
||||
|
|
||||
| routine main
|
||||
@ -2248,20 +2617,48 @@ declared appropriately.
|
||||
| }
|
||||
= 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
|
||||
| inputs y
|
||||
| outputs y
|
||||
| inputs x, y, a
|
||||
| outputs x, y
|
||||
| trashes z, n
|
||||
| vec
|
||||
|
|
||||
| routine foo
|
||||
| inputs x
|
||||
| outputs x
|
||||
| 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
|
||||
|
||||
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
|
||||
@ -2272,21 +2669,24 @@ But not if the vector is declared inappropriately.
|
||||
| }
|
||||
? IncompatibleConstraintsError
|
||||
|
||||
"Appropriately" means, if the routine affects no more than what is named
|
||||
in the input/output sets of the vector.
|
||||
If the vector produces an output that the routine doesn't produce, that's not an error.
|
||||
(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
|
||||
| inputs a, x
|
||||
| outputs x
|
||||
| trashes a, z, n
|
||||
| inputs x, y
|
||||
| outputs x, y, a
|
||||
| trashes z, n
|
||||
| vec
|
||||
|
|
||||
| routine foo
|
||||
| inputs x
|
||||
| outputs x
|
||||
| inputs x, y
|
||||
| outputs x, y
|
||||
| trashes z, n
|
||||
| {
|
||||
| inc x
|
||||
| inc y
|
||||
| }
|
||||
|
|
||||
| routine main
|
||||
@ -2297,6 +2697,86 @@ in the input/output sets of the vector.
|
||||
| }
|
||||
= 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.
|
||||
|
||||
| vector routine
|
||||
|
@ -7,7 +7,7 @@ SixtyPical to 6502 machine code.
|
||||
[Falderal]: http://catseye.tc/node/Falderal
|
||||
|
||||
-> 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"
|
||||
|
||||
@ -112,7 +112,8 @@ Memory location with explicit address.
|
||||
= $080F STA $0400
|
||||
= $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 screen @ 100
|
||||
@ -126,12 +127,18 @@ Accesses to memory locations in zero-page with `ld` and `st` use zero-page addre
|
||||
| st a, screen
|
||||
| ld a, zp
|
||||
| st a, zp
|
||||
| and a, zp
|
||||
| or a, zp
|
||||
| xor a, zp
|
||||
| }
|
||||
= $080D LDA $64
|
||||
= $080F STA $64
|
||||
= $0811 LDA $00
|
||||
= $0813 STA $00
|
||||
= $0815 RTS
|
||||
= $0815 AND $00
|
||||
= $0817 ORA $00
|
||||
= $0819 EOR $00
|
||||
= $081B RTS
|
||||
|
||||
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.
|
||||
|
||||
| word table[8] message : 65535, 0, 127
|
||||
| word table[4] message : 65535, 0, 127, 127
|
||||
|
|
||||
| routine main
|
||||
| {
|
||||
@ -225,7 +232,7 @@ Initialized word table, initialized with list of word values.
|
||||
= $0811 BRK
|
||||
= $0812 .byte $7F
|
||||
= $0813 BRK
|
||||
= $0814 BRK
|
||||
= $0814 .byte $7F
|
||||
= $0815 BRK
|
||||
|
||||
Some instructions.
|
||||
@ -267,40 +274,116 @@ Some instructions.
|
||||
| cmp y, foo
|
||||
| shl a
|
||||
| shr a
|
||||
| shl foo
|
||||
| shr foo
|
||||
| }
|
||||
= $080D LDA #$00
|
||||
= $080F LDX #$00
|
||||
= $0811 LDY #$00
|
||||
= $0813 STA $0853
|
||||
= $0816 STX $0853
|
||||
= $0819 STY $0853
|
||||
= $0813 STA $0859
|
||||
= $0816 STX $0859
|
||||
= $0819 STY $0859
|
||||
= $081C SEC
|
||||
= $081D CLC
|
||||
= $081E ADC #$01
|
||||
= $0820 ADC $0853
|
||||
= $0820 ADC $0859
|
||||
= $0823 SBC #$01
|
||||
= $0825 SBC $0853
|
||||
= $0828 INC $0853
|
||||
= $0825 SBC $0859
|
||||
= $0828 INC $0859
|
||||
= $082B INX
|
||||
= $082C INY
|
||||
= $082D DEC $0853
|
||||
= $082D DEC $0859
|
||||
= $0830 DEX
|
||||
= $0831 DEY
|
||||
= $0832 AND #$FF
|
||||
= $0834 AND $0853
|
||||
= $0834 AND $0859
|
||||
= $0837 ORA #$FF
|
||||
= $0839 ORA $0853
|
||||
= $0839 ORA $0859
|
||||
= $083C EOR #$FF
|
||||
= $083E EOR $0853
|
||||
= $083E EOR $0859
|
||||
= $0841 CMP #$01
|
||||
= $0843 CMP $0853
|
||||
= $0843 CMP $0859
|
||||
= $0846 CPX #$01
|
||||
= $0848 CPX $0853
|
||||
= $0848 CPX $0859
|
||||
= $084B CPY #$01
|
||||
= $084D CPY $0853
|
||||
= $084D CPY $0859
|
||||
= $0850 ROL 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`.
|
||||
|
||||
@ -494,6 +577,49 @@ Compiling `for ... down to`.
|
||||
= $0815 BNE $080F
|
||||
= $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.
|
||||
|
||||
| byte one
|
||||
@ -514,7 +640,7 @@ Indexed access.
|
||||
= $0814 LDA $0819,X
|
||||
= $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] tab2
|
||||
@ -1030,6 +1156,35 @@ Read through a pointer, into a byte storage location, or the `a` register.
|
||||
= $081C LDA ($FE),Y
|
||||
= $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.
|
||||
|
||||
| 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)"
|
||||
|
||||
-> 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"
|
||||
|
||||
|
@ -29,6 +29,12 @@ Program with comments.
|
||||
| routine main {
|
||||
| ld a, 0
|
||||
| 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
|
||||
|
||||
@ -155,6 +161,20 @@ Basic "open-faced for" loops, up and down.
|
||||
| }
|
||||
= 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.
|
||||
|
||||
| byte byt
|
||||
@ -167,13 +187,26 @@ User-defined memory addresses of different types.
|
||||
| }
|
||||
= ok
|
||||
|
||||
Tables of different types.
|
||||
Tables of different types and some operations on them.
|
||||
|
||||
| byte table[256] tab
|
||||
| word table[256] wtab
|
||||
| vector (routine trashes a) table[256] vtab
|
||||
| byte table[256] many
|
||||
| word table[256] wmany
|
||||
| vector (routine trashes a) table[256] vmany
|
||||
|
|
||||
| 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
|
||||
|
||||
@ -268,6 +301,8 @@ Explicit memory address.
|
||||
| routine main {
|
||||
| ld a, 100
|
||||
| st a, screen
|
||||
| shl screen
|
||||
| shr screen
|
||||
| }
|
||||
= ok
|
||||
|
||||
@ -565,12 +600,14 @@ Buffers and pointers.
|
||||
|
||||
| buffer[2048] buf
|
||||
| pointer ptr
|
||||
| pointer ptrb
|
||||
| byte foo
|
||||
|
|
||||
| routine main {
|
||||
| copy ^buf, ptr
|
||||
| copy 123, [ptr] + y
|
||||
| copy [ptr] + y, foo
|
||||
| copy [ptr] + y, [ptrb] + y
|
||||
| }
|
||||
= ok
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
#!/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
|
||||
|
||||
import sys
|
||||
|
Loading…
x
Reference in New Issue
Block a user