1
0
mirror of https://github.com/catseye/SixtyPical.git synced 2024-06-18 03:29:32 +00:00

Merge pull request #3 from catseye/develop-0.9

Develop 0.9
This commit is contained in:
Chris Pressey 2017-12-12 11:14:51 +00:00 committed by GitHub
commit 53074bc224
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 1205 additions and 99 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
*.pyc
vicerc

View File

@ -1,6 +1,19 @@
History of SixtyPical
=====================
0.9
---
* Add word (constant or memory location) to word memory location.
* Add word to pointer (unchecked for now).
* Added `word table` type.
* Can `copy` from word storage location to word table and back.
* A `vector` can name itself in its `inputs` and `outputs` or `trashes` sets.
* Implementation: `--debug` shows some extra info during analysis.
* Fixed bug where `copy`ing literal word into word storage used wrong endianness.
* Fixed bug where every memory location was allocated 2 bytes of storage, regardless of type.
* Tests: use https://github.com/tcarmelveilleux/dcc6502 to disassemble code for comparison.
0.8
---

View File

@ -22,9 +22,8 @@ based on common machine-language programming idioms, such as
The reference implementation can execute, analyze, and compile SixtyPical
programs to 6502 machine code.
It is a **work in progress**, currently at the **proof-of-concept** stage.
The current development version of SixtyPical is 0.8.
SixtyPical is a work in progress. The current released version of SixtyPical
is 0.9.
Documentation
-------------
@ -41,38 +40,55 @@ Documentation
TODO
----
### Add 16 bit values.
### Demo game
I guess this means making `add` a bit more like `copy`.
Finish the little demo "game" where you can move a block around the screen with
the joystick (i.e. bring it up to par with the original demo game that was written
for SixtyPical)
And then: add to pointer. (Not necessarily range-checked yet though.)
### `call` routines that are defined further down in the source code
And then write a little demo "game" where you can move a block around the screen with
the joystick.
We might have a graph of states that refer to each other and that want to `goto`
each other. Thus we need this. We have it for vectors, but we need it for `call`.
### `word table` and `vector table` types
### Allow branches to diverge in what they touch
For example, if the routine inputs and outputs `foo`, and one branch of an `if`
sets `foo` and the other does not touch it, that should be OK.
### `vector table` type
### `low` and `high` address operators
To turn `word` type into `byte`.
### save registers on stack
### Save registers on stack
This preserves them, so semantically, they can be used even though they
This preserves them, so that, semantically, they can be used later even though they
are trashed inside the block.
### Range checking in the abstract interpretation
If you copy the address of a buffer (say it is size N) to a pointer, it is valid.
If you add a value from 0 to N-1 to the pointer, it is still valid.
But if you add a value ≥ N to it, it becomes invalid.
This should be tracked in the abstract interpretation.
(If only because abstract interpretation is the major point of this project!)
### And at some point...
* Compare word (constant or memory location) with memory location or pointer. (Maybe?)
* `copy x, [ptr] + y`
* Maybe even `copy [ptra] + y, [ptrb] + y`, which can be compiled to indirect LDA then indirect STA!
* Check that the buffer being read or written to through pointer, appears in approporiate inputs or outputs set.
* `byte table` and `word table` of sizes other than 256
* initialized `byte table` memory locations
* always analyze before executing or compiling, unless told not to
* `trash` instruction.
* `interrupt` routines.
* 6502-mnemonic aliases (`sec`, `clc`)
* other handy aliases (`eq` for `z`, etc.)
* have `copy` instruction able to copy a constant to a user-def mem loc, etc.
* `interrupt` routines -- to indicate that "the supervisor" has stored values on the stack, so we can trash them.
* pre-initialized `word` variables
* error messages that include the line number of the source code
* have `copy` instruction able to copy a byte to a user-def mem loc, etc.
* add absolute addressing in shl/shr, absolute-indexed for add, sub, etc.
* check and disallow recursion.
* automatic tail-call optimization (could be tricky, w/constraints?)

View File

@ -56,7 +56,7 @@ if __name__ == '__main__':
if options.analyze:
try:
analyzer = Analyzer()
analyzer = Analyzer(debug=options.debug)
analyzer.analyze_program(program)
except Exception as e:
if options.traceback:

View File

@ -1,7 +1,7 @@
SixtyPical
==========
This document describes the SixtyPical programming language version 0.8,
This document describes the SixtyPical programming language version 0.9,
both its execution aspect and its static analysis aspect (even though
these are, technically speaking, separate concepts.)
@ -25,7 +25,8 @@ There are six *primitive types* in SixtyPical:
There are also two *type constructors*:
* X table (256 entries, each holding a value of type X, where X is `byte`)
* T table (256 entries, each holding a value of type T, where T is either
`byte` or `word`)
* buffer[N] (N entries; each entry is a byte; N is a power of 2, ≤ 64K)
Memory locations
@ -302,6 +303,12 @@ and initializing them afterwards.
dest and src continue to be initialized afterwards.
In addition, if dest is of `word` type, then src must also be of `word`
type, and in this case this instruction trashes the `a` register.
NOTE: If dest is a pointer, the addition does not check if the result of
the pointer arithmetic continues to be valid (within a buffer) or not.
### inc ###
inc <dest-memory-location>

9
eg/add-word.60p Normal file
View File

@ -0,0 +1,9 @@
word score
routine main
inputs score
outputs score
trashes a, c, z, v, n
{
st off, c
add score, 1999
}

250
eg/proto-game.60p Normal file
View File

