1
0
mirror of https://github.com/catseye/SixtyPical.git synced 2025-02-10 08:30:38 +00:00

Merge pull request #6 from catseye/develop-0.11

Develop 0.11
This commit is contained in:
Chris Pressey 2018-02-07 16:57:39 +00:00 committed by GitHub
commit f87c07e52a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 999 additions and 1341 deletions

View File

@ -1,6 +1,21 @@
History of SixtyPical
=====================
0.11
----
* Each table has a specified size now (although, bounds checking is not performed.)
* Initialized `byte table` values need not have all 256 bytes initialized.
* Syntax for types has changed. `routine` (with constraints) is a type, while
`vector` is now a type constructor (taking `routine`s only) and `table` is
also a type constructor. This permits a new `vector table` type.
* Added `typedef`, allowing the user to define type aliases for readability.
* Added `define name routine {...}` syntax; `routine name {...}` is now legacy.
* Ability to copy vectors and routines into vector tables, and vectors out of same.
* Removed the evaluator. The reference implementation only analyzes and compiles.
* Fixed bug where index register wasn't required to be initialized before table access.
* Fixed bug where trampolines for indirect calls weren't including a final `RTS`.
0.10
----
@ -12,6 +27,7 @@ History of SixtyPical
* 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.
* Initialized `byte table` memory locations.
* 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.

View File

@ -1,6 +1,8 @@
SixtyPical
==========
_Version 0.11. Work-in-progress, everything is subject to change._
SixtyPical is a very low-level programming language, similar to 6502 assembly,
with static analysis through abstract interpretation.
@ -19,11 +21,8 @@ based on common machine-language programming idioms, such as
* explicit tail calls
* indirect subroutine calls
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.10.
The reference implementation can analyze and compile SixtyPical programs to
6502 machine code.
Documentation
-------------
@ -40,14 +39,6 @@ Documentation
TODO
----
### Demo game
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)
### `vector table` type
### `low` and `high` address operators
To turn `word` type into `byte`.
@ -80,11 +71,14 @@ assumed to be meaningful.
* `copy x, [ptr] + y`
* Maybe even `copy [ptra] + y, [ptrb] + y`, which can be compiled to indirect LDA then indirect STA!
### Union rule for trashes in `if`
If one branch trashes {`a`} and the other branch trashes {`b`} then the whole
`if` statement trashes {`a`, `b`}.
### 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
* always analyze before executing or compiling, unless told not to
* `interrupt` routines -- to indicate that "the supervisor" has stored values on the stack, so we can trash them.
* error messages that include the line number of the source code
* add absolute addressing in shl/shr, absolute-indexed for add, sub, etc.

View File

@ -2,7 +2,7 @@
"""Usage: sixtypical [OPTIONS] FILES
Analyzes and/or executes and/or compiles a Sixtypical program.
Analyzes and compiles a Sixtypical program.
"""
from os.path import realpath, dirname, join
@ -19,7 +19,6 @@ import sys
import traceback
from sixtypical.parser import Parser
from sixtypical.evaluator import Evaluator
from sixtypical.analyzer import Analyzer
from sixtypical.emitter import Emitter, Byte, Word
from sixtypical.compiler import Compiler
@ -28,67 +27,73 @@ from sixtypical.compiler import Compiler
if __name__ == '__main__':
optparser = OptionParser(__doc__.strip())
optparser.add_option("--analyze",
optparser.add_option("--analyze-only",
action="store_true",
help="")
help="Only parse and analyze the program; do not compile it.")
optparser.add_option("--basic-prelude",
action="store_true",
help="")
optparser.add_option("--compile",
action="store_true",
help="")
help="Insert a Commodore BASIC 2.0 snippet before the program "
"so that it can be LOADed and RUN on Commodore platforms.")
optparser.add_option("--debug",
action="store_true",
help="")
help="Display debugging information when analyzing and compiling.")
optparser.add_option("--parse-only",
action="store_true",
help="Only parse the program; do not analyze or compile it.")
optparser.add_option("--traceback",
action="store_true",
help="")
optparser.add_option("--execute",
action="store_true",
help="")
help="When an error occurs, display a full Python traceback.")
(options, args) = optparser.parse_args(sys.argv[1:])
for filename in args:
text = open(filename).read()
parser = Parser(text)
program = parser.program()
if options.analyze:
try:
analyzer = Analyzer(debug=options.debug)
analyzer.analyze_program(program)
except Exception as e:
if options.traceback:
raise
else:
traceback.print_exception(e.__class__, e, None)
sys.exit(1)
if options.compile:
fh = sys.stdout
start_addr = 0xc000
prelude = []
if options.basic_prelude:
start_addr = 0x0801
prelude = [0x10, 0x08, 0xc9, 0x07, 0x9e, 0x32,
0x30, 0x36, 0x31, 0x00, 0x00, 0x00]
# we are outputting a .PRG, so we output the load address first
# we don't use the Emitter for this b/c not part of addr space
if not options.debug:
fh.write(Word(start_addr).serialize(0))
emitter = Emitter(start_addr)
for byte in prelude:
emitter.emit(Byte(byte))
compiler = Compiler(emitter)
compiler.compile_program(program)
if options.debug:
pprint(emitter.accum)
try:
parser = Parser(text)
program = parser.program()
except Exception as e:
if options.traceback:
raise
else:
emitter.serialize(fh)
traceback.print_exception(e.__class__, e, None)
sys.exit(1)
if options.execute:
context = Evaluator().eval_program(program)
print str(context)
if options.parse_only:
sys.exit(0)
try:
analyzer = Analyzer(debug=options.debug)
analyzer.analyze_program(program)
except Exception as e:
if options.traceback:
raise
else:
traceback.print_exception(e.__class__, e, None)
sys.exit(1)
if options.analyze_only:
sys.exit(0)
fh = sys.stdout
start_addr = 0xc000
prelude = []
if options.basic_prelude:
start_addr = 0x0801
prelude = [0x10, 0x08, 0xc9, 0x07, 0x9e, 0x32,
0x30, 0x36, 0x31, 0x00, 0x00, 0x00]
# we are outputting a .PRG, so we output the load address first
# we don't use the Emitter for this b/c not part of addr space
if not options.debug:
fh.write(Word(start_addr).serialize(0))
emitter = Emitter(start_addr)
for byte in prelude:
emitter.emit(Byte(byte))
compiler = Compiler(emitter)
compiler.compile_program(program)
if options.debug:
pprint(emitter.accum)
else:
emitter.serialize(fh)

View File

@ -1,9 +1,10 @@
SixtyPical
==========
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.)
This document describes the SixtyPical programming language version 0.11,
both its static semantics (the capabilities and limits of the static
analyses it defines) and its runtime semantics (with reference to the
semantics of 6502 machine code.)
This document is nominally normative, but the tests in the `tests` directory
are even more normative.
@ -14,20 +15,28 @@ the language.
Types
-----
There are six *primitive types* in SixtyPical:
There are five *primitive types* in SixtyPical:
* bit (2 possible values)
* byte (256 possible values)
* word (65536 possible values)
* routine (code stored somewhere in memory, read-only)
* vector (address of a routine)
* pointer (address of a byte in a buffer)
There are also two *type constructors*:
There are also three *type constructors*:
* T table (256 entries, each holding a value of type T, where T is either
`byte` or `word`)
* T table[N] (N is a power of 2, 1 ≤ N ≤ 256; each entry holds a value
of type T, where T is `byte`, `word`, or `vector`)
* buffer[N] (N entries; each entry is a byte; N is a power of 2, ≤ 64K)
* vector T (address of a value of type T; T must be a routine type)
### User-defined ###
A program may define its own types using the `typedef` feature. Typedefs
must occur before everything else in the program. A typedef takes a
type expression and an identifier which has not previously been used in
the program. It associates that identifer with that type. This is merely
a type alias; two types with different names will compare as equal.
Memory locations
----------------
@ -110,11 +119,11 @@ and `trashes` lists like a routine (see below), and it may only hold addresses
of routines which are compatible. (Meaning, the routine's inputs (resp. outputs,
trashes) must be a subset of the vector's inputs (resp. outputs, trashes.))
vector actor_logic
inputs a, score
outputs x
trashes y
@ $c000
vector routine
inputs a, score
outputs x
trashes y
actor_logic @ $c000
Note that in the code of a routine, if a memory location is named by a
user-defined symbol, it is an address in memory, and can be read and written.
@ -500,11 +509,22 @@ The sense of the test can be inverted with `not`.
Grammar
-------
Program ::= {Defn} {Routine}.
Program ::= {TypeDefn} {Defn} {Routine}.
TypeDefn::= "typedef" Type Ident<new>.
Defn ::= Type Ident<new> [Constraints] (":" Literal | "@" LitWord).
Type ::= "byte" ["table"] | "vector"
Type ::= "(" Type ")" | TypeExpr ["table" TypeSize].
TypeExpr::= "byte"
| "word"
| "buffer" TypeSize
| "pointer"
| "vector" Type
| "routine" Constraints
.
TypeSize::= "[" LitWord "]".
Constrnt::= ["inputs" LocExprs] ["outputs" LocExprs] ["trashes" LocExprs].
Routine ::= "routine" Ident<new> Constraints (Block | "@" LitWord).
Routine ::= "define" Ident<new> Type (Block | "@" LitWord).
| "routine" Ident<new> Constraints (Block | "@" LitWord)
.
LocExprs::= LocExpr {"," LocExpr}.
LocExpr ::= Register | Flag | Literal | Ident.
Register::= "a" | "x" | "y".

