1
0
mirror of https://github.com/catseye/SixtyPical.git synced 2025-03-31 23:29:38 +00:00

Merge pull request from catseye/develop-0.10

Develop 0.10
This commit is contained in:
Chris Pressey 2018-01-08 12:22:35 +00:00 committed by GitHub
commit 55ae845345
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 1160 additions and 149 deletions

@ -1,6 +1,21 @@
History of SixtyPical
=====================
0.10
----
* Can `call` and `goto` routines that are defined further down in the source code.
* The `forward` modifier can also be used to indicate that the symbol being copied
in a `copy` to a vector is a routine that is defined further down in the source.
* Initialized `word` memory locations.
* Can `copy` a literal word to a word table.
* Subtract word (constant or memory location) from word memory location.
* `trash` instruction explicitly indicates a value is no longer considered meaningful.
* `copy []+y, a` can indirectly read a byte value into the `a` register.
* Fixed bug which was preventing `if` branches to diverge in what they initialized,
if it was already initialized when going into the `if`.
* Fixed a bug which was making it crash when trying to analyze `repeat forever` loops.
0.9
---

@ -23,7 +23,7 @@ The reference implementation can execute, analyze, and compile SixtyPical
programs to 6502 machine code.
SixtyPical is a work in progress. The current released version of SixtyPical
is 0.9.
is 0.10.
Documentation
-------------
@ -46,16 +46,6 @@ 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)
### `call` routines that are defined further down in the source code
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`.
### 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
@ -75,20 +65,28 @@ 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...
### Routine-local static memory locations
* Compare word (constant or memory location) with memory location or pointer. (Maybe?)
These would not need to appear in the inputs/outputs/trashes sets of the routines
that call this routine.
These might be forced to specify an initial value so that they can always be
assumed to be meaningful.
### More modes for `copy`
* don't allow `copy foo, a` probably. insist on `ld a, foo` for this.
* have `copy` instruction able to copy a byte to a user-def mem loc, etc.
* `copy x, [ptr] + y`
* Maybe even `copy [ptra] + y, [ptrb] + y`, which can be compiled to indirect LDA then indirect STA!
### And at some point...
* 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 -- 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?)

@ -1,7 +1,7 @@
SixtyPical
==========
This document describes the SixtyPical programming language version 0.9,
This document describes the SixtyPical programming language version 0.10,
both its execution aspect and its static analysis aspect (even though
these are, technically speaking, separate concepts.)

@ -27,18 +27,38 @@ byte joy2 @ $dc00
// ----------------------------------------------------------------
pointer ptr @ 254
word table actor_pos
word pos
word new_pos
word table actor_delta
word delta
byte button_down : 0 // effectively static-local to check_button
byte table press_fire_msg: "PRESS`FIRE`TO`PLAY"
byte save_x
word compare_target
//
// Points to the routine that implements the current game state.
//
// It's very arguable that screen1/2/3/4 and colormap1/2/3/4 are not REALLY inputs.
// They're only there to support the fact that game states sometimes clear the
// screen, and sometimes don't. When they don't, they preserve the screen, and
// currently the way to say "we preserve the screen" is to have it as both input
// and output. There is probably a better way to do this, but it needs thought.
//
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
inputs joy2, button_down, press_fire_msg, dispatch_game_state, save_x,
actor_pos, pos, new_pos, actor_delta, delta,
screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4
outputs button_down, dispatch_game_state,
actor_pos, pos, new_pos, actor_delta, delta,
screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4
trashes a, x, y, c, z, n, v, ptr, save_x, compare_target
//
// The constraints on these 2 vectors are kind-of sort-of big fibs.
@ -53,15 +73,23 @@ vector dispatch_game_state
//
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
inputs joy2, button_down, press_fire_msg, dispatch_game_state, save_x,
actor_pos, pos, new_pos, actor_delta, delta,
screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4
outputs button_down, dispatch_game_state,
actor_pos, pos, new_pos, actor_delta, delta,
screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4
trashes a, x, y, c, z, n, v, ptr, save_x, compare_target
@ 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
inputs joy2, button_down, press_fire_msg, dispatch_game_state, save_x,
actor_pos, pos, new_pos, actor_delta, delta,
screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4
outputs button_down, dispatch_game_state,
actor_pos, pos, new_pos, actor_delta, delta,
screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4
trashes a, x, y, c, z, n, v, ptr, save_x, compare_target
// ----------------------------------------------------------------
// Utility Routines
@ -154,6 +182,197 @@ routine clear_screen
} until z
}
routine calculate_new_position
inputs pos, delta
outputs new_pos
trashes a, c, n, z, v
{
copy pos, new_pos
st off, c
add new_pos, delta
}
routine check_new_position_in_bounds
inputs new_pos
outputs c
trashes compare_target, a, z, n, v
{
copy 1000, compare_target
st on, c
sub compare_target, new_pos
if not c {
copy word 0, compare_target
st on, c
sub compare_target, new_pos
if not c {
st off, c
} else {
st on, c
}
} else {
st on, c
}
}
routine init_game
inputs actor_pos, actor_delta
outputs actor_pos, actor_delta, pos
trashes a, y, z, n, c, v
{
ld y, 0
copy word 0, pos
repeat {
copy pos, actor_pos + y
copy word 40, actor_delta + y
st off, c
add pos, word 7
inc y
cmp y, 16
} until z
ld y, 0
copy word 0, actor_pos + y
copy word 0, actor_delta + y
}
// ----------------------------------------------------------------
// Actor Logics
// ----------------------------------------------------------------
//
// Sets carry if the player perished. Carry clear otherwise.
//
routine player_logic
inputs pos, delta, joy2, screen
outputs pos, delta, new_pos, screen, c
trashes a, x, y, z, n, v, ptr, compare_target
{
call read_stick
call calculate_new_position
call check_new_position_in_bounds
if c {
copy ^screen, ptr
st off, c
add ptr, new_pos
ld y, 0
// check collision.
copy [ptr] + y, a
// if "collision" is with your own self, treat it as if it's blank space!
cmp a, 81
if z {
ld a, 32
}
cmp a, 32
if z {
copy ^screen, ptr
st off, c
add ptr, pos
copy 32, [ptr] + y
copy new_pos, pos
copy ^screen, ptr
st off, c
add ptr, pos
copy 81, [ptr] + y
st off, c
} else {
st on, c
trash n
trash a
trash z
}
// FIXME these trashes, strictly speaking, probably shouldn't be needed,
// but currently the compiler cares too much about values that are
// initialized in one branch of an `if`, but not the other, but trashed
// at the end of the routine anyway.
trash ptr
trash y
trash a
trash v
} else {
st off, c
}
}
//
// Sets carry if the player perished. Carry clear otherwise.
//
routine enemy_logic
inputs pos, delta, screen
outputs pos, delta, new_pos, screen, c
trashes a, x, y, z, n, v, ptr, compare_target
{
call calculate_new_position
call check_new_position_in_bounds
if c {
copy ^screen, ptr
st off, c
add ptr, new_pos
ld y, 0
// check collision.
copy [ptr] + y, a
// if "collision" is with your own self, treat it as if it's blank space!
cmp a, 82
if z {
ld a, 32
}
cmp a, 32
if z {
copy ^screen, ptr
st off, c
add ptr, pos
copy 32, [ptr] + y
copy new_pos, pos
copy ^screen, ptr
st off, c
add ptr, pos
copy 82, [ptr] + y
st off, c
} else {
st on, c
trash n
trash a
trash z
}
// FIXME these trashes, strictly speaking, probably shouldn't be needed,
// but currently the compiler cares too much about values that are
// initialized in one branch of an `if`, but not the other, but trashed
// at the end of the routine anyway.
trash ptr
trash y
trash a
} else {
copy delta, compare_target
st on, c
sub compare_target, word 40
if not z {
copy word 40, delta
} else {
copy $ffd8, delta
}
trash compare_target
}
st off, c
}
// ----------------------------------------------------------------
// Game States
// ----------------------------------------------------------------
@ -162,49 +381,132 @@ routine clear_screen
// 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
inputs joy2, button_down, press_fire_msg, dispatch_game_state, save_x,
actor_pos, pos, new_pos, actor_delta, delta,
screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4
outputs button_down, dispatch_game_state,
actor_pos, pos, new_pos, actor_delta, delta,
screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4
trashes a, x, y, c, z, n, v, ptr, save_x, compare_target
{
ld y, 10
ld y, 0
repeat {
ld a, 90
ld a, press_fire_msg + y
st on, c
sub a, 64 // yuck. oh well
st a, screen1 + y
inc y
cmp y, 20
cmp y, 18
} until z
st off, c
call check_button
if c {
// call clear_screen
// call init_game
copy game_state_play, dispatch_game_state
call clear_screen
call init_game
copy forward game_state_play, dispatch_game_state
// FIXME these trashes, strictly speaking, probably shouldn't be needed,
// but currently the compiler cares too much about values that are
// initialized in one branch of an `if`, but not the other, but trashed
// at the end of the routine anyway.
trash a
trash n
trash z
} else {
// This is sort of a hack. FIXME: let `if` branches diverge this much.
copy dispatch_game_state, dispatch_game_state
trash y
trash c
trash v
}
goto save_cinv
}
routine game_state_play
inputs joy2, button_down, press_fire_msg, dispatch_game_state, save_x,
actor_pos, pos, new_pos, actor_delta, delta,
screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4
outputs button_down, dispatch_game_state,
actor_pos, pos, new_pos, actor_delta, delta,
screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4
trashes a, x, y, c, z, n, v, ptr, save_x, compare_target
{
ld x, 0
repeat {
copy actor_pos + x, pos
copy actor_delta + x, delta
st x, save_x
// FIXME need VECTOR TABLEs to make this happen:
// copy actor_logic, x dispatch_logic
// call indirect_jsr_logic
// For now, just check the actor ID to see what type it is, and go from there.
cmp x, 0
if z {
call player_logic
} else {
call enemy_logic
}
if c {
// Player died! Want no dead! Break out of the loop (this is a bit awkward.)
call clear_screen
copy forward game_state_game_over, dispatch_game_state
ld x, 15
st x, save_x
trash n
trash z
trash x
} else {
trash c
}
ld x, save_x
copy pos, actor_pos + x
copy delta, actor_delta + x
inc x
cmp x, 16
} until z
goto save_cinv
}
routine game_state_game_over
inputs joy2, button_down, press_fire_msg, dispatch_game_state, save_x,
actor_pos, pos, new_pos, actor_delta, delta,
screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4
outputs button_down, dispatch_game_state,
actor_pos, pos, new_pos, actor_delta, delta,
screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4
trashes a, x, y, c, z, n, v, ptr, save_x, compare_target
{
st off, c
call check_button
if c {
call clear_screen
call init_game
copy game_state_title_screen, dispatch_game_state
// FIXME these trashes, strictly speaking, probably shouldn't be needed,
// but currently the compiler cares too much about values that are
// initialized in one branch of an `if`, but not the other, but trashed
// at the end of the routine anyway.
trash a
trash n
trash z
} else {
trash y
trash c
trash v
}
goto save_cinv
@ -215,9 +517,13 @@ routine game_state_title_screen
// *************************
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
inputs joy2, button_down, press_fire_msg, dispatch_game_state, save_x,
actor_pos, pos, new_pos, actor_delta, delta,
screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4
outputs button_down, dispatch_game_state,
actor_pos, pos, new_pos, actor_delta, delta,
screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4
trashes a, x, y, c, z, n, v, ptr, save_x, compare_target
{
goto dispatch_game_state
}
@ -243,8 +549,6 @@ routine main
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
repeat { } forever
}

