1
0
mirror of https://github.com/catseye/SixtyPical.git synced 2025-01-10 02:29:23 +00:00

Merge pull request #13 from catseye/develop-0.16

Develop 0.16
This commit is contained in:
Chris Pressey 2018-05-08 12:49:36 +01:00 committed by GitHub
commit 642ca138a3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 1164 additions and 165 deletions

View File

@ -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
View File

@ -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.

View File

@ -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
View 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.

View File

@ -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
}

View File

@ -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

View File

@ -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)

View File

@ -90,3 +90,8 @@ class For(Instr):
class WithInterruptsOff(Instr):
child_attrs = ('block',)
class Save(Instr):
value_attrs = ('locations',)
child_attrs = ('block',)

View File

@ -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)))

View File

@ -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,
}

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -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