@ -0,0 +1,250 @@
// ****************************
// * Demo Game for SixtyPical *
// ****************************
// ----------------------------------------------------------------
// System Locations
// ----------------------------------------------------------------
byte vic_border @ 53280
byte vic_bg @ 53281
byte table screen1 @ 1024
byte table screen2 @ 1274
byte table screen3 @ 1524
byte table screen4 @ 1774
byte table colormap1 @ 55296
byte table colormap2 @ 55546
byte table colormap3 @ 55796
byte table colormap4 @ 56046
buffer[2048] screen @ 1024
byte joy2 @ $dc00
// ----------------------------------------------------------------
// Global Variables
// ----------------------------------------------------------------
pointer ptr @ 254
word pos
word delta
byte button_down : 0 // effectively static-local to check_button
//
// Points to the routine that implements the current game state.
//
vector dispatch_game_state
inputs joy2, pos, button_down, dispatch_game_state
outputs delta, pos, screen, screen1, button_down, dispatch_game_state
trashes a, x, y, c, z, n, v, ptr
//
// The constraints on these 2 vectors are kind-of sort-of big fibs.
// They're only written this way so they can be compatible with our
// routine. In fact, CINV is an interrupt routine where it doesn't
// really matter what you trash anyway, because all registers were
/// saved by the caller (the KERNAL) and will be restored by the end
// of the code of the saved origin cinv routine that we goto.
//
// I wonder if this could be arranged somehow to be less fibby, in
// a future version of SixtyPical.
//
vector cinv
inputs joy2, pos, button_down, dispatch_game_state
outputs delta, pos, screen, screen1, button_down, dispatch_game_state
trashes a, x, y, c, z, n, v, ptr
@ 788
vector save_cinv
inputs joy2, pos, button_down, dispatch_game_state
outputs delta, pos, screen, screen1, button_down, dispatch_game_state
trashes a, x, y, c, z, n, v, ptr
// ----------------------------------------------------------------
// Utility Routines
// ----------------------------------------------------------------
routine read_stick
inputs joy2
outputs delta
trashes a, x, z, n
{
ld x, joy2
ld a, x
and a, 1 // up
if z {
copy $ffd8, delta // -40
} else {
ld a, x
and a, 2 // down
if z {
copy word 40, delta
} else {
ld a, x
and a, 4 // left
if z {
copy $ffff, delta // -1
} else {
ld a, x
and a, 8 // right
if z {
copy word 1, delta
} else {
copy word 0, delta
}
}
}
}
}
// You can repeatedly (i.e. as part of actor logic or an IRQ handler)
// call this routine.
// Upon return, if carry is set, the button was pressed then released.
routine check_button
inputs joy2, button_down
outputs c, button_down
trashes a, z, n
{
ld a, button_down
if z {
ld a, joy2
and a, $10
if z {
ld a, 1
st a, button_down
}
st off, c
} else {
ld a, joy2
and a, $10
if not z {
ld a, 0
st a, button_down
st on, c
} else {
st off, c
}
}
}
routine clear_screen
outputs screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4
trashes a, y, c, n, z
{
ld y, 0
repeat {
ld a, 1
st a, colormap1 + y
st a, colormap2 + y
st a, colormap3 + y
st a, colormap4 + y
ld a, 32
st a, screen1 + y
st a, screen2 + y
st a, screen3 + y
st a, screen4 + y
inc y
cmp y, 250
} until z
}
// ----------------------------------------------------------------
// Game States
// ----------------------------------------------------------------
//
// Because these all `goto save_cinv` at the end, they must have the same signature as that routine.
//
routine game_state_play
inputs joy2, pos, button_down, dispatch_game_state
outputs delta, pos, screen, screen1, button_down, dispatch_game_state
trashes a, x, y, c, z, n, v, ptr
{
call read_stick
st off, c
add pos, delta
copy ^screen, ptr
st off, c
add ptr, pos
ld y, 0
copy 81, [ptr] + y
goto save_cinv
}
routine game_state_title_screen
inputs joy2, pos, button_down, dispatch_game_state
outputs delta, pos, screen, screen1, button_down, dispatch_game_state
trashes a, x, y, c, z, n, v, ptr
{
ld y, 10
repeat {
ld a, 90
st a, screen1 + y
inc y
cmp y, 20
} until z
st off, c
call check_button
if c {
// call clear_screen
// call init_game
copy game_state_play, dispatch_game_state
} else {
// This is sort of a hack. FIXME: let `if` branches diverge this much.
copy dispatch_game_state, dispatch_game_state
}
goto save_cinv
}
// *************************
// * Main Game Loop Driver *
// *************************
routine our_cinv
inputs joy2, pos, button_down, dispatch_game_state
outputs delta, pos, screen, screen1, button_down, dispatch_game_state
trashes a, x, y, c, z, n, v, ptr
{
goto dispatch_game_state
}
routine main
inputs cinv
outputs cinv, save_cinv, pos, dispatch_game_state,
screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4
trashes a, y, n, c, z, vic_border, vic_bg
{
ld a, 5
st a, vic_border
ld a, 0
st a, vic_bg
ld y, 0
call clear_screen
copy game_state_title_screen, dispatch_game_state
copy word 0, pos
with interrupts off {
copy cinv, save_cinv
copy our_cinv, cinv
}
// FIXME: find out why `repeat { } forever` does not analyze OK
repeat {
ld a, 0
} until not z
}

16
eg/word-table.60p Normal file
View File

@ -0,0 +1,16 @@
word one
word table many
routine main
inputs one, many
outputs one, many
trashes a, x, y, n, z
{
ld x, 0
ld y, 0
copy 777, one
copy one, many + x
copy one, many + y
copy many + x, one
copy many + y, one
}

View File

@ -1,7 +1,15 @@
#!/bin/sh
SRC=$1
if [ "X$1" = "X" ]; then
echo "Usage: ./loadngo.sh <source.60p>"
exit 1
fi
OUT=/tmp/a-out.prg
bin/sixtypical --analyze --compile --basic-prelude $SRC > $OUT || exit 1
x64 $OUT
bin/sixtypical --traceback --analyze --compile --basic-prelude $SRC > $OUT || exit 1
if [ -e vicerc ]; then
x64 -config vicerc $OUT
else
x64 $OUT
fi
rm -f $OUT

View File