@ -89,17 +89,8 @@ class Context(object):
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)
LocationRef.format_set(self._touched), LocationRef.format_set(self._meaningful), LocationRef.format_set(self._writeable)
)
def clone(self):
@ -185,22 +176,12 @@ class Analyzer(object):
)
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)
message = 'in %s: %s are %s but affects %s which exceeds it by: %s ' % (
self.current_routine.name, name,
LocationRef.format_set(limited_to), LocationRef.format_set(affected), LocationRef.format_set(overage)
)
raise IncompatibleConstraintsError(message)
@ -292,9 +273,15 @@ class Analyzer(object):
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)
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, dest)
context.set_written(dest, FLAG_Z, FLAG_N, FLAG_C, FLAG_V)
context.set_touched(REG_A)
context.set_unmeaningful(REG_A)
elif opcode in ('inc', 'dec'):
self.assert_type(TYPE_BYTE, dest)
context.assert_meaningful(dest)
@ -332,23 +319,27 @@ class Analyzer(object):
# doesn't really matter if you modified it or not, coming out.
for ref in context1.each_meaningful():
context2.assert_meaningful(
ref, exception_class=InconsistentInitializationError, message='initialized in block 1 but not in block 2'
ref, exception_class=InconsistentInitializationError,
message='initialized in block 1 but not in block 2 of `if {}`'.format(src)
)
for ref in context2.each_meaningful():
context1.assert_meaningful(
ref, exception_class=InconsistentInitializationError, message='initialized in block 2 but not in block 1'
ref, exception_class=InconsistentInitializationError,
message='initialized in block 2 but not in block 1 of `if {}`'.format(src)
)
context.set_from(context1)
elif opcode == 'repeat':
# it will always be executed at least once, so analyze it having
# been executed the first time.
self.analyze_block(instr.block, context)
context.assert_meaningful(src)
if src is not None: # None indicates 'repeat forever'
context.assert_meaningful(src)
# now analyze it having been executed a second time, with the context
# of it having already been executed.
self.analyze_block(instr.block, context)
context.assert_meaningful(src)
if src is not None:
context.assert_meaningful(src)
elif opcode == 'copy':
# 1. check that their types are compatible
@ -369,7 +360,7 @@ class Analyzer(object):
else:
raise TypeMismatchError((src, dest))
elif isinstance(src, LocationRef) and isinstance(dest, IndexedRef):
elif isinstance(src, (LocationRef, ConstantRef)) and isinstance(dest, IndexedRef):
if src.type == TYPE_WORD and dest.ref.type == TYPE_WORD_TABLE:
pass
else:
@ -403,11 +394,15 @@ class Analyzer(object):
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) # TODO and REG_Y? if not, why not?
context.set_touched(dest)
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, ConstantRef) and isinstance(dest, IndexedRef):
context.assert_meaningful(src, dest.ref, 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?
@ -417,7 +412,15 @@ class Analyzer(object):
context.set_written(dest)
context.set_touched(REG_A, FLAG_Z, FLAG_N)
context.set_unmeaningful(REG_A, FLAG_Z, FLAG_N)
context.set_unmeaningful(FLAG_Z, FLAG_N)
# FIXME: this is just to support "copy [foo] + y, a". consider disallowing `a` as something
# that can be used in `copy`. should be using `st` or `ld` instead, probably.
if dest == REG_A:
context.set_touched(REG_A)
context.set_written(REG_A)
else:
context.set_unmeaningful(REG_A)
elif opcode == 'with-sei':
self.analyze_block(instr.block, context)
@ -439,5 +442,7 @@ class Analyzer(object):
self.assert_affected_within('trashes', type_.trashes, current_type.trashes)
self.has_encountered_goto = True
elif opcode == 'trash':
context.set_unmeaningful(instr.dest)
else:
raise NotImplementedError(opcode)

@ -6,7 +6,7 @@ from sixtypical.model import (
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
from sixtypical.emitter import Byte, Word, Table, Label, Offset, LowAddressByte, HighAddressByte
from sixtypical.gen6502 import (
Immediate, Absolute, AbsoluteX, AbsoluteY, ZeroPage, Indirect, IndirectY, Relative,
LDA, LDX, LDY, STA, STX, STY,
@ -31,6 +31,18 @@ class Compiler(object):
self.labels = {}
self.trampolines = {} # Location -> Label
# helper methods
def addressing_mode_for_index(self, index):
if index == REG_X:
return AbsoluteX
elif index == REG_Y:
return AbsoluteY
else:
raise NotImplementedError(index)
# visitor methods
def compile_program(self, program):
assert isinstance(program, Program)
@ -72,7 +84,16 @@ 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
initial_data = None
type_ = defn.location.type
if type_ == TYPE_BYTE:
initial_data = Byte(defn.initial)
elif type_ == TYPE_WORD:
initial_data = Word(defn.initial)
elif type_ == TYPE_BYTE_TABLE:
initial_data = Table(defn.initial)
else:
raise NotImplementedError(type_)
label.set_length(initial_data.size())
self.emitter.resolve_label(label)
self.emitter.emit(initial_data)
@ -209,6 +230,26 @@ class Compiler(object):
self.emitter.emit(SBC(Immediate(Byte(src.value))))
else:
self.emitter.emit(SBC(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(SBC(Immediate(Byte(src.low_byte()))))
self.emitter.emit(STA(Absolute(dest_label)))
self.emitter.emit(LDA(Absolute(Offset(dest_label, 1))))
self.emitter.emit(SBC(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(SBC(Absolute(src_label)))
self.emitter.emit(STA(Absolute(dest_label)))
self.emitter.emit(LDA(Absolute(Offset(dest_label, 1))))
self.emitter.emit(SBC(Absolute(Offset(src_label, 1))))
self.emitter.emit(STA(Absolute(Offset(dest_label, 1))))
else:
raise UnsupportedOpcodeError(instr)
else:
raise UnsupportedOpcodeError(instr)
elif opcode == 'inc':
@ -307,7 +348,7 @@ class Compiler(object):
elif opcode == 'repeat':
top_label = self.emitter.make_label()
self.compile_block(instr.block)
if src is None:
if src is None: # indicates 'repeat forever'
self.emitter.emit(JMP(Absolute(top_label)))
else:
cls = {
@ -344,7 +385,10 @@ class Compiler(object):
else:
raise NotImplementedError((src, dest))
elif isinstance(src, IndirectRef) and isinstance(dest, LocationRef):
if dest.type == TYPE_BYTE and isinstance(src.ref.type, PointerType):
if dest == REG_A and isinstance(src.ref.type, PointerType):
src_label = self.labels[src.ref.name]
self.emitter.emit(LDA(IndirectY(src_label)))
elif dest.type == TYPE_BYTE and isinstance(src.ref.type, PointerType):
src_label = self.labels[src.ref.name]
dest_label = self.labels[dest.name]
self.emitter.emit(LDA(IndirectY(src_label)))
@ -363,33 +407,28 @@ class Compiler(object):
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(STA(self.addressing_mode_for_index(dest.index)(dest_label)))
self.emitter.emit(LDA(Absolute(Offset(src_label, 1))))
self.emitter.emit(STA(addressing_mode(Offset(dest_label, 256))))
self.emitter.emit(STA(self.addressing_mode_for_index(dest.index)(Offset(dest_label, 256))))
else:
raise NotImplementedError
elif isinstance(src, ConstantRef) and isinstance(dest, IndexedRef):
if src.type == TYPE_WORD and dest.ref.type == TYPE_WORD_TABLE:
dest_label = self.labels[dest.ref.name]
self.emitter.emit(LDA(Immediate(Byte(src.low_byte()))))
self.emitter.emit(STA(self.addressing_mode_for_index(dest.index)(dest_label)))
self.emitter.emit(LDA(Immediate(Byte(src.high_byte()))))
self.emitter.emit(STA(self.addressing_mode_for_index(dest.index)(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(LDA(self.addressing_mode_for_index(src.index)(src_label)))
self.emitter.emit(STA(Absolute(dest_label)))
self.emitter.emit(LDA(addressing_mode(Offset(src_label, 256))))
self.emitter.emit(LDA(self.addressing_mode_for_index(src.index)(Offset(src_label, 256))))
self.emitter.emit(STA(Absolute(Offset(dest_label, 1))))
else:
raise NotImplementedError
@ -434,5 +473,7 @@ class Compiler(object):
self.emitter.emit(STA(Absolute(Offset(dest_label, 1))))
else:
raise NotImplementedError(src.type)
elif opcode == 'trash':
pass
else:
raise NotImplementedError(opcode)

@ -48,11 +48,20 @@ class Word(Emittable):
class Table(Emittable):
def __init__(self, value):
# TODO: range-checking
self.value = value
def size(self):
return 256
def serialize(self, addr=None):
return chr(0) * self.size()
bytes = []
for b in self.value:
bytes.append(chr(ord(b)))
while len(bytes) < self.size():
bytes.append(chr(0))
return ''.join(bytes)
def __repr__(self):
return "%s()" % (self.__class__.__name__)

@ -8,6 +8,9 @@ class Type(object):
def __repr__(self):
return 'Type(%r)' % self.name
def __str__(self):
return self.name
def __eq__(self, other):
return isinstance(other, Type) and other.name == self.name
@ -86,11 +89,12 @@ class LocationRef(Ref):
def __eq__(self, other):
# Ordinarily there will only be one ref with a given name,
# but because we store the type in here and we want to treat
# these objects as immutable, we compare the types, too.
# Not sure if very wise.
return isinstance(other, self.__class__) and (
other.name == self.name and other.type == self.type
)
# these objects as immutable, we compare the types, too,
# just to be sure.
equal = isinstance(other, self.__class__) and other.name == self.name
if equal:
assert other.type == self.type
return equal
def __hash__(self):
return hash(self.name + str(self.type))
@ -98,9 +102,23 @@ class LocationRef(Ref):
def __repr__(self):
return '%s(%r, %r)' % (self.__class__.__name__, self.type, self.name)
def __str__(self):
return "{}:{}".format(self.name, self.type)
def is_constant(self):
return isinstance(self.type, RoutineType)
def backpatch_vector_labels(self, resolver):
if isinstance(self.type, ExecutableType):
t = self.type
t.inputs = set([resolver(w) for w in t.inputs])
t.outputs = set([resolver(w) for w in t.outputs])
t.trashes = set([resolver(w) for w in t.trashes])
@classmethod
def format_set(cls, location_refs):
return '{%s}' % ', '.join([str(loc) for loc in sorted(location_refs)])
class IndirectRef(Ref):
def __init__(self, ref):

@ -23,6 +23,7 @@ class Parser(object):
self.symbols[token] = SymEntry(None, LocationRef(TYPE_BYTE, token))
for token in ('c', 'z', 'n', 'v'):
self.symbols[token] = SymEntry(None, LocationRef(TYPE_BIT, token))
self.backpatch_instrs = []
def lookup(self, name):
if name not in self.symbols:
@ -49,19 +50,28 @@ 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])
defn.location.backpatch_vector_labels(lambda w: self.lookup(w))
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])
routine.location.backpatch_vector_labels(lambda w: self.lookup(w))
for instr in self.backpatch_instrs:
if instr.opcode in ('call', 'goto'):
name = instr.location
if name not in self.symbols:
raise SyntaxError('Undefined routine "%s"' % name)
if not isinstance(self.symbols[name].model.type, ExecutableType):
raise SyntaxError('Illegal call of non-executable "%s"' % name)
instr.location = self.symbols[name].model
if instr.opcode in ('copy',) and isinstance(instr.src, basestring):
name = instr.src
if name not in self.symbols:
raise SyntaxError('Undefined routine "%s"' % name)
if not isinstance(self.symbols[name].model.type, ExecutableType):
raise SyntaxError('Illegal copy of non-executable "%s"' % name)
instr.src = self.symbols[name].model
return Program(defns=defns, routines=routines)
def defn(self):
@ -79,8 +89,11 @@ class Parser(object):
initial = None
if self.scanner.consume(':'):
self.scanner.check_type('integer literal')
initial = int(self.scanner.token)
if type_ == TYPE_BYTE_TABLE and self.scanner.on_type('string literal'):
initial = self.scanner.token
else:
self.scanner.check_type('integer literal')
initial = int(self.scanner.token)
self.scanner.scan()
addr = None
@ -194,7 +207,9 @@ class Parser(object):
return loc
def indlocexpr(self):
if self.scanner.consume('['):
if self.scanner.consume('forward'):
return self.label()
elif self.scanner.consume('['):
loc = self.locexpr()
self.scanner.expect(']')
self.scanner.expect('+')
@ -273,22 +288,25 @@ class Parser(object):
self.scanner.scan()
name = self.scanner.token
self.scanner.scan()
if name not in self.symbols:
raise SyntaxError('Undefined routine "%s"' % name)
if not isinstance(self.symbols[name].model.type, ExecutableType):
raise SyntaxError('Illegal call of non-executable "%s"' % name)
return Instr(opcode=opcode, location=self.symbols[name].model, dest=None, src=None)
instr = Instr(opcode=opcode, location=name, dest=None, src=None)
self.backpatch_instrs.append(instr)
return instr
elif self.scanner.token in ("copy",):
opcode = self.scanner.token
self.scanner.scan()
src = self.indlocexpr()
self.scanner.expect(',')
dest = self.indlocexpr()
return Instr(opcode=opcode, dest=dest, src=src)
instr = Instr(opcode=opcode, dest=dest, src=src)
self.backpatch_instrs.append(instr)
return instr
elif self.scanner.consume("with"):
self.scanner.expect("interrupts")
self.scanner.expect("off")
block = self.block()
return Instr(opcode='with-sei', dest=None, src=None, block=block)
elif self.scanner.consume("trash"):
dest = self.locexpr()
return Instr(opcode='trash', src=None, dest=dest)
else:
raise ValueError('bad opcode "%s"' % self.scanner.token)

@ -89,6 +89,24 @@ If a routine modifies a location, it needs to either output it or trash it.
| }
= ok
If a routine reads or writes a user-define memory location, it needs to declare that too.
| byte b1 @ 60000
| byte b2 : 3
| word w1 @ 60001
| word w2 : 2000
|
| routine main
| inputs b1, w1
| outputs b2, w2
| trashes a, z, n
| {
| ld a, b1
| st a, b2
| copy w1, w2
| }
= ok
### ld ###
Can't `ld` from a memory location that isn't initialized.
@ -370,6 +388,20 @@ Copying to and from a word table.
| }
? TypeMismatchError
You can also copy a literal word to a word table.
| word table many
|
| routine main
| inputs many
| outputs many
| trashes a, x, n, z
| {
| ld x, 0
| copy 9999, many + x
| }
= ok
### add ###
Can't `add` from or to a memory location that isn't initialized.
@ -561,6 +593,60 @@ Can't `sub` to a memory location that isn't writeable.
| }
? ForbiddenWriteError: a in main
You can `sub` a word constant from a word memory location.
| word score
| routine main
| inputs a, score
| outputs score
| trashes a, c, z, v, n
| {
| st on, c
| sub score, 1999
| }
= ok
`sub`ing a word constant from a word memory location trashes `a`.
| word score
| routine main
| inputs a, score
| outputs score, a
| trashes c, z, v, n
| {
| st on, c
| sub score, 1999
| }
? UnmeaningfulOutputError: a in main
You can `sub` a word memory location from another word memory location.
| word score
| word delta
| routine main
| inputs score, delta
| outputs score
| trashes a, c, z, v, n
| {
| st off, c
| sub score, delta
| }
= ok
`sub`ing a word memory location from 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
| sub score, delta
| }
? ForbiddenWriteError: a in main
### inc ###
Location must be initialized and writeable.
@ -1003,6 +1089,43 @@ same constraints.
| }
? UnmeaningfulReadError: a in main
### trash ###
Trash does nothing except indicate that we do not care about the value anymore.
| routine foo
| inputs a
| outputs x
| trashes a, z, n
| {
| st a, x
| ld a, 0
| trash a
| }
= ok
| routine foo
| inputs a
| outputs a, x
| trashes z, n
| {
| st a, x
| ld a, 0
| trash a
| }
? UnmeaningfulOutputError: a in foo
| routine foo
| inputs a
| outputs x
| trashes a, z, n
| {
| st a, x
| trash a
| st a, x
| }
? UnmeaningfulReadError: a in foo
### if ###
Both blocks of an `if` are analyzed.
@ -1065,6 +1188,26 @@ If a location is initialized in one block, is must be initialized in the other a
| }
? InconsistentInitializationError: x
However, this only pertains to initialization. If a value is already
initialized, either because it was set previous to the `if`, or is an
input to the routine, and it is initialized in one branch, it need not
be initialized in the other.
| routine foo
| inputs x
| outputs x
| trashes a, z, n, c
| {
| ld a, 0
| cmp a, 42
| if z {
| ld x, 7
| } else {
| ld a, 23
| }
| }
= ok
An `if` with a single block is analyzed as if it had an empty `else` block.
| routine foo
@ -1175,6 +1318,15 @@ this is an error too.
| }
? UnmeaningfulReadError: z in main
The body of `repeat forever` can be empty.
| routine main
| {
| repeat {
| } forever
| }
= ok
### copy ###
Can't `copy` from a memory location that isn't initialized.
@ -1234,7 +1386,7 @@ a, z, and n are trashed, and must be declared as such
| {
| copy 0, lives
| }
? ForbiddenWriteError: a in main
? ForbiddenWriteError: n in main
a, z, and n are trashed, and must not be declared as outputs.
@ -1244,7 +1396,7 @@ a, z, and n are trashed, and must not be declared as outputs.
| {
| copy 0, lives
| }
? UnmeaningfulOutputError: a in main
? UnmeaningfulOutputError: n in main
Unless of course you subsequently initialize them.

