1
0
mirror of https://github.com/catseye/SixtyPical.git synced 2024-11-25 23:49:17 +00:00

Merge pull request #14 from catseye/develop-0.17

Develop 0.17
This commit is contained in:
Chris Pressey 2018-11-16 11:48:24 +00:00 committed by GitHub
commit 3cd28bdb3e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
49 changed files with 1207 additions and 938 deletions

View File

@ -1,6 +1,23 @@
History of SixtyPical
=====================
0.17
----
* `save X, Y, Z { }` now allowed as a shortcut for nested `save`s.
* If the name in a location expression isn't found in the symbol
table, a forward reference will _always_ be generated; and forward
references in _all_ operations will be resolved after parsing.
* As a consequence, trying to call or goto a non-routine-typed symbol
is now an analysis error, not a syntax error.
* Deprecated `routine foo ...` syntax has been removed.
* Split TODO off into own file.
* `sixtypical` no longer writes the compiled binary to standard
output. The `--output` command-line argument should be given
to get a compiled binary; otherwise only analysis is run.
* Internal cleanups, including a hierarchy of `Outputters`.
* All tests pass when `sixtypical` is run under Python 3.5.2.
0.16
----

160
README.md
View File

@ -1,32 +1,38 @@
SixtyPical
==========
_Version 0.16. Work-in-progress, everything is subject to change._
_Version 0.17. Work-in-progress, everything is subject to change._
**SixtyPical** is a 6502-like programming language with advanced
static analysis.
**SixtyPical** is a low-level programming language with advanced
static analysis. Many of its primitive instructions resemble
those of the 6502 CPU — in fact it is intended to be compiled to
6502 machine code — but along with these instructions are
constructs which ease structuring and analyzing the code. The
language aims to fill this niche:
"6502-like" means that it has similar restrictions as programming
in 6502 assembly (e.g. the programmer must choose the registers that
values will be stored in) and is concomitantly easy for a compiler to
translate it to 6502 machine language code.
* You'd use assembly, but you don't want to spend hours
debugging (say) a memory overrun that happened because of a
ridiculous silly error.
* You'd use C or some other "high-level" language, but you don't
want the extra overhead added by the compiler to manage the
stack and registers.
"Advanced static analysis" includes _abstract interpretation_, where we
go through the program step by step, tracking not just the changes that
happen during a _specific_ execution of the program, but _sets_ of changes
that could _possibly_ happen in any run of the program. This lets us
determine that certain things can never happen, which we can then formulate
as safety checks.
In practice, this means it catches things like
SixtyPical gives the programmer a coding regimen on par with assembly
language in terms of size and hands-on-ness, but also able to catch
many ridiculous silly errors at compile time, such as
* you forgot to clear carry before adding something to the accumulator
* a subroutine that you call trashes a register you thought was preserved
* a subroutine that you called trashes a register you thought it preserved
* you tried to read or write a byte beyond the end of a byte array
* you tried to write the address of something that was not a routine, to
a jump vector
and suchlike. It also provides some convenient operations based on
Many of these checks are done with _abstract interpretation_, where we
go through the program step by step, tracking not just the changes that
happen during a _specific_ execution of the program, but _sets_ of changes
that could _possibly_ happen in any run of the program.
SixtyPical also provides some convenient operations based on
machine-language programming idioms, such as
* copying values from one register to another (via a third register when
@ -35,8 +41,15 @@ machine-language programming idioms, such as
* explicit tail calls
* indirect subroutine calls
The reference implementation can analyze and compile SixtyPical programs to
6502 machine code.
SixtyPical is defined by a specification document, a set of test cases,
and a reference implementation written in Python 2. The reference
implementation can analyze and compile SixtyPical programs to 6502 machine
code, which can be run on several 6502-based 8-bit architectures:
* Commodore 64
* Commodore VIC-20
* Atari 2600 VCS
* Apple II
Quick Start
-----------
@ -68,111 +81,4 @@ Documentation
* [Literate test suite for SixtyPical fallthru optimization](tests/SixtyPical%20Fallthru.md)
* [6502 Opcodes used/not used in SixtyPical](doc/6502%20Opcodes.md)
* [Output formats supported by `sixtypical`](doc/Output%20Formats.md)
TODO
----
### `low` and `high` address operators
To turn `word` type into `byte`.
Trying to remember if we have a compelling case for this or now. The best I can think
of is for implementing 16-bit `cmp` in an efficient way. Maybe we should see if we
can get by with 16-bit `cmp` instead though.
The problem is that once a byte is extracted, putting it back into a word is awkward.
The address operators have to modify a destination in a special way. That is, when
you say `st a, >word`, you are updating `word` to be `word & $ff | a << 8`, somelike.
Is that consistent with `st`? Well, probably it is, but we have to explain it.
It might make more sense, then, for it to be "part of the operation" instead of "part of
the reference"; something like `st.hi x, word`; `st.lo y, word`. Dunno.
### Save multiple values in single block
As a shortcut for the idiom
save a { save var {
...
} }
allow
save a, var {
...
}
### Save values to other-than-the-stack
Allow
save a to temp_a {
...
}
Which uses some other storage location instead of the stack. A local static
would be a good candidate for such.
### Make all symbols forward-referencable
Basically, don't do symbol-table lookups when parsing, but do have a more formal
"symbol resolution" (linking) phase right after parsing.
### Associate each pointer with the buffer it points into
Check that the buffer being read or written to through pointer, appears in appropriate
inputs or outputs set.
In the analysis, when we obtain a pointer, we need to record, in contect, what buffer
that pointer came from.
When we write through that pointer, we need to set that buffer as written.
When we read through the pointer, we need to check that the buffer is readable.
### Table overlays
They are uninitialized, but the twist is, the address is a buffer that is
an input to and/or output of the routine. So, they are defined (insofar
as the buffer is defined.)
They are therefore a "view" of a section of a buffer.
This is slightly dangerous since it does permit aliases: the buffer and the
table refer to the same memory.
Although, if they are `static`, you could say, in the routine in which they
are `static`, as soon as you've established one, you can no longer use the
buffer; and the ones you establish must be disjoint.
(That seems to be the most compelling case for restricting them to `static`.)
An alternative would be `static` pointers, which are currently not possible because
pointers must be zero-page, thus `@`, thus uninitialized.
### Question "consistent initialization"
Question the value of the "consistent initialization" principle for `if` statement analysis.
Part of this is the trashes at the end; I think what it should be is that the trashes
after the `if` is the union of the trashes in each of the branches; this would obviate the
need to `trash` values explicitly, but if you tried to access them afterwards, it would still
error.
### Tail-call optimization
More generally, define a block as having zero or one `goto`s at the end. (and `goto`s cannot
appear elsewhere.)
If a block ends in a `call` can that be converted to end in a `goto`? Why not? I think it can.
The constraints should iron out the same both ways.
And - once we have this - why do we need `goto` to be in tail position, strictly?
As long as the routine has consistent type context every place it exits, that should be fine.
### "Include" directives
Search a searchlist of include paths. And use them to make libraries of routines.
One such library routine might be an `interrupt routine` type for various architectures.
Since "the supervisor" has stored values on the stack, we should be able to trash them
with impunity, in such a routine.
* [TODO](TODO.md)

89
TODO.md Normal file
View File

@ -0,0 +1,89 @@
TODO for SixtyPical
===================
### 16-bit `cmp`
This is because we don't actually want `low` and `high` address operators
that turn `word` type into `byte`.
This is because this immediately makes things harder (that is, effectively
impossible) to analyze.
16-bit `cmp` also benefits from some special differences between `cmp`
and `sub` on 6502, so it would be nice to capture them.
### Save values to other-than-the-stack
Allow
save a to temp_a {
...
}
Which uses some other storage location instead of the stack. A local static
would be a good candidate for such.
### Associate each pointer with the buffer it points into
Check that the buffer being read or written to through pointer, appears in appropriate
inputs or outputs set.
In the analysis, when we obtain a pointer, we need to record, in context, what buffer
that pointer came from.
When we write through that pointer, we need to set that buffer as written.
When we read through the pointer, we need to check that the buffer is readable.
### Table overlays
They are uninitialized, but the twist is, the address is a buffer that is
an input to and/or output of the routine. So, they are defined (insofar
as the buffer is defined.)
They are therefore a "view" of a section of a buffer.
This is slightly dangerous since it does permit aliases: the buffer and the
table refer to the same memory.
Although, if they are `static`, you could say, in the routine in which they
are `static`, as soon as you've established one, you can no longer use the
buffer; and the ones you establish must be disjoint.
(That seems to be the most compelling case for restricting them to `static`.)
An alternative would be `static` pointers, which are currently not possible because
pointers must be zero-page, thus `@`, thus uninitialized.
### Question "consistent initialization"
Question the value of the "consistent initialization" principle for `if` statement analysis.
Part of this is the trashes at the end; I think what it should be is that the trashes
after the `if` is the union of the trashes in each of the branches; this would obviate the
need to `trash` values explicitly, but if you tried to access them afterwards, it would still
error.
### Tail-call optimization
More generally, define a block as having zero or one `goto`s at the end. (and `goto`s cannot
appear elsewhere.)
If a block ends in a `call` can that be converted to end in a `goto`? Why not? I think it can,
if the block is in tail position. The constraints should iron out the same both ways.
And - once we have this - why do we need `goto` to be in tail position, strictly?
As long as the routine has consistent type context every place it exits, that should be fine.
### "Include" directives
Search a searchlist of include paths. And use them to make libraries of routines.
One such library routine might be an `interrupt routine` type for various architectures.
Since "the supervisor" has stored values on the stack, we should be able to trash them
with impunity, in such a routine.
### Line numbers in analysis error messages
For analysis errors, there is a line number, but it's the line of the routine
after the routine in which the analysis error occurred. Fix this.

View File

@ -18,25 +18,12 @@ from pprint import pprint
import sys
import traceback
from sixtypical.parser import Parser, ParsingContext
from sixtypical.parser import Parser, ParsingContext, merge_programs
from sixtypical.analyzer import Analyzer
from sixtypical.emitter import Emitter, Byte, Word
from sixtypical.outputter import outputter_class_for
from sixtypical.compiler import Compiler
def merge_programs(programs):
"""Assumes that the programs do not have any conflicts."""
from sixtypical.ast import Program
full = Program(1, defns=[], routines=[])
for p in programs:
full.defns.extend(p.defns)
full.routines.extend(p.routines)
return full
def process_input_files(filenames, options):
context = ParsingContext()
@ -68,7 +55,7 @@ def process_input_files(filenames, options):
return
if label:
sys.stdout.write("*** {}:\n".format(label))
sys.stdout.write(json.dumps(data, indent=4, sort_keys=True))
sys.stdout.write(json.dumps(data, indent=4, sort_keys=True, separators=(',', ':')))
sys.stdout.write("\n")
fa = FallthruAnalyzer(debug=options.debug)
@ -76,60 +63,26 @@ def process_input_files(filenames, options):
compilation_roster = fa.serialize()
dump(compilation_roster)
if options.analyze_only:
if options.analyze_only or options.output is None:
return
fh = sys.stdout
if options.output_format == 'raw':
start_addr = 0x0000
prelude = []
elif options.output_format == 'prg':
start_addr = 0xc000
prelude = []
elif options.output_format == 'c64-basic-prg':
start_addr = 0x0801
prelude = [0x10, 0x08, 0xc9, 0x07, 0x9e, 0x32,
0x30, 0x36, 0x31, 0x00, 0x00, 0x00]
elif options.output_format == 'vic20-basic-prg':
start_addr = 0x1001
prelude = [0x0b, 0x10, 0xc9, 0x07, 0x9e, 0x34,
0x31, 0x30, 0x39, 0x00, 0x00, 0x00]
elif options.output_format == 'atari2600-cart':
start_addr = 0xf000
prelude = [0x78, 0xd8, 0xa2, 0xff, 0x9a, 0xa9,
0x00, 0x95, 0x00, 0xca, 0xd0, 0xfb]
else:
raise NotImplementedError("Unknown output format: {}".format(options.output_format))
start_addr = None
if options.origin is not None:
if options.origin.startswith('0x'):
start_addr = int(options.origin, 16)
else:
start_addr = int(options.origin, 10)
# If we are outputting a .PRG, we output the load address first.
# We don't use the Emitter for this b/c not part of addr space.
if options.output_format in ('prg', 'c64-basic-prg', 'vic20-basic-prg'):
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, compilation_roster=compilation_roster)
# If we are outputting a cartridge with boot and BRK address
# at the end, pad to ROM size minus 4 bytes, and emit addresses.
if options.output_format == 'atari2600-cart':
emitter.pad_to_size(4096 - 4)
emitter.emit(Word(start_addr))
emitter.emit(Word(start_addr))
if options.debug:
pprint(emitter.accum)
else:
emitter.serialize(fh)
with open(options.output, 'wb') as fh:
outputter = outputter_class_for(options.output_format)(fh, start_addr=start_addr)
outputter.write_prelude()
compiler = Compiler(outputter.emitter)
compiler.compile_program(program, compilation_roster=compilation_roster)
outputter.write_postlude()
if options.debug:
pprint(outputter.emitter)
else:
outputter.emitter.serialize_to(fh)
if __name__ == '__main__':
@ -140,6 +93,10 @@ if __name__ == '__main__':
help="The SixtyPical source files to compile."
)
argparser.add_argument(
"--output", "-o", type=str, metavar='FILENAME',
help="File to which generated 6502 code will be written."
)
argparser.add_argument(
"--origin", type=str, default=None,
help="Location in memory where the `main` routine will be "

View File

@ -8,8 +8,7 @@ Output Formats
The file contains only the emitted bytes of the compiled SixtyPical
program.
The default origin is $0000; it is not unlikely you will want to
override this.
The default origin is $0000; you will likely want to override this.
Note that the origin is not stored in the output file in this format;
that information must be recorded separately.
@ -20,8 +19,7 @@ The first two bytes of the file contain the origin address in
little-endian format. The remainder of the file is the emitted bytes
of the compiled SixtyPical program, starting at that origin.
The default origin is $C000; it is likely you will want to
override this.
The default origin is $C000; you will likely want override this.
This format coincides with Commodore's PRG format for disk files,
thus its name.

20
eg/apple2/lores.60p Normal file
View File

@ -0,0 +1,20 @@
byte ds_graphics @ $C050
byte ds_text @ $C051
byte ds_full @ $C052
byte ds_split @ $C053
byte ds_page1 @ $C054
byte ds_page2 @ $C055
byte ds_lores @ $C056
byte ds_hires @ $C057
define main routine
inputs a
outputs ds_lores, ds_page1, ds_split, ds_graphics
trashes a, z, n
{
ld a, 0
st a, ds_lores
st a, ds_page1
st a, ds_split
st a, ds_graphics
}

19
eg/apple2/print.60p Normal file
View File

@ -0,0 +1,19 @@
// Write ">AB>" to "standard output"
define cout routine
inputs a
trashes a
@ $FDED
define main routine
trashes a, z, n
{
ld a, 62
call cout
ld a, 65
call cout
ld a, 66
call cout
ld a, 62
call cout
}

View File

@ -33,9 +33,11 @@
typedef routine
inputs joy2, press_fire_msg, dispatch_game_state,
actor_pos, actor_delta, actor_logic,
player_died,
screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4
outputs dispatch_game_state,
actor_pos, actor_delta, actor_logic,
player_died,
screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4
trashes a, x, y, c, z, n, v, pos, new_pos, delta, ptr, dispatch_logic
game_state_routine
@ -45,13 +47,13 @@ typedef routine
//
// Routines that conform to this type also follow this convention:
//
// Set carry if the player perished. Carry clear otherwise.
// Set player_died to 1 if the player perished. Unchanged otherwise.
//
typedef routine
inputs pos, delta, joy2, screen
outputs pos, delta, new_pos, screen, c
trashes a, x, y, z, n, v, ptr
inputs pos, delta, joy2, screen, player_died
outputs pos, delta, new_pos, screen, player_died
trashes a, x, y, z, n, v, c, ptr
logic_routine
// ----------------------------------------------------------------
@ -87,6 +89,8 @@ word new_pos
word table[256] actor_delta
word delta
byte player_died
vector logic_routine table[256] actor_logic
vector logic_routine dispatch_logic
@ -117,7 +121,7 @@ vector game_state_routine
// Utility Routines
// ----------------------------------------------------------------
routine read_stick
define read_stick routine
inputs joy2
outputs delta
trashes a, x, z, n
@ -182,7 +186,7 @@ define check_button routine
}
}
routine clear_screen
define clear_screen routine
outputs screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4
trashes a, y, c, n, z
{
@ -205,7 +209,7 @@ routine clear_screen
} until z
}
routine calculate_new_position
define calculate_new_position routine
inputs pos, delta
outputs new_pos
trashes a, c, n, z, v
@ -239,9 +243,9 @@ define check_new_position_in_bounds routine
}
}
routine init_game
define init_game routine
inputs actor_pos, actor_delta, actor_logic
outputs actor_pos, actor_delta, actor_logic
outputs actor_pos, actor_delta, actor_logic, player_died
trashes pos, a, y, z, n, c, v
{
ld y, 0
@ -259,9 +263,11 @@ routine init_game
} until z
ld y, 0
copy word 0, actor_pos + y
copy word 40, actor_pos + y
copy word 0, actor_delta + y
copy player_logic, actor_logic + y
st y, player_died
}
// ----------------------------------------------------------------
@ -301,10 +307,9 @@ define player_logic logic_routine
st off, c
add ptr, pos
copy 81, [ptr] + y
st off, c
} else {
st on, c
ld a, 1
st a, player_died
}
// FIXME these trashes, strictly speaking, probably shouldn't be needed,
@ -314,8 +319,6 @@ define player_logic logic_routine
trash ptr
trash y
trash v
} else {
st off, c
}
}
@ -351,10 +354,6 @@ define enemy_logic logic_routine
st off, c
add ptr, pos
copy 82, [ptr] + y
st off, c
} else {
st on, c
}
// FIXME these trashes, strictly speaking, probably shouldn't be needed,
@ -373,8 +372,6 @@ define enemy_logic logic_routine
copy $ffd8, delta
}
}
st off, c
}
// ----------------------------------------------------------------
@ -408,6 +405,7 @@ define game_state_title_screen game_state_routine
define game_state_play game_state_routine
{
ld x, 0
st x, player_died
for x up to 15 {
copy actor_pos + x, pos
copy actor_delta + x, delta
@ -420,18 +418,19 @@ define game_state_play game_state_routine
save x {
copy actor_logic + x, dispatch_logic
call dispatch_logic
if c {
// Player died! Want no dead!
call clear_screen
copy game_state_game_over, dispatch_game_state
}
}
copy pos, actor_pos + x
copy delta, actor_delta + x
}
ld a, player_died
if not z {
// Player died! Want no dead!
call clear_screen
copy game_state_game_over, dispatch_game_state
}
goto save_cinv
}
@ -458,7 +457,7 @@ define our_cinv game_state_routine
goto dispatch_game_state
}
routine main
define main routine
inputs cinv
outputs cinv, save_cinv, pos, dispatch_game_state,
screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4