@ -2,8 +2,8 @@
from sixtypical.ast import Program, Routine, Block, Instr
from sixtypical.model import (
TYPE_BYTE, TYPE_BYTE_TABLE, BufferType, PointerType, VectorType, ExecutableType,
ConstantRef, LocationRef, IndirectRef, AddressRef,
TYPE_BYTE, TYPE_WORD, TYPE_BYTE_TABLE, TYPE_WORD_TABLE, BufferType, PointerType, VectorType, ExecutableType,
ConstantRef, LocationRef, IndirectRef, IndexedRef, AddressRef,
REG_A, REG_Y, FLAG_Z, FLAG_N, FLAG_V, FLAG_C
)
@ -88,6 +88,20 @@ class Context(object):
raise InconsistentConstraintsError('%s in %s' % (ref.name, routine.name))
self._writeable.add(ref)
def __str__(self):
def locstr(loc):
if isinstance(loc, LocationRef):
return "{}:{}".format(loc.name, loc.type)
else:
return str(loc)
def locsetstr(s):
return '{' + ', '.join([locstr(loc) for loc in list(s)]) + '}'
return "Context(\n _touched={},\n _meaningful={},\n _writeable={}\n)".format(
locsetstr(self._touched), locsetstr(self._meaningful), locsetstr(self._writeable)
)
def clone(self):
c = Context(self.routines, self.routine, [], [], [])
c._touched = set(self._touched)
@ -157,10 +171,11 @@ class Context(object):
class Analyzer(object):
def __init__(self):
def __init__(self, debug=False):
self.current_routine = None
self.has_encountered_goto = False
self.routines = {}
self.debug = debug
def assert_type(self, type, *locations):
for location in locations:
@ -169,6 +184,26 @@ class Analyzer(object):
(location.name, self.current_routine.name)
)
def assert_affected_within(self, name, affected, limited_to):
# We reduce the set of LocationRefs to a set of strings (their labels).
# This is necessary because currently, two LocationRefs that refer to the
# same location are not considered euqal. (But two LocationRefs with the
# same label should always be the same type.)
affected = set([loc.name for loc in affected])
limited_to = set([loc.name for loc in limited_to])
def loc_list(label_set):
return ', '.join(sorted(label_set))
overage = affected - limited_to
if not overage:
return
message = 'in %s: %s are {%s} but affects {%s} which exceeds by: {%s} ' % (
self.current_routine.name, name, loc_list(limited_to), loc_list(affected), loc_list(overage)
)
raise IncompatibleConstraintsError(message)
def analyze_program(self, program):
assert isinstance(program, Program)
self.routines = {r.location: r for r in program.routines}
@ -184,7 +219,13 @@ class Analyzer(object):
return
type = routine.location.type
context = Context(self.routines, routine, type.inputs, type.outputs, type.trashes)
if self.debug:
print "at start of routine `{}`:".format(routine.name)
print context
self.analyze_block(routine.block, context)
if self.debug:
print "at end of routine `{}`:".format(routine.name)
print context
if not self.has_encountered_goto:
for ref in type.outputs:
context.assert_meaningful(ref, exception_class=UnmeaningfulOutputError)
@ -233,7 +274,24 @@ class Analyzer(object):
)
context.assert_meaningful(src)
context.set_written(dest)
elif opcode in ('add', 'sub'):
elif opcode == 'add':
context.assert_meaningful(src, dest, FLAG_C)
if 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)
elif opcode == 'sub':
self.assert_type(TYPE_BYTE, src, dest)
context.assert_meaningful(src, dest, FLAG_C)
context.set_written(dest, FLAG_Z, FLAG_N, FLAG_C, FLAG_V)
@ -310,19 +368,26 @@ class Analyzer(object):
pass
else:
raise TypeMismatchError((src, dest))
elif isinstance(src, LocationRef) and isinstance(dest, IndexedRef):
if src.type == TYPE_WORD and dest.ref.type == TYPE_WORD_TABLE:
pass
else:
raise TypeMismatchError((src, dest))
elif isinstance(src, IndexedRef) and isinstance(dest, LocationRef):
if src.ref.type == TYPE_WORD_TABLE and dest.type == TYPE_WORD:
pass
else:
raise TypeMismatchError((src, dest))
elif isinstance(src, (LocationRef, ConstantRef)) and isinstance(dest, LocationRef):
if src.type == dest.type:
pass
elif isinstance(src.type, ExecutableType) and \
isinstance(dest.type, VectorType):
# if dealing with routines and vectors,
# check that they're not incompatible
if not (src.type.inputs <= dest.type.inputs):
raise IncompatibleConstraintsError(src.type.inputs - dest.type.inputs)
if not (src.type.outputs <= dest.type.outputs):
raise IncompatibleConstraintsError(src.type.outputs - dest.type.outputs)
if not (src.type.trashes <= dest.type.trashes):
raise IncompatibleConstraintsError(src.type.trashes - dest.type.trashes)
elif isinstance(src.type, ExecutableType) and isinstance(dest.type, VectorType):
self.assert_affected_within('inputs', src.type.inputs, dest.type.inputs)
self.assert_affected_within('outputs', src.type.outputs, dest.type.outputs)
self.assert_affected_within('trashes', src.type.trashes, dest.type.trashes)
else:
raise TypeMismatchError((src, dest))
else:
@ -337,7 +402,15 @@ class Analyzer(object):
elif isinstance(src, IndirectRef) and isinstance(dest, LocationRef):
context.assert_meaningful(src.ref, REG_Y)
# TODO this will need to be more sophisticated. the thing ref points to is touched, as well.
context.set_touched(src.ref)
context.set_touched(src.ref) # TODO and REG_Y? if not, why not?
context.set_written(dest)
elif isinstance(src, LocationRef) and isinstance(dest, IndexedRef):
context.assert_meaningful(src, dest.ref, dest.index)
context.set_touched(src) # TODO and dest.index?
context.set_written(dest.ref)
elif isinstance(src, IndexedRef) and isinstance(dest, LocationRef):
context.assert_meaningful(src.ref, src.index, dest)
context.set_touched(dest) # TODO and src.index?
context.set_written(dest)
else:
context.assert_meaningful(src)
@ -350,22 +423,21 @@ class Analyzer(object):
self.analyze_block(instr.block, context)
elif opcode == 'goto':
location = instr.location
type = location.type
type_ = location.type
if not isinstance(type, ExecutableType):
if not isinstance(type_, ExecutableType):
raise TypeMismatchError(location)
# assert that the dest routine's inputs are all initialized
for ref in type.inputs:
for ref in type_.inputs:
context.assert_meaningful(ref)
# and that this routine's trashes and output constraints are a
# superset of the called routine's
current_type = self.current_routine.location.type
if not (type.outputs <= current_type.outputs):
raise IncompatibleConstraintsError(type.outputs - current_type.outputs)
if not (type.trashes <= current_type.trashes):
raise IncompatibleConstraintsError(type.trashes - current_type.trashes)
self.assert_affected_within('outputs', type_.outputs, current_type.outputs)
self.assert_affected_within('trashes', type_.trashes, current_type.trashes)
self.has_encountered_goto = True
else:
raise NotImplementedError(opcode)

View File