@ -117,6 +117,296 @@ Memory location with initial value.
= $0810 RTS
= $0811 .byte $03
Word memory locations with explicit address, initial value.
| word w1 @ 60001
| word w2 : 3003
|
| routine main
| inputs w1
| outputs w2
| trashes a, z, n
| {
| copy w1, w2
| }
= $080D LDA $EA61
= $0810 STA $081A
= $0813 LDA $EA62
= $0816 STA $081B
= $0819 RTS
= $081A .byte $BB
= $081B .byte $0B
Initialized byte table.
| byte table message : "WHAT?"
|
| routine main
| inputs message
| outputs x, a, z, n
| {
| ld x, 0
| ld a, message + x
| }
= $080D LDX #$00
= $080F LDA $0813,X
= $0812 RTS
= $0813 .byte $57
= $0814 PHA
= $0815 EOR ($54,X)
= $0817 .byte $3F
= $0818 BRK
= $0819 BRK
= $081A BRK
= $081B BRK
= $081C BRK
= $081D BRK
= $081E BRK
= $081F BRK
= $0820 BRK
= $0821 BRK
= $0822 BRK
= $0823 BRK
= $0824 BRK
= $0825 BRK
= $0826 BRK
= $0827 BRK
= $0828 BRK
= $0829 BRK
= $082A BRK
= $082B BRK
= $082C BRK
= $082D BRK
= $082E BRK
= $082F BRK
= $0830 BRK
= $0831 BRK
= $0832 BRK
= $0833 BRK
= $0834 BRK
= $0835 BRK
= $0836 BRK
= $0837 BRK
= $0838 BRK
= $0839 BRK
= $083A BRK
= $083B BRK
= $083C BRK
= $083D BRK
= $083E BRK
= $083F BRK
= $0840 BRK
= $0841 BRK
= $0842 BRK
= $0843 BRK
= $0844 BRK
= $0845 BRK
= $0846 BRK
= $0847 BRK
= $0848 BRK
= $0849 BRK
= $084A BRK
= $084B BRK
= $084C BRK
= $084D BRK
= $084E BRK
= $084F BRK
= $0850 BRK
= $0851 BRK
= $0852 BRK
= $0853 BRK
= $0854 BRK
= $0855 BRK
= $0856 BRK
= $0857 BRK
= $0858 BRK
= $0859 BRK
= $085A BRK
= $085B BRK
= $085C BRK
= $085D BRK
= $085E BRK
= $085F BRK
= $0860 BRK
= $0861 BRK
= $0862 BRK
= $0863 BRK
= $0864 BRK
= $0865 BRK
= $0866 BRK
= $0867 BRK
= $0868 BRK
= $0869 BRK
= $086A BRK
= $086B BRK
= $086C BRK
= $086D BRK
= $086E BRK
= $086F BRK
= $0870 BRK
= $0871 BRK
= $0872 BRK
= $0873 BRK
= $0874 BRK
= $0875 BRK
= $0876 BRK
= $0877 BRK
= $0878 BRK
= $0879 BRK
= $087A BRK
= $087B BRK
= $087C BRK
= $087D BRK
= $087E BRK
= $087F BRK
= $0880 BRK
= $0881 BRK
= $0882 BRK
= $0883 BRK
= $0884 BRK
= $0885 BRK
= $0886 BRK
= $0887 BRK
= $0888 BRK
= $0889 BRK
= $088A BRK
= $088B BRK
= $088C BRK
= $088D BRK
= $088E BRK
= $088F BRK
= $0890 BRK
= $0891 BRK
= $0892 BRK
= $0893 BRK
= $0894 BRK
= $0895 BRK
= $0896 BRK
= $0897 BRK
= $0898 BRK
= $0899 BRK
= $089A BRK
= $089B BRK
= $089C BRK
= $089D BRK
= $089E BRK
= $089F BRK
= $08A0 BRK
= $08A1 BRK
= $08A2 BRK
= $08A3 BRK
= $08A4 BRK
= $08A5 BRK
= $08A6 BRK
= $08A7 BRK
= $08A8 BRK
= $08A9 BRK
= $08AA BRK
= $08AB BRK
= $08AC BRK
= $08AD BRK
= $08AE BRK
= $08AF BRK
= $08B0 BRK
= $08B1 BRK
= $08B2 BRK
= $08B3 BRK
= $08B4 BRK
= $08B5 BRK
= $08B6 BRK
= $08B7 BRK
= $08B8 BRK
= $08B9 BRK
= $08BA BRK
= $08BB BRK
= $08BC BRK
= $08BD BRK
= $08BE BRK
= $08BF BRK
= $08C0 BRK
= $08C1 BRK
= $08C2 BRK
= $08C3 BRK
= $08C4 BRK
= $08C5 BRK
= $08C6 BRK
= $08C7 BRK
= $08C8 BRK
= $08C9 BRK
= $08CA BRK
= $08CB BRK
= $08CC BRK
= $08CD BRK
= $08CE BRK
= $08CF BRK
= $08D0 BRK
= $08D1 BRK
= $08D2 BRK
= $08D3 BRK
= $08D4 BRK
= $08D5 BRK
= $08D6 BRK
= $08D7 BRK
= $08D8 BRK
= $08D9 BRK
= $08DA BRK
= $08DB BRK
= $08DC BRK
= $08DD BRK
= $08DE BRK
= $08DF BRK
= $08E0 BRK
= $08E1 BRK
= $08E2 BRK
= $08E3 BRK
= $08E4 BRK
= $08E5 BRK
= $08E6 BRK
= $08E7 BRK
= $08E8 BRK
= $08E9 BRK
= $08EA BRK
= $08EB BRK
= $08EC BRK
= $08ED BRK
= $08EE BRK
= $08EF BRK
= $08F0 BRK
= $08F1 BRK
= $08F2 BRK
= $08F3 BRK
= $08F4 BRK
= $08F5 BRK
= $08F6 BRK
= $08F7 BRK
= $08F8 BRK
= $08F9 BRK
= $08FA BRK
= $08FB BRK
= $08FC BRK
= $08FD BRK
= $08FE BRK
= $08FF BRK
= $0900 BRK
= $0901 BRK
= $0902 BRK
= $0903 BRK
= $0904 BRK
= $0905 BRK
= $0906 BRK
= $0907 BRK
= $0908 BRK
= $0909 BRK
= $090A BRK
= $090B BRK
= $090C BRK
= $090D BRK
= $090E BRK
= $090F BRK
= $0910 BRK
= $0911 BRK
= $0912 BRK
Some instructions.
| byte foo
@ -297,6 +587,16 @@ Compiling `repeat forever`.
= $0810 JMP $080F
= $0813 RTS
The body of `repeat forever` can be empty.
| routine main
| {
| repeat {
| } forever
| }
= $080D JMP $080D
= $0810 RTS
Indexed access.
| byte one
@ -404,6 +704,25 @@ Copy literal word to word.
= $0814 STA $0819
= $0817 RTS
You can also copy a literal word to a word table.
| word table many
|
| routine main
| inputs many
| outputs many
| trashes a, x, n, z
| {
| ld x, 0
| copy 9999, many + x
| }
= $080D LDX #$00
= $080F LDA #$0F
= $0811 STA $081A,X
= $0814 LDA #$27
= $0816 STA $091A,X
= $0819 RTS
Copy vector to vector.
| vector bar
@ -576,6 +895,47 @@ Adding a word memory location to another word memory location.
= $081D STA $0822
= $0820 RTS
Subtracting a constant word from a word memory location.
| word score
| routine main
| inputs score
| outputs score
| trashes a, c, z, v, n
| {
| st on, c
| sub score, 1999
| }
= $080D SEC
= $080E LDA $081F
= $0811 SBC #$CF
= $0813 STA $081F
= $0816 LDA $0820
= $0819 SBC #$07
= $081B STA $0820
= $081E RTS
Subtracting a word memory location from another word memory location.
| word score
| word delta
| routine main
| inputs score, delta
| outputs score
| trashes a, c, z, v, n
| {
| st on, c
| sub score, delta
| }
= $080D SEC
= $080E LDA $0821
= $0811 SBC $0823
= $0814 STA $0821
= $0817 LDA $0822
= $081A SBC $0824
= $081D STA $0822
= $0820 RTS
### Buffers and Pointers
Load address into pointer.
@ -645,7 +1005,7 @@ Write stored value through a pointer.
= $081A STA ($FE),Y
= $081C RTS
Read through a pointer.
Read through a pointer, into a byte storage location, or the `a` register.
| buffer[2048] buf
| pointer ptr @ 254
@ -659,15 +1019,17 @@ Read through a pointer.
| ld y, 0
| copy ^buf, ptr
| copy [ptr] + y, foo
| copy [ptr] + y, a
| }
= $080D LDY #$00
= $080F LDA #$1D
= $080F LDA #$1F
= $0811 STA $FE
= $0813 LDA #$08
= $0815 STA $FF
= $0817 LDA ($FE),Y
= $0819 STA $101D
= $081C RTS
= $0819 STA $101F
= $081C LDA ($FE),Y
= $081E 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.)
@ -713,3 +1075,20 @@ Note that this is *not* range-checked. (Yet.)
= $083B LDA ($FE),Y
= $083D STA $1041
= $0840 RTS
### Trash
Trash does nothing except indicate that we do not care about the value anymore.
| routine main
| inputs a
| outputs x
| trashes a, z, n
| {
| ld x, a
| ld a, 0
| trash a
| }
= $080D TAX
= $080E LDA #$00
= $0810 RTS