View File

@ -3,7 +3,7 @@
// Define where the screen starts in memory:
byte table[256] screen @ 1024
routine main
define main routine
// These are the values that will be written to by this routine:
trashes a, x, z, n, screen
{

View File

@ -24,7 +24,7 @@ vector routine
trashes z, n
save_cinv
routine our_cinv
define our_cinv routine
inputs vic_border
outputs vic_border
trashes z, n
@ -33,7 +33,7 @@ routine our_cinv
goto save_cinv
}
routine main
define main routine
inputs cinv
outputs cinv, save_cinv
trashes a, n, z

View File

@ -3,7 +3,7 @@ byte joy2 @ $dc00
word delta
routine read_stick
define read_stick routine
inputs joy2
outputs delta
trashes a, x, z, n
@ -36,7 +36,7 @@ routine read_stick
}
}
routine main
define main routine
inputs joy2
outputs delta
trashes a, x, z, n, screen

View File

@ -49,7 +49,7 @@ byte scanline : 85 // %01010101
// generating them as part of a SixtyPical program would not
// be practical. So we just jump to this location instead.
routine pla_tay_pla_tax_pla_rti
define pla_tay_pla_tax_pla_rti routine
inputs a
trashes a
@ $EA81

View File

@ -1,6 +1,6 @@
byte screen @ 1024
routine main
define main routine
trashes a, z, n, screen
{
ld a, 83

View File

@ -1,4 +1,4 @@
routine add_four
define add_four routine
inputs a
outputs a
{

View File

@ -1,4 +1,4 @@
routine main
define main routine
inputs a
outputs a
trashes c, z, n, v

View File

@ -1,5 +1,5 @@
word score
routine main
define main routine
inputs score
outputs score
trashes a, c, z, v, n

View File

@ -2,7 +2,7 @@ buffer[2048] buf
pointer ptr @ 254
byte foo
routine main
define main routine
inputs buf
outputs buf, y, foo
trashes a, z, n, ptr

View File

@ -1,16 +1,16 @@
routine chrout
define chrout routine
inputs a
trashes a
@ 65490
routine print
define print routine
trashes a, z, n
{
ld a, 65
call chrout
}
routine main
define main routine
trashes a, z, n
{
call print

View File

@ -1,9 +1,9 @@
routine chrout
define chrout routine
inputs a
trashes a
@ 65490
routine main
define main routine
trashes a, x, y, z, n, c, v
{
ld a, 0

View File

@ -1,9 +1,9 @@
routine chrout
define chrout routine
inputs a
trashes a
@ 65490
routine main
define main routine
trashes a, x, y, z, n, c, v
{
ld a, 0

View File

@ -1,7 +1,7 @@
byte bar
byte baz
routine main
define main routine
inputs baz
outputs bar
trashes a, n, z

View File

@ -1,9 +1,9 @@
byte lives
routine main
define main routine
inputs lives
outputs lives
trashes a, x
trashes a, x, z, n, c, v
{
ld a, 0
st a, lives

View File

@ -1,4 +1,4 @@
routine main
define main routine
trashes a, y, z, n, c
{
ld y, 65

View File

@ -1,14 +1,14 @@
routine chrout
define chrout routine
inputs a
trashes a
@ 65490
routine bar trashes a, z, n {
define bar routine trashes a, z, n {
ld a, 66
call chrout
}
routine main trashes a, z, n {
define main routine trashes a, z, n {
ld a, 65
call chrout
goto bar

View File

@ -1,6 +1,7 @@
routine foo
define main routine
inputs a
outputs a
trashes z, n, c
{
cmp a, 42
if z {

View File

@ -1,9 +1,9 @@
routine chrout
define chrout routine
inputs a
trashes a
@ 65490
routine main
define main routine
trashes a, y, z, n, c
{
ld y, 65

View File

@ -1,11 +1,11 @@
byte foo
routine chrout
define chrout routine
inputs a
trashes a
@ 65490
routine print
define print routine
inputs foo
trashes a, z, n
{
@ -13,7 +13,7 @@ routine print
call chrout
}
routine main
define main routine
trashes a, y, z, n, foo
{
ld y, 65

View File

@ -1,9 +1,9 @@
routine chrout
define chrout routine
inputs a
trashes a
@ 65490
routine main
define main routine
inputs a
trashes a, z, n
{

View File

@ -1,6 +1,6 @@
byte table[8] message : "WHAT?"
routine main
define main routine
inputs message
outputs x, a, z, n
{

View File

@ -1,19 +1,19 @@
// This will not compile on its own, because there is no `main`.
// But this and `vector-main.60p` together will compile.
routine chrout
define chrout routine
inputs a
trashes a
@ 65490
routine printa
define printa routine
trashes a, z, n
{
ld a, 65
call chrout
}
routine printb
define printb routine
trashes a, z, n
{
ld a, 66

View File

@ -12,7 +12,7 @@ vector routine
// call chrout
// }
routine main
define main routine
trashes print, a, z, n
{
copy printa, print

View File

@ -11,26 +11,26 @@ vector (routine
trashes a, z, n)
table[32] vectors
routine chrout
define chrout routine
inputs a
trashes a
@ 65490
routine printa
define printa routine
trashes a, z, n
{
ld a, 65
call chrout
}
routine printb
define printb routine
trashes a, z, n
{
ld a, 66
call chrout
}
routine main
define main routine
inputs vectors
outputs vectors
trashes print, a, x, z, n, c

View File

@ -2,26 +2,26 @@ vector routine
trashes a, z, n
print
routine chrout
define chrout routine
inputs a
trashes a
@ 65490
routine printa
define printa routine
trashes a, z, n
{
ld a, 65
call chrout
}
routine printb
define printb routine
trashes a, z, n
{
ld a, 66
call chrout
}
routine main
define main routine
trashes print, a, z, n
{
copy printa, print

View File

@ -1,7 +1,7 @@
word one
word table[256] many
routine main
define main routine
inputs one, many
outputs one, many
trashes a, x, y, n, z

View File

@ -3,7 +3,7 @@
// Define where the screen starts in memory:
byte table[256] screen @ 7680
routine main
define main routine
// These are the values that will be written to by this routine:
trashes a, x, z, n, screen
{

View File

@ -1,6 +1,6 @@
#!/bin/sh
usage="Usage: loadngo.sh (c64|vic20|atari2600) [--dry-run] <source.60p>"
usage="Usage: loadngo.sh (c64|vic20|atari2600|apple2) [--dry-run] <source.60p>"
arch="$1"
shift 1
@ -21,6 +21,21 @@ elif [ "X$arch" = "Xvic20" ]; then
elif [ "X$arch" = "Xatari2600" ]; then
output_format='atari2600-cart'
emu='stella'
elif [ "X$arch" = "Xapple2" ]; then
src="$1"
out=/tmp/a-out.bin
bin/sixtypical --traceback --origin=0x2000 --output-format=raw $src --output $out || exit 1
ls -la $out
cp ~/scratchpad/linapple/res/Master.dsk sixtypical.dsk
# TODO: replace HELLO with something that does like
# BLOAD "PROG"
# CALL 8192
# (not BRUN because it does not always return to BASIC afterwards not sure why)
a2rm sixtypical.dsk PROG
a2in B sixtypical.dsk PROG $out
linapple -d1 sixtypical.dsk -autoboot
rm -f $out sixtypical.dsk
exit 0
else
echo $usage && exit 1
fi
@ -38,7 +53,7 @@ fi
### do it ###
out=/tmp/a-out.prg
bin/sixtypical --traceback --output-format=$output_format $src > $out || exit 1
bin/sixtypical --traceback --output-format=$output_format $src --output $out || exit 1
ls -la $out
$emu $out
rm -f $out

View File

@ -1,6 +1,6 @@
# encoding: UTF-8
from sixtypical.ast import Program, Routine, Block, Instr, SingleOp, If, Repeat, For, WithInterruptsOff, Save
from sixtypical.ast import Program, Routine, Block, SingleOp, If, Repeat, For, WithInterruptsOff, Save
from sixtypical.model import (
TYPE_BYTE, TYPE_WORD,
TableType, BufferType, PointerType, VectorType, RoutineType,
@ -372,23 +372,23 @@ class Analyzer(object):
context = Context(self.routines, routine, type_.inputs, type_.outputs, type_.trashes)
if self.debug:
print "at start of routine `{}`:".format(routine.name)
print context
print("at start of routine `{}`:".format(routine.name))
print(context)
self.analyze_block(routine.block, context)
trashed = set(context.each_touched()) - set(context.each_meaningful())
if self.debug:
print "at end of routine `{}`:".format(routine.name)
print context
print "trashed: ", LocationRef.format_set(trashed)
print "outputs: ", LocationRef.format_set(type_.outputs)
print("at end of routine `{}`:".format(routine.name))
print(context)
print("trashed: ", LocationRef.format_set(trashed))
print("outputs: ", LocationRef.format_set(type_.outputs))
trashed_outputs = type_.outputs & trashed
if trashed_outputs:
print "TRASHED OUTPUTS: ", LocationRef.format_set(trashed_outputs)
print ''
print '-' * 79
print ''
print("TRASHED OUTPUTS: ", LocationRef.format_set(trashed_outputs))
print('')
print('-' * 79)
print('')
# even if we goto another routine, we can't trash an output.
for ref in trashed:
@ -550,6 +550,8 @@ class Analyzer(object):
context.invalidate_range(dest)
elif opcode == 'call':
type = instr.location.type
if not isinstance(type, (RoutineType, VectorType)):
raise TypeMismatchError(instr, instr.location)
if isinstance(type, VectorType):
type = type.of_type
for ref in type.inputs:
@ -771,17 +773,22 @@ class Analyzer(object):
context.set_writeable(instr.dest)
def analyze_save(self, instr, context):
if len(instr.locations) != 1:
raise NotImplementedError("Only 1 location in save is supported right now")
location = instr.locations[0]
self.assert_type(TYPE_BYTE, location)
batons = []
for location in instr.locations:
self.assert_type(TYPE_BYTE, location)
baton = context.extract(location)
batons.append(baton)
baton = context.extract(location)
self.analyze_block(instr.block, context)
if context.encountered_gotos():
raise IllegalJumpError(instr, instr)
context.re_introduce(baton)
for location in reversed(instr.locations):
baton = batons.pop()
context.re_introduce(baton)
# We do this check outside the loop, because A is only preserved
# if it is the outermost thing being `save`d.
if location == REG_A:
pass
else:

View File

@ -37,14 +37,16 @@ class AST(object):
def all_children(self):
for attr in self.children_attrs:
for child in self.attrs[attr]:
if child is not None:
yield child
for subchild in child.all_children():
yield subchild
for attr in self.child_attrs:
child = self.attrs[attr]
if child is not None:
yield child
for subchild in child.all_children():
yield subchild
for attr in self.child_attrs:
child = self.attrs[attr]
yield child
for subchild in child.all_children():
yield subchild
class Program(AST):

View File

@ -1,6 +1,6 @@
# encoding: UTF-8
from sixtypical.ast import Program, Routine, Block, Instr, SingleOp, If, Repeat, For, WithInterruptsOff, Save
from sixtypical.ast import Program, Routine, Block, SingleOp, If, Repeat, For, WithInterruptsOff, Save
from sixtypical.model import (
ConstantRef, LocationRef, IndexedRef, IndirectRef, AddressRef,
TYPE_BIT, TYPE_BYTE, TYPE_WORD,
@ -112,7 +112,7 @@ class Compiler(object):
routine_name = roster_row[-1]
self.compile_routine(self.routines[routine_name])
for location, label in self.trampolines.iteritems():
for location, label in self.trampolines.items():
self.emitter.resolve_label(label)
self.emitter.emit(JMP(Indirect(self.get_label(location.name))))
self.emitter.emit(RTS())
@ -618,27 +618,32 @@ class Compiler(object):
self.emitter.emit(CLI())
def compile_save(self, instr):
location = instr.locations[0]
if location == REG_A:
self.emitter.emit(PHA())
self.compile_block(instr.block)
self.emitter.emit(PLA())
elif location == REG_X:
self.emitter.emit(TXA())
self.emitter.emit(PHA())
self.compile_block(instr.block)
self.emitter.emit(PLA())
self.emitter.emit(TAX())
elif location == REG_Y:
self.emitter.emit(TYA())
self.emitter.emit(PHA())
self.compile_block(instr.block)
self.emitter.emit(PLA())
self.emitter.emit(TAY())
else:
src_label = self.get_label(location.name)
self.emitter.emit(LDA(Absolute(src_label)))
self.emitter.emit(PHA())
self.compile_block(instr.block)
self.emitter.emit(PLA())
self.emitter.emit(STA(Absolute(src_label)))
for location in instr.locations:
if location == REG_A:
self.emitter.emit(PHA())
elif location == REG_X:
self.emitter.emit(TXA())
self.emitter.emit(PHA())
elif location == REG_Y:
self.emitter.emit(TYA())
self.emitter.emit(PHA())
else:
src_label = self.get_label(location.name)
self.emitter.emit(LDA(Absolute(src_label)))
self.emitter.emit(PHA())
self.compile_block(instr.block)
for location in reversed(instr.locations):
if location == REG_A:
self.emitter.emit(PLA())
elif location == REG_X:
self.emitter.emit(PLA())
self.emitter.emit(TAX())
elif location == REG_Y:
self.emitter.emit(PLA())
self.emitter.emit(TAY())
else:
src_label = self.get_label(location.name)
self.emitter.emit(PLA())
self.emitter.emit(STA(Absolute(src_label)))

View File

@ -8,12 +8,15 @@ class Emittable(object):
raise NotImplementedError
def serialize(self, addr):
"""Should return an array of unsigned bytes (integers from 0 to 255.)
`addr` is the address the value is being serialized at; for most objects
it makes no difference, but some objects (like relative branches) do care."""
raise NotImplementedError
class Byte(Emittable):
def __init__(self, value):
if isinstance(value, basestring):
if isinstance(value, str):
value = ord(value)
if value < -127 or value > 255:
raise IndexError(value)
@ -24,8 +27,8 @@ class Byte(Emittable):
def size(self):
return 1
def serialize(self, addr=None):
return chr(self.value)
def serialize(self, addr):
return [self.value]
def __repr__(self):
return "%s(%r)" % (self.__class__.__name__, self.value)
@ -39,11 +42,11 @@ class Word(Emittable):
def size(self):
return 2
def serialize(self, addr=None):
def serialize(self, addr):
word = self.value
low = word & 255
high = (word >> 8) & 255
return chr(low) + chr(high)
return [low, high]
def __repr__(self):
return "%s(%r)" % (self.__class__.__name__, self.value)
@ -59,10 +62,12 @@ class Table(Emittable):
def size(self):
return self._size
def serialize(self, addr=None):
buf = ''.join([emittable.serialize() for emittable in self.value])
def serialize(self, addr):
buf = []
for emittable in self.value:
buf.extend(emittable.serialize(addr)) # FIXME: addr + offset
while len(buf) < self.size():
buf += chr(0)
buf.append(0)
return buf
def __repr__(self):
@ -84,17 +89,17 @@ class Label(Emittable):
def size(self):
return 2
def serialize(self, addr=None, offset=0):
def serialize(self, addr, offset=0):
assert self.addr is not None, "unresolved label: %s" % self.name
return Word(self.addr + offset).serialize()
return Word(self.addr + offset).serialize(addr)
def serialize_relative_to(self, addr):
assert self.addr is not None, "unresolved label: %s" % self.name
return Byte(self.addr - (addr + 2)).serialize()
return Byte(self.addr - (addr + 2)).serialize(addr)
def serialize_as_zero_page(self, offset=0):
def serialize_as_zero_page(self, addr, offset=0):
assert self.addr is not None, "unresolved label: %s" % self.name
return Byte(self.addr + offset).serialize()
return Byte(self.addr + offset).serialize(addr)
def __repr__(self):
addr_s = ', addr=%r' % self.addr if self.addr is not None else ''
@ -111,11 +116,11 @@ class Offset(Emittable):
def size(self):
self.label.size()
def serialize(self, addr=None):
return self.label.serialize(offset=self.offset)
def serialize(self, addr):
return self.label.serialize(addr, offset=self.offset)
def serialize_as_zero_page(self, offset=0):
return self.label.serialize_as_zero_page(offset=self.offset)
def serialize_as_zero_page(self, addr, offset=0):
return self.label.serialize_as_zero_page(addr, offset=self.offset)
def __repr__(self):
return "%s(%r, %r)" % (self.__class__.__name__, self.label, self.offset)
@ -129,8 +134,8 @@ class HighAddressByte(Emittable):
def size(self):
return 1
def serialize(self, addr=None):
return self.label.serialize()[0]
def serialize(self, addr):
return [self.label.serialize(addr)[0]]
def __repr__(self):
return "%s(%r)" % (self.__class__.__name__, self.label)
@ -144,8 +149,8 @@ class LowAddressByte(Emittable):
def size(self):
return 1
def serialize(self, addr=None):
return self.label.serialize()[1]
def serialize(self, addr):
return [self.label.serialize(addr)[1]]
def __repr__(self):
return "%s(%r)" % (self.__class__.__name__, self.label)
@ -166,11 +171,12 @@ class Emitter(object):
self.accum.append(thing)
self.addr += thing.size()
def serialize(self, stream):
def serialize_to(self, stream):
"""`stream` should be a file opened in binary mode."""
addr = self.start_addr
for emittable in self.accum:
chunk = emittable.serialize(addr)
stream.write(chunk)
stream.write(bytearray(chunk))
addr += len(chunk)
def make_label(self, name=None):

View File

@ -43,7 +43,7 @@ class FallthruAnalyzer(object):
while pending_routines:
chains = [self.find_chain(k, pending_routines) for k in pending_routines.keys()]
chains.sort(key=len, reverse=True)
chains.sort(key=lambda x: (len(x), str(x)), reverse=True)
c = chains[0]
roster.append(c)
for k in c:

View File

@ -8,7 +8,7 @@ class AddressingMode(Emittable):
"""Size of the operand for the mode (not including the opcode)"""
raise NotImplementedError
def serialize(self, addr=None):
def serialize(self, addr):
raise NotImplementedError
def __repr__(self):
@ -19,8 +19,8 @@ class Implied(AddressingMode):
def size(self):
return 0
def serialize(self, addr=None):
return ''
def serialize(self, addr):
return []
def __repr__(self):
return "%s()" % (self.__class__.__name__)
@ -34,8 +34,8 @@ class Immediate(AddressingMode):
def size(self):
return 1
def serialize(self, addr=None):
return self.value.serialize()
def serialize(self, addr):
return self.value.serialize(addr)
class Absolute(AddressingMode):
@ -46,8 +46,8 @@ class Absolute(AddressingMode):
def size(self):
return 2
def serialize(self, addr=None):
return self.value.serialize()
def serialize(self, addr):
return self.value.serialize(addr)
class AbsoluteX(Absolute):
@ -66,8 +66,8 @@ class ZeroPage(AddressingMode):
def size(self):
return 1
def serialize(self, addr=None):
return self.value.serialize_as_zero_page()
def serialize(self, addr):
return self.value.serialize_as_zero_page(addr)
class Indirect(AddressingMode):
@ -78,8 +78,8 @@ class Indirect(AddressingMode):
def size(self):
return 2
def serialize(self, addr=None):
return self.value.serialize()
def serialize(self, addr):
return self.value.serialize(addr)
class IndirectY(ZeroPage):
@ -94,7 +94,7 @@ class Relative(AddressingMode):
def size(self):
return 1
def serialize(self, addr=None):
def serialize(self, addr):
return self.value.serialize_relative_to(addr)
@ -108,11 +108,8 @@ class Instruction(Emittable):
def size(self):
return 1 + self.operand.size() if self.operand else 0
def serialize(self, addr=None):
return (
chr(self.opcodes[self.operand.__class__]) +
self.operand.serialize(addr)
)
def serialize(self, addr):
return [self.opcodes[self.operand.__class__]] + self.operand.serialize(addr)
def __repr__(self):
return "%s(%r)" % (self.__class__.__name__, self.operand)

View File

@ -18,20 +18,6 @@ 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', max_range=(0, 1))
TYPE_BYTE = Type('byte', max_range=(0, 255))
@ -41,11 +27,11 @@ TYPE_WORD = Type('word', max_range=(0, 65535))
class RoutineType(Type):
"""This memory location contains the code for a routine."""
def __init__(self, inputs=None, outputs=None, trashes=None):
def __init__(self, inputs, outputs, trashes):
self.name = 'routine'
self.inputs = inputs or set()
self.outputs = outputs or set()
self.trashes = trashes or set()
self.inputs = inputs
self.outputs = outputs
self.trashes = trashes
def __repr__(self):
return '%s(%r, inputs=%r, outputs=%r, trashes=%r)' % (
@ -172,7 +158,7 @@ class LocationRef(Ref):
@classmethod
def format_set(cls, location_refs):
return '{%s}' % ', '.join([str(loc) for loc in sorted(location_refs)])
return '{%s}' % ', '.join([str(loc) for loc in sorted(location_refs, key=lambda x: x.name)])
class IndirectRef(Ref):

View File

@ -0,0 +1,74 @@
"""Executable file writer."""
from sixtypical.emitter import Emitter, Byte, Word
class Outputter(object):
def __init__(self, fh, start_addr=None):
self.start_addr = self.__class__.start_addr
if start_addr is not None:
self.start_addr = start_addr
self.prelude = self.__class__.prelude
self.fh = fh
self.emitter = Emitter(self.start_addr)
def write_header(self):
pass
def write_prelude(self):
self.write_header()
for byte in self.prelude:
self.emitter.emit(Byte(byte))
def write_postlude(self):
pass
class RawOutputter(Outputter):
start_addr = 0x0000
prelude = []
class PrgOutputter(Outputter):
start_addr = 0xc000
prelude = []
def write_header(self):
# If we are outputting a .PRG, we output the load address first.
# We don't use the Emitter for this b/c not part of addr space.
self.fh.write(bytearray(Word(self.start_addr).serialize(0)))
class C64BasicPrgOutputter(PrgOutputter):
start_addr = 0x0801
prelude = [0x10, 0x08, 0xc9, 0x07, 0x9e, 0x32,
0x30, 0x36, 0x31, 0x00, 0x00, 0x00]
class Vic20BasicPrgOutputter(PrgOutputter):
start_addr = 0x1001
prelude = [0x0b, 0x10, 0xc9, 0x07, 0x9e, 0x34,
0x31, 0x30, 0x39, 0x00, 0x00, 0x00]
class Atari2600CartOutputter(Outputter):
start_addr = 0xf000
prelude = [0x78, 0xd8, 0xa2, 0xff, 0x9a, 0xa9,
0x00, 0x95, 0x00, 0xca, 0xd0, 0xfb]
def write_postlude(self):
# If we are outputting a cartridge with boot and BRK address
# at the end, pad to ROM size minus 4 bytes, and emit addresses.
self.emitter.pad_to_size(4096 - 4)
self.emitter.emit(Word(self.start_addr))
self.emitter.emit(Word(self.start_addr))
def outputter_class_for(output_format):
return {
'raw': RawOutputter,
'prg': PrgOutputter,
'c64-basic-prg': C64BasicPrgOutputter,
'vic20-basic-prg': Vic20BasicPrgOutputter,
'atari2600-cart': Atari2600CartOutputter,
}[output_format]

View File

@ -18,6 +18,14 @@ class SymEntry(object):
return "%s(%r, %r)" % (self.__class__.__name__, self.ast_node, self.model)
class ForwardReference(object):
def __init__(self, name):
self.name = name
def __repr__(self):
return "%s(%r)" % (self.__class__.__name__, self.name)
class ParsingContext(object):
def __init__(self):
self.symbols = {} # token -> SymEntry
@ -33,7 +41,7 @@ class ParsingContext(object):
def __str__(self):
return "Symbols: {}\nStatics: {}\nTypedefs: {}\nConsts: {}".format(self.symbols, self.statics, self.typedefs, self.consts)
def lookup(self, name):
def fetch(self, name):
if name in self.statics:
return self.statics[name].model
if name in self.symbols:
@ -45,17 +53,65 @@ class Parser(object):
def __init__(self, context, text, filename):
self.context = context
self.scanner = Scanner(text, filename)
self.backpatch_instrs = []
def syntax_error(self, msg):
self.scanner.syntax_error(msg)
def lookup(self, name):
model = self.context.lookup(name)
model = self.context.fetch(name)
if model is None:
self.syntax_error('Undefined symbol "{}"'.format(name))
return model
def declare(self, name, symentry, static=False):
if self.context.fetch(name):
self.syntax_error('Symbol "%s" already declared' % name)
if static:
self.context.statics[name] = symentry
else:
self.context.symbols[name] = symentry
def clear_statics(self):
self.context.statics = {}
# ---- symbol resolution
def resolve_symbols(self, program):
# This could stand to be better unified.
def backpatch_constraint_labels(type_):
def resolve(w):
if not isinstance(w, ForwardReference):
return w
return self.lookup(w.name)
if isinstance(type_, TableType):
backpatch_constraint_labels(type_.of_type)
elif isinstance(type_, VectorType):
backpatch_constraint_labels(type_.of_type)
elif isinstance(type_, RoutineType):
type_.inputs = set([resolve(w) for w in type_.inputs])
type_.outputs = set([resolve(w) for w in type_.outputs])
type_.trashes = set([resolve(w) for w in type_.trashes])
for defn in program.defns:
backpatch_constraint_labels(defn.location.type)
for routine in program.routines:
backpatch_constraint_labels(routine.location.type)
def resolve_fwd_reference(obj, field):
field_value = getattr(obj, field, None)
if isinstance(field_value, ForwardReference):
setattr(obj, field, self.lookup(field_value.name))
elif isinstance(field_value, IndexedRef):
if isinstance(field_value.ref, ForwardReference):
field_value.ref = self.lookup(field_value.ref.name)
for node in program.all_children():
if isinstance(node, SingleOp):
resolve_fwd_reference(node, 'location')
resolve_fwd_reference(node, 'src')
resolve_fwd_reference(node, 'dest')
# --- grammar productions
def program(self):
@ -70,47 +126,19 @@ class Parser(object):
typenames.extend(self.context.typedefs.keys())
while self.scanner.on(*typenames):
defn = self.defn()
name = defn.name
if self.context.lookup(name):
self.syntax_error('Symbol "%s" already declared' % name)
self.context.symbols[name] = SymEntry(defn, defn.location)
self.declare(defn.name, SymEntry(defn, defn.location))
defns.append(defn)
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 self.context.lookup(name):
self.syntax_error('Symbol "%s" already declared' % name)
self.context.symbols[name] = SymEntry(routine, routine.location)
while self.scanner.consume('define'):
name = self.scanner.token
self.scanner.scan()
routine = self.routine(name)
self.declare(name, SymEntry(routine, routine.location))
routines.append(routine)
self.scanner.check_type('EOF')
# now backpatch the executable types.
#for type_name, type_ in self.context.typedefs.iteritems():
# type_.backpatch_constraint_labels(lambda w: self.lookup(w))
for defn in defns:
defn.location.type.backpatch_constraint_labels(lambda w: self.lookup(w))
for routine in routines:
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
model = self.lookup(name)
if not isinstance(model.type, (RoutineType, VectorType)):
self.syntax_error('Illegal call of non-executable "%s"' % name)
instr.location = model
if instr.opcode in ('copy',) and isinstance(instr.src, basestring):
name = instr.src
model = self.lookup(name)
if not isinstance(model.type, (RoutineType, VectorType)):
self.syntax_error('Illegal copy of non-executable "%s"' % name)
instr.src = model
return Program(self.scanner.line_number, defns=defns, routines=routines)
program = Program(self.scanner.line_number, defns=defns, routines=routines)
self.resolve_symbols(program)
return program
def typedef(self):
self.scanner.expect('typedef')
@ -250,27 +278,10 @@ class Parser(object):
outputs = set(self.labels())
if self.scanner.consume('trashes'):
trashes = set(self.labels())
return (inputs, outputs, trashes)
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
addr = int(self.scanner.token)
self.scanner.scan()
else:
block = self.block()
addr = None
location = LocationRef(type_, name)
return Routine(
self.scanner.line_number,
name=name, block=block, addr=addr,
location=location
return (
set([ForwardReference(n) for n in inputs]),
set([ForwardReference(n) for n in outputs]),
set([ForwardReference(n) for n in trashes])
)
def routine(self, name):
@ -286,9 +297,11 @@ class Parser(object):
else:
statics = self.statics()
self.context.statics = self.compose_statics_dict(statics)
self.clear_statics()
for defn in statics:
self.declare(defn.name, SymEntry(defn, defn.location), static=True)
block = self.block()
self.context.statics = {}
self.clear_statics()
addr = None
location = LocationRef(type_, name)
@ -298,15 +311,6 @@ class Parser(object):
location=location, statics=statics
)
def compose_statics_dict(self, statics):
c = {}
for defn in statics:
name = defn.name
if self.context.lookup(name):
self.syntax_error('Symbol "%s" already declared' % name)
c[name] = SymEntry(defn, defn.location)
return c
def labels(self):
accum = []
accum.append(self.label())
@ -328,23 +332,19 @@ class Parser(object):
accum.append(self.locexpr())
return accum
def locexpr(self, forward=False):
def locexpr(self):
if self.scanner.token in ('on', 'off', 'word') or self.scanner.token in self.context.consts or self.scanner.on_type('integer literal'):
return self.const()
elif forward:
else:
name = self.scanner.token
self.scanner.scan()
loc = self.context.lookup(name)
if loc is not None:
loc = self.context.fetch(name)
if loc:
return loc
else:
return name
else:
loc = self.lookup(self.scanner.token)
self.scanner.scan()
return loc
return ForwardReference(name)
def indlocexpr(self, forward=False):
def indlocexpr(self):
if self.scanner.consume('['):
loc = self.locexpr()
self.scanner.expect(']')
@ -355,11 +355,11 @@ class Parser(object):
loc = self.locexpr()
return AddressRef(loc)
else:
return self.indexed_locexpr(forward=forward)
return self.indexed_locexpr()
def indexed_locexpr(self, forward=False):
loc = self.locexpr(forward=forward)
if not isinstance(loc, basestring):
def indexed_locexpr(self):
loc = self.locexpr()
if not isinstance(loc, str):
index = None
if self.scanner.consume('+'):
index = self.locexpr()
@ -453,17 +453,15 @@ class Parser(object):
self.scanner.scan()
name = self.scanner.token
self.scanner.scan()
instr = SingleOp(self.scanner.line_number, opcode=opcode, location=name, dest=None, src=None)
self.backpatch_instrs.append(instr)
instr = SingleOp(self.scanner.line_number, opcode=opcode, location=ForwardReference(name), dest=None, src=None)
return instr
elif self.scanner.token in ("copy",):
opcode = self.scanner.token
self.scanner.scan()
src = self.indlocexpr(forward=True)
src = self.indlocexpr()
self.scanner.expect(',')
dest = self.indlocexpr()
instr = SingleOp(self.scanner.line_number, opcode=opcode, dest=dest, src=src)
self.backpatch_instrs.append(instr)
return instr
elif self.scanner.consume("with"):
self.scanner.expect("interrupts")
@ -479,3 +477,17 @@ class Parser(object):
return SingleOp(self.scanner.line_number, opcode='trash', src=None, dest=dest)
else:
self.syntax_error('bad opcode "%s"' % self.scanner.token)
# - - - -
def merge_programs(programs):
"""Assumes that the programs do not have any conflicts."""
full = Program(1, defns=[], routines=[])
for p in programs:
full.defns.extend(p.defns)
full.routines.extend(p.routines)
return full

File diff suppressed because it is too large Load Diff

View File

@ -7,20 +7,20 @@ SixtyPical to 6502 machine code.
[Falderal]: http://catseye.tc/node/Falderal
-> Functionality "Compile SixtyPical program" is implemented by
-> shell command "bin/sixtypical --output-format=c64-basic-prg --traceback %(test-body-file) >/tmp/foo && tests/appliances/bin/dcc6502-adapter </tmp/foo"
-> shell command "bin/sixtypical --output-format=c64-basic-prg --traceback %(test-body-file) --output /tmp/foo && tests/appliances/bin/dcc6502-adapter </tmp/foo"
-> Tests for functionality "Compile SixtyPical program"
Null program.
| routine main
| define main routine
| {
| }
= $080D RTS
`nop` program.
| routine main
| define main routine
| {
| nop
| }
@ -29,7 +29,7 @@ Null program.
Rudimentary program.
| routine main
| define main routine
| inputs a
| outputs a
| trashes c, z, n, v
@ -43,12 +43,12 @@ Rudimentary program.
Call extern.
| routine chrout
| define chrout routine
| inputs a
| trashes a
| @ 65490
|
| routine main
| define main routine
| inputs a
| trashes a, z, n
| {
@ -61,7 +61,7 @@ Call extern.
Call defined routine.
| routine foo
| define foo routine
| outputs a, x, y
| trashes z, n
| {
@ -70,7 +70,7 @@ Call defined routine.
| ld y, 0
| }
|
| routine main
| define main routine
| trashes a, x, y, z, n
| {
| call foo
@ -86,7 +86,7 @@ Access a defined memory location.
| byte foo
|
| routine main
| define main routine
| trashes a, y, z, n, foo
| {
| ld y, 0
@ -102,7 +102,7 @@ Memory location with explicit address.
| byte screen @ 1024
|
| routine main
| define main routine
| trashes a, z, n, screen
| {
| ld a, 100
@ -118,7 +118,7 @@ and `and`, `or`, and `xor` use zero-page addressing.
| byte zp @ $00
| byte screen @ 100
|
| routine main
| define main routine
| inputs screen, zp
| outputs screen, zp
| trashes a, z, n
@ -144,7 +144,7 @@ Memory location with initial value.
| byte lives : 3
|
| routine main
| define main routine
| inputs lives
| trashes a, z, n
| {
@ -159,7 +159,7 @@ Word memory locations with explicit address, initial value.
| word w1 @ 60001
| word w2 : 3003
|
| routine main
| define main routine
| inputs w1
| outputs w2
| trashes a, z, n
@ -178,7 +178,7 @@ Initialized byte table, initialized with ASCII string. Bytes allocated, but bey
| byte table[8] message : "WHAT?"
|
| routine main
| define main routine
| inputs message
| outputs x, a, z, n
| {
@ -200,7 +200,7 @@ Initialized byte table, initialized with list of byte values.
| byte table[8] message : 255, 0, 129, 128, 127
|
| routine main
| define main routine
| inputs message
| outputs x, a, z, n
| {
@ -222,7 +222,7 @@ Initialized word table, initialized with list of word values.
| word table[4] message : 65535, 0, 127, 127
|
| routine main
| define main routine
| {
| }
= $080D RTS
@ -239,7 +239,7 @@ Some instructions.
| byte foo
|
| routine main
| define main routine
| trashes a, x, y, z, n, c, v, foo
| {
| ld a, 0
@ -317,7 +317,7 @@ Some instructions on tables. (1/3)
| byte table[256] many
|
| routine main
| define main routine
| inputs many
| outputs many
| trashes a, x, c, n, z, v
@ -341,7 +341,7 @@ Some instructions on tables. (2/3)
| byte table[256] many
|
| routine main
| define main routine
| inputs many
| outputs many
| trashes a, x, c, n, z
@ -363,7 +363,7 @@ Some instructions on tables. (3/3)
| byte table[256] many
|
| routine main
| define main routine
| inputs many
| outputs many
| trashes a, x, c, n, z
@ -387,7 +387,7 @@ Some instructions on tables. (3/3)
Compiling `if`.
| routine main
| define main routine
| trashes a, x, y, z, n, c, v
| {
| ld a, 0
@ -406,7 +406,7 @@ Compiling `if`.
Compiling `if not`.
| routine main
| define main routine
| trashes a, x, y, z, n, c, v
| {
| ld a, 0
@ -425,7 +425,7 @@ Compiling `if not`.
Compiling `if` without `else`.
| routine main
| define main routine
| trashes a, x, y, z, n, c, v
| {
| ld a, 0
@ -442,7 +442,7 @@ Compiling `if` without `else`.
Compiling `repeat ... until z`.
| routine main
| define main routine
| trashes a, y, z, n, c
| {
| ld y, 65
@ -461,7 +461,7 @@ Compiling `repeat ... until z`.
Compiling `repeat ... until not z`.
| routine main
| define main routine
| trashes a, y, z, n, c
| {
| ld y, 65
@ -480,7 +480,7 @@ Compiling `repeat ... until not z`.
Compiling `repeat ... until n`.
| routine main
| define main routine
| trashes a, y, z, n, c
| {
| ld y, 65
@ -497,7 +497,7 @@ Compiling `repeat ... until n`.
Compiling `repeat ... until not n`.
| routine main
| define main routine
| trashes a, y, z, n, c
| {
| ld y, 199
@ -514,7 +514,7 @@ Compiling `repeat ... until not n`.
Compiling `repeat forever`.
| routine main
| define main routine
| trashes a, y, z, n, c
| {
| ld y, 65
@ -529,7 +529,7 @@ Compiling `repeat forever`.
The body of `repeat forever` can be empty.
| routine main
| define main routine
| {
| repeat {
| } forever
@ -579,7 +579,7 @@ Compiling `for ... down to`.
Compiling `save`.
| routine main
| define main routine
| inputs a
| outputs a
| trashes z, n
@ -601,10 +601,32 @@ Compiling `save`.
= $0816 PLA
= $0817 RTS
Compiling `save` with shortcut syntax.
| define main routine
| inputs a
| outputs a
| trashes z, n
| {
| save a, x {
| ld a, 0
| ld x, 1
| }
| }
= $080D PHA
= $080E TXA
= $080F PHA
= $0810 LDA #$00
= $0812 LDX #$01
= $0814 PLA
= $0815 TAX
= $0816 PLA
= $0817 RTS
Compiling `save` on a user-defined location.
| byte foo
| routine main
| define main routine
| trashes a, z, n
| {
| save foo {
@ -625,7 +647,7 @@ Indexed access.
| byte one
| byte table[256] many
|
| routine main
| define main routine
| outputs many
| trashes a, x, n, z
| {
@ -645,7 +667,7 @@ Byte tables take up, at most, 256 bytes in memory.
| byte table[256] tab1
| byte table[256] tab2
|
| routine main
| define main routine
| inputs tab1
| outputs tab2
| trashes a, x, n, z
@ -664,7 +686,7 @@ Byte storage locations take up only 1 byte in memory.
| byte one
| byte two
|
| routine main
| define main routine
| outputs one, two
| trashes a, x, n, z
| {
@ -682,7 +704,7 @@ Copy byte to byte.
| byte bar
| byte baz
|
| routine main
| define main routine
| inputs baz
| outputs bar
| trashes a, n, z
@ -698,7 +720,7 @@ Copy word to word.
| word bar
| word baz
|
| routine main
| define main routine
| inputs baz
| outputs bar
| trashes a, n, z
@ -715,7 +737,7 @@ Copy literal word to word.
| word bar
|
| routine main
| define main routine
| outputs bar
| trashes a, n, z
| {
@ -731,7 +753,7 @@ You can also copy a literal word to a word table.
| word table[256] many
|
| routine main
| define main routine
| inputs many
| outputs many
| trashes a, x, n, z
@ -751,7 +773,7 @@ Copy vector to vector.
| vector routine bar
| vector routine baz
|
| routine main
| define main routine
| inputs baz
| outputs bar
| trashes a, n, z
@ -772,7 +794,7 @@ Copy routine to vector, inside an `interrupts off` block.
| trashes z, n
| bar
|
| routine foo
| define foo routine
| inputs x
| outputs x
| trashes z, n
@ -780,7 +802,7 @@ Copy routine to vector, inside an `interrupts off` block.
| inc x
| }
|
| routine main
| define main routine
| outputs bar
| trashes a, n, z
| {
@ -806,14 +828,14 @@ Copy routine (by forward reference) to vector.
| trashes z, n
| bar
|
| routine main
| define main routine
| outputs bar
| trashes a, n, z
| {
| copy foo, bar
| }
|
| routine foo
| define foo routine
| inputs x
| outputs x
| trashes z, n
@ -833,7 +855,7 @@ Copy word to word table and back, with both `x` and `y` as indexes.
| word one
| word table[256] many
|
| routine main
| define main routine
| inputs one, many
| outputs one, many
| trashes a, x, y, n, z
@ -877,14 +899,14 @@ Indirect call.
| trashes z, n
| foo
|
| routine bar
| define bar routine
| outputs x
| trashes z, n
| {
| ld x, 200
| }
|
| routine main
| define main routine
| outputs x, foo
| trashes a, z, n
| {
@ -904,7 +926,7 @@ Indirect call.
Compiling `goto`. Note that no `RTS` is emitted after the `JMP`.
| routine bar
| define bar routine
| inputs y
| outputs x, y
| trashes z, n
@ -912,7 +934,7 @@ Compiling `goto`. Note that no `RTS` is emitted after the `JMP`.
| ld x, 200
| }
|
| routine main
| define main routine
| outputs x, y
| trashes a, z, n
| {
@ -937,11 +959,11 @@ Copying to and from a vector table.
| trashes a, z, n
| table[256] many
|
| routine bar outputs x trashes a, z, n {
| define bar routine outputs x trashes a, z, n {
| ld x, 200
| }
|
| routine main
| define main routine
| inputs one, many
| outputs one, many
| trashes a, x, n, z
@ -982,7 +1004,7 @@ Copying to and from a vector table.
Adding a constant word to a word memory location.
| word score
| routine main
| define main routine
| inputs score
| outputs score
| trashes a, c, z, v, n
@ -1003,7 +1025,7 @@ Adding a word memory location to another word memory location.
| word score
| word delta
| routine main
| define main routine
| inputs score, delta
| outputs score
| trashes a, c, z, v, n
@ -1023,7 +1045,7 @@ Adding a word memory location to another word memory location.
Subtracting a constant word from a word memory location.
| word score
| routine main
| define main routine
| inputs score
| outputs score
| trashes a, c, z, v, n
@ -1044,7 +1066,7 @@ Subtracting a word memory location from another word memory location.
| word score
| word delta
| routine main
| define main routine
| inputs score, delta
| outputs score
| trashes a, c, z, v, n
@ -1068,7 +1090,7 @@ Load address into pointer.
| buffer[2048] buf
| pointer ptr @ 254
|
| routine main
| define main routine
| inputs buf
| outputs buf, y
| trashes a, z, n, ptr
@ -1088,7 +1110,7 @@ Write literal through a pointer.
| buffer[2048] buf
| pointer ptr @ 254
|
| routine main
| define main routine
| inputs buf
| outputs buf, y
| trashes a, z, n, ptr
@ -1112,7 +1134,7 @@ Write stored value through a pointer.
| pointer ptr @ 254
| byte foo
|
| routine main
| define main routine
| inputs foo, buf
| outputs y, buf
| trashes a, z, n, ptr
@ -1136,7 +1158,7 @@ Read through a pointer, into a byte storage location, or the `a` register.
| pointer ptr @ 254
| byte foo
|
| routine main
| define main routine
| inputs buf
| outputs y, foo
| trashes a, z, n, ptr
@ -1162,7 +1184,7 @@ Read and write through two pointers.
| pointer ptra @ 252
| pointer ptrb @ 254
|
| routine main
| define main routine
| inputs buf
| outputs buf
| trashes a, y, z, n, ptra, ptrb
@ -1191,7 +1213,7 @@ Write the `a` register through a pointer.
| pointer ptr @ 254
| byte foo
|
| routine main
| define main routine
| inputs buf
| outputs buf
| trashes a, y, z, n, ptr
@ -1218,7 +1240,7 @@ Note that this is *not* range-checked. (Yet.)
| byte foo
| word delta
|
| routine main
| define main routine
| inputs buf
| outputs y, foo, delta
| trashes a, c, v, z, n, ptr
@ -1261,7 +1283,7 @@ Note that this is *not* range-checked. (Yet.)
Trash does nothing except indicate that we do not care about the value anymore.
| routine main
| define main routine
| inputs a
| outputs x
| trashes a, z, n

View File

@ -48,7 +48,7 @@ Treat R as a mutable set and start with an empty list of lists L. Then,
- Remove all elements occurring in C, from R.
- Repeat until R is empty.
When times comes to generate code, generate it in the order given by L.
When time comes to generate code, generate it in the order given by L.
In addition, each sublist in L represents a number of routines to
generate; all except the final routine in such a sublist need not have
any jump instruction generated for its final `goto`.
@ -65,7 +65,7 @@ to pass these tests to be considered an implementation of SixtyPical.
-> shell command "bin/sixtypical --optimize-fallthru --dump-fallthru-info --analyze-only --traceback %(test-body-file)"
-> Functionality "Compile SixtyPical program with fallthru optimization" is implemented by
-> shell command "bin/sixtypical --output-format=c64-basic-prg --optimize-fallthru --traceback %(test-body-file) >/tmp/foo && tests/appliances/bin/dcc6502-adapter </tmp/foo"
-> shell command "bin/sixtypical --output-format=c64-basic-prg --optimize-fallthru --traceback %(test-body-file) --output /tmp/foo && tests/appliances/bin/dcc6502-adapter </tmp/foo"
-> Tests for functionality "Dump fallthru info for SixtyPical program"
@ -94,7 +94,7 @@ If `main` does a `goto foo`, then it can fall through to `foo`.
| }
= [
= [
= "main",
= "main",
= "foo"
= ]
= ]
@ -119,9 +119,9 @@ of them to fall through, when selecting the order of routines.
| }
= [
= [
= "main",
= "main",
= "foo"
= ],
= ],
= [
= "bar"
= ]
@ -144,7 +144,7 @@ nothing ever falls through to `main`.
= [
= [
= "main"
= ],
= ],
= [
= "foo"
= ]
@ -172,10 +172,10 @@ fall through to the other.
= [
= [
= "main"
= ],
= ],
= [
= "bar",
= "foo"
= "foo",
= "bar"
= ]
= ]
@ -204,12 +204,12 @@ routine.
= [
= [
= "main"
= ],
= [
= "bar"
= ],
= ],
= [
= "foo"
= ],
= [
= "bar"
= ]
= ]
@ -238,9 +238,9 @@ If, however, they are the same goto, one can be optimized away.
= [
= [
= "main"
= ],
= ],
= [
= "foo",
= "foo",
= "bar"
= ]
= ]
@ -269,12 +269,12 @@ because we don't necessarily know what actual routine the vector contains.
= [
= [
= "main"
= ],
= [
= "bar"
= ],
= ],
= [
= "foo"
= ],
= [
= "bar"
= ]
= ]
@ -321,14 +321,14 @@ Our algorithm might not be strictly optimal, but it does a good job.
| }
= [
= [
= "main",
= "r1",
= "r2",
= "r3",
= "main",
= "r1",
= "r2",
= "r3",
= "r4"
= ],
= ],
= [
= "r5",
= "r5",
= "r6"
= ]
= ]
@ -416,12 +416,12 @@ in the "true" branch is a `goto`.
| {
| }
= $080D RTS
= $080E LDA #$FF
= $0810 RTS
= $0811 LDA #$00
= $0813 BNE $081D
= $0815 LDA #$01
= $0817 JMP $080E
= $081A JMP $0822
= $081D LDA #$02
= $081F JMP $080D
= $080E LDA #$00
= $0810 BNE $081A
= $0812 LDA #$01
= $0814 JMP $081F
= $0817 JMP $081F
= $081A LDA #$02
= $081C JMP $080D
= $081F LDA #$FF
= $0821 RTS

View File

@ -16,7 +16,7 @@ but not necessarily sensible programs.
Rudimentary program.
| routine main {
| define main routine {
| ld a, 0
| add a, 1
| }
@ -26,7 +26,7 @@ Program with comments.
| // Welcome to my program.
|
| routine main {
| define main routine {
| ld a, 0
| add a, 1 // We are adding the thing.
| sub a, 1
@ -40,7 +40,7 @@ Program with comments.
Hex literals.
| routine main {
| define main routine {
| ld a, $ff
| add a, $01
| }
@ -48,7 +48,7 @@ Hex literals.
Syntax error.
| routine foo (
| define foo routine (
| ld a, 0
| add a, 1
| )
@ -65,12 +65,12 @@ Another syntax error.
Extern routines
| routine chrout
| define chrout routine
| inputs a
| trashes a
| @ 65490
|
| routine chrin
| define chrin routine
| outputs a
| trashes x
| @ 65487
@ -78,7 +78,7 @@ Extern routines
Trash.
| routine main {
| define main routine {
| trash a
| trash n
| }
@ -86,7 +86,7 @@ Trash.
`nop`.
| routine main
| define main routine
| {
| nop
| }
@ -94,7 +94,7 @@ Trash.
If with not
| routine foo {
| define foo routine {
| ld y, 0
| cmp y, 10
| if not z {
@ -106,7 +106,7 @@ If with not
Repeat loop
| routine foo {
| define foo routine {
| ld y, 0
| repeat {
| inc y
@ -117,7 +117,7 @@ Repeat loop
"While" loop
| routine foo inputs y {
| define foo routine inputs y {
| repeat {
| cmp y, 10
| if not z {
@ -129,7 +129,7 @@ Repeat loop
Repeat forever
| routine foo inputs y {
| define foo routine inputs y {
| repeat {
| inc y
| } forever
@ -138,7 +138,7 @@ Repeat forever
Repeat with not
| routine foo inputs y {
| define foo routine inputs y {
| repeat {
| inc y
| } until not z
@ -149,7 +149,7 @@ Basic "open-faced for" loops, up and down.
| byte table[256] tab
|
| routine foo trashes a, x, c, z, v {
| define foo routine trashes a, x, c, z, v {
| ld x, 0
| for x up to 15 {
| ld a, tab + x
@ -163,7 +163,7 @@ Basic "open-faced for" loops, up and down.
Other blocks.
| routine main trashes a, x, c, z, v {
| define main routine trashes a, x, c, z, v {
| with interrupts off {
| save a, x, c {
| ld a, 0
@ -183,7 +183,7 @@ User-defined memory addresses of different types.
| buffer[2048] buf
| pointer ptr
|
| routine main {
| define main routine {
| }
= ok
@ -193,7 +193,7 @@ Tables of different types and some operations on them.
| word table[256] wmany
| vector (routine trashes a) table[256] vmany
|
| routine main {
| define main routine {
| ld x, 0
| ld a, 0
| st off, c
@ -215,7 +215,7 @@ greater than 0 and less than or equal to 256.
| word table[512] many
|
| routine main
| define main routine
| inputs many
| outputs many
| trashes a, x, n, z
@ -227,7 +227,7 @@ greater than 0 and less than or equal to 256.
| word table[0] many
|
| routine main
| define main routine
| inputs many
| outputs many
| trashes a, x, n, z
@ -239,7 +239,7 @@ greater than 0 and less than or equal to 256.
| word table[48] many
|
| routine main
| define main routine
| inputs many
| outputs many
| trashes a, x, n, z
@ -256,7 +256,7 @@ Typedefs of different types.
| typedef routine trashes a game_routine
| vector game_routine start_game
|
| routine main {
| define main routine {
| }
= ok
@ -265,7 +265,7 @@ Can't have two typedefs with the same name.
| typedef byte frank
| typedef word frank
|
| routine main {
| define main routine {
| }
? SyntaxError
@ -280,7 +280,7 @@ Constants.
|
| byte lark: lives
|
| routine main {
| define main routine {
| ld a, lives
| }
= ok
@ -290,7 +290,7 @@ Can't have two constants with the same name.
| const w1 1000
| const w1 word 0
|
| routine main {
| define main routine {
| }
? SyntaxError
@ -298,7 +298,7 @@ Explicit memory address.
| byte screen @ 1024
|
| routine main {
| define main routine {
| ld a, 100
| st a, screen
| shl screen
@ -310,7 +310,7 @@ Initialized memory locations.
| byte lives : 3
|
| routine main {
| define main routine {
| ld a, lives
| st a, lives
| }
@ -320,7 +320,7 @@ Cannot have both initial value and explicit address.
| byte screen : 3 @ 1024
|
| routine main {
| define main routine {
| ld a, lives
| st a, lives
| }
@ -333,7 +333,7 @@ User-defined locations of other types.
| word r2 @ 60000
| word r3 : 2000
|
| routine main {
| define main routine {
| }
= ok
@ -341,15 +341,15 @@ Initialized byte table, initialized with ASCII string.
| byte table[32] message : "WHAT DO YOU WANT TO DO NEXT?"
|
| routine main {
| define main routine {
| }
= ok
Can't initialize anything but a byte table with a string.
| word message : "WHAT DO YOU WANT TO DO NEXT?"
| word message : "OUCH! WHAT DO YOU DO?"
|
| routine main {
| define main routine {
| }
? SyntaxError
@ -357,13 +357,13 @@ Initialized byte table, initialized with list of bytes.
| byte table[8] charmap : 0, 255, 129, 192, 0, 1, 2, 4
|
| routine main {
| define main routine {
| }
= ok
Can't access an undeclared memory location.
| routine main {
| define main routine {
| ld a, 0
| st a, lives
| }
@ -374,7 +374,7 @@ Can't define two memory locations with the same name.
| byte lives
| byte lives
|
| routine main {
| define main routine {
| ld a, 0
| st a, lives
| }
@ -384,19 +384,19 @@ Can't shadow the name of a register or a flag.
| byte a
|
| routine main {
| define main routine {
| }
? SyntaxError
| byte z
|
| routine main {
| define main routine {
| }
? SyntaxError
Can't call routine that hasn't been defined.
| routine main {
| define main routine {
| ld x, 0
| ld y, 1
| call up
@ -404,44 +404,26 @@ Can't call routine that hasn't been defined.
| }
? SyntaxError
And you can't call a non-routine.
| byte up
|
| routine main {
| ld x, 0
| ld y, 1
| call up
| }
? SyntaxError
| routine main {
| ld x, 0
| ld y, 1
| call x
| }
? SyntaxError
But you can call a routine that is yet to be defined, further on.
| routine main {
| define main routine {
| ld x, 0
| ld y, 1
| call up
| call up
| }
| routine up {
| define up routine {
| ld a, 0
| }
= ok
Can't define two routines with the same name.
| routine main {
| define main routine {
| inc x
| inc y
| }
| routine main {
| define main routine {
| ld x, 0
| ld y, 1
| }
@ -451,7 +433,7 @@ Declaring byte and word table memory location.
| byte table[256] tab
|
| routine main {
| define main routine {
| ld x, 0
| ld y, 0
| ld a, tab + x
@ -462,7 +444,7 @@ Declaring byte and word table memory location.
| word one
| word table[256] many
|
| routine main {
| define main routine {
| ld x, 0
| copy one, many + x
| copy word 0, many + x
@ -478,10 +460,10 @@ Declaring and calling a vector.
| trashes a, x, z, n
| cinv @ 788
|
| routine foo {
| define foo routine {
| ld a, 0
| }
| routine main {
| define main routine {
| with interrupts off {
| copy foo, cinv
| }
@ -497,7 +479,7 @@ Only vectors can be decorated with constraints like that.
| trashes a, x, z, n
| @ 788
|
| routine main {
| define main routine {
| }
? SyntaxError
@ -509,10 +491,10 @@ Constraints set may only contain labels.
| trashes a, x, z, n
| cinv @ 788
|
| routine foo {
| define foo routine {
| ld a, 0
| }
| routine main {
| define main routine {
| with interrupts off {
| copy foo, cinv
| }
@ -528,10 +510,10 @@ A vector can name itself in its inputs, outputs, and trashes.
| trashes a, x, z, n
| cinv @ 788
|
| routine foo {
| define foo routine {
| ld a, 0
| }
| routine main {
| define main routine {
| with interrupts off {
| copy foo, cinv
| }
@ -548,50 +530,43 @@ references in the source of a `copy` instruction.
| outputs cinv, x
| trashes a, x, z, n
| cinv @ 788
| routine main {
| define main routine {
| with interrupts off {
| copy foo, cinv
| }
| call cinv
| }
| routine foo {
| define foo routine {
| ld a, 0
| }
= ok
goto.
| routine foo {
| define foo routine {
| ld a, 0
| }
| routine main {
| define main routine {
| goto foo
| }
= ok
| routine main {
| define main routine {
| goto foo
| }
| routine foo {
| define foo routine {
| ld a, 0
| }
= ok
| vector routine foo
|
| routine main {
| define main routine {
| goto foo
| }
= ok
| routine main {
| goto foo
| }
? SyntaxError
| byte foo
|
| routine main {
| define main routine {
| goto foo
| }
? SyntaxError
@ -603,7 +578,7 @@ Buffers and pointers.
| pointer ptrb
| byte foo
|
| routine main {
| define main routine {
| copy ^buf, ptr
| copy 123, [ptr] + y
| copy [ptr] + y, foo
@ -629,7 +604,28 @@ Routines can be defined in a new style.
| inc x
| }
|
| routine main
| define main routine
| outputs vec
| trashes a, z, n
| {
| copy foo, vec
| }
= ok
| typedef routine
| inputs x
| outputs x
| trashes z, n
| routine_type
|
| vector routine_type vec
|
| define foo routine_type
| {
| inc x
| }
|
| define main routine
| outputs vec
| trashes a, z, n
| {