@ -2,8 +2,8 @@
from sixtypical.ast import Program, Routine, Block, Instr
from sixtypical.model import (
ConstantRef, LocationRef, IndirectRef, AddressRef,
TYPE_BIT, TYPE_BYTE, TYPE_WORD, BufferType, PointerType, RoutineType, VectorType,
ConstantRef, LocationRef, IndexedRef, IndirectRef, AddressRef,
TYPE_BIT, TYPE_BYTE, TYPE_BYTE_TABLE, TYPE_WORD, TYPE_WORD_TABLE, BufferType, PointerType, RoutineType, VectorType,
REG_A, REG_X, REG_Y, FLAG_C
)
from sixtypical.emitter import Byte, Label, Offset, LowAddressByte, HighAddressByte
@ -35,10 +35,22 @@ class Compiler(object):
assert isinstance(program, Program)
for defn in program.defns:
label = Label(defn.name)
if defn.addr is not None:
label.set_addr(defn.addr)
self.labels[defn.name] = label
# compute length of memory pointed to. this is awful.
length = None
type_ = defn.location.type
if type_ == TYPE_BYTE:
length = 1
elif type_ == TYPE_WORD or isinstance(type_, (PointerType, VectorType)):
length = 2
elif type_ == TYPE_BYTE_TABLE:
length = 256
elif type_ == TYPE_WORD_TABLE:
length = 512
elif isinstance(type_, BufferType):
length = type_.size
if length is None:
raise NotImplementedError("Need size for type {}".format(type_))
self.labels[defn.name] = Label(defn.name, addr=defn.addr, length=length)
for routine in program.routines:
self.routines[routine.name] = routine
@ -60,8 +72,10 @@ class Compiler(object):
for defn in program.defns:
if defn.initial is not None:
label = self.labels[defn.name]
initial_data = Byte(defn.initial) # TODO: support other types than Byte
label.set_length(initial_data.size())
self.emitter.resolve_label(label)
self.emitter.emit(Byte(defn.initial))
self.emitter.emit(initial_data)
# uninitialized, "BSS" data
for defn in program.defns:
@ -147,6 +161,46 @@ class Compiler(object):
self.emitter.emit(ADC(Immediate(Byte(src.value))))
else:
self.emitter.emit(ADC(Absolute(self.labels[src.name])))
elif isinstance(dest, LocationRef) and src.type == TYPE_WORD and dest.type == TYPE_WORD:
if isinstance(src, ConstantRef):
dest_label = self.labels[dest.name]
self.emitter.emit(LDA(Absolute(dest_label)))
self.emitter.emit(ADC(Immediate(Byte(src.low_byte()))))
self.emitter.emit(STA(Absolute(dest_label)))
self.emitter.emit(LDA(Absolute(Offset(dest_label, 1))))
self.emitter.emit(ADC(Immediate(Byte(src.high_byte()))))
self.emitter.emit(STA(Absolute(Offset(dest_label, 1))))
elif isinstance(src, LocationRef):
src_label = self.labels[src.name]
dest_label = self.labels[dest.name]
self.emitter.emit(LDA(Absolute(dest_label)))
self.emitter.emit(ADC(Absolute(src_label)))
self.emitter.emit(STA(Absolute(dest_label)))
self.emitter.emit(LDA(Absolute(Offset(dest_label, 1))))
self.emitter.emit(ADC(Absolute(Offset(src_label, 1))))
self.emitter.emit(STA(Absolute(Offset(dest_label, 1))))
else:
raise UnsupportedOpcodeError(instr)
elif isinstance(dest, LocationRef) and src.type == TYPE_WORD and isinstance(dest.type, PointerType):
if isinstance(src, ConstantRef):
dest_label = self.labels[dest.name]
self.emitter.emit(LDA(ZeroPage(dest_label)))
self.emitter.emit(ADC(Immediate(Byte(src.low_byte()))))
self.emitter.emit(STA(ZeroPage(dest_label)))
self.emitter.emit(LDA(ZeroPage(Offset(dest_label, 1))))
self.emitter.emit(ADC(Immediate(Byte(src.high_byte()))))
self.emitter.emit(STA(ZeroPage(Offset(dest_label, 1))))
elif isinstance(src, LocationRef):
src_label = self.labels[src.name]
dest_label = self.labels[dest.name]
self.emitter.emit(LDA(ZeroPage(dest_label)))
self.emitter.emit(ADC(Absolute(src_label)))
self.emitter.emit(STA(ZeroPage(dest_label)))
self.emitter.emit(LDA(ZeroPage(Offset(dest_label, 1))))
self.emitter.emit(ADC(Absolute(Offset(src_label, 1))))
self.emitter.emit(STA(ZeroPage(Offset(dest_label, 1))))
else:
raise UnsupportedOpcodeError(instr)
else:
raise UnsupportedOpcodeError(instr)
elif opcode == 'sub':
@ -305,6 +359,41 @@ class Compiler(object):
self.emitter.emit(STA(ZeroPage(dest_label)))
self.emitter.emit(LDA(Immediate(LowAddressByte(src_label))))
self.emitter.emit(STA(ZeroPage(Offset(dest_label, 1))))
elif isinstance(src, LocationRef) and isinstance(dest, IndexedRef):
if src.type == TYPE_WORD and dest.ref.type == TYPE_WORD_TABLE:
src_label = self.labels[src.name]
dest_label = self.labels[dest.ref.name]
addressing_mode = None
if dest.index == REG_X:
addressing_mode = AbsoluteX
elif dest.index == REG_Y:
addressing_mode = AbsoluteY
else:
raise NotImplementedError(dest)
self.emitter.emit(LDA(Absolute(src_label)))
self.emitter.emit(STA(addressing_mode(dest_label)))
self.emitter.emit(LDA(Absolute(Offset(src_label, 1))))
self.emitter.emit(STA(addressing_mode(Offset(dest_label, 256))))
else:
raise NotImplementedError
elif isinstance(src, IndexedRef) and isinstance(dest, LocationRef):
if src.ref.type == TYPE_WORD_TABLE and dest.type == TYPE_WORD:
src_label = self.labels[src.ref.name]
dest_label = self.labels[dest.name]
addressing_mode = None
if src.index == REG_X:
addressing_mode = AbsoluteX
elif src.index == REG_Y:
addressing_mode = AbsoluteY
else:
raise NotImplementedError(src)
self.emitter.emit(LDA(addressing_mode(src_label)))
self.emitter.emit(STA(Absolute(dest_label)))
self.emitter.emit(LDA(addressing_mode(Offset(src_label, 256))))
self.emitter.emit(STA(Absolute(Offset(dest_label, 1))))
else:
raise NotImplementedError
elif not isinstance(src, (ConstantRef, LocationRef)) or not isinstance(dest, LocationRef):
raise NotImplementedError((src, dest))
elif src.type == TYPE_BYTE and dest.type == TYPE_BYTE:
@ -318,11 +407,9 @@ class Compiler(object):
elif src.type == TYPE_WORD and dest.type == TYPE_WORD:
if isinstance(src, ConstantRef):
dest_label = self.labels[dest.name]
hi = (src.value >> 8) & 255
lo = src.value & 255
self.emitter.emit(LDA(Immediate(Byte(hi))))
self.emitter.emit(LDA(Immediate(Byte(src.low_byte()))))
self.emitter.emit(STA(Absolute(dest_label)))
self.emitter.emit(LDA(Immediate(Byte(lo))))
self.emitter.emit(LDA(Immediate(Byte(src.high_byte()))))
self.emitter.emit(STA(Absolute(Offset(dest_label, 1))))
else:
src_label = self.labels[src.name]