@ -70,6 +70,14 @@ Extern routines
| @ 65487
= ok
Trash.
| routine main {
| trash a
| trash n
| }
= ok
If with not
| routine foo {
@ -178,6 +186,22 @@ User-defined locations of other types.
| }
= ok
Initialized byte table.
| byte table message : "WHAT DO YOU WANT TO DO NEXT?"
|
| routine main {
| }
= ok
Can't initialize anything but a byte table with a string.
| word message : "WHAT DO YOU WANT TO DO NEXT?"
|
| routine main {
| }
? SyntaxError
Can't access an undeclared memory location.
| routine main {
@ -239,6 +263,19 @@ And you can't call a non-routine.
| }
? SyntaxError
But you can call a routine that is yet to be defined, further on.
| routine main {
| ld x, 0
| ld y, 1
| call up
| call up
| }
| routine up {
| ld a, 0
| }
= ok
Can't define two routines with the same name.
| routine main {
@ -343,6 +380,33 @@ A vector can name itself in its inputs, outputs, and trashes.
| }
= ok
A routine can be copied into a vector before the routine appears in the program,
*however*, it must be marked as such with the keyword `forward`.
| vector cinv inputs cinv, a outputs cinv, x trashes a, x, z, n @ 788
| routine main {
| with interrupts off {
| copy foo, cinv
| }
| call cinv
| }
| routine foo {
| ld a, 0
| }
? SyntaxError: Undefined symbol
| vector cinv inputs cinv, a outputs cinv, x trashes a, x, z, n @ 788
| routine main {
| with interrupts off {
| copy forward foo, cinv
| }
| call cinv
| }
| routine foo {
| ld a, 0
| }
= ok
goto.
| routine foo {
@ -353,6 +417,14 @@ goto.
| }
= ok
| routine main {
| goto foo
| }
| routine foo {
| ld a, 0
| }
= ok
| vector foo
|
| routine main {