View File

@ -2,6 +2,58 @@
// * Demo Game for SixtyPical *
// ****************************
// ----------------------------------------------------------------
// Type Definitions
// ----------------------------------------------------------------
//
// Type of routines (and vectors to those routines) which are called on each frame
// to implement a certain state of the game (title screen, in play, game over, etc.)
//
// This type is also used as the type for the interrupt vector, even though
// the interrupt routine saves and restores everything before being called and
// thus clearly does not actually trash all the registers. It is declared this
// way so that the game state routines, which do trash these registers, can be
// assigned to it.
//
// This type is also used as the type for the location the old interrupt vector
// is backed up to, because all the game state routines `goto` the old handler
// and the end of their own routines, so the type needs to be compatible.
// (In a good sense, it is a continuation.)
//
// Further,
//
// 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.
//
typedef routine
inputs joy2, button_down, press_fire_msg, dispatch_game_state, save_x,
actor_pos, pos, new_pos, actor_delta, delta, actor_logic,
screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4
outputs button_down, dispatch_game_state,
actor_pos, pos, new_pos, actor_delta, delta, actor_logic,
screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4
trashes a, x, y, c, z, n, v, ptr, save_x, compare_target, dispatch_logic
game_state_routine
//
// Routines that are called to get the new state of each actor (player, enemy, etc.)
//
// Routines that conform to this type also follow this convention:
//
// Set carry if the player perished. Carry clear otherwise.
//
typedef routine
inputs pos, delta, joy2, screen
outputs pos, delta, new_pos, screen, c
trashes a, x, y, z, n, v, ptr, compare_target
logic_routine
// ----------------------------------------------------------------
// System Locations
// ----------------------------------------------------------------
@ -9,15 +61,15 @@
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[256] screen1 @ 1024
byte table[256] screen2 @ 1274
byte table[256] screen3 @ 1524
byte table[256] screen4 @ 1774
byte table colormap1 @ 55296
byte table colormap2 @ 55546
byte table colormap3 @ 55796
byte table colormap4 @ 56046
byte table[256] colormap1 @ 55296
byte table[256] colormap2 @ 55546
byte table[256] colormap3 @ 55796
byte table[256] colormap4 @ 56046
buffer[2048] screen @ 1024
byte joy2 @ $dc00
@ -28,15 +80,18 @@ byte joy2 @ $dc00
pointer ptr @ 254
word table actor_pos
word table[256] actor_pos
word pos
word new_pos
word table actor_delta
word table[256] actor_delta
word delta
vector (logic_routine) table[256] actor_logic
vector logic_routine dispatch_logic
byte button_down : 0 // effectively static-local to check_button
byte table press_fire_msg: "PRESS`FIRE`TO`PLAY"
byte table[32] press_fire_msg: "PRESS`FIRE`TO`PLAY"
byte save_x
word compare_target
@ -44,52 +99,23 @@ 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, 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
vector game_state_routine
dispatch_game_state
//
// 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.
// Interrupt vector. Has same type as game states (see above.)
//
vector cinv
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 game_state_routine
cinv @ 788
vector save_cinv
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
//
// Location to which the old interrupt vector is saved before replacement.
//
vector game_state_routine
save_cinv
// ----------------------------------------------------------------
// Utility Routines
@ -216,8 +242,8 @@ routine check_new_position_in_bounds
}
routine init_game
inputs actor_pos, actor_delta
outputs actor_pos, actor_delta, pos
inputs actor_pos, actor_delta, actor_logic
outputs actor_pos, actor_delta, pos, actor_logic
trashes a, y, z, n, c, v
{
ld y, 0
@ -225,6 +251,7 @@ routine init_game
repeat {
copy pos, actor_pos + y
copy word 40, actor_delta + y
copy forward enemy_logic, actor_logic + y
st off, c
add pos, word 7
@ -236,20 +263,14 @@ routine init_game
ld y, 0
copy word 0, actor_pos + y
copy word 0, actor_delta + y
copy forward player_logic, actor_logic + 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
define player_logic logic_routine
{
call read_stick
@ -304,14 +325,7 @@ routine player_logic
}
}
//
// 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
define enemy_logic logic_routine
{
call calculate_new_position
call check_new_position_in_bounds
@ -377,18 +391,7 @@ routine enemy_logic
// Game States
// ----------------------------------------------------------------
//
// Because these all `goto save_cinv` at the end, they must have the same signature as that routine.
//
routine game_state_title_screen
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
define game_state_title_screen game_state_routine
{
ld y, 0
repeat {
@ -426,14 +429,7 @@ routine game_state_title_screen
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
define game_state_play game_state_routine
{
ld x, 0
repeat {
@ -442,33 +438,19 @@ routine game_state_play
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
}
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 forward game_state_game_over, dispatch_game_state
ld x, 15
st x, save_x
trash n
trash z
trash x
} else {
ld x, save_x
trash c
}
ld x, save_x
copy pos, actor_pos + x
copy delta, actor_delta + x
@ -479,14 +461,7 @@ routine game_state_play
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
define game_state_game_over game_state_routine
{
st off, c
call check_button
@ -516,14 +491,7 @@ routine game_state_game_over
// * Main Game Loop Driver *
// *************************
routine our_cinv
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
define our_cinv game_state_routine
{
goto dispatch_game_state
}

58
eg/vector-table.60p Normal file
View File

@ -0,0 +1,58 @@
vector routine
trashes a, z, n
print
vector (routine
trashes a, z, n)
table[32] vectors
routine chrout
inputs a
trashes a
@ 65490
routine printa
trashes a, z, n
{
ld a, 65
call chrout
}
routine printb
trashes a, z, n
{
ld a, 66
call chrout
}
routine main
inputs vectors
outputs vectors
trashes print, a, x, z, n, c
{
ld x, 0
copy printa, print
copy print, vectors + x
inc x
copy printa, print
copy print, vectors + x
inc x
copy printb, print
copy print, vectors + x
inc x
copy printa, print
copy print, vectors + x
inc x
copy printb, print
copy print, vectors + x
copy printa, print
ld x, 0
repeat {
copy vectors + x, print
call print
inc x
cmp x, 5
} until z
}

51
eg/vector-table2.60p Normal file
View File

@ -0,0 +1,51 @@
vector routine
trashes a, z, n
print
vector (routine
trashes a, z, n)
table[32] vectors
routine chrout
inputs a
trashes a
@ 65490
routine printa
trashes a, z, n
{
ld a, 65
call chrout
}
routine printb
trashes a, z, n
{
ld a, 66
call chrout
}
routine main
inputs vectors
outputs vectors
trashes print, a, x, z, n, c
{
ld x, 0
copy printa, vectors + x
inc x
copy printa, vectors + x
inc x
copy printb, vectors + x
inc x
copy printa, vectors + x
inc x
copy printb, vectors + x
ld x, 0
repeat {
copy vectors + x, print
call print
inc x
cmp x, 5
} until z
}

View File

@ -1,5 +1,6 @@
vector print
vector routine
trashes a, z, n
print
routine chrout
inputs a
@ -21,7 +22,6 @@ routine printb
}
routine main
inputs printa, printb
trashes print, a, z, n
{
copy printa, print

View File

@ -6,7 +6,7 @@ if [ "X$1" = "X" ]; then
exit 1
fi
OUT=/tmp/a-out.prg
bin/sixtypical --traceback --analyze --compile --basic-prelude $SRC > $OUT || exit 1
bin/sixtypical --traceback --basic-prelude $SRC > $OUT || exit 1
if [ -e vicerc ]; then
x64 -config vicerc $OUT
else

View File

@ -2,7 +2,8 @@
from sixtypical.ast import Program, Routine, Block, Instr
from sixtypical.model import (
TYPE_BYTE, TYPE_WORD, TYPE_BYTE_TABLE, TYPE_WORD_TABLE, BufferType, PointerType, VectorType, ExecutableType,
TYPE_BYTE, TYPE_WORD,
TableType, BufferType, PointerType, VectorType, RoutineType,
ConstantRef, LocationRef, IndirectRef, IndexedRef, AddressRef,
REG_A, REG_Y, FLAG_Z, FLAG_N, FLAG_V, FLAG_C
)
@ -175,13 +176,18 @@ class Analyzer(object):
(location.name, self.current_routine.name)
)
def assert_affected_within(self, name, affected, limited_to):
def assert_affected_within(self, name, affecting_type, limiting_type):
assert name in ('inputs', 'outputs', 'trashes')
affected = getattr(affecting_type, name)
limited_to = getattr(limiting_type, name)
overage = affected - limited_to
if not overage:
return
message = 'in %s: %s are %s but affects %s which exceeds it by: %s ' % (
message = 'in %s: %s for %s are %s\n\nbut %s affects %s\n\nwhich exceeds it by: %s ' % (
self.current_routine.name, name,
LocationRef.format_set(limited_to), LocationRef.format_set(affected), LocationRef.format_set(overage)
limiting_type, LocationRef.format_set(limited_to),
affecting_type, LocationRef.format_set(affected),
LocationRef.format_set(overage)
)
raise IncompatibleConstraintsError(message)
@ -231,12 +237,13 @@ class Analyzer(object):
if opcode == 'ld':
if instr.index:
if src.type == TYPE_BYTE_TABLE and dest.type == TYPE_BYTE:
if TableType.is_a_table_type(src.type, TYPE_BYTE) and dest.type == TYPE_BYTE:
pass
else:
raise TypeMismatchError('%s and %s in %s' %
(src.name, dest.name, self.current_routine.name)
)
context.assert_meaningful(instr.index)
elif src.type != dest.type:
raise TypeMismatchError('%s and %s in %s' %
(src.name, dest.name, self.current_routine.name)
@ -245,13 +252,14 @@ class Analyzer(object):
context.set_written(dest, FLAG_Z, FLAG_N)
elif opcode == 'st':
if instr.index:
if src.type == TYPE_BYTE and dest.type == TYPE_BYTE_TABLE:
if src.type == TYPE_BYTE and TableType.is_a_table_type(dest.type, TYPE_BYTE):
pass
else:
raise TypeMismatchError((src, dest))
context.assert_meaningful(instr.index)
elif src.type != dest.type:
raise TypeMismatchError('%s and %s in %s' %
(src.name, dest.name, self.current_routine.name)
raise TypeMismatchError('%r and %r in %s' %
(src, dest, self.current_routine.name)
)
context.assert_meaningful(src)
context.set_written(dest)
@ -300,6 +308,8 @@ class Analyzer(object):
context.set_written(dest, FLAG_Z, FLAG_N, FLAG_C)
elif opcode == 'call':
type = instr.location.type
if isinstance(type, VectorType):
type = type.of_type
for ref in type.inputs:
context.assert_meaningful(ref)
for ref in type.outputs:
@ -361,13 +371,22 @@ class Analyzer(object):
raise TypeMismatchError((src, dest))
elif isinstance(src, (LocationRef, ConstantRef)) and isinstance(dest, IndexedRef):
if src.type == TYPE_WORD and dest.ref.type == TYPE_WORD_TABLE:
if src.type == TYPE_WORD and TableType.is_a_table_type(dest.ref.type, TYPE_WORD):
pass
elif (isinstance(src.type, VectorType) and isinstance(dest.ref.type, TableType) and
RoutineType.executable_types_compatible(src.type.of_type, dest.ref.type.of_type)):
pass
elif (isinstance(src.type, RoutineType) and isinstance(dest.ref.type, TableType) and
RoutineType.executable_types_compatible(src.type, dest.ref.type.of_type)):
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:
if TableType.is_a_table_type(src.ref.type, TYPE_WORD) and dest.type == TYPE_WORD:
pass
elif (isinstance(src.ref.type, TableType) and isinstance(dest.type, VectorType) and
RoutineType.executable_types_compatible(src.ref.type.of_type, dest.type.of_type)):
pass
else:
raise TypeMismatchError((src, dest))
@ -375,10 +394,10 @@ class Analyzer(object):
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):
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)
elif isinstance(src.type, RoutineType) and isinstance(dest.type, VectorType):
self.assert_affected_within('inputs', src.type, dest.type.of_type)
self.assert_affected_within('outputs', src.type, dest.type.of_type)
self.assert_affected_within('trashes', src.type, dest.type.of_type)
else:
raise TypeMismatchError((src, dest))
else:
@ -393,19 +412,16 @@ 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) # 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?
context.assert_meaningful(src.ref, src.index)
context.set_touched(dest)
context.set_written(dest)
else:
context.assert_meaningful(src)
@ -428,18 +444,20 @@ class Analyzer(object):
location = instr.location
type_ = location.type
if not isinstance(type_, ExecutableType):
if not isinstance(type_, (RoutineType, VectorType)):
raise TypeMismatchError(location)
# assert that the dest routine's inputs are all initialized
if isinstance(type_, VectorType):
type_ = type_.of_type
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
self.assert_affected_within('outputs', type_.outputs, current_type.outputs)
self.assert_affected_within('trashes', type_.trashes, current_type.trashes)
self.assert_affected_within('outputs', type_, current_type)
self.assert_affected_within('trashes', type_, current_type)
self.has_encountered_goto = True
elif opcode == 'trash':

View File

@ -3,7 +3,8 @@
from sixtypical.ast import Program, Routine, Block, Instr
from sixtypical.model import (
ConstantRef, LocationRef, IndexedRef, IndirectRef, AddressRef,
TYPE_BIT, TYPE_BYTE, TYPE_BYTE_TABLE, TYPE_WORD, TYPE_WORD_TABLE, BufferType, PointerType, RoutineType, VectorType,
TYPE_BIT, TYPE_BYTE, TYPE_WORD,
TableType, BufferType, PointerType, RoutineType, VectorType,
REG_A, REG_X, REG_Y, FLAG_C
)
from sixtypical.emitter import Byte, Word, Table, Label, Offset, LowAddressByte, HighAddressByte
@ -54,10 +55,8 @@ class Compiler(object):
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_, TableType):
length = type_.size * (1 if type_.of_type == TYPE_BYTE else 2)
elif isinstance(type_, BufferType):
length = type_.size
if length is None:
@ -79,6 +78,7 @@ class Compiler(object):
for location, label in self.trampolines.iteritems():
self.emitter.resolve_label(label)
self.emitter.emit(JMP(Indirect(self.labels[location.name])))
self.emitter.emit(RTS())
# initialized data
for defn in program.defns:
@ -90,8 +90,8 @@ class Compiler(object):
initial_data = Byte(defn.initial)
elif type_ == TYPE_WORD:
initial_data = Word(defn.initial)
elif type_ == TYPE_BYTE_TABLE:
initial_data = Table(defn.initial)
elif TableType.is_a_table_type(type_, TYPE_BYTE):
initial_data = Table(defn.initial, type_.size)
else:
raise NotImplementedError(type_)
label.set_length(initial_data.size())
@ -404,17 +404,32 @@ class Compiler(object):
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:
if src.type == TYPE_WORD and TableType.is_a_table_type(dest.ref.type, TYPE_WORD):
src_label = self.labels[src.name]
dest_label = self.labels[dest.ref.name]
self.emitter.emit(LDA(Absolute(src_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(self.addressing_mode_for_index(dest.index)(Offset(dest_label, 256))))
elif isinstance(src.type, VectorType) and isinstance(dest.ref.type, TableType) and isinstance(dest.ref.type.of_type, VectorType):
# FIXME this is the exact same as above - can this be simplified?
src_label = self.labels[src.name]
dest_label = self.labels[dest.ref.name]
self.emitter.emit(LDA(Absolute(src_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(self.addressing_mode_for_index(dest.index)(Offset(dest_label, 256))))
elif isinstance(src.type, RoutineType) and isinstance(dest.ref.type, TableType) and isinstance(dest.ref.type.of_type, VectorType):
src_label = self.labels[src.name]
dest_label = self.labels[dest.ref.name]
self.emitter.emit(LDA(Immediate(HighAddressByte(src_label))))
self.emitter.emit(STA(self.addressing_mode_for_index(dest.index)(dest_label)))
self.emitter.emit(LDA(Immediate(LowAddressByte(src_label))))
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:
if src.type == TYPE_WORD and TableType.is_a_table_type(dest.ref.type, TYPE_WORD):
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)))
@ -423,7 +438,15 @@ class Compiler(object):
else:
raise NotImplementedError
elif isinstance(src, IndexedRef) and isinstance(dest, LocationRef):
if src.ref.type == TYPE_WORD_TABLE and dest.type == TYPE_WORD:
if TableType.is_a_table_type(src.ref.type, TYPE_WORD) and dest.type == TYPE_WORD:
src_label = self.labels[src.ref.name]
dest_label = self.labels[dest.name]
self.emitter.emit(LDA(self.addressing_mode_for_index(src.index)(src_label)))
self.emitter.emit(STA(Absolute(dest_label)))
self.emitter.emit(LDA(self.addressing_mode_for_index(src.index)(Offset(src_label, 256))))
self.emitter.emit(STA(Absolute(Offset(dest_label, 1))))
elif isinstance(dest.type, VectorType) and isinstance(src.ref.type, TableType) and isinstance(src.ref.type.of_type, VectorType):
# FIXME this is the exact same as above - can this be simplified?
src_label = self.labels[src.ref.name]
dest_label = self.labels[dest.name]
self.emitter.emit(LDA(self.addressing_mode_for_index(src.index)(src_label)))

View File

@ -48,12 +48,13 @@ class Word(Emittable):
class Table(Emittable):
def __init__(self, value):
def __init__(self, value, size):
# TODO: range-checking
self.value = value
self._size = size
def size(self):
return 256
return self._size
def serialize(self, addr=None):
bytes = []

View File

@ -1,208 +0,0 @@
# encoding: UTF-8
from sixtypical.ast import Program, Routine, Block, Instr
from sixtypical.model import (
ConstantRef, LocationRef, PartRef, IndirectRef,
REG_A, REG_X, REG_Y, FLAG_Z, FLAG_N, FLAG_V, FLAG_C
)
class Context(object):
def __init__(self):
self._store = {}
def __str__(self):
return '\n'.join("%s: %s" % (name, value)
for (name, value) in sorted(self._store.iteritems())
if not isinstance(value, Routine))
def get(self, ref):
if isinstance(ref, ConstantRef):
return ref.value
elif isinstance(ref, LocationRef):
return self._store[ref.name]
elif isinstance(ref, PartRef):
value = self.get(ref.ref)
if ref.height == 0:
return value & 255
elif ref.height == 1:
return (value >> 8) & 255
else:
raise NotImplementedError
else:
raise ValueError(ref)
def set(self, ref, value):
if isinstance(ref, PartRef):
old = self.get(ref.ref)
if ref.height == 0:
value = (old & (255 << 8)) | value
elif ref.height == 1:
value = (value << 8) | (old & 255)
else:
raise NotImplementedError
ref = ref.ref
assert isinstance(ref, LocationRef)
self._store[ref.name] = value
class Evaluator(object):
def eval_program(self, program):
assert isinstance(program, Program)
context = Context()
for ref in (REG_A, REG_X, REG_Y, FLAG_Z, FLAG_N, FLAG_V, FLAG_C):
context.set(ref, 0)
main = None
for defn in program.defns:
if defn.initial is not None:
context.set(defn.location, defn.initial)
for routine in program.routines:
context.set(routine.location, routine)
if routine.name == 'main':
main = routine
self.eval_routine(main, context)
return context
def eval_routine(self, routine, context):
assert isinstance(routine, Routine)
self.next_routine = routine
while self.next_routine:
routine = self.next_routine
self.next_routine = None
self.eval_block(routine.block, context)
def eval_block(self, block, context):
assert isinstance(block, Block)
for i in block.instrs:
self.eval_instr(i, context)
if self.next_routine:
break
def eval_instr(self, instr, context):
assert isinstance(instr, Instr)
opcode = instr.opcode
dest = instr.dest
src = instr.src
if opcode == 'ld':
result = context.get(src)
context.set(FLAG_Z, 1 if result == 0 else 0)
context.set(FLAG_N, 1 if result & 128 else 0)
context.set(dest, result)
elif opcode == 'st':
context.set(dest, context.get(src))
elif opcode == 'add':
carry = context.get(FLAG_C)
val = context.get(src)
now = context.get(dest)
result = now + val + carry
if result > 255:
result &= 255
context.set(FLAG_C, 1)
else:
context.set(FLAG_C, 0)
context.set(FLAG_Z, 1 if result == 0 else 0)
context.set(FLAG_N, 1 if result & 128 else 0)
context.set(dest, result)
elif opcode == 'sub':
carry = context.get(FLAG_C)
val = context.get(src)
now = context.get(dest)
result = now - val - carry
if result < 0:
result &= 255
context.set(FLAG_C, 1)
else:
context.set(FLAG_C, 0)
context.set(FLAG_Z, 1 if result == 0 else 0)
context.set(FLAG_N, 1 if result & 128 else 0)
context.set(dest, result)
elif opcode == 'inc':
val = context.get(dest)
result = (val + 1) & 255
context.set(FLAG_Z, 1 if result == 0 else 0)
context.set(FLAG_N, 1 if result & 128 else 0)
context.set(dest, result)
elif opcode == 'dec':
val = context.get(dest)
result = (val - 1) & 255
context.set(FLAG_Z, 1 if result == 0 else 0)
context.set(FLAG_N, 1 if result & 128 else 0)
context.set(dest, result)
elif opcode == 'cmp':
val = context.get(src)
now = context.get(dest)
result = now - val
context.set(FLAG_Z, 1 if result == 0 else 0)
context.set(FLAG_N, 1 if result & 128 else 0)
if result < 0:
result &= 255
context.set(FLAG_C, 1)
else:
context.set(FLAG_C, 0)
elif opcode == 'and':
result = context.get(dest) & context.get(src)
context.set(FLAG_Z, 1 if result == 0 else 0)
context.set(FLAG_N, 1 if result & 128 else 0)
context.set(dest, result)
elif opcode == 'or':
result = context.get(dest) | context.get(src)
context.set(FLAG_Z, 1 if result == 0 else 0)
context.set(FLAG_N, 1 if result & 128 else 0)
context.set(dest, result)
elif opcode == 'xor':
result = context.get(dest) ^ context.get(src)
context.set(FLAG_Z, 1 if result == 0 else 0)
context.set(FLAG_N, 1 if result & 128 else 0)
context.set(dest, result)
elif opcode == 'shl':
val = context.get(dest)
carry = context.get(FLAG_C)
context.set(FLAG_C, 1 if val & 128 else 0)
result = ((val << 1) + carry) & 255
context.set(FLAG_Z, 1 if result == 0 else 0)
context.set(FLAG_N, 1 if result & 128 else 0)
context.set(dest, result)
elif opcode == 'shr':
val = context.get(dest)
carry = context.get(FLAG_C)
context.set(FLAG_C, 1 if val & 1 else 0)
result = (val >> 1) + (carry * 128)
context.set(FLAG_Z, 1 if result == 0 else 0)
context.set(FLAG_N, 1 if result & 128 else 0)
context.set(dest, result)
elif opcode == 'call':
self.eval_routine(context.get(instr.location), context)
elif opcode == 'goto':
self.next_routine = context.get(instr.location)
elif opcode == 'if':
val = context.get(src)
test = (val != 0) if not instr.inverted else (val == 0)
if test:
self.eval_block(instr.block1, context)
elif instr.block2:
self.eval_block(instr.block2, context)
elif opcode == 'repeat':
self.eval_block(instr.block, context)
while context.get(src) == 0:
self.eval_block(instr.block, context)
elif opcode == 'copy':
if isinstance(src, IndirectRef):
raise NotImplementedError("this doesn't actually work")
src = src.ref
if isinstance(dest, IndirectRef):
raise NotImplementedError("this doesn't actually work")
dest = dest.ref
context.set(dest, context.get(src))
# these are trashed; so could be anything really
context.set(REG_A, 0)
context.set(FLAG_Z, 0)
context.set(FLAG_N, 0)
elif opcode == 'with-sei':
self.eval_block(instr.block)
else:
raise NotImplementedError

View File

@ -17,25 +17,38 @@ class Type(object):
def __hash__(self):
return hash(self.name)
def backpatch_constraint_labels(self, resolver):
def resolve(w):
if not isinstance(w, basestring):
return w
return resolver(w)
if isinstance(self, TableType):
self.of_type.backpatch_constraint_labels(resolver)
elif isinstance(self, VectorType):
self.of_type.backpatch_constraint_labels(resolver)
elif isinstance(self, RoutineType):
self.inputs = set([resolve(w) for w in self.inputs])
self.outputs = set([resolve(w) for w in self.outputs])
self.trashes = set([resolve(w) for w in self.trashes])
TYPE_BIT = Type('bit')
TYPE_BYTE = Type('byte')
TYPE_BYTE_TABLE = Type('byte table')
TYPE_WORD = Type('word')
TYPE_WORD_TABLE = Type('word table')
class ExecutableType(Type):
"""Used for routines and vectors."""
def __init__(self, name, inputs=None, outputs=None, trashes=None):
self.name = name
class RoutineType(Type):
"""This memory location contains the code for a routine."""
def __init__(self, inputs=None, outputs=None, trashes=None):
self.name = 'routine'
self.inputs = inputs or set()
self.outputs = outputs or set()
self.trashes = trashes or set()
def __repr__(self):
return 'RoutineType(%r, inputs=%r, outputs=%r, trashes=%r)' % (
self.name, self.inputs, self.outputs, self.trashes
return '%s(%r, inputs=%r, outputs=%r, trashes=%r)' % (
self.__class__.__name__, self.name, self.inputs, self.outputs, self.trashes
)
def __eq__(self, other):
@ -49,17 +62,56 @@ class ExecutableType(Type):
def __hash__(self):
return hash(self.name) ^ hash(self.inputs) ^ hash(self.outputs) ^ hash(self.trashes)
class RoutineType(ExecutableType):
"""This memory location contains the code for a routine."""
def __init__(self, **kwargs):
super(RoutineType, self).__init__('routine', **kwargs)
@classmethod
def executable_types_compatible(cls_, src, dest):
"""Returns True iff a value of type `src` can be assigned to a storage location of type `dest`."""
if isinstance(src, VectorType):
src = src.of_type
if isinstance(dest, VectorType):
dest = dest.of_type
if isinstance(src, RoutineType) and isinstance(dest, RoutineType):
# TODO: I'm sure we can replace some of these with subset-containment, but that requires thought
return (
src.inputs == dest.inputs and
src.outputs == dest.outputs and
src.trashes == dest.trashes
)
else:
return False
class VectorType(ExecutableType):
"""This memory location contains the address of a routine."""
def __init__(self, **kwargs):
super(VectorType, self).__init__('vector', **kwargs)
class VectorType(Type):
"""This memory location contains the address of some other type (currently, only RoutineType)."""
def __init__(self, of_type):
self.name = 'vector'
self.of_type = of_type
def __repr__(self):
return '%s(%r)' % (
self.__class__.__name__, self.of_type
)
def __eq__(self, other):
return self.name == other.name and self.of_type == other.of_type
def __hash__(self):
return hash(self.name) ^ hash(self.of_type)
class TableType(Type):
def __init__(self, of_type, size):
self.of_type = of_type
self.size = size
self.name = '{} table[{}]'.format(self.of_type.name, self.size)
def __repr__(self):
return '%s(%r, %r)' % (
self.__class__.__name__, self.of_type, self.size
)
@classmethod
def is_a_table_type(cls_, x, of_type):
return isinstance(x, TableType) and x.of_type == of_type
class BufferType(Type):
@ -108,13 +160,6 @@ class LocationRef(Ref):
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)])

View File

@ -2,8 +2,8 @@
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,
TYPE_BIT, TYPE_BYTE, TYPE_WORD,
RoutineType, VectorType, TableType, BufferType, PointerType,
LocationRef, ConstantRef, IndirectRef, IndexedRef, AddressRef,
)
from sixtypical.scanner import Scanner
@ -19,6 +19,7 @@ class Parser(object):
def __init__(self, text):
self.scanner = Scanner(text)
self.symbols = {} # token -> SymEntry
self.typedefs = {} # token -> Type AST
for token in ('a', 'x', 'y'):
self.symbols[token] = SymEntry(None, LocationRef(TYPE_BYTE, token))
for token in ('c', 'z', 'n', 'v'):
@ -35,16 +36,25 @@ class Parser(object):
def program(self):
defns = []
routines = []
while self.scanner.on('byte', 'word', 'vector', 'buffer', 'pointer'):
while self.scanner.on('typedef'):
typedef = self.typedef()
typenames = ['byte', 'word', 'table', 'vector', 'buffer', 'pointer'] # 'routine',
typenames.extend(self.typedefs.keys())
while self.scanner.on(*typenames):
defn = self.defn()
name = defn.name
if name in self.symbols:
raise SyntaxError('Symbol "%s" already declared' % name)
self.symbols[name] = SymEntry(defn, defn.location)
defns.append(defn)
while self.scanner.on('routine'):
routine = self.routine()
name = routine.name
while self.scanner.on('define', 'routine'):
if self.scanner.consume('define'):
name = self.scanner.token
self.scanner.scan()
routine = self.routine(name)
else:
routine = self.legacy_routine()
name = routine.name
if name in self.symbols:
raise SyntaxError('Symbol "%s" already declared' % name)
self.symbols[name] = SymEntry(routine, routine.location)
@ -52,44 +62,46 @@ class Parser(object):
self.scanner.check_type('EOF')
# now backpatch the executable types.
#for type_name, type_ in self.typedefs.iteritems():
# type_.backpatch_constraint_labels(lambda w: self.lookup(w))
for defn in defns:
defn.location.backpatch_vector_labels(lambda w: self.lookup(w))
defn.location.type.backpatch_constraint_labels(lambda w: self.lookup(w))
for routine in routines:
routine.location.backpatch_vector_labels(lambda w: self.lookup(w))
routine.location.type.backpatch_constraint_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):
if not isinstance(self.symbols[name].model.type, (RoutineType, VectorType)):
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):
if not isinstance(self.symbols[name].model.type, (RoutineType, VectorType)):
raise SyntaxError('Illegal copy of non-executable "%s"' % name)
instr.src = self.symbols[name].model
return Program(defns=defns, routines=routines)
def typedef(self):
self.scanner.expect('typedef')
type_ = self.defn_type()
name = self.defn_name()
if name in self.typedefs:
raise SyntaxError('Type "%s" already declared' % name)
self.typedefs[name] = type_
return type_
def defn(self):
type_ = self.defn_type()
self.scanner.check_type('identifier')
name = self.scanner.token
self.scanner.scan()
(inputs, outputs, trashes) = self.constraints()
if type_ == 'vector':
type_ = VectorType(inputs=inputs, outputs=outputs, trashes=trashes)
elif inputs or outputs or trashes:
raise SyntaxError("Cannot apply constraints to non-vector type")
name = self.defn_name()
initial = None
if self.scanner.consume(':'):
if type_ == TYPE_BYTE_TABLE and self.scanner.on_type('string literal'):
if isinstance(type_, TableType) and self.scanner.on_type('string literal'):
initial = self.scanner.token
else:
self.scanner.check_type('integer literal')
@ -109,27 +121,57 @@ class Parser(object):
return Defn(name=name, addr=addr, initial=initial, location=location)
def defn_size(self):
self.scanner.expect('[')
self.scanner.check_type('integer literal')
size = int(self.scanner.token)
self.scanner.scan()
self.scanner.expect(']')
return size
def defn_type(self):
type_ = None
if self.scanner.consume('('):
type_ = self.defn_type()
self.scanner.expect(')')
return type_
if self.scanner.consume('byte'):
if self.scanner.consume('table'):
return TYPE_BYTE_TABLE
return TYPE_BYTE
type_ = TYPE_BYTE
elif self.scanner.consume('word'):
if self.scanner.consume('table'):
return TYPE_WORD_TABLE
return TYPE_WORD
type_ = TYPE_WORD
elif self.scanner.consume('vector'):
return 'vector' # will be resolved to a Type by caller
type_ = self.defn_type()
if not isinstance(type_, RoutineType):
raise SyntaxError("Vectors can only be of a routine, not %r" % type_)
type_ = VectorType(type_)
elif self.scanner.consume('routine'):
(inputs, outputs, trashes) = self.constraints()
type_ = RoutineType(inputs=inputs, outputs=outputs, trashes=trashes)
elif self.scanner.consume('buffer'):
self.scanner.expect('[')
self.scanner.check_type('integer literal')
size = int(self.scanner.token)
self.scanner.scan()
self.scanner.expect(']')
return BufferType(size)
size = self.defn_size()
type_ = BufferType(size)
elif self.scanner.consume('pointer'):
type_ = PointerType()
else:
self.scanner.expect('pointer')
return PointerType()
type_name = self.scanner.token
self.scanner.scan()
if type_name not in self.typedefs:
raise SyntaxError("Undefined type '%s'" % type_name)
type_ = self.typedefs[type_name]
if self.scanner.consume('table'):
size = self.defn_size()
type_ = TableType(type_, size)
return type_
def defn_name(self):
self.scanner.check_type('identifier')
name = self.scanner.token
self.scanner.scan()
return name
def constraints(self):
inputs = set()
@ -143,11 +185,12 @@ class Parser(object):
trashes = set(self.labels())
return (inputs, outputs, trashes)
def routine(self):
def legacy_routine(self):
self.scanner.expect('routine')
name = self.scanner.token
self.scanner.scan()
(inputs, outputs, trashes) = self.constraints()
type_ = RoutineType(inputs=inputs, outputs=outputs, trashes=trashes)
if self.scanner.consume('@'):
self.scanner.check_type('integer literal')
block = None
@ -156,10 +199,25 @@ class Parser(object):
else:
block = self.block()
addr = None
location = LocationRef(
RoutineType(inputs=inputs, outputs=outputs, trashes=trashes),
name
location = LocationRef(type_, name)
return Routine(
name=name, block=block, addr=addr,
location=location
)
def routine(self, name):
type_ = self.defn_type()
if not isinstance(type_, RoutineType):
raise SyntaxError("Can only define a routine, not %r" % type_)
if self.scanner.consume('@'):
self.scanner.check_type('integer literal')
block = None
addr = int(self.scanner.token)
self.scanner.scan()
else:
block = self.block()
addr = None
location = LocationRef(type_, name)
return Routine(
name=name, block=block, addr=addr,
location=location

View File

@ -2,6 +2,5 @@
falderal --substring-error \
tests/SixtyPical\ Syntax.md \
tests/SixtyPical\ Execution.md \
tests/SixtyPical\ Analysis.md \
tests/SixtyPical\ Compilation.md

View File

@ -7,7 +7,7 @@ static analysis rules.
[Falderal]: http://catseye.tc/node/Falderal
-> Functionality "Analyze SixtyPical program" is implemented by
-> shell command "bin/sixtypical --analyze --traceback %(test-body-file) && echo ok"
-> shell command "bin/sixtypical --analyze-only --traceback %(test-body-file) && echo ok"
-> Tests for functionality "Analyze SixtyPical program"
@ -236,14 +236,14 @@ Can't `st` a `word` type.
| ld a, 0
| st a, foo
| }
? TypeMismatchError: a and foo in main
? TypeMismatchError
### tables ###
Storing to a table, you must use an index, and vice-versa.
Storing to a table, you must use an index.
| byte one
| byte table many
| byte table[256] many
|
| routine main
| outputs one
@ -256,7 +256,7 @@ Storing to a table, you must use an index, and vice-versa.
= ok
| byte one
| byte table many
| byte table[256] many
|
| routine main
| outputs many
@ -269,7 +269,7 @@ Storing to a table, you must use an index, and vice-versa.
? TypeMismatchError
| byte one
| byte table many
| byte table[256] many
|
| routine main
| outputs one
@ -282,7 +282,7 @@ Storing to a table, you must use an index, and vice-versa.
? TypeMismatchError
| byte one
| byte table many
| byte table[256] many
|
| routine main
| outputs many
@ -294,7 +294,21 @@ Storing to a table, you must use an index, and vice-versa.
| }
= ok
Reading from a table, you must use an index, and vice-versa.
The index must be initialized.
| byte one
| byte table[256] many
|
| routine main
| outputs many
| trashes a, x, n, z
| {
| ld a, 0
| st a, many + x
| }
? UnmeaningfulReadError: x
Reading from a table, you must use an index.
| byte one
|
@ -320,7 +334,7 @@ Reading from a table, you must use an index, and vice-versa.
| }
? TypeMismatchError
| byte table many
| byte table[256] many
|
| routine main
| outputs many
@ -333,7 +347,7 @@ Reading from a table, you must use an index, and vice-versa.
| }
? TypeMismatchError
| byte table many
| byte table[256] many
|
| routine main
| outputs many
@ -346,10 +360,35 @@ Reading from a table, you must use an index, and vice-versa.
| }
= ok
| byte table[256] many
|
| routine main
| inputs many
| outputs many
| trashes a, x, n, z
| {
| ld x, 0
| ld a, many + x
| }
= ok
The index must be initialized.
| byte table[256] many
|
| routine main
| inputs many
| outputs many
| trashes a, x, n, z
| {
| ld a, many + x
| }
? UnmeaningfulReadError: x
Copying to and from a word table.
| word one
| word table many
| word table[256] many
|
| routine main
| inputs one, many
@ -363,7 +402,7 @@ Copying to and from a word table.
= ok
| word one
| word table many
| word table[256] many
|
| routine main
| inputs one, many
@ -376,7 +415,7 @@ Copying to and from a word table.
? TypeMismatchError
| word one
| word table many
| word table[256] many
|
| routine main
| inputs one, many
@ -390,7 +429,7 @@ Copying to and from a word table.
You can also copy a literal word to a word table.
| word table many
| word table[256] many
|
| routine main
| inputs many
@ -1554,10 +1593,11 @@ Read through a pointer.
Routines are constants. You need not, and in fact cannot, specify a constant
as an input to, an output of, or as a trashed value of a routine.
| vector vec
| vector routine
| inputs x
| outputs x
| trashes z, n
| vec
|
| routine foo
| inputs x
@ -1576,10 +1616,11 @@ as an input to, an output of, or as a trashed value of a routine.
| }
? ConstantConstraintError: foo in main
| vector vec
| vector routine
| inputs x
| outputs x
| trashes z, n
| vec
|
| routine foo
| inputs x
@ -1597,10 +1638,11 @@ as an input to, an output of, or as a trashed value of a routine.
| }
? ConstantConstraintError: foo in main
| vector vec
| vector routine
| inputs x
| outputs x
| trashes z, n
| vec
|
| routine foo
| inputs x
@ -1621,10 +1663,11 @@ as an input to, an output of, or as a trashed value of a routine.
You can copy the address of a routine into a vector, if that vector is
declared appropriately.
| vector vec
| vector routine
| inputs x
| outputs x
| trashes z, n
| vec
|
| routine foo
| inputs x
@ -1644,10 +1687,11 @@ declared appropriately.
But not if the vector is declared inappropriately.
| vector vec
| vector routine
| inputs y
| outputs y
| trashes z, n
| vec
|
| routine foo
| inputs x
@ -1668,10 +1712,11 @@ But not if the vector is declared inappropriately.
"Appropriately" means, if the routine affects no more than what is named
in the input/output sets of the vector.
| vector vec
| vector routine
| inputs a, x
| outputs x
| trashes a, z, n
| vec
|
| routine foo
| inputs x
@ -1691,10 +1736,11 @@ in the input/output sets of the vector.
Routines are read-only.
| vector vec
| vector routine
| inputs x
| outputs x
| trashes z, n
| vec
|
| routine foo
| inputs x
@ -1714,7 +1760,9 @@ Routines are read-only.
Indirect call.
| vector foo outputs x trashes z, n
| vector routine
| outputs x trashes z, n
| foo
|
| routine bar outputs x trashes z, n {
| ld x, 200
@ -1728,7 +1776,7 @@ Indirect call.
Calling the vector does indeed trash the things the vector says it does.
| vector foo trashes x, z, n
| vector routine trashes x, z, n foo
|
| routine bar trashes x, z, n {
| ld x, 200
@ -1828,7 +1876,7 @@ Can `goto` a routine that outputs or trashes less than the current routine.
Indirect goto.
| vector foo outputs x trashes a, z, n
| vector routine outputs x trashes a, z, n foo
|
| routine bar outputs x trashes a, z, n {
| ld x, 200
@ -1843,8 +1891,9 @@ Indirect goto.
Jumping through the vector does indeed trash, or output, the things the
vector says it does.
| vector foo
| vector routine
| trashes a, x, z, n
| foo
|
| routine bar
| trashes a, x, z, n {
@ -1866,9 +1915,9 @@ vector says it does.
| }
? UnmeaningfulReadError: x in main
| vector foo
| vector routine
| outputs x
| trashes a, z, n
| trashes a, z, n foo
|
| routine bar
| outputs x
@ -1891,3 +1940,152 @@ vector says it does.
| ld a, x
| }
= ok
### vector tables ###
A vector can be copied into a vector table.
| vector routine
| outputs x
| trashes a, z, n
| one
| vector (routine
| outputs x
| trashes a, z, n)
| table[256] many
|
| routine bar outputs x trashes a, z, n {
| ld x, 200
| }
|
| routine main
| inputs one, many
| outputs one, many
| trashes a, x, n, z
| {
| ld x, 0
| copy bar, one
| copy one, many + x
| }
= ok
A vector can be copied out of a vector table.
| vector routine
| outputs x
| trashes a, z, n
| one
| vector (routine
| outputs x
| trashes a, z, n)
| table[256] many
|
| routine bar outputs x trashes a, z, n {
| ld x, 200
| }
|
| routine main
| inputs one, many
| outputs one, many
| trashes a, x, n, z
| {
| ld x, 0
| copy many + x, one
| call one
| }
= ok
A routine can be copied into a vector table.
| vector (routine
| outputs x
| trashes a, z, n)
| table[256] many
|
| routine bar outputs x trashes a, z, n {
| ld x, 200
| }
|
| routine main
| inputs many
| outputs many
| trashes a, x, n, z
| {
| ld x, 0
| copy bar, many + x
| }
= ok
A vector in a vector table cannot be directly called.
| vector (routine
| outputs x
| trashes a, z, n)
| table[256] many
|
| routine bar outputs x trashes a, z, n {
| ld x, 200
| }
|
| routine main
| inputs many
| outputs many
| trashes a, x, n, z
| {
| ld x, 0
| copy bar, many + x
| call many + x
| }
? ValueError
### typedef ###
A typedef is a more-readable alias for a type. "Alias" means
that types have structural equivalence, not name equivalence.
| typedef routine
| inputs x
| outputs x
| trashes z, n
| routine_type
|
| vector routine_type vec
|
| routine foo
| inputs x
| outputs x
| trashes z, n
| {
| inc x
| }
|
| routine main
| outputs vec
| trashes a, z, n
| {
| copy foo, vec
| }
= ok
The new style routine definitions support typedefs.
| typedef routine
| inputs x
| outputs x
| trashes z, n
| routine_type
|
| vector routine_type vec
|
| define foo routine_type
| {
| inc x
| }
|
| routine main
| outputs vec
| trashes a, z, n
| {
| copy foo, vec
| }
= ok

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 --basic-prelude --compile %(test-body-file) | tests/appliances/bin/dcc6502-adapter"
-> shell command "bin/sixtypical --basic-prelude --traceback %(test-body-file) >/tmp/foo && tests/appliances/bin/dcc6502-adapter </tmp/foo"
-> Tests for functionality "Compile SixtyPical program"
@ -137,9 +137,9 @@ Word memory locations with explicit address, initial value.
= $081A .byte $BB
= $081B .byte $0B
Initialized byte table.
Initialized byte table. Bytes allocated, but beyond the string, are 0's.
| byte table message : "WHAT?"
| byte table[8] message : "WHAT?"
|
| routine main
| inputs message
@ -158,254 +158,6 @@ Initialized byte table.
= $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.
@ -525,14 +277,16 @@ Compiling `if` without `else`.
| trashes a, x, y, z, n, c, v
| {
| ld a, 0
| ld y, 0
| if z {
| ld y, 1
| }
| }
= $080D LDA #$00
= $080F BNE $0813
= $0811 LDY #$01
= $0813 RTS
= $080F LDY #$00
= $0811 BNE $0815
= $0813 LDY #$01
= $0815 RTS
Compiling `repeat`.
@ -600,7 +354,7 @@ The body of `repeat forever` can be empty.
Indexed access.
| byte one
| byte table many
| byte table[256] many
|
| routine main
| outputs many
@ -619,8 +373,8 @@ Indexed access.
Byte tables take up 256 bytes in memory.
| byte table tab1
| byte table tab2
| byte table[256] tab1
| byte table[256] tab2
|
| routine main
| inputs tab1
@ -706,7 +460,7 @@ Copy literal word to word.
You can also copy a literal word to a word table.
| word table many
| word table[256] many
|
| routine main
| inputs many
@ -725,8 +479,8 @@ You can also copy a literal word to a word table.
Copy vector to vector.
| vector bar
| vector baz
| vector routine bar
| vector routine baz
|
| routine main
| inputs baz
@ -743,7 +497,11 @@ Copy vector to vector.
Copy routine to vector, inside an `interrupts off` block.
| vector bar
| vector routine
| inputs x
| outputs x
| trashes z, n
| bar
|
| routine foo
| inputs x
@ -754,7 +512,6 @@ Copy routine to vector, inside an `interrupts off` block.
| }
|
| routine main
| inputs foo
| outputs bar
| trashes a, n, z
| {
@ -775,7 +532,7 @@ Copy routine to vector, inside an `interrupts off` block.
Copy word to word table and back, with both `x` and `y` as indexes.
| word one
| word table many
| word table[256] many
|
| routine main
| inputs one, many
@ -816,33 +573,50 @@ Copy word to word table and back, with both `x` and `y` as indexes.
Indirect call.
| vector foo outputs x trashes z, n
| vector routine
| outputs x
| trashes z, n
| foo
|
| routine bar outputs x trashes z, n {
| routine bar
| outputs x
| trashes z, n
| {
| ld x, 200
| }
|
| routine main inputs bar outputs x, foo trashes a, z, n {
| routine main
| outputs x, foo
| trashes a, z, n
| {
| copy bar, foo
| call foo
| }
= $080D LDA #$1B
= $080F STA $0821
= $080F STA $0822
= $0812 LDA #$08
= $0814 STA $0822
= $0814 STA $0823
= $0817 JSR $081E
= $081A RTS
= $081B LDX #$C8
= $081D RTS
= $081E JMP ($0821)
= $081E JMP ($0822)
= $0821 RTS
goto.
| routine bar outputs x trashes z, n {
| routine bar
| inputs y
| outputs x, y
| trashes z, n
| {
| ld x, 200
| }
|
| routine main outputs x trashes a, z, n {
| routine main
| outputs x, y
| trashes a, z, n
| {
| ld y, 200
| goto bar
| }
@ -852,6 +626,59 @@ goto.
= $0813 LDX #$C8
= $0815 RTS
### Vector tables
Copying to and from a vector table.
| vector routine
| outputs x
| trashes a, z, n
| one
| vector (routine
| outputs x
| trashes a, z, n)
| table[256] many
|
| routine bar outputs x trashes a, z, n {
| ld x, 200
| }
|
| routine main
| inputs one, many
| outputs one, many
| trashes a, x, n, z
| {
| ld x, 0
| copy bar, one
| copy bar, many + x
| copy one, many + x
| copy many + x, one
| call one
| }
= $080D LDX #$00
= $080F LDA #$3F
= $0811 STA $0846
= $0814 LDA #$08
= $0816 STA $0847
= $0819 LDA #$3F
= $081B STA $0848,X
= $081E LDA #$08
= $0820 STA $0948,X
= $0823 LDA $0846
= $0826 STA $0848,X
= $0829 LDA $0847
= $082C STA $0948,X
= $082F LDA $0848,X
= $0832 STA $0846
= $0835 LDA $0948,X
= $0838 STA $0847
= $083B JSR $0842
= $083E RTS
= $083F LDX #$C8
= $0841 RTS
= $0842 JMP ($0846)
= $0845 RTS
### word operations
Adding a constant word to a word memory location.
@ -1042,39 +869,41 @@ Note that this is *not* range-checked. (Yet.)
| routine main
| inputs buf
| outputs y, foo, delta
| trashes a, z, n, ptr
| trashes a, c, v, z, n, ptr
| {
| copy 619, delta
| ld y, 0
| st off, c
| copy ^buf, ptr
| add ptr, delta
| add ptr, word 1
| copy [ptr] + y, foo
| }
= $080D LDA #$6B
= $080F STA $1042
= $080F STA $1043
= $0812 LDA #$02
= $0814 STA $1043
= $0814 STA $1044
= $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
= $0819 CLC
= $081A LDA #$42
= $081C STA $FE
= $081E LDA #$08
= $0820 STA $FF
= $0822 LDA $FE
= $0824 ADC $1043
= $0827 STA $FE
= $0829 LDA $FF
= $082B ADC $1044
= $082E STA $FF
= $0830 LDA $FE
= $0832 ADC #$01
= $0834 STA $FE
= $0836 LDA $FF
= $0838 ADC #$00
= $083A STA $FF
= $083C LDA ($FE),Y
= $083E STA $1042
= $0841 RTS
### Trash

View File

@ -1,490 +0,0 @@
SixtyPical Execution
====================
This is a test suite, written in [Falderal][] format, for the dynamic
execution behaviour of the Sixtypical language, disgregarding static analysis.
[Falderal]: http://catseye.tc/node/Falderal
-> Functionality "Execute SixtyPical program" is implemented by
-> shell command "bin/sixtypical --execute %(test-body-file)"
-> Tests for functionality "Execute SixtyPical program"
Rudimentary program.
| routine main {
| ld a, 0
| add a, 1
| }
= a: 1
= c: 0
= n: 0
= v: 0
= x: 0
= y: 0
= z: 0
Program accesses a memory location.
| byte lives
|
| routine main {
| ld a, 0
| st a, lives
| ld x, lives
| add x, 1
| st x, lives
| }
= a: 0
= c: 0
= lives: 1
= n: 0
= v: 0
= x: 1
= y: 0
= z: 0
Program accesses a memory location with initial value.
| byte lives : 3
|
| routine main {
| ld a, lives
| }
= a: 3
= c: 0
= lives: 3
= n: 0
= v: 0
= x: 0
= y: 0
= z: 0
Add honours carry.
| routine main {
| ld a, 255
| st on, c
| add a, 0
| }
= a: 0
= c: 1
= n: 0
= v: 0
= x: 0
= y: 0
= z: 1
| routine main {
| ld a, $ff
| st off, c
| add a, 1
| }
= a: 0
= c: 1
= n: 0
= v: 0
= x: 0
= y: 0
= z: 1
Subtract honours carry.
| routine main {
| ld a, 0
| st on, c
| sub a, 0
| }
= a: 255
= c: 1
= n: 1
= v: 0
= x: 0
= y: 0
= z: 0
| routine main {
| ld a, 0
| st off, c
| sub a, 1
| }
= a: 255
= c: 1
= n: 1
= v: 0
= x: 0
= y: 0
= z: 0
Inc and dec do not honour carry, but do set n and z.
| routine main {
| ld x, 254
| st on, c
| inc x
| }
= a: 0
= c: 1
= n: 1
= v: 0
= x: 255
= y: 0
= z: 0
| routine main {
| ld y, 1
| st on, c
| dec y
| }
= a: 0
= c: 1
= n: 0
= v: 0
= x: 0
= y: 0
= z: 1
Compare affects, but does not use, carry.
| routine main {
| ld a, 1
| st on, c
| cmp a, 1
| }
= a: 1
= c: 0
= n: 0
= v: 0
= x: 0
= y: 0
= z: 1
| routine main {
| ld a, 1
| st off, c
| cmp a, 5
| }
= a: 1
= c: 1
= n: 1
= v: 0
= x: 0
= y: 0
= z: 0
AND.
| routine main {
| ld a, 15
| and a, 18
| }
= a: 2
= c: 0
= n: 0
= v: 0
= x: 0
= y: 0
= z: 0
OR.
| routine main {
| ld a, 34
| or a, 18
| }
= a: 50
= c: 0
= n: 0
= v: 0
= x: 0
= y: 0
= z: 0
XOR.
| routine main {
| ld a, 34
| xor a, 18
| }
= a: 48
= c: 0
= n: 0
= v: 0
= x: 0
= y: 0
= z: 0
Shift left.
| routine main {
| ld a, 129
| st off, c
| shl a
| }
= a: 2
= c: 1
= n: 0
= v: 0
= x: 0
= y: 0
= z: 0
| routine main {
| ld a, 0
| st on, c
| shl a
| }
= a: 1
= c: 0
= n: 0
= v: 0
= x: 0
= y: 0
= z: 0
Shift right.
| routine main {
| ld a, 129
| st off, c
| shr a
| }
= a: 64
= c: 1
= n: 0
= v: 0
= x: 0
= y: 0
= z: 0
| routine main {
| ld a, 0
| st on, c
| shr a
| }
= a: 128
= c: 0
= n: 1
= v: 0
= x: 0
= y: 0
= z: 0
Call routine.
| routine up {
| inc x
| inc y
| }
| routine main {
| ld x, 0
| ld y, 1
| call up
| call up
| }
= a: 0
= c: 0
= n: 0
= v: 0
= x: 2
= y: 3
= z: 0
If.
| routine main {
| ld x, 40
| cmp x, 40
| if z {
| ld a, 1
| } else {
| ld a, 8
| }
| ld x, 2
| }
= a: 1
= c: 0
= n: 0
= v: 0
= x: 2
= y: 0
= z: 0
| routine main {
| ld x, 39
| cmp x, 40
| if z {
| ld a, 1
| } else {
| ld a, 8
| }
| ld x, 2
| }
= a: 8
= c: 1
= n: 0
= v: 0
= x: 2
= y: 0
= z: 0
If without else.
| routine main {
| ld x, 39
| cmp x, 40
| if z {
| ld a, 1
| }
| ld x, 2
| }
= a: 0
= c: 1
= n: 0
= v: 0
= x: 2
= y: 0
= z: 0
`not` inverts the sense of the test.
| routine main {
| ld x, 40
| cmp x, 40
| if not z {
| ld a, 1
| } else {
| ld a, 8
| }
| ld x, 2
| }
= a: 8
= c: 0
= n: 0
= v: 0
= x: 2
= y: 0
= z: 0
| routine main {
| ld x, 39
| cmp x, 40
| if not z {
| ld a, 1
| }
| ld x, 2
| }
= a: 1
= c: 1
= n: 0
= v: 0
= x: 2
= y: 0
= z: 0
Repeat loop.
| routine main {
| ld x, 0
| ld y, 15
| repeat {
| inc x
| inc y
| cmp x, 10
| } until z
| }
= a: 0
= c: 0
= n: 0
= v: 0
= x: 10
= y: 25
= z: 1
Copy instruction. Note that the state of a, z, and n are not defined
after copy executes.
| routine main {
| ld x, 5
| copy x, y
| }
= a: 0
= c: 0
= n: 0
= v: 0
= x: 5
= y: 5
= z: 0
Copy word to word.
| word foo : 2000
| word bar
|
| routine main {
| copy foo, bar
| }
= a: 0
= bar: 2000
= c: 0
= foo: 2000
= n: 0
= v: 0
= x: 0
= y: 0
= z: 0
Copy literal word to word.
| word bar
|
| routine main {
| copy word 2000, bar
| }
= a: 0
= bar: 2000
= c: 0
= n: 0
= v: 0
= x: 0
= y: 0
= z: 0
Indirect call.
| vector foo outputs x trashes z, n
|
| routine bar outputs x trashes z, n {
| ld x, 200
| }
|
| routine main inputs bar outputs x, foo trashes a, z, n {
| copy bar, foo
| call foo
| }
= a: 0
= c: 0
= n: 1
= v: 0
= x: 200
= y: 0
= z: 0
goto.
| routine bar outputs x trashes z, n {
| ld x, 200
| }
|
| routine main outputs x trashes a, z, n {
| ld y, 200
| goto bar
| }
= a: 0
= c: 0
= n: 1
= v: 0
= x: 200
= y: 200
= z: 0

View File

@ -10,7 +10,7 @@ but not necessarily sensible programs.
[Falderal]: http://catseye.tc/node/Falderal
-> Functionality "Check syntax of SixtyPical program" is implemented by
-> shell command "bin/sixtypical %(test-body-file) && echo ok"
-> shell command "bin/sixtypical --parse-only --traceback %(test-body-file) && echo ok"
-> Tests for functionality "Check syntax of SixtyPical program"
@ -135,9 +135,7 @@ User-defined memory addresses of different types.
| byte byt
| word wor
| vector vec
| byte table tab
| word table wtab
| vector routine trashes a vec
| buffer[2048] buf
| pointer ptr
|
@ -145,6 +143,36 @@ User-defined memory addresses of different types.
| }
= ok
Tables of different types.
| byte table[256] tab
| word table[256] wtab
| vector (routine trashes a) table[256] vtab
|
| routine main {
| }
= ok
Typedefs of different types.
| typedef byte octet
| typedef octet table[256] twokay
| typedef routine trashes a game_routine
| vector game_routine start_game
|
| routine main {
| }
= ok
Can't have two typedefs with the same name.
| typedef byte frank
| typedef word frank
|
| routine main {
| }
? SyntaxError
Explicit memory address.
| byte screen @ 1024
@ -177,7 +205,7 @@ Cannot have both initial value and explicit address.
User-defined locations of other types.
| byte table screen @ 1024
| byte table[256] screen @ 1024
| word r1
| word r2 @ 60000
| word r3 : 2000
@ -188,7 +216,7 @@ User-defined locations of other types.
Initialized byte table.
| byte table message : "WHAT DO YOU WANT TO DO NEXT?"
| byte table[32] message : "WHAT DO YOU WANT TO DO NEXT?"
|
| routine main {
| }
@ -290,7 +318,7 @@ Can't define two routines with the same name.
Declaring byte and word table memory location.
| byte table tab
| byte table[256] tab
|
| routine main {
| ld x, 0
@ -301,7 +329,7 @@ Declaring byte and word table memory location.
= ok
| word one
| word table many
| word table[256] many
|
| routine main {
| ld x, 0
@ -313,11 +341,11 @@ Declaring byte and word table memory location.
Declaring and calling a vector.
| vector cinv
| vector routine
| inputs a
| outputs x
| trashes a, x, z, n
| @ 788
| cinv @ 788
|
| routine foo {
| ld a, 0
@ -344,11 +372,11 @@ Only vectors can be decorated with constraints like that.
Constraints set may only contain labels.
| vector cinv
| vector routine
| inputs a
| outputs 200
| trashes a, x, z, n
| @ 788
| cinv @ 788
|
| routine foo {
| ld a, 0
@ -363,11 +391,11 @@ Constraints set may only contain labels.
A vector can name itself in its inputs, outputs, and trashes.
| vector cinv
| vector routine
| inputs cinv, a
| outputs cinv, x
| trashes a, x, z, n
| @ 788
| cinv @ 788
|
| routine foo {
| ld a, 0
@ -383,7 +411,11 @@ A vector can name itself in its inputs, outputs, and trashes.
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
| vector routine
| inputs cinv, a
| outputs cinv, x
| trashes a, x, z, n
| cinv @ 788
| routine main {
| with interrupts off {
| copy foo, cinv
@ -395,7 +427,11 @@ A routine can be copied into a vector before the routine appears in the program,
| }
? SyntaxError: Undefined symbol
| vector cinv inputs cinv, a outputs cinv, x trashes a, x, z, n @ 788
| vector routine
| inputs cinv, a
| outputs cinv, x
| trashes a, x, z, n
| cinv @ 788
| routine main {
| with interrupts off {
| copy forward foo, cinv
@ -425,7 +461,7 @@ goto.
| }
= ok
| vector foo
| vector routine foo
|
| routine main {
| goto foo
@ -456,3 +492,40 @@ Buffers and pointers.
| copy [ptr] + y, foo
| }
= ok
Routines can be defined in a new style.
| typedef routine
| inputs x
| outputs x
| trashes z, n
| routine_type
|
| vector routine_type vec
|
| define foo routine
| inputs x
| outputs x
| trashes z, n
| {
| inc x
| }
|
| routine main
| outputs vec
| trashes a, z, n
| {
| copy foo, vec
| }
= ok
Only routines can be defined in the new style.
| define foo byte table[256]
|
| routine main
| trashes a, z, n
| {
| ld a, 0
| }
? SyntaxError