View File

@ -1,3 +1,8 @@
"""Binary machine code emitter. Used in SixtyPical to emit 6502 machine code,
but not specific to SixtyPical, or 6502. Not even necessarily machine code -
though some parts are written around the assumptions of 8-bit architectures."""
class Emittable(object):
def size(self):
raise NotImplementedError
@ -42,14 +47,29 @@ class Word(Emittable):
return "%s(%r)" % (self.__class__.__name__, self.value)
class Table(Emittable):
def size(self):
return 256
def serialize(self, addr=None):
return chr(0) * self.size()
def __repr__(self):
return "%s()" % (self.__class__.__name__)
class Label(Emittable):
def __init__(self, name, addr=None):
def __init__(self, name, addr=None, length=None):
self.name = name
self.addr = addr
self.length = length
def set_addr(self, addr):
self.addr = addr
def set_length(self, length):
self.length = length
def size(self):
return 2
@ -66,8 +86,9 @@ class Label(Emittable):
return Byte(self.addr + offset).serialize()
def __repr__(self):
addrs = ', addr=%r' % self.addr if self.addr is not None else ''
return "%s(%r%s)" % (self.__class__.__name__, self.name, addrs)
addr_s = ', addr=%r' % self.addr if self.addr is not None else ''
length_s = ', length=%r' % self.length if self.length is not None else ''
return "%s(%r%s%s)" % (self.__class__.__name__, self.name, addr_s, length_s)
class Offset(Emittable):
@ -154,4 +175,4 @@ class Emitter(object):
"""Set the given label to be at the current address and
advance the address for the next label, but don't emit anything."""
self.resolve_label(label)
self.addr += label.size()
self.addr += label.length

View File

@ -1,5 +1,6 @@
"""Data/storage model for SixtyPical."""
class Type(object):
def __init__(self, name):
self.name = name
@ -106,7 +107,7 @@ class IndirectRef(Ref):
self.ref = ref
def __eq__(self, other):
return self.ref == other.ref
return isinstance(other, self.__class__) and self.ref == other.ref
def __hash__(self):
return hash(self.__class__.name) ^ hash(self.ref)
@ -122,6 +123,28 @@ class IndirectRef(Ref):
return False
class IndexedRef(Ref):
def __init__(self, ref, index):
self.ref = ref
self.index = index
def __eq__(self, other):
return isinstance(other, self.__class__) and self.ref == other.ref and self.index == other.index
def __hash__(self):
return hash(self.__class__.name) ^ hash(self.ref) ^ hash(self.index)
def __repr__(self):
return '%s(%r, %r)' % (self.__class__.__name__, self.ref, self.index)
@property
def name(self):
return '{}+{}'.format(self.ref.name, self.index.name)
def is_constant(self):
return False
class AddressRef(Ref):
def __init__(self, ref):
self.ref = ref
@ -191,6 +214,12 @@ class ConstantRef(Ref):
def is_constant(self):
return True
def high_byte(self):
return (self.value >> 8) & 255
def low_byte(self):
return self.value & 255
REG_A = LocationRef(TYPE_BYTE, 'a')
REG_X = LocationRef(TYPE_BYTE, 'x')

View File

@ -4,7 +4,7 @@ from sixtypical.ast import Program, Defn, Routine, Block, Instr
from sixtypical.model import (
TYPE_BIT, TYPE_BYTE, TYPE_BYTE_TABLE, TYPE_WORD, TYPE_WORD_TABLE,
RoutineType, VectorType, ExecutableType, BufferType, PointerType,
LocationRef, ConstantRef, IndirectRef, AddressRef,
LocationRef, ConstantRef, IndirectRef, IndexedRef, AddressRef,
)
from sixtypical.scanner import Scanner
@ -29,6 +29,8 @@ class Parser(object):
raise SyntaxError('Undefined symbol "%s"' % name)
return self.symbols[name].model
# --- grammar productions
def program(self):
defns = []
routines = []
@ -47,6 +49,19 @@ class Parser(object):
self.symbols[name] = SymEntry(routine, routine.location)
routines.append(routine)
self.scanner.check_type('EOF')
# now backpatch the executable types.
for defn in defns:
if isinstance(defn.location.type, VectorType):
t = defn.location.type
t.inputs = set([self.lookup(w) for w in t.inputs])
t.outputs = set([self.lookup(w) for w in t.outputs])
t.trashes = set([self.lookup(w) for w in t.trashes])
for routine in routines:
if isinstance(routine.location.type, ExecutableType):
t = routine.location.type
t.inputs = set([self.lookup(w) for w in t.inputs])
t.outputs = set([self.lookup(w) for w in t.outputs])
t.trashes = set([self.lookup(w) for w in t.trashes])
return Program(defns=defns, routines=routines)
def defn(self):
@ -108,11 +123,11 @@ class Parser(object):
outputs = set()
trashes = set()
if self.scanner.consume('inputs'):
inputs = set(self.locexprs())
inputs = set(self.labels())
if self.scanner.consume('outputs'):
outputs = set(self.locexprs())
outputs = set(self.labels())
if self.scanner.consume('trashes'):
trashes = set(self.locexprs())
trashes = set(self.labels())
return (inputs, outputs, trashes)
def routine(self):
@ -137,6 +152,20 @@ class Parser(object):
location=location
)
def labels(self):
accum = []
accum.append(self.label())
while self.scanner.consume(','):
accum.append(self.label())
return accum
def label(self):
"""Like a locexpr, but does not allow literal values, and the labels do not
need to be defined yet. They will be resolved at the end of parsing."""
loc = self.scanner.token
self.scanner.scan()
return loc
def locexprs(self):
accum = []
accum.append(self.locexpr())
@ -175,7 +204,12 @@ class Parser(object):
loc = self.locexpr()
return AddressRef(loc)
else:
return self.locexpr()
loc = self.locexpr()
index = None
if self.scanner.consume('+'):
index = self.locexpr()
loc = IndexedRef(loc, index)
return loc
def block(self):
instrs = []

View File

@ -207,6 +207,21 @@ Can't `st` to a memory location that doesn't appear in (outputs trashes).
| }
? ForbiddenWriteError: lives in main
Can't `st` a `word` type.
| word foo
|
| routine main
| outputs foo
| trashes a, n, z
| {
| ld a, 0
| st a, foo
| }
? TypeMismatchError: a and foo in main
### tables ###
Storing to a table, you must use an index, and vice-versa.
| byte one
@ -313,18 +328,47 @@ Reading from a table, you must use an index, and vice-versa.
| }
= ok
Can't `st` a `word` type.
Copying to and from a word table.
| word foo
| word one
| word table many
|
| routine main
| outputs foo
| trashes a, n, z
| inputs one, many
| outputs one, many
| trashes a, x, n, z
| {
| ld a, 0
| st a, foo
| ld x, 0
| copy one, many + x
| copy many + x, one
| }
? TypeMismatchError: a and foo in main
= ok
| word one
| word table many
|
| routine main
| inputs one, many
| outputs one, many
| trashes a, x, n, z
| {
| ld x, 0
| copy one, many
| }
? TypeMismatchError
| word one
| word table many
|
| routine main
| inputs one, many
| outputs one, many
| trashes a, x, n, z
| {
| ld x, 0
| copy one + x, many
| }
? TypeMismatchError
### add ###
@ -373,6 +417,103 @@ Can't `add` to a memory location that isn't writeable.
| }
? ForbiddenWriteError: a in main
You can `add` a word constant to a word memory location.
| word score
| routine main
| inputs a, score
| outputs score
| trashes a, c, z, v, n
| {
| st off, c
| add score, 1999
| }
= ok
`add`ing a word constant to a word memory location trashes `a`.
| word score
| routine main
| inputs a, score
| outputs score, a
| trashes c, z, v, n
| {
| st off, c
| add score, 1999
| }
? UnmeaningfulOutputError: a in main
To be sure, `add`ing a word constant to a word memory location trashes `a`.
| word score
| routine main
| inputs score
| outputs score
| trashes c, z, v, n
| {
| st off, c
| add score, 1999
| }
? ForbiddenWriteError: a in main
You can `add` a word memory location to another word memory location.
| word score
| word delta
| routine main
| inputs score, delta
| outputs score
| trashes a, c, z, v, n
| {
| st off, c
| add score, delta
| }
= ok
`add`ing a word memory location to a word memory location trashes `a`.
| word score
| word delta
| routine main
| inputs score, delta
| outputs score
| trashes c, z, v, n
| {
| st off, c
| add score, delta
| }
? ForbiddenWriteError: a in main
You can `add` a word memory location, or a constant, to a pointer.
| pointer ptr
| word delta
| routine main
| inputs ptr, delta
| outputs ptr
| trashes a, c, z, v, n
| {
| st off, c
| add ptr, delta
| add ptr, word 1
| }
= ok
`add`ing a word memory location, or a constant, to a pointer, trashes `a`.
| pointer ptr
| word delta
| routine main
| inputs ptr, delta
| outputs ptr
| trashes c, z, v, n
| {
| st off, c
| add ptr, delta
| add ptr, word 1
| }
? ForbiddenWriteError: a in main
### sub ###
Can't `sub` from or to a memory location that isn't initialized.
@ -1372,6 +1513,30 @@ 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.
| vector vec
| inputs a, x
| outputs x
| trashes a, z, n
|
| routine foo
| inputs x
| outputs x
| trashes z, n
| {
| inc x
| }
|
| routine main
| outputs vec
| trashes a, z, n
| {
| copy foo, vec
| }
= ok
Routines are read-only.
| vector vec

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 --compile %(test-body-file) | fa-bin-to-hex"
-> shell command "bin/sixtypical --basic-prelude --compile %(test-body-file) | tests/appliances/bin/dcc6502-adapter"
-> Tests for functionality "Compile SixtyPical program"
@ -16,7 +16,7 @@ Null program.
| routine main
| {
| }
= 00c060
= $080D RTS
Rudimentary program.
@ -28,7 +28,9 @@ Rudimentary program.
| st off, c
| add a, 4
| }
= 00c018690460
= $080D CLC
= $080E ADC #$04
= $0810 RTS
Call extern.
@ -44,7 +46,9 @@ Call extern.
| ld a, 65
| call chrout
| }
= 00c0a94120d2ff60
= $080D LDA #$41
= $080F JSR $FFD2
= $0812 RTS
Call defined routine.
@ -62,7 +66,12 @@ Call defined routine.
| {
| call foo
| }
= 00c02004c060a900a200a00060
= $080D JSR $0811
= $0810 RTS
= $0811 LDA #$00
= $0813 LDX #$00
= $0815 LDY #$00
= $0817 RTS
Access a defined memory location.
@ -75,7 +84,10 @@ Access a defined memory location.
| st y, foo
| ld a, foo
| }
= 00c0a0008c09c0ad09c060
= $080D LDY #$00
= $080F STY $0816
= $0812 LDA $0816
= $0815 RTS
Memory location with explicit address.
@ -87,7 +99,9 @@ Memory location with explicit address.
| ld a, 100
| st a, screen
| }
= 00c0a9648d000460
= $080D LDA #$64
= $080F STA $0400
= $0812 RTS
Memory location with initial value.
@ -99,7 +113,9 @@ Memory location with initial value.
| {
| ld a, lives
| }
= 00c0ad04c06003
= $080D LDA $0811
= $0810 RTS
= $0811 .byte $03
Some instructions.
@ -141,7 +157,39 @@ Some instructions.
| shl a
| shr a
| }
= 00c0a900a200a0008d46c08e46c08c46c0381869016d46c0e901ed46c0ee46c0e8c8ce46c0ca8829ff2d46c009ff0d46c049ff4d46c0c901cd46c0e001ec46c0c001cc46c02a6a60
= $080D LDA #$00
= $080F LDX #$00
= $0811 LDY #$00
= $0813 STA $0853
= $0816 STX $0853
= $0819 STY $0853
= $081C SEC
= $081D CLC
= $081E ADC #$01
= $0820 ADC $0853
= $0823 SBC #$01
= $0825 SBC $0853
= $0828 INC $0853
= $082B INX
= $082C INY
= $082D DEC $0853
= $0830 DEX
= $0831 DEY
= $0832 AND #$FF
= $0834 AND $0853
= $0837 ORA #$FF
= $0839 ORA $0853
= $083C EOR #$FF
= $083E EOR $0853
= $0841 CMP #$01
= $0843 CMP $0853
= $0846 CPX #$01
= $0848 CPX $0853
= $084B CPY #$01
= $084D CPY $0853
= $0850 ROL A
= $0851 ROR A
= $0852 RTS
Compiling `if`.
@ -155,7 +203,12 @@ Compiling `if`.
| ld y, 2
| }
| }
= 00c0a900d005a0014c0bc0a00260
= $080D LDA #$00
= $080F BNE $0816
= $0811 LDY #$01
= $0813 JMP $0818
= $0816 LDY #$02
= $0818 RTS
Compiling `if not`.
@ -169,7 +222,12 @@ Compiling `if not`.
| ld y, 2
| }
| }
= 00c0a900f005a0014c0bc0a00260
= $080D LDA #$00
= $080F BEQ $0816
= $0811 LDY #$01
= $0813 JMP $0818
= $0816 LDY #$02
= $0818 RTS
Compiling `if` without `else`.
@ -181,7 +239,10 @@ Compiling `if` without `else`.
| ld y, 1
| }
| }
= 00c0a900d002a00160
= $080D LDA #$00
= $080F BNE $0813
= $0811 LDY #$01
= $0813 RTS
Compiling `repeat`.
@ -195,7 +256,12 @@ Compiling `repeat`.
| cmp y, 91
| } until z
| }
= 00c0a04198c8c05bd0fa60
= $080D LDY #$41
= $080F TYA
= $0810 INY
= $0811 CPY #$5B
= $0813 BNE $080F
= $0815 RTS
Compiling `repeat until not`.
@ -209,7 +275,12 @@ Compiling `repeat until not`.
| cmp y, 91
| } until not z
| }
= 00c0a04198c8c05bf0fa60
= $080D LDY #$41
= $080F TYA
= $0810 INY
= $0811 CPY #$5B
= $0813 BEQ $080F
= $0815 RTS
Compiling `repeat forever`.
@ -221,7 +292,10 @@ Compiling `repeat forever`.
| inc y
| } forever
| }
= 00c0a041c84c02c060
= $080D LDY #$41
= $080F INY
= $0810 JMP $080F
= $0813 RTS
Indexed access.
@ -237,7 +311,48 @@ Indexed access.
| st a, many + x
| ld a, many + x
| }
= 00c0a200a9009d0dc0bd0dc060
= $080D LDX #$00
= $080F LDA #$00
= $0811 STA $0819,X
= $0814 LDA $0819,X
= $0817 RTS
Byte tables take up 256 bytes in memory.
| byte table tab1
| byte table tab2
|
| routine main
| inputs tab1
| outputs tab2
| trashes a, x, n, z
| {
| ld x, 0
| ld a, tab1 + x
| st a, tab2 + x
| }
= $080D LDX #$00
= $080F LDA $0816,X
= $0812 STA $0916,X
= $0815 RTS
Byte storage locations take up only 1 byte in memory.
| byte one
| byte two
|
| routine main
| outputs one, two
| trashes a, x, n, z
| {
| ld a, 0
| st a, one
| st a, two
| }
= $080D LDA #$00
= $080F STA $0816
= $0812 STA $0817
= $0815 RTS
Copy byte to byte.
@ -251,7 +366,9 @@ Copy byte to byte.
| {
| copy baz, bar
| }
= 00c0ad09c08d07c060
= $080D LDA $0815
= $0810 STA $0814
= $0813 RTS
Copy word to word.
@ -265,7 +382,11 @@ Copy word to word.
| {
| copy baz, bar
| }
= 00c0ad0fc08d0dc0ad10c08d0ec060
= $080D LDA $081C
= $0810 STA $081A
= $0813 LDA $081D
= $0816 STA $081B
= $0819 RTS
Copy literal word to word.
@ -275,9 +396,13 @@ Copy literal word to word.
| outputs bar
| trashes a, n, z
| {
| copy 65535, bar
| copy 2000, bar
| }
= 00c0a9ff8d0bc0a9ff8d0cc060
= $080D LDA #$D0
= $080F STA $0818
= $0812 LDA #$07
= $0814 STA $0819
= $0817 RTS
Copy vector to vector.
@ -291,7 +416,11 @@ Copy vector to vector.
| {
| copy baz, bar
| }
= 00c0ad0fc08d0dc0ad10c08d0ec060
= $080D LDA $081C
= $0810 STA $081A
= $0813 LDA $081D
= $0816 STA $081B
= $0819 RTS
Copy routine to vector, inside an `interrupts off` block.
@ -314,7 +443,57 @@ Copy routine to vector, inside an `interrupts off` block.
| copy foo, bar
| }
| }
= 00c078a90d8d0fc0a9c08d10c05860e860
= $080D SEI
= $080E LDA #$1A
= $0810 STA $081C
= $0813 LDA #$08
= $0815 STA $081D
= $0818 CLI
= $0819 RTS
= $081A INX
= $081B RTS
Copy word to word table and back, with both `x` and `y` as indexes.
| word one
| word table many
|
| routine main
| inputs one, many
| outputs one, many
| trashes a, x, y, n, z
| {
| ld x, 0
| ld y, 0
| copy 777, one
| copy one, many + x
| copy one, many + y
| copy many + x, one
| copy many + y, one
| }
= $080D LDX #$00
= $080F LDY #$00
= $0811 LDA #$09
= $0813 STA $084C
= $0816 LDA #$03
= $0818 STA $084D
= $081B LDA $084C
= $081E STA $084E,X
= $0821 LDA $084D
= $0824 STA $094E,X
= $0827 LDA $084C
= $082A STA $084E,Y
= $082D LDA $084D
= $0830 STA $094E,Y
= $0833 LDA $084E,X
= $0836 STA $084C
= $0839 LDA $094E,X
= $083C STA $084D
= $083F LDA $084E,Y
= $0842 STA $084C
= $0845 LDA $094E,Y
= $0848 STA $084D
= $084B RTS
Indirect call.
@ -328,7 +507,15 @@ Indirect call.
| copy bar, foo
| call foo
| }
= 00c0a90e8d14c0a9c08d15c02011c060a2c8606c14c0
= $080D LDA #$1B
= $080F STA $0821
= $0812 LDA #$08
= $0814 STA $0822
= $0817 JSR $081E
= $081A RTS
= $081B LDX #$C8
= $081D RTS
= $081E JMP ($0821)
goto.
@ -340,7 +527,54 @@ goto.
| ld y, 200
| goto bar
| }
= 00c0a0c84c06c060a2c860
= $080D LDY #$C8
= $080F JMP $0813
= $0812 RTS
= $0813 LDX #$C8
= $0815 RTS
### word operations
Adding a constant word to a word memory location.
| word score
| routine main
| inputs score
| outputs score
| trashes a, c, z, v, n
| {
| st off, c
| add score, 1999
| }
= $080D CLC
= $080E LDA $081F
= $0811 ADC #$CF
= $0813 STA $081F
= $0816 LDA $0820
= $0819 ADC #$07
= $081B STA $0820
= $081E RTS
Adding a word memory location to another word memory location.
| word score
| word delta
| routine main
| inputs score, delta
| outputs score
| trashes a, c, z, v, n
| {
| st off, c
| add score, delta
| }
= $080D CLC
= $080E LDA $0821
= $0811 ADC $0823
= $0814 STA $0821
= $0817 LDA $0822
= $081A ADC $0824
= $081D STA $0822
= $0820 RTS
### Buffers and Pointers
@ -357,7 +591,12 @@ Load address into pointer.
| ld y, 0
| copy ^buf, ptr
| }
= 00c0a000a90b85fea9c085ff60
= $080D LDY #$00
= $080F LDA #$18
= $0811 STA $FE
= $0813 LDA #$08
= $0815 STA $FF
= $0817 RTS
Write literal through a pointer.
@ -373,7 +612,14 @@ Write literal through a pointer.
| copy ^buf, ptr
| copy 123, [ptr] + y
| }
= 00c0a000a90f85fea9c085ffa97b91fe60
= $080D LDY #$00
= $080F LDA #$1C
= $0811 STA $FE
= $0813 LDA #$08
= $0815 STA $FF
= $0817 LDA #$7B
= $0819 STA ($FE),Y
= $081B RTS
Write stored value through a pointer.
@ -390,7 +636,14 @@ Write stored value through a pointer.
| copy ^buf, ptr
| copy foo, [ptr] + y
| }
= 00c0a000a91085fea9c085ffad12c091fe60
= $080D LDY #$00
= $080F LDA #$1D
= $0811 STA $FE
= $0813 LDA #$08
= $0815 STA $FF
= $0817 LDA $101D
= $081A STA ($FE),Y
= $081C RTS
Read through a pointer.
@ -407,4 +660,56 @@ Read through a pointer.
| copy ^buf, ptr
| copy [ptr] + y, foo
| }
= 00c0a000a91085fea9c085ffb1fe8d12c060
= $080D LDY #$00
= $080F LDA #$1D
= $0811 STA $FE
= $0813 LDA #$08
= $0815 STA $FF
= $0817 LDA ($FE),Y
= $0819 STA $101D
= $081C RTS
Add a word memory location, and a literal word, to a pointer, and then read through it.
Note that this is *not* range-checked. (Yet.)
| buffer[2048] buf
| pointer ptr @ 254
| byte foo
| word delta
|
| routine main
| inputs buf
| outputs y, foo, delta
| trashes a, z, n, ptr
| {
| copy 619, delta
| ld y, 0
| copy ^buf, ptr
| add ptr, delta
| add ptr, word 1
| copy [ptr] + y, foo
| }
= $080D LDA #$6B
= $080F STA $1042
= $0812 LDA #$02
= $0814 STA $1043
= $0817 LDY #$00
= $0819 LDA #$41
= $081B STA $FE
= $081D LDA #$08
= $081F STA $FF
= $0821 LDA $FE
= $0823 ADC $1042
= $0826 STA $FE
= $0828 LDA $FF
= $082A ADC $1043
= $082D STA $FF
= $082F LDA $FE
= $0831 ADC #$01
= $0833 STA $FE
= $0835 LDA $FF
= $0837 ADC #$00
= $0839 STA $FF
= $083B LDA ($FE),Y
= $083D STA $1041
= $0840 RTS

View File

@ -129,6 +129,7 @@ User-defined memory addresses of different types.
| word wor
| vector vec
| byte table tab
| word table wtab
| buffer[2048] buf
| pointer ptr
|
@ -250,7 +251,7 @@ Can't define two routines with the same name.
| }
? SyntaxError
Declaring a byte table memory location.
Declaring byte and word table memory location.
| byte table tab
|
@ -262,6 +263,17 @@ Declaring a byte table memory location.
| }
= ok
| word one
| word table many
|
| routine main {
| ld x, 0
| copy one, many + x
| copy word 0, many + x
| copy many + x, one
| }
= ok
Declaring and calling a vector.
| vector cinv
@ -293,6 +305,44 @@ Only vectors can be decorated with constraints like that.
| }
? SyntaxError
Constraints set may only contain labels.
| vector cinv
| inputs a
| outputs 200
| trashes a, x, z, n
| @ 788
|
| routine foo {
| ld a, 0
| }
| routine main {
| with interrupts off {
| copy foo, cinv
| }
| call cinv
| }
? SyntaxError
A vector can name itself in its inputs, outputs, and trashes.
| vector cinv
| inputs cinv, a
| outputs cinv, x
| trashes a, x, z, n
| @ 788
|
| routine foo {
| ld a, 0
| }
| routine main {
| with interrupts off {
| copy foo, cinv
| }
| call cinv
| }
= ok
goto.
| routine foo {

View File

@ -0,0 +1,23 @@
#!/usr/bin/env python
# script that allows the binary output of sixtypical --basic-prelude --compile to be
# disassembled by https://github.com/tcarmelveilleux/dcc6502
import sys
import re
from subprocess import check_output
from tempfile import NamedTemporaryFile
bytes = sys.stdin.read()
bytes = bytes[14:]
f = NamedTemporaryFile(delete=False)
filename = f.name
f.write(bytes)
f.close()
lines = [line for line in check_output("dcc6502 -o 2061 {}".format(filename), shell=True).split('\n') if line and not line.startswith(';')]
lines = [re.sub(r'\s*\;.*$', '', line) for line in lines]
lines.pop()
sys.stdout.write('\n'.join(lines))