mirror of
https://github.com/catseye/SixtyPical.git
synced 2024-06-02 03:41:28 +00:00
commit
330e61f327
14
HISTORY.md
14
HISTORY.md
|
@ -1,6 +1,20 @@
|
|||
History of SixtyPical
|
||||
=====================
|
||||
|
||||
0.14
|
||||
----
|
||||
|
||||
* Added the so-called "open-faced `for` loop", which spans a loop
|
||||
variable over a finite range, the end of which is fixed.
|
||||
* "Tail position" is now more correctly determined for the purposes of
|
||||
insisting that `goto` only appears in it.
|
||||
* New `--origin` and `--output-format` options added to the compiler.
|
||||
* Fixed bug when `--prelude` option was missing.
|
||||
* Fixed bug when reporting line numbers of scanner-level syntax errors.
|
||||
* Translated the small demo projects Ribos and "The PETulant Cursor" to
|
||||
SixtyPical, and added them to the `eg/c64/` section of the repo.
|
||||
* Added a `eg/vic20` example directory, with one VIC-20 example program.
|
||||
|
||||
0.13
|
||||
----
|
||||
|
||||
|
|
47
README.md
47
README.md
|
@ -1,12 +1,12 @@
|
|||
SixtyPical
|
||||
==========
|
||||
|
||||
_Version 0.13. Work-in-progress, everything is subject to change._
|
||||
_Version 0.14. Work-in-progress, everything is subject to change._
|
||||
|
||||
**SixtyPical** is a 6502-assembly-like programming language with advanced
|
||||
**SixtyPical** is a 6502-like programming language with advanced
|
||||
static analysis.
|
||||
|
||||
"6502-assembly-like" means that it has similar restrictions as programming
|
||||
"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.
|
||||
|
@ -15,8 +15,8 @@ translate it to 6502 machine language code.
|
|||
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 present as
|
||||
safety guarantees.
|
||||
determine that certain things can never happen, which we can then formulate
|
||||
as safety checks.
|
||||
|
||||
In practice, this means it catches things like
|
||||
|
||||
|
@ -51,7 +51,8 @@ automatically start it in the `x64` emulator, and you should see:
|
|||
![Screenshot of result of running hearts.60p](https://raw.github.com/catseye/SixtyPical/master/images/hearts.png)
|
||||
|
||||
You can try the `loadngo.sh` script on other sources in the `eg` directory
|
||||
tree. There is an entire small game(-like program) in [demo-game.60p](eg/c64/demo-game.60p).
|
||||
tree, which contains more extensive examples, including an entire
|
||||
game(-like program); see [eg/README.md](eg/README.md) for a listing.
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
@ -67,37 +68,6 @@ Documentation
|
|||
TODO
|
||||
----
|
||||
|
||||
### `for`-like loop
|
||||
|
||||
We have range-checking in the abstract analysis now, but we lack practical ways
|
||||
to use it.
|
||||
|
||||
We can `and` a value to ensure it is within a certain range. However, in the 6502
|
||||
ISA the only register you can `and` is `A`, while loops are done with `X` or `Y`.
|
||||
Insisting this as the way to do it would result in a lot of `TXA`s and `TAX`s.
|
||||
|
||||
What would be better is a dedicated `for` loop, like
|
||||
|
||||
for x in 0 to 15 {
|
||||
// in here, we know the range of x is exactly 0-15 inclusive
|
||||
// also in here: we are disallowed from changing x
|
||||
}
|
||||
|
||||
However, this is slightly restrictive, and hides a lot.
|
||||
|
||||
However however, options which do not hide a lot, require a lot of looking at
|
||||
(to ensure: did you increment the loop variable? only once? etc.)
|
||||
|
||||
The leading compromise so far is an "open-faced for loop", like
|
||||
|
||||
ld x, 15
|
||||
for x downto 0 {
|
||||
// same as above
|
||||
}
|
||||
|
||||
This makes it a little more explicit, at least, even though the loop
|
||||
decrementation is still hidden.
|
||||
|
||||
### Save registers on stack
|
||||
|
||||
This preserves them, so that, semantically, they can be used later even though they
|
||||
|
@ -114,6 +84,7 @@ is probably NP-complete. But doing it adequately is probably not that hard.
|
|||
* `const`s that can be used in defining the size of tables, etc.
|
||||
* Tests, and implementation, ensuring a routine can be assigned to a vector of "wider" type
|
||||
* Related: can we simply view a (small) part of a buffer as a byte table? If not, why not?
|
||||
* Related: add constant to buffer to get new buffer. (Or to table, but... well, maybe.)
|
||||
* Check that the buffer being read or written to through pointer, appears in approporiate inputs or outputs set.
|
||||
(Associate each pointer with the buffer it points into.)
|
||||
* `static` pointers -- currently not possible because pointers must be zero-page, thus `@`, thus uninitialized.
|
||||
|
@ -123,5 +94,7 @@ is probably NP-complete. But doing it adequately is probably not that hard.
|
|||
* Automatic tail-call optimization (could be tricky, w/constraints?)
|
||||
* Possibly `ld x, [ptr] + y`, possibly `st x, [ptr] + y`.
|
||||
* Maybe even `copy [ptra] + y, [ptrb] + y`, which can be compiled to indirect LDA then indirect STA!
|
||||
* Optimize `ld a, z` and `st a, z` to zero-page operations if address of z < 256.
|
||||
* Include files?
|
||||
|
||||
[VICE]: http://vice-emu.sourceforge.net/
|
||||
|
|
|
@ -36,11 +36,21 @@ if __name__ == '__main__':
|
|||
action="store_true",
|
||||
help="Only parse and analyze the program; do not compile it."
|
||||
)
|
||||
argparser.add_argument(
|
||||
"--origin", type=str, default='0xc000',
|
||||
help="Location in memory where the `main` routine will be "
|
||||
"located. Default: 0xc000."
|
||||
)
|
||||
argparser.add_argument(
|
||||
"--output-format", type=str, default='prg',
|
||||
help="Executable format to produce. Options are: prg (.PRG file "
|
||||
"for Commodore 8-bit). Default: prg."
|
||||
)
|
||||
argparser.add_argument(
|
||||
"--prelude", type=str,
|
||||
help="Insert a snippet before the compiled program "
|
||||
"so that it can be LOADed and RUN on a certain platforms. "
|
||||
"Also sets the origin. "
|
||||
"Also sets the origin and format. "
|
||||
"Options are: c64 or vic20."
|
||||
)
|
||||
argparser.add_argument(
|
||||
|
@ -92,22 +102,31 @@ if __name__ == '__main__':
|
|||
sys.exit(0)
|
||||
|
||||
fh = sys.stdout
|
||||
start_addr = 0xc000
|
||||
|
||||
if options.origin.startswith('0x'):
|
||||
start_addr = int(options.origin, 16)
|
||||
else:
|
||||
start_addr = int(options.origin, 10)
|
||||
|
||||
output_format = options.output_format
|
||||
|
||||
prelude = []
|
||||
if options.prelude == 'c64':
|
||||
output_format = 'prg'
|
||||
start_addr = 0x0801
|
||||
prelude = [0x10, 0x08, 0xc9, 0x07, 0x9e, 0x32,
|
||||
0x30, 0x36, 0x31, 0x00, 0x00, 0x00]
|
||||
elif options.prelude == 'vic20':
|
||||
output_format = 'prg'
|
||||
start_addr = 0x1001
|
||||
prelude = [0x0b, 0x10, 0xc9, 0x07, 0x9e, 0x34,
|
||||
0x31, 0x30, 0x39, 0x00, 0x00, 0x00]
|
||||
else:
|
||||
raise NotImplementedError
|
||||
elif options.prelude:
|
||||
raise NotImplementedError("Unknown prelude: {}".format(options.prelude))
|
||||
|
||||
# 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:
|
||||
# If we are outputting a .PRG, we output the load address first.
|
||||
# We don't use the Emitter for this b/c not part of addr space.
|
||||
if output_format == 'prg':
|
||||
fh.write(Word(start_addr).serialize(0))
|
||||
|
||||
emitter = Emitter(start_addr)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
SixtyPical
|
||||
==========
|
||||
|
||||
This document describes the SixtyPical programming language version 0.11,
|
||||
This document describes the SixtyPical programming language version 0.14,
|
||||
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.)
|
||||
|
@ -234,9 +234,12 @@ changed by this instruction; they must be named in the WRITES, and they
|
|||
are considered initialized after it has executed.
|
||||
|
||||
If and only if src is a byte table, the index-memory-location must be given.
|
||||
In this case, it is illegal if the value of the index-memory-location falls
|
||||
outside of the range of the table.
|
||||
|
||||
Some combinations, such as `ld x, y`, are illegal because they do not map to
|
||||
underlying opcodes.
|
||||
underlying opcodes. (For an instruction which maps more flexibly to underlying
|
||||
opcodes, see `copy`.)
|
||||
|
||||
There is another mode of `ld` which reads into `a` indirectly through a pointer.
|
||||
|
||||
|
@ -265,6 +268,8 @@ After execution, dest is considered initialized. No flags are
|
|||
changed by this instruction (unless of course dest is a flag.)
|
||||
|
||||
If and only if dest is a byte table, the index-memory-location must be given.
|
||||
In this case, it is illegal if the value of the index-memory-location falls
|
||||
outside of the range of the table.
|
||||
|
||||
There is another mode of `st` which write `a` into memory, indirectly through
|
||||
a pointer.
|
||||
|
@ -531,6 +536,22 @@ The sense of the test can be inverted with `not`.
|
|||
}
|
||||
} until not z
|
||||
|
||||
### for ###
|
||||
|
||||
for <dest-memory-location> (up|down) to <literal-byte> {
|
||||
<block>
|
||||
}
|
||||
|
||||
Executes the block repeatedly, incrementing or decrementing the
|
||||
dest-memory-location at the end of the block, until the value of
|
||||
the dest-memory-location has gone past the literal-byte.
|
||||
|
||||
The block is always executed as least once.
|
||||
|
||||
* It is illegal if any memory location is uninitialized at the exit of
|
||||
the loop when that memory location is initialized at the start of
|
||||
the loop.
|
||||
|
||||
Grammar
|
||||
-------
|
||||
|
||||
|
@ -555,9 +576,10 @@ Grammar
|
|||
LocExpr ::= Register | Flag | Literal | Ident.
|
||||
Register::= "a" | "x" | "y".
|
||||
Flag ::= "c" | "z" | "n" | "v".
|
||||
Literal ::= LitByte | LitWord.
|
||||
Literal ::= LitByte | LitWord | LitBit.
|
||||
LitByte ::= "0" ... "255".
|
||||
LitWord ::= "0" ... "65535".
|
||||
LitBit ::= "on" | "off".
|
||||
Block ::= "{" {Instr} "}".
|
||||
Instr ::= "ld" LocExpr "," LocExpr ["+" LocExpr]
|
||||
| "st" LocExpr "," LocExpr ["+" LocExpr]
|
||||
|
@ -573,7 +595,9 @@ Grammar
|
|||
| "dec" LocExpr
|
||||
| "call" Ident<routine>
|
||||
| "goto" Ident<executable>
|
||||
| "copy" LocExpr "," LocExpr ["+" LocExpr]
|
||||
| "if" ["not"] LocExpr Block ["else" Block]
|
||||
| "repeat" Block ("until" ["not"] LocExpr | "forever")
|
||||
| "copy" LocExpr "," LocExpr ["+" LocExpr]
|
||||
| "for" LocExpr ("up"|"down") "to" Literal Block
|
||||
| "with" "interrupts" LitBit Block
|
||||
.
|
||||
|
|
41
eg/README.md
Normal file
41
eg/README.md
Normal file
|
@ -0,0 +1,41 @@
|
|||
This directory contains SixtyPical example programs, categorized
|
||||
in subdirectories by machine architecture.
|
||||
|
||||
### rudiments
|
||||
|
||||
In the [rudiments](rudiments/) directory are programs which are not for
|
||||
any particular machine, but meant to demonstrate the features of SixtyPical.
|
||||
Some are meant to fail and produce an error message. Others can run on
|
||||
any architecture where there is a routine at 65490 which outputs the value
|
||||
of the accumulator as an ASCII character.
|
||||
|
||||
### c64
|
||||
|
||||
In the [c64](c64/) directory are programs that run on the Commodore 64.
|
||||
The directory itself contains some simple demos, for example
|
||||
[hearts.60p](c64/hearts.60p), while there are subdirectories for more
|
||||
elaborate demos:
|
||||
|
||||
* [demo-game](c64/demo-game/): a little game-like program written as a
|
||||
"can we write something you'd see in practice?" test case for SixtyPical.
|
||||
|
||||
* [ribos](c64/ribos/): a well-commented example of a C64 raster interrupt
|
||||
routine. Originally written with the P65 assembler (which has since
|
||||
been reborn as [Ophis][]).
|
||||
|
||||
The second version of Ribos has been translated to SixtyPical.
|
||||
|
||||
* [petulant](c64/petulant/): "The PETulant Cursor", a tiny (44 bytes)
|
||||
"display hack". Originally written in the late 80's. Rewritten with
|
||||
the P65 assembler (now Ophis) and re-released on April 1st, 2008 (a
|
||||
hint as to its nature).
|
||||
|
||||
Translated to SixtyPical (in 2018), it's 48 bytes.
|
||||
|
||||
### vic20
|
||||
|
||||
In the [vic20](vic20/) directory are programs that run on the
|
||||
Commodore VIC-20. The directory itself contains some simple demos,
|
||||
for example [hearts.60p](vic20/hearts.60p).
|
||||
|
||||
[Ophis]: http://michaelcmartin.github.io/Ophis/
|
5
eg/c64/README.md
Normal file
5
eg/c64/README.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
This directory contains SixtyPical example programs
|
||||
specifically for the Commodore 64.
|
||||
|
||||
See the [README in the parent directory](../README.md) for
|
||||
more information on these example programs.
|
|
@ -90,7 +90,7 @@ word delta
|
|||
vector logic_routine table[256] actor_logic
|
||||
vector logic_routine dispatch_logic
|
||||
|
||||
byte table[32] press_fire_msg: "PRESS`FIRE`TO`PLAY"
|
||||
byte table[18] press_fire_msg: "PRESS`FIRE`TO`PLAY"
|
||||
|
||||
//
|
||||
// Points to the routine that implements the current game state.
|
||||
|
@ -384,23 +384,14 @@ define enemy_logic logic_routine
|
|||
define game_state_title_screen game_state_routine
|
||||
{
|
||||
ld y, 0
|
||||
repeat {
|
||||
|
||||
// First we "clip" the index to 0-31 to ensure we don't
|
||||
// read outside the bounds of the table:
|
||||
ld a, y
|
||||
and a, 31
|
||||
ld y, a
|
||||
|
||||
for y up to 17 {
|
||||
ld a, press_fire_msg + y
|
||||
|
||||
st on, c
|
||||
sub a, 64 // yuck. oh well
|
||||
|
||||
st a, screen1 + y
|
||||
inc y
|
||||
cmp y, 18
|
||||
} until z
|
||||
}
|
||||
|
||||
st off, c
|
||||
call check_button
|
|
@ -12,16 +12,17 @@ byte vic_border @ 53280
|
|||
// a future version of SixtyPical.
|
||||
//
|
||||
|
||||
vector cinv
|
||||
vector routine
|
||||
inputs vic_border
|
||||
outputs vic_border
|
||||
trashes z, n
|
||||
@ 788
|
||||
cinv @ 788
|
||||
|
||||
vector save_cinv
|
||||
vector routine
|
||||
inputs vic_border
|
||||
outputs vic_border
|
||||
trashes z, n
|
||||
save_cinv
|
||||
|
||||
routine our_cinv
|
||||
inputs vic_border
|
||||
|
|
9
eg/c64/petulant/build.sh
Executable file
9
eg/c64/petulant/build.sh
Executable file
|
@ -0,0 +1,9 @@
|
|||
#!/bin/sh
|
||||
|
||||
sixtypical --output-format=prg --origin=0x02a7 petulant.60p > petulant-60p.prg
|
||||
if [ "x$COMPARE" != "x" ]; then
|
||||
dcc6502 petulant.prg > petulant.prg.disasm.txt
|
||||
dcc6502 petulant-60p.prg > petulant-60p.prg.disasm.txt
|
||||
paste petulant.prg.disasm.txt petulant-60p.prg.disasm.txt | pr -t -e24
|
||||
rm -f *.disasm.txt
|
||||
fi
|
BIN
eg/c64/petulant/petulant-60p.prg
Normal file
BIN
eg/c64/petulant/petulant-60p.prg
Normal file
Binary file not shown.
54
eg/c64/petulant/petulant.60p
Normal file
54
eg/c64/petulant/petulant.60p
Normal file
|
@ -0,0 +1,54 @@
|
|||
// petulant.60p - The PETulant Cursor, a "display hack" for the Commodore 64
|
||||
// Originally written by Chris Pressey sometime in the late 1980's
|
||||
// Rewritten in P65 assembly and released March 2008
|
||||
// Rewritten in SixtyPical in March 2018
|
||||
// This work is part of the public domain.
|
||||
|
||||
// ----- Types -----
|
||||
|
||||
typedef routine
|
||||
inputs border, blnon, color, background
|
||||
outputs border
|
||||
trashes a, z, n
|
||||
irq_handler
|
||||
|
||||
// ----- Addresses -----
|
||||
|
||||
vector irq_handler cinv @ $314 // hw irq interrupt, 60x per second
|
||||
|
||||
byte blnon @ $cf // was last cursor blink on or off?
|
||||
byte color @ $286 // current foreground colour for text
|
||||
|
||||
byte border @ $d020 // colour of border of the screen
|
||||
byte background @ $d021 // colour of background of the screen
|
||||
|
||||
// ----- Variables -----
|
||||
|
||||
vector irq_handler save_cinv
|
||||
|
||||
// ----- Interrupt Handler -----
|
||||
|
||||
define our_cinv irq_handler
|
||||
{
|
||||
ld a, blnon
|
||||
if not z {
|
||||
ld a, color
|
||||
} else {
|
||||
ld a, background
|
||||
}
|
||||
st a, border
|
||||
goto save_cinv
|
||||
}
|
||||
|
||||
// ----- Main Program -----
|
||||
|
||||
define main routine
|
||||
inputs cinv
|
||||
outputs cinv, save_cinv
|
||||
trashes a, n, z
|
||||
{
|
||||
with interrupts off {
|
||||
copy cinv, save_cinv
|
||||
copy our_cinv, cinv
|
||||
}
|
||||
}
|
52
eg/c64/petulant/petulant.p65
Normal file
52
eg/c64/petulant/petulant.p65
Normal file
|
@ -0,0 +1,52 @@
|
|||
; petulant.p65 - The PETulant Cursor, a "display hack" for the Commodore 64
|
||||
; Originally written by Chris Pressey sometime in the late 1980's
|
||||
; Rewritten in P65 assembly and released March 2008
|
||||
; This work is part of the public domain.
|
||||
|
||||
; ----- BEGIN petulant.p65 -----
|
||||
|
||||
; PRG file header
|
||||
|
||||
.org 0
|
||||
.word $02a7
|
||||
.org $02a7
|
||||
|
||||
; ----- Constants -----
|
||||
|
||||
.alias cinv $0314 ; hw irq interrupt, 60x per second
|
||||
.alias blnon $cf ; was last cursor blink on or off?
|
||||
.alias color $286 ; current foreground colour for text
|
||||
|
||||
.alias vic $d000 ; base address of VIC-II chip
|
||||
.alias border vic+$20 ; colour of border of the screen
|
||||
.alias background vic+$21 ; colour of background of the screen
|
||||
|
||||
; ----- Start of Program -----
|
||||
|
||||
start: sei ; disable interrupts
|
||||
lda cinv ; save low byte of existing handler
|
||||
sta savecinv
|
||||
lda cinv+1 ; save high byte
|
||||
sta savecinv+1
|
||||
|
||||
lda #<newcinv ; install new interrupt handler
|
||||
sta cinv
|
||||
lda #>newcinv
|
||||
sta cinv+1
|
||||
|
||||
cli ; re-enable interrupts
|
||||
rts
|
||||
|
||||
newcinv: lda blnon ; is the cursor on?
|
||||
beq cursor_off
|
||||
cursor_on: lda color ; yes, get its colour
|
||||
jmp egress
|
||||
cursor_off: lda background ; no, get the background colour
|
||||
egress: sta border ; colour the border
|
||||
jmp (savecinv) ; continue pre-existing interrupt handler
|
||||
|
||||
; ----- Uninitialized Data -----
|
||||
|
||||
.space savecinv 2
|
||||
|
||||
; ----- END of petulant.p65 -----
|
1
eg/c64/petulant/petulant.prg
Normal file
1
eg/c64/petulant/petulant.prg
Normal file
|
@ -0,0 +1 @@
|
|||
§x<03>Ó<03>Ô©À<C2A9>©<02>X`¥Ïð†LÍ!Ð<> ÐlÓ
|
32
eg/c64/petulant/readme.txt
Normal file
32
eg/c64/petulant/readme.txt
Normal file
|
@ -0,0 +1,32 @@
|
|||
The PETulant Cursor
|
||||
===================
|
||||
|
||||
This is a tiny (44 bytes long) machine-language demo for the Commodore 64,
|
||||
somewhat in the style of later "display hacks" for the Amiga -- surprising
|
||||
and silly ways to play with the user interface.
|
||||
|
||||
So as not to not spoil the fun, try running it before reading the source.
|
||||
|
||||
To run it, make the file PETULANT.PRG accessible to your favourite Commodore
|
||||
64 (or Commodore 64 emulator) in your favourite way, then
|
||||
|
||||
LOAD "PETULANT.PRG",8,1
|
||||
SYS 679
|
||||
|
||||
For further fun, try changing the text colour (hold the C= or CTRL key while
|
||||
pressing a number) or the background colour (POKE 53281 with a number from
|
||||
0 to 15) while it is running.
|
||||
|
||||
I must have originally wrote this sometime in the late 80's. I disassembled
|
||||
and rewrote it (to make it play more nicely with existing interrupt handlers)
|
||||
in 2008.
|
||||
|
||||
Who knows -- this could actually be useful, in a ridiculously minor way: it
|
||||
makes it much more obvious when control has returned to BASIC immediate mode
|
||||
(e.g., when a program has finished loading.)
|
||||
|
||||
Enjoy!
|
||||
|
||||
-Chris Pressey
|
||||
April 1, 2008
|
||||
Chicago, IL
|
9
eg/c64/ribos/build.sh
Executable file
9
eg/c64/ribos/build.sh
Executable file
|
@ -0,0 +1,9 @@
|
|||
#!/bin/sh
|
||||
|
||||
sixtypical --output-format=prg --origin=0xc000 ribos2.60p > ribos2-60p.prg
|
||||
if [ "x$COMPARE" != "x" ]; then
|
||||
dcc6502 ribos2.prg > ribos2.prg.disasm.txt
|
||||
dcc6502 ribos2-60p.prg > ribos2-60p.prg.disasm.txt
|
||||
paste ribos2.prg.disasm.txt ribos2-60p.prg.disasm.txt | pr -t -e24
|
||||
rm -f *.disasm.txt
|
||||
fi
|
79
eg/c64/ribos/readme.txt
Normal file
79
eg/c64/ribos/readme.txt
Normal file
|
@ -0,0 +1,79 @@
|
|||
Ribos
|
||||
=====
|
||||
|
||||
This little demo is intended to be a well-commented example of how to
|
||||
program a raster interrupt in 6502 assembly language on a Commodore 64.
|
||||
|
||||
This (r)aster (i)nterrupt changes the colour of a region of the
|
||||
(bo)rder of the C64 (s)creen; thus, RIBOS. Also, it's the name of a
|
||||
planet from Dr. Who, if that means anything.
|
||||
|
||||
|
||||
How to Run the Demo (using the VICE C64 emulator, x64)
|
||||
------------------------------------------------------
|
||||
|
||||
0. Obtain VICE from http://www.viceteam.org/, install it,
|
||||
and run x64
|
||||
|
||||
1. Mount this project's directory as drive 8:
|
||||
|
||||
Make sure
|
||||
Peripheral settings > Device #8 > Enable IEC Device
|
||||
is checked, then select
|
||||
Peripheral settings > Device #8 > File system directory...
|
||||
and enter the path to the project directory.
|
||||
|
||||
2. LOAD "RIBOS.PRG",8,1
|
||||
|
||||
3. SYS 49152
|
||||
|
||||
4. You should see the colour of the middle of the border change
|
||||
while you get a READY. prompt and can continue working.
|
||||
|
||||
|
||||
How to Assemble the Program (using the p65 assembler)
|
||||
-----------------------------------------------------
|
||||
|
||||
0. Obtain p65 from http://hkn.berkeley.edu/~mcmartin/P65/
|
||||
(I used p65-Perl version 1.1) and install it somewhere
|
||||
on your path. If your Perl interpreter isn't located at
|
||||
/usr/bin/perl, change the first line of p65 appropriately.
|
||||
|
||||
1. p65 -v -t -b ribos.p65 ribos.prg
|
||||
|
||||
The switches aren't necessary, but they make it feel like
|
||||
p65 is doing something difficult and important. It also
|
||||
isn't necessary to add the '.prg' extension on the end of
|
||||
the binary object's filename, since it will appear as a
|
||||
PRG file to VICE anyway, but it's nice as a reminder when
|
||||
you're working in a modern operating system.
|
||||
|
||||
2. Follow the steps under 'How to Run this Demo' to see that
|
||||
it worked.
|
||||
|
||||
|
||||
How it Works
|
||||
------------
|
||||
|
||||
Read the source! I've tried to make it very well-commented,
|
||||
including what happens when you leave out some steps.
|
||||
|
||||
I wrote this demo because it was a long time since I had done any C64
|
||||
programming, and, having just obtained a copy of the 'Commodore 64
|
||||
Programmer's Reference Guide,' I wanted to code something challenging,
|
||||
yet not too involved. I remembered raster interrupts as one of those
|
||||
quintessential C64 low-level graphics tricks, so I decided to try my
|
||||
hand at that. Looking around on the Internet, I found this page:
|
||||
|
||||
http://everything2.com/index.pl?node_id=79254
|
||||
|
||||
Although it's a fairly detailed description, it took me a couple of
|
||||
frustrating hours to implement it successfully - both the everything2
|
||||
article and the Reference Guide were pretty muddy on a couple of
|
||||
points. What I learned in the process is written into the comments.
|
||||
|
||||
Happy raster-interrupting!
|
||||
|
||||
-Chris Pressey
|
||||
April 10, 2007
|
||||
Vancouver, BC
|
291
eg/c64/ribos/ribos.p65
Normal file
291
eg/c64/ribos/ribos.p65
Normal file
|
@ -0,0 +1,291 @@
|
|||
; ribos.p65 - p65 assembly source for RIBOS:
|
||||
; Demonstration of the VIC-II raster interrupt on the Commodore 64:
|
||||
; Alter the border colour in the middle part of the screen only.
|
||||
; Original (hardware IRQ vector) version.
|
||||
; By Chris Pressey, Cat's Eye Technologies.
|
||||
; This work has been placed in the public domain.
|
||||
|
||||
; ----- BEGIN ribos.p65 -----
|
||||
|
||||
; This source file is intented to be assembled to produce a PRG file
|
||||
; which can be loaded into the C64's memory from a peripheral device.
|
||||
; All C64 PRG files start with a 16-bit word which represents the
|
||||
; location in memory to which they will be loaded. We can provide this
|
||||
; using p65 directives as follows:
|
||||
|
||||
.org 0
|
||||
.word $C000
|
||||
|
||||
; Now the actual assembly starts (at memory location 49152.)
|
||||
|
||||
.org $C000
|
||||
|
||||
; ----- Constants -----
|
||||
|
||||
; We first define some symbolic constants to add some clarity to our
|
||||
; references to hardware registers and other locations in memory.
|
||||
; Descriptions of the registers follow those given in the 'Commodore 64
|
||||
; Programmer's Reference Guide'.
|
||||
|
||||
; The CIA #1 chip.
|
||||
|
||||
.alias cia1 $dc00 ; pp. 328-331
|
||||
.alias intr_ctrl cia1+$d ; "CIA Interrupt Control Register
|
||||
; (Read IRQs/Write Mask)"
|
||||
|
||||
; The VIC-II chip.
|
||||
|
||||
.alias vic $d000 ; Appendix G:
|
||||
.alias vic_ctrl vic+$11 ; "Y SCROLL MODE"
|
||||
.alias vic_raster vic+$12 ; "RASTER"
|
||||
.alias vic_intr vic+$19 ; "Interrupt Request's" (sic)
|
||||
.alias vic_intr_enable vic+$1a ; "Interrupt Request MASKS"
|
||||
.alias border_color vic+$20 ; "BORDER COLOR"
|
||||
|
||||
; The address at which the IRQ vector is stored.
|
||||
|
||||
.alias irq_vec $fffe ; p. 411
|
||||
|
||||
; The zero-page address of the 6510's I/O register.
|
||||
|
||||
.alias io_ctrl $01 ; p. 310, "6510 On-Chip 8-Bit
|
||||
; Input/Output Register"
|
||||
|
||||
; KERNAL and BASIC ROMs, p. 320
|
||||
|
||||
.alias kernal $e000
|
||||
.alias basic $a000
|
||||
|
||||
; Zero-page addresses that the memory-copy routine uses for
|
||||
; scratch space: "FREKZP", p. 316
|
||||
|
||||
.alias zp $fb
|
||||
.alias stop_at $fd
|
||||
|
||||
; ----- Main Routine -----
|
||||
|
||||
; This routine is intended to be called by the user (by, e.g., SYS 49152).
|
||||
; It installs the raster interrupt handler and returns to the caller.
|
||||
|
||||
; Key to installing the interrupt handler is altering the IRQ service
|
||||
; vector. However, under normal circumstances, the address at which
|
||||
; this vector is stored ($ffffe) maps to the C64's KERNAL ROM, which
|
||||
; cannot be changed. So, in order to alter the vector, we must enable
|
||||
; the RAM that underlies the ROM (i.e. the RAM that maps to the same
|
||||
; address space as the KERNAL ROM.) If we were writing a bare-metal
|
||||
; game, and didn't need any KERNAL routines or support, we could just
|
||||
; switch it off. But for this demo, we'd like the raster effect to
|
||||
; occur in the background as we use BASIC and whatnot, so we need to
|
||||
; continue to have access to the KERNAL ROM. So what we do is copy the
|
||||
; KERNAL ROM to the underlying RAM, then switch the RAM for the ROM.
|
||||
|
||||
jsr copy_rom_to_ram
|
||||
|
||||
; Interrupts can occur at any time. If one were to occur while we were
|
||||
; changing the interrupt vector - for example, after we have stored the
|
||||
; low byte of the address but before we have stored the high byte -
|
||||
; unpredictable behaviour would result. To be safe, we disable interrupts
|
||||
; with the 'sei' instruction before changing anything.
|
||||
|
||||
sei
|
||||
|
||||
; We obtain the address of the current IRQ service routine and save it
|
||||
; in the variable 'saved_irq_vec'.
|
||||
|
||||
lda irq_vec ; save low byte
|
||||
sta saved_irq_vec
|
||||
lda irq_vec+1 ; save high byte
|
||||
sta saved_irq_vec+1
|
||||
|
||||
; We then store the address of our IRQ service routine in its place.
|
||||
|
||||
lda #<our_service_routine
|
||||
sta irq_vec
|
||||
lda #>our_service_routine
|
||||
sta irq_vec+1
|
||||
|
||||
; Now we must specify the raster line at which the interrupt gets called.
|
||||
|
||||
lda scanline
|
||||
sta vic_raster
|
||||
|
||||
; Note that the position of the raster line is given by a 9-bit value,
|
||||
; and we can't just assume that, because the raster line we want is less
|
||||
; than 256, that the high bit will automatically be set to zero, because
|
||||
; it won't. We have to explicitly set it high or low. It is found as
|
||||
; the most significant bit of a VIC-II control register which has many
|
||||
; different functions, so we must be careful to preserve all other bits.
|
||||
|
||||
lda vic_ctrl
|
||||
and #%01111111
|
||||
sta vic_ctrl
|
||||
|
||||
; Then we enable the raster interrupt on the VIC-II chip.
|
||||
|
||||
lda #$01
|
||||
sta vic_intr_enable
|
||||
|
||||
; The article at everything2 suggests that we read the interrupt control
|
||||
; port of the CIA #1 chip, presumably to acknowledge any pending IRQ and
|
||||
; avoid the problem of having some sort of lockup due to a spurious IRQ.
|
||||
; I've tested leaving this out, and the interrupt handler still seems get
|
||||
; installed alright. But, I haven't tested it very demandingly, and it's
|
||||
; likely to open up a race condition that I just haven't encountered (much
|
||||
; like if we were to forget to execute the 'sei' instruction, above.)
|
||||
; So, to play it safe, we read the port here.
|
||||
|
||||
lda intr_ctrl
|
||||
|
||||
; We re-enable interrupts to resume normal operation - normal, that is,
|
||||
; except that our raster interrupt service routine will be called the next
|
||||
; time the raster reaches the line stored in the 'vic_raster' register of
|
||||
; the VIC-II chip.
|
||||
|
||||
cli
|
||||
|
||||
; Finally, we return to the caller.
|
||||
|
||||
rts
|
||||
|
||||
; ----- Raster Interrupt Service Routine ------
|
||||
|
||||
our_service_routine:
|
||||
|
||||
; This is an interrupt service routine (a.k.a. interrupt handler,) and as
|
||||
; such, it can be called from anywhere. Since the code that was interrupted
|
||||
; likely cares deeply about the values in its registers, we must be careful
|
||||
; to save any that we change, and restore them before switching back to it.
|
||||
; In this case, we only affect the processor flags and the accumulator, so
|
||||
; we push them onto the stack.
|
||||
|
||||
php
|
||||
pha
|
||||
|
||||
; The interrupt service routine on the Commodore 64 is very general-purpose,
|
||||
; and may be invoked by any number of different kinds of interrupts. We,
|
||||
; however, only care about a certain kind - the VIC-II's raster interrupt.
|
||||
; We check to see if the current interrupt was caused by the raster by
|
||||
; looking at the low bit of the VIC-II interrupt register. Note that we
|
||||
; immediately store back the value found there before testing it. This is
|
||||
; to acknowledge to the VIC-II chip that we got the interrupt. If we don't
|
||||
; do this, it won't send us another interrupt next time.
|
||||
|
||||
lda vic_intr
|
||||
sta vic_intr
|
||||
and #$01
|
||||
cmp #$01
|
||||
beq we_handle_it
|
||||
|
||||
; If the interrupt was not caused by the raster, we restore the values
|
||||
; of the registers from the stack, and continue execution as normal with
|
||||
; the existing interrupt service routine.
|
||||
|
||||
pla
|
||||
plp
|
||||
jmp (saved_irq_vec)
|
||||
|
||||
we_handle_it:
|
||||
|
||||
; If we got here, the interrupt _was_ caused by the raster. So, we get
|
||||
; to do our thing. To keep things simple, we just invert the border colour.
|
||||
|
||||
lda border_color
|
||||
eor #$ff
|
||||
sta border_color
|
||||
|
||||
; Now, we make the interrupt trigger on a different scan line so that we'll
|
||||
; invert the colour back to normal lower down on the screen.
|
||||
|
||||
lda scanline
|
||||
eor #$ff
|
||||
sta scanline
|
||||
sta vic_raster
|
||||
|
||||
; Restore the registers that we saved at the beginning of this routine.
|
||||
|
||||
pla
|
||||
plp
|
||||
|
||||
; Return to normal operation. Note that we must issue an 'rti' instruction
|
||||
; here, not 'rts', as we are returning from an interrupt.
|
||||
|
||||
rti
|
||||
|
||||
|
||||
; ----- Utility Routine: copy KERNAL ROM to underlying RAM -----
|
||||
|
||||
copy_rom_to_ram:
|
||||
|
||||
; This is somewhat more involved than I let on above. The memory mapping
|
||||
; facilities of the C64 are a bit convoluted. The Programmer's Reference
|
||||
; Guide states on page 261 that the way to map out the KERNAL ROM, and
|
||||
; map in the RAM underlying it, is to set the HIRAM signal on the 6510's
|
||||
; I/O line (which is memory-mapped to address $0001) to 0. This is true.
|
||||
; However, it is not the whole story: setting HIRAM to 0 *also* maps out
|
||||
; BASIC ROM and maps in the RAM underlying *it*. I suppose this makes
|
||||
; sense from a design point of view; after all, BASIC uses the KERNAL, so
|
||||
; there wouldn't be much sense leaving it mapped when the KERNAL is mapped
|
||||
; out. Anyway, what this means for us is that we must copy both of these
|
||||
; ROMs to their respective underlying RAMs if we want to survive returning
|
||||
; to BASIC.
|
||||
|
||||
ldx #>basic
|
||||
ldy #$c0
|
||||
jsr copy_block
|
||||
|
||||
ldx #>kernal
|
||||
ldy #$00
|
||||
jsr copy_block
|
||||
|
||||
; To actually substitute the RAM for the ROM in the memory map, we
|
||||
; set HIRAM (the second least significant bit) to 0.
|
||||
|
||||
lda io_ctrl
|
||||
and #%11111101
|
||||
sta io_ctrl
|
||||
|
||||
rts
|
||||
|
||||
|
||||
; ----- Utility Routine: copy a ROM memory block to the underlying RAM -----
|
||||
|
||||
; Input: x register = high byte of start address (low byte = #$00)
|
||||
; y register = high byte of end address (stops at address $yy00 - 1)
|
||||
|
||||
; This subroutine is a fairly straightforward memory copy loop. A somewhat
|
||||
; counter-intuitive feature is that we immediately store each byte in the
|
||||
; same location where we just read it from. We can do this because, even
|
||||
; when the KERNAL or BASIC ROM is mapped in, writes to those locations still
|
||||
; go to the underlying RAM.
|
||||
|
||||
copy_block: stx zp+1
|
||||
sty stop_at
|
||||
ldy #$00
|
||||
sty zp
|
||||
|
||||
copy_loop: lda (zp), y
|
||||
sta (zp), y
|
||||
iny
|
||||
cpy #$00
|
||||
bne copy_loop
|
||||
ldx zp+1
|
||||
inx
|
||||
stx zp+1
|
||||
cpx stop_at
|
||||
bne copy_loop
|
||||
rts
|
||||
|
||||
; ----- Variables -----
|
||||
|
||||
; 'scanline' stores the raster line that we want the interrupt to trigger
|
||||
; on; it gets loaded into the VIC-II's 'vic_raster' register.
|
||||
|
||||
scanline: .byte %01010101
|
||||
|
||||
; We also reserve space to store the address of the interrupt service
|
||||
; routine that we are replacing in the IRQ vector, so that we can transfer
|
||||
; control to it at the end of our routine.
|
||||
|
||||
.space saved_irq_vec 2
|
||||
|
||||
; ----- END of ribos.p65 -----
|
BIN
eg/c64/ribos/ribos.png
Normal file
BIN
eg/c64/ribos/ribos.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.7 KiB |
BIN
eg/c64/ribos/ribos.prg
Normal file
BIN
eg/c64/ribos/ribos.prg
Normal file
Binary file not shown.
BIN
eg/c64/ribos/ribos2-60p.prg
Normal file
BIN
eg/c64/ribos/ribos2-60p.prg
Normal file
Binary file not shown.
103
eg/c64/ribos/ribos2.60p
Normal file
103
eg/c64/ribos/ribos2.60p
Normal file
|
@ -0,0 +1,103 @@
|
|||
// ribos2.60p - SixtyPical source for RIBOS2:
|
||||
// Demonstration of the VIC-II raster interrupt on the Commodore 64:
|
||||
// Alter the border colour in the middle part of the screen only,
|
||||
// Simplified (KERNAL IRQ vector) version.
|
||||
// By Chris Pressey, Cat's Eye Technologies.
|
||||
// This work has been placed in the public domain.
|
||||
|
||||
// For comments, see ribos2.p65.
|
||||
|
||||
// ----- Types -----
|
||||
|
||||
typedef routine
|
||||
inputs border_color, vic_intr, scanline
|
||||
outputs border_color, vic_intr, scanline
|
||||
trashes a, z, n, c, vic_raster
|
||||
irq_handler
|
||||
|
||||
// ----- Addresses -----
|
||||
|
||||
// The CIA #1 chip.
|
||||
|
||||
byte cia1 @ $dc00 // pp. 328-331
|
||||
byte intr_ctrl @ $dc0d // "CIA Interrupt Control Register
|
||||
// (Read IRQs/Write Mask)"
|
||||
|
||||
// The VIC-II chip.
|
||||
|
||||
byte vic @ $d000 // Appendix G:
|
||||
byte vic_ctrl @ $d011 // "Y SCROLL MODE"
|
||||
byte vic_raster @ $d012 // "RASTER"
|
||||
byte vic_intr @ $d019 // "Interrupt Request's" (sic)
|
||||
byte vic_intr_enable @ $d01a // "Interrupt Request MASKS"
|
||||
byte border_color @ $d020 // "BORDER COLOR"
|
||||
|
||||
// The address at which the IRQ vector is stored.
|
||||
|
||||
vector irq_handler cinv @ $314 // p. 319, "Vector: Hardware
|
||||
// IRQ Interrupt"
|
||||
|
||||
// ----- Variables -----
|
||||
|
||||
vector irq_handler saved_irq_vec
|
||||
byte scanline : 85 // %01010101
|
||||
|
||||
// ----- Externals ------
|
||||
|
||||
// An address in ROM which contains "PLA TAY PLA TAX RTI".
|
||||
// ribos2.p65 just contains these instructions itself, but
|
||||
// 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
|
||||
inputs a
|
||||
trashes a
|
||||
@ $EA81
|
||||
|
||||
// ----- Interrupt Handler -----
|
||||
|
||||
define our_service_routine irq_handler
|
||||
{
|
||||
ld a, vic_intr
|
||||
st a, vic_intr
|
||||
and a, 1
|
||||
cmp a, 1
|
||||
if not z {
|
||||
goto saved_irq_vec
|
||||
} else {
|
||||
ld a, border_color
|
||||
xor a, $ff
|
||||
st a, border_color
|
||||
|
||||
ld a, scanline
|
||||
xor a, $ff
|
||||
st a, scanline
|
||||
st a, vic_raster
|
||||
|
||||
trash vic_raster
|
||||
|
||||
goto pla_tay_pla_tax_pla_rti
|
||||
}
|
||||
}
|
||||
|
||||
// ----- Main Program -----
|
||||
|
||||
define main routine
|
||||
inputs cinv, scanline, vic_ctrl, intr_ctrl
|
||||
outputs cinv, saved_irq_vec, vic_ctrl, vic_intr_enable
|
||||
trashes a, n, z, vic_raster
|
||||
{
|
||||
with interrupts off {
|
||||
copy cinv, saved_irq_vec
|
||||
copy our_service_routine, cinv
|
||||
|
||||
ld a, scanline
|
||||
st a, vic_raster
|
||||
ld a, vic_ctrl
|
||||
and a, 127
|
||||
st a, vic_ctrl
|
||||
ld a, 1
|
||||
st a, vic_intr_enable
|
||||
ld a, intr_ctrl
|
||||
}
|
||||
}
|
179
eg/c64/ribos/ribos2.p65
Normal file
179
eg/c64/ribos/ribos2.p65
Normal file
|
@ -0,0 +1,179 @@
|
|||
; ribos2.p65 - p65 assembly source for RIBOS2:
|
||||
; Demonstration of the VIC-II raster interrupt on the Commodore 64:
|
||||
; Alter the border colour in the middle part of the screen only,
|
||||
; Simplified (KERNAL IRQ vector) version.
|
||||
; By Chris Pressey, Cat's Eye Technologies.
|
||||
; This work has been placed in the public domain.
|
||||
|
||||
; ----- BEGIN ribos2.p65 -----
|
||||
|
||||
; This is a simplified version of ribos.p65 which uses the Commodore 64
|
||||
; KERNAL's support for interrupt handling. Whereas ribos.p65 could run
|
||||
; with the KERNAL completely disabled, ribos2.p65 relies on it.
|
||||
|
||||
; I'll assume you've read through ribos.p65 at least once, and won't
|
||||
; say much about the code that's shared between the two programs.
|
||||
; Again, page references are from the 'Commodore 64 Programmer's
|
||||
; Reference Guide'.
|
||||
|
||||
.org 0
|
||||
.word $C000
|
||||
.org $C000
|
||||
|
||||
; ----- Constants -----
|
||||
|
||||
; The CIA #1 chip.
|
||||
|
||||
.alias cia1 $dc00 ; pp. 328-331
|
||||
.alias intr_ctrl cia1+$d ; "CIA Interrupt Control Register
|
||||
; (Read IRQs/Write Mask)"
|
||||
|
||||
; The VIC-II chip.
|
||||
|
||||
.alias vic $d000 ; Appendix G:
|
||||
.alias vic_ctrl vic+$11 ; "Y SCROLL MODE"
|
||||
.alias vic_raster vic+$12 ; "RASTER"
|
||||
.alias vic_intr vic+$19 ; "Interrupt Request's" (sic)
|
||||
.alias vic_intr_enable vic+$1a ; "Interrupt Request MASKS"
|
||||
.alias border_color vic+$20 ; "BORDER COLOR"
|
||||
|
||||
; The address at which the IRQ vector is stored.
|
||||
|
||||
.alias cinv $0314 ; p. 319, "Vector: Hardware
|
||||
; IRQ Interrupt"
|
||||
|
||||
; ----- Main Routine -----
|
||||
|
||||
; This routine is intended to be called by the user (by, e.g., SYS 49152).
|
||||
; It installs the raster interrupt handler and returns to the caller.
|
||||
|
||||
; Key to installing the interrupt handler is altering the IRQ service
|
||||
; vector. As I learned from a careful reading of Sheldon Leemon's
|
||||
; "Mapping the Commodore 64", if we wish to leave the KERNAL intact and
|
||||
; operational, it gives us an easy way to do that. Whenever it handles
|
||||
; an IRQ, it has to decide whether the IRQ was generated by a BRK
|
||||
; instruction, or some external device issuing an interrupt request. In
|
||||
; either case, it jumps indirectly through its IRQ-handling vector. We
|
||||
; can simply modify this vector (at $314) to point to our routine. In
|
||||
; fact, this is the "CINV" vector, which may already be familiar to you
|
||||
; if you have done any modest amount of C64 machine language programming;
|
||||
; it is normally called every 1/60 of a second due to the interrupt
|
||||
; request generated by CIA#1 Timer B. Here we will simply be using it
|
||||
; to detect when a raster interrupt has occurred, as well.
|
||||
|
||||
; First, disable interrupts before changing interrupt vectors.
|
||||
|
||||
sei
|
||||
|
||||
; We obtain the address of the current IRQ service routine in CINV
|
||||
; and save it in the variable 'saved_irq_vec'.
|
||||
|
||||
lda cinv
|
||||
sta saved_irq_vec
|
||||
lda cinv+1
|
||||
sta saved_irq_vec+1
|
||||
|
||||
; We then store the address of our IRQ service routine in its place.
|
||||
|
||||
lda #<our_service_routine
|
||||
sta cinv
|
||||
lda #>our_service_routine
|
||||
sta cinv+1
|
||||
|
||||
; Now we must specify the raster line at which the interrupt gets called,
|
||||
; setting the ninth bit to zero.
|
||||
|
||||
lda scanline
|
||||
sta vic_raster
|
||||
lda vic_ctrl
|
||||
and #%01111111
|
||||
sta vic_ctrl
|
||||
|
||||
; Then we enable the raster interrupt on the VIC-II chip.
|
||||
|
||||
lda #$01
|
||||
sta vic_intr_enable
|
||||
|
||||
; We read the interrupt control port of CIA #1 for good measure.
|
||||
|
||||
lda intr_ctrl
|
||||
|
||||
; And we re-enable interrupts to resume normal operation -- plus our jazz.
|
||||
|
||||
cli
|
||||
|
||||
; Finally, we return to the caller.
|
||||
|
||||
rts
|
||||
|
||||
; ----- Raster Interrupt Service Routine ------
|
||||
|
||||
our_service_routine:
|
||||
|
||||
; First, we check to see if the current interrupt was caused by the raster.
|
||||
|
||||
lda vic_intr
|
||||
sta vic_intr
|
||||
and #$01
|
||||
cmp #$01
|
||||
beq we_handle_it
|
||||
|
||||
; If the interrupt was not caused by the raster, we continue execution as
|
||||
; normal, with the existing interrupt service routine.
|
||||
|
||||
jmp (saved_irq_vec)
|
||||
|
||||
we_handle_it:
|
||||
|
||||
; If we got here, the interrupt was caused by the raster, so we do our thing.
|
||||
|
||||
lda border_color
|
||||
eor #$ff
|
||||
sta border_color
|
||||
|
||||
; Now, we make the interrupt trigger on the other scan line next time.
|
||||
|
||||
lda scanline
|
||||
eor #$ff
|
||||
sta scanline
|
||||
sta vic_raster
|
||||
|
||||
; Return to normal operation. If we simply continue with the standard
|
||||
; interrupt service routine by jumping through saved_irq_req, we will
|
||||
; confuse it a bit, as it expects to be called 60 times per second, and
|
||||
; continuing it here would make it occur more frequently. The result
|
||||
; would be the cursor blinking more frequently and the time of day clock
|
||||
; running fast.
|
||||
|
||||
; So, we issue a plain return from interrupt (RTI) here. But first, we
|
||||
; must make sure that the state of the system is back to the way it was
|
||||
; when the interrupt service routine was called. Since it can be called
|
||||
; from anywhere, and since the code that was interrupted likely cares
|
||||
; deeply about the values in its registers, the KERNAL routine which
|
||||
; dispatches through CINV first carefully saves the contents of the
|
||||
; registers on the stack. We must be equally careful about restoring
|
||||
; those values before switching back to that interrupted code.
|
||||
|
||||
pla
|
||||
tay
|
||||
pla
|
||||
tax
|
||||
pla
|
||||
|
||||
rti
|
||||
|
||||
|
||||
; ----- Variables -----
|
||||
|
||||
; 'scanline' stores the raster line that we want the interrupt to trigger
|
||||
; on; it gets loaded into the VIC-II's 'vic_raster' register.
|
||||
|
||||
scanline: .byte %01010101
|
||||
|
||||
; We also reserve space to store the address of the interrupt service
|
||||
; routine that we are replacing in the IRQ vector, so that we can transfer
|
||||
; control to it at the end of our routine.
|
||||
|
||||
.space saved_irq_vec 2
|
||||
|
||||
; ----- END of ribos2.p65 -----
|
BIN
eg/c64/ribos/ribos2.prg
Normal file
BIN
eg/c64/ribos/ribos2.prg
Normal file
Binary file not shown.
16
eg/vic20/hearts.60p
Normal file
16
eg/vic20/hearts.60p
Normal file
|
@ -0,0 +1,16 @@
|
|||
// Displays 256 hearts at the top of the VIC-20's screen.
|
||||
|
||||
// Define where the screen starts in memory:
|
||||
byte table[256] screen @ 7680
|
||||
|
||||
routine main
|
||||
// These are the values that will be written to by this routine:
|
||||
trashes a, x, z, n, screen
|
||||
{
|
||||
ld x, 0
|
||||
ld a, 83 // 83 = screen code for heart
|
||||
repeat {
|
||||
st a, screen + x
|
||||
inc x
|
||||
} until z // this flag will be set when x wraps around from 255 to 0
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
# encoding: UTF-8
|
||||
|
||||
from sixtypical.ast import Program, Routine, Block, Instr, SingleOp, If, Repeat, WithInterruptsOff
|
||||
from sixtypical.ast import Program, Routine, Block, Instr, SingleOp, If, Repeat, For, WithInterruptsOff
|
||||
from sixtypical.model import (
|
||||
TYPE_BYTE, TYPE_WORD,
|
||||
TableType, BufferType, PointerType, VectorType, RoutineType,
|
||||
|
@ -92,7 +92,8 @@ class Context(object):
|
|||
the range of a word is 0..65535.
|
||||
|
||||
A location is writeable if it was listed in the outputs and trashes
|
||||
lists of this routine.
|
||||
lists of this routine. A location can also be temporarily marked
|
||||
unwriteable in certain contexts, such as `for` loops.
|
||||
"""
|
||||
def __init__(self, routines, routine, inputs, outputs, trashes):
|
||||
self.routines = routines # Location -> AST node
|
||||
|
@ -100,6 +101,7 @@ class Context(object):
|
|||
self._touched = set()
|
||||
self._range = dict()
|
||||
self._writeable = set()
|
||||
self._has_encountered_goto = False
|
||||
|
||||
for ref in inputs:
|
||||
if ref.is_constant():
|
||||
|
@ -206,6 +208,15 @@ class Context(object):
|
|||
(bottom, _) = self._range[ref]
|
||||
self._range[ref] = (bottom, top)
|
||||
|
||||
def set_bottom_of_range(self, ref, bottom):
|
||||
self.assert_meaningful(ref)
|
||||
(top, _) = self._range[ref]
|
||||
self._range[ref] = (bottom, top)
|
||||
|
||||
def set_range(self, ref, bottom, top):
|
||||
self.assert_meaningful(ref)
|
||||
self._range[ref] = (bottom, top)
|
||||
|
||||
def get_top_of_range(self, ref):
|
||||
if isinstance(ref, ConstantRef):
|
||||
return ref.value
|
||||
|
@ -213,6 +224,20 @@ class Context(object):
|
|||
(_, top) = self._range[ref]
|
||||
return top
|
||||
|
||||
def get_bottom_of_range(self, ref):
|
||||
if isinstance(ref, ConstantRef):
|
||||
return ref.value
|
||||
self.assert_meaningful(ref)
|
||||
(bottom, _) = self._range[ref]
|
||||
return bottom
|
||||
|
||||
def get_range(self, ref):
|
||||
if isinstance(ref, ConstantRef):
|
||||
return (ref.value, ref.value)
|
||||
self.assert_meaningful(ref)
|
||||
(bottom, top) = self._range[ref]
|
||||
return bottom, top
|
||||
|
||||
def copy_range(self, src, dest):
|
||||
self.assert_meaningful(src)
|
||||
if src in self._range:
|
||||
|
@ -238,12 +263,27 @@ class Context(object):
|
|||
self.set_touched(*refs)
|
||||
self.set_meaningful(*refs)
|
||||
|
||||
def set_unwriteable(self, *refs):
|
||||
"""Intended to be used for implementing analyzing `for`."""
|
||||
for ref in refs:
|
||||
self._writeable.remove(ref)
|
||||
|
||||
def set_writeable(self, *refs):
|
||||
"""Intended to be used for implementing analyzing `for`."""
|
||||
for ref in refs:
|
||||
self._writeable.add(ref)
|
||||
|
||||
def set_encountered_goto(self):
|
||||
self._has_encountered_goto = True
|
||||
|
||||
def has_encountered_goto(self):
|
||||
return self._has_encountered_goto
|
||||
|
||||
|
||||
class Analyzer(object):
|
||||
|
||||
def __init__(self, debug=False):
|
||||
self.current_routine = None
|
||||
self.has_encountered_goto = False
|
||||
self.routines = {}
|
||||
self.debug = debug
|
||||
|
||||
|
@ -276,7 +316,6 @@ class Analyzer(object):
|
|||
def analyze_routine(self, routine):
|
||||
assert isinstance(routine, Routine)
|
||||
self.current_routine = routine
|
||||
self.has_encountered_goto = False
|
||||
if routine.block is None:
|
||||
# it's an extern, that's fine
|
||||
return
|
||||
|
@ -307,7 +346,7 @@ class Analyzer(object):
|
|||
if ref in type_.outputs:
|
||||
raise UnmeaningfulOutputError(routine, ref.name)
|
||||
|
||||
if not self.has_encountered_goto:
|
||||
if not context.has_encountered_goto():
|
||||
for ref in type_.outputs:
|
||||
context.assert_meaningful(ref, exception_class=UnmeaningfulOutputError)
|
||||
for ref in context.each_touched():
|
||||
|
@ -318,8 +357,6 @@ class Analyzer(object):
|
|||
def analyze_block(self, block, context):
|
||||
assert isinstance(block, Block)
|
||||
for i in block.instrs:
|
||||
if self.has_encountered_goto:
|
||||
raise IllegalJumpError(i, i)
|
||||
self.analyze_instr(i, context)
|
||||
|
||||
def analyze_instr(self, instr, context):
|
||||
|
@ -329,6 +366,8 @@ class Analyzer(object):
|
|||
self.analyze_if(instr, context)
|
||||
elif isinstance(instr, Repeat):
|
||||
self.analyze_repeat(instr, context)
|
||||
elif isinstance(instr, For):
|
||||
self.analyze_for(instr, context)
|
||||
elif isinstance(instr, WithInterruptsOff):
|
||||
self.analyze_block(instr.block, context)
|
||||
else:
|
||||
|
@ -339,7 +378,10 @@ class Analyzer(object):
|
|||
opcode = instr.opcode
|
||||
dest = instr.dest
|
||||
src = instr.src
|
||||
|
||||
|
||||
if context.has_encountered_goto():
|
||||
raise IllegalJumpError(instr, instr)
|
||||
|
||||
if opcode == 'ld':
|
||||
if isinstance(src, IndexedRef):
|
||||
if TableType.is_a_table_type(src.ref.type, TYPE_BYTE) and dest.type == TYPE_BYTE:
|
||||
|
@ -553,7 +595,7 @@ class Analyzer(object):
|
|||
self.assert_affected_within('outputs', type_, current_type)
|
||||
self.assert_affected_within('trashes', type_, current_type)
|
||||
|
||||
self.has_encountered_goto = True
|
||||
context.set_encountered_goto()
|
||||
elif opcode == 'trash':
|
||||
context.set_touched(instr.dest)
|
||||
context.set_unmeaningful(instr.dest)
|
||||
|
@ -594,6 +636,8 @@ class Analyzer(object):
|
|||
context._touched = set(context1._touched) | set(context2._touched)
|
||||
context.set_meaningful(*list(outgoing_meaningful))
|
||||
context._writeable = set(context1._writeable) | set(context2._writeable)
|
||||
if context1.has_encountered_goto() or context2.has_encountered_goto():
|
||||
context.set_encountered_goto()
|
||||
|
||||
for ref in outgoing_trashes:
|
||||
context.set_touched(ref)
|
||||
|
@ -611,3 +655,40 @@ class Analyzer(object):
|
|||
self.analyze_block(instr.block, context)
|
||||
if instr.src is not None:
|
||||
context.assert_meaningful(instr.src)
|
||||
|
||||
def analyze_for(self, instr, context):
|
||||
context.assert_meaningful(instr.dest)
|
||||
context.assert_writeable(instr.dest)
|
||||
|
||||
bottom, top = context.get_range(instr.dest)
|
||||
final = instr.final.value
|
||||
|
||||
if instr.direction > 0:
|
||||
if top >= final:
|
||||
raise RangeExceededError(instr, "Top of range of {} is {} but must be lower than {}".format(
|
||||
instr.dest, top, final
|
||||
))
|
||||
top = final
|
||||
|
||||
if instr.direction < 0:
|
||||
if bottom <= final:
|
||||
raise RangeExceededError(instr, "Bottom of range of {} is {} but must be higher than {}".format(
|
||||
instr.dest, bottom, final
|
||||
))
|
||||
bottom = final
|
||||
|
||||
# inside the block, the loop variable cannot be modified, and we know its range.
|
||||
context.set_range(instr.dest, bottom, top)
|
||||
context.set_unwriteable(instr.dest)
|
||||
|
||||
# it will always be executed at least once, so analyze it having
|
||||
# been executed the first time.
|
||||
self.analyze_block(instr.block, context)
|
||||
|
||||
# now analyze it having been executed a second time, with the context
|
||||
# of it having already been executed.
|
||||
self.analyze_block(instr.block, context)
|
||||
|
||||
# after it is executed, we know the range of the loop variable.
|
||||
context.set_range(instr.dest, instr.final, instr.final)
|
||||
context.set_writeable(instr.dest)
|
||||
|
|
|
@ -74,12 +74,17 @@ class SingleOp(Instr):
|
|||
|
||||
|
||||
class If(Instr):
|
||||
value_attrs = ('src', 'inverted')
|
||||
value_attrs = ('src', 'inverted',)
|
||||
child_attrs = ('block1', 'block2',)
|
||||
|
||||
|
||||
class Repeat(Instr):
|
||||
value_attrs = ('src', 'inverted')
|
||||
value_attrs = ('src', 'inverted',)
|
||||
child_attrs = ('block',)
|
||||
|
||||
|
||||
class For(Instr):
|
||||
value_attrs = ('dest', 'direction', 'final',)
|
||||
child_attrs = ('block',)
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# encoding: UTF-8
|
||||
|
||||
from sixtypical.ast import Program, Routine, Block, Instr, SingleOp, If, Repeat, WithInterruptsOff
|
||||
from sixtypical.ast import Program, Routine, Block, Instr, SingleOp, If, Repeat, For, WithInterruptsOff
|
||||
from sixtypical.model import (
|
||||
ConstantRef, LocationRef, IndexedRef, IndirectRef, AddressRef,
|
||||
TYPE_BIT, TYPE_BYTE, TYPE_WORD,
|
||||
|
@ -148,6 +148,8 @@ class Compiler(object):
|
|||
return self.compile_if(instr)
|
||||
elif isinstance(instr, Repeat):
|
||||
return self.compile_repeat(instr)
|
||||
elif isinstance(instr, For):
|
||||
return self.compile_for(instr)
|
||||
elif isinstance(instr, WithInterruptsOff):
|
||||
return self.compile_with_interrupts_off(instr)
|
||||
else:
|
||||
|
@ -300,31 +302,11 @@ class Compiler(object):
|
|||
else:
|
||||
raise UnsupportedOpcodeError(instr)
|
||||
elif opcode == 'inc':
|
||||
if dest == REG_X:
|
||||
self.emitter.emit(INX())
|
||||
elif dest == REG_Y:
|
||||
self.emitter.emit(INY())
|
||||
else:
|
||||
self.emitter.emit(INC(Absolute(self.get_label(dest.name))))
|
||||
self.compile_inc(instr, instr.dest)
|
||||
elif opcode == 'dec':
|
||||
if dest == REG_X:
|
||||
self.emitter.emit(DEX())
|
||||
elif dest == REG_Y:
|
||||
self.emitter.emit(DEY())
|
||||
else:
|
||||
self.emitter.emit(DEC(Absolute(self.get_label(dest.name))))
|
||||
self.compile_dec(instr, instr.dest)
|
||||
elif opcode == 'cmp':
|
||||
cls = {
|
||||
'a': CMP,
|
||||
'x': CPX,
|
||||
'y': CPY,
|
||||
}.get(dest.name)
|
||||
if cls is None:
|
||||
raise UnsupportedOpcodeError(instr)
|
||||
if isinstance(src, ConstantRef):
|
||||
self.emitter.emit(cls(Immediate(Byte(src.value))))
|
||||
else:
|
||||
self.emitter.emit(cls(Absolute(self.get_label(src.name))))
|
||||
self.compile_cmp(instr, instr.src, instr.dest)
|
||||
elif opcode in ('and', 'or', 'xor',):
|
||||
cls = {
|
||||
'and': AND,
|
||||
|
@ -369,18 +351,45 @@ class Compiler(object):
|
|||
else:
|
||||
raise NotImplementedError
|
||||
elif opcode == 'copy':
|
||||
self.compile_copy_op(instr)
|
||||
self.compile_copy(instr, instr.src, instr.dest)
|
||||
elif opcode == 'trash':
|
||||
pass
|
||||
else:
|
||||
raise NotImplementedError(opcode)
|
||||
|
||||
def compile_copy_op(self, instr):
|
||||
def compile_cmp(self, instr, src, dest):
|
||||
"""`instr` is only for reporting purposes"""
|
||||
cls = {
|
||||
'a': CMP,
|
||||
'x': CPX,
|
||||
'y': CPY,
|
||||
}.get(dest.name)
|
||||
if cls is None:
|
||||
raise UnsupportedOpcodeError(instr)
|
||||
if isinstance(src, ConstantRef):
|
||||
self.emitter.emit(cls(Immediate(Byte(src.value))))
|
||||
else:
|
||||
self.emitter.emit(cls(Absolute(self.get_label(src.name))))
|
||||
|
||||
opcode = instr.opcode
|
||||
dest = instr.dest
|
||||
src = instr.src
|
||||
def compile_inc(self, instr, dest):
|
||||
"""`instr` is only for reporting purposes"""
|
||||
if dest == REG_X:
|
||||
self.emitter.emit(INX())
|
||||
elif dest == REG_Y:
|
||||
self.emitter.emit(INY())
|
||||
else:
|
||||
self.emitter.emit(INC(Absolute(self.get_label(dest.name))))
|
||||
|
||||
def compile_dec(self, instr, dest):
|
||||
"""`instr` is only for reporting purposes"""
|
||||
if dest == REG_X:
|
||||
self.emitter.emit(DEX())
|
||||
elif dest == REG_Y:
|
||||
self.emitter.emit(DEY())
|
||||
else:
|
||||
self.emitter.emit(DEC(Absolute(self.get_label(dest.name))))
|
||||
|
||||
def compile_copy(self, instr, src, dest):
|
||||
if isinstance(src, ConstantRef) and isinstance(dest, IndirectRef) and src.type == TYPE_BYTE and isinstance(dest.ref.type, PointerType):
|
||||
### copy 123, [ptr] + y
|
||||
dest_label = self.get_label(dest.ref.name)
|
||||
|
@ -540,6 +549,20 @@ class Compiler(object):
|
|||
raise UnsupportedOpcodeError(instr)
|
||||
self.emitter.emit(cls(Relative(top_label)))
|
||||
|
||||
def compile_for(self, instr):
|
||||
top_label = self.emitter.make_label()
|
||||
|
||||
self.compile_block(instr.block)
|
||||
|
||||
if instr.direction > 0:
|
||||
self.compile_inc(instr, instr.dest)
|
||||
final = instr.final.succ()
|
||||
elif instr.direction < 0:
|
||||
self.compile_dec(instr, instr.dest)
|
||||
final = instr.final.pred()
|
||||
self.compile_cmp(instr, final, instr.dest)
|
||||
self.emitter.emit(BNE(Relative(top_label)))
|
||||
|
||||
def compile_with_interrupts_off(self, instr):
|
||||
self.emitter.emit(SEI())
|
||||
self.compile_block(instr.block)
|
||||
|
|
|
@ -296,6 +296,20 @@ class ConstantRef(Ref):
|
|||
def low_byte(self):
|
||||
return self.value & 255
|
||||
|
||||
def pred(self):
|
||||
assert self.type == TYPE_BYTE
|
||||
value = self.value - 1
|
||||
while value < 0:
|
||||
value += 256
|
||||
return ConstantRef(self.type, value)
|
||||
|
||||
def succ(self):
|
||||
assert self.type == TYPE_BYTE
|
||||
value = self.value + 1
|
||||
while value > 255:
|
||||
value -= 256
|
||||
return ConstantRef(self.type, value)
|
||||
|
||||
|
||||
REG_A = LocationRef(TYPE_BYTE, 'a')
|
||||
REG_X = LocationRef(TYPE_BYTE, 'x')
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# encoding: UTF-8
|
||||
|
||||
from sixtypical.ast import Program, Defn, Routine, Block, SingleOp, If, Repeat, WithInterruptsOff
|
||||
from sixtypical.ast import Program, Defn, Routine, Block, SingleOp, If, Repeat, For, WithInterruptsOff
|
||||
from sixtypical.model import (
|
||||
TYPE_BIT, TYPE_BYTE, TYPE_WORD,
|
||||
RoutineType, VectorType, TableType, BufferType, PointerType,
|
||||
|
@ -136,11 +136,21 @@ class Parser(object):
|
|||
|
||||
return Defn(self.scanner.line_number, name=name, addr=addr, initial=initial, location=location)
|
||||
|
||||
def literal_int(self):
|
||||
self.scanner.check_type('integer literal')
|
||||
c = int(self.scanner.token)
|
||||
self.scanner.scan()
|
||||
return c
|
||||
|
||||
def literal_int_const(self):
|
||||
value = self.literal_int()
|
||||
type_ = TYPE_WORD if value > 255 else TYPE_BYTE
|
||||
loc = ConstantRef(type_, value)
|
||||
return loc
|
||||
|
||||
def defn_size(self):
|
||||
self.scanner.expect('[')
|
||||
self.scanner.check_type('integer literal')
|
||||
size = int(self.scanner.token)
|
||||
self.scanner.scan()
|
||||
size = self.literal_int()
|
||||
self.scanner.expect(']')
|
||||
return size
|
||||
|
||||
|
@ -289,11 +299,7 @@ class Parser(object):
|
|||
self.scanner.scan()
|
||||
return loc
|
||||
elif self.scanner.on_type('integer literal'):
|
||||
value = int(self.scanner.token)
|
||||
type_ = TYPE_WORD if value > 255 else TYPE_BYTE
|
||||
loc = ConstantRef(type_, value)
|
||||
self.scanner.scan()
|
||||
return loc
|
||||
return self.literal_int_const()
|
||||
elif self.scanner.consume('word'):
|
||||
loc = ConstantRef(TYPE_WORD, int(self.scanner.token))
|
||||
self.scanner.scan()
|
||||
|
@ -372,6 +378,18 @@ class Parser(object):
|
|||
else:
|
||||
self.scanner.expect('forever')
|
||||
return Repeat(self.scanner.line_number, src=src, block=block, inverted=inverted)
|
||||
elif self.scanner.consume('for'):
|
||||
dest = self.locexpr()
|
||||
if self.scanner.consume('down'):
|
||||
direction = -1
|
||||
elif self.scanner.consume('up'):
|
||||
direction = 1
|
||||
else:
|
||||
self.syntax_error('expected "up" or "down", found "%s"' % self.scanner.token)
|
||||
self.scanner.expect('to')
|
||||
final = self.literal_int_const()
|
||||
block = self.block()
|
||||
return For(self.scanner.line_number, dest=dest, direction=direction, final=final, block=block)
|
||||
elif self.scanner.token in ("ld",):
|
||||
# the same as add, sub, cmp etc below, except supports an indlocexpr for the src
|
||||
opcode = self.scanner.token
|
||||
|
|
|
@ -62,7 +62,7 @@ class Scanner(object):
|
|||
if self.token == token:
|
||||
self.scan()
|
||||
else:
|
||||
raise SixtyPicalSyntaxError(self.scanner.line_number, "Expected '{}', but found '{}'".format(
|
||||
raise SixtyPicalSyntaxError(self.line_number, "Expected '{}', but found '{}'".format(
|
||||
token, self.token
|
||||
))
|
||||
|
||||
|
@ -74,7 +74,7 @@ class Scanner(object):
|
|||
|
||||
def check_type(self, type):
|
||||
if not self.type == type:
|
||||
raise SixtyPicalSyntaxError(self.scanner.line_number, "Expected {}, but found '{}'".format(
|
||||
raise SixtyPicalSyntaxError(self.line_number, "Expected {}, but found '{}'".format(
|
||||
self.type, self.token
|
||||
))
|
||||
|
||||
|
|
|
@ -1659,6 +1659,204 @@ The body of `repeat forever` can be empty.
|
|||
| }
|
||||
= ok
|
||||
|
||||
### for ###
|
||||
|
||||
Basic "open-faced for" loop. We'll start with the "upto" variant.
|
||||
|
||||
In a "for" loop, we know the exact range the loop variable takes on.
|
||||
|
||||
| byte table[16] tab
|
||||
|
|
||||
| define foo routine inputs tab trashes a, x, c, z, v, n {
|
||||
| ld x, 0
|
||||
| for x up to 15 {
|
||||
| ld a, tab + x
|
||||
| }
|
||||
| }
|
||||
= ok
|
||||
|
||||
| byte table[15] tab
|
||||
|
|
||||
| define foo routine inputs tab trashes a, x, c, z, v, n {
|
||||
| ld x, 0
|
||||
| for x up to 15 {
|
||||
| ld a, tab + x
|
||||
| }
|
||||
| }
|
||||
? RangeExceededError
|
||||
|
||||
You need to initialize the loop variable before the loop.
|
||||
|
||||
| byte table[16] tab
|
||||
|
|
||||
| define foo routine inputs tab trashes a, x, c, z, v, n {
|
||||
| for x up to 15 {
|
||||
| ld a, 0
|
||||
| }
|
||||
| }
|
||||
? UnmeaningfulReadError
|
||||
|
||||
You cannot modify the loop variable in a "for" loop.
|
||||
|
||||
| byte table[16] tab
|
||||
|
|
||||
| define foo routine inputs tab trashes a, x, c, z, v, n {
|
||||
| ld x, 0
|
||||
| for x up to 15 {
|
||||
| ld x, 0
|
||||
| }
|
||||
| }
|
||||
? ForbiddenWriteError
|
||||
|
||||
This includes nesting a loop on the same variable.
|
||||
|
||||
| byte table[16] tab
|
||||
|
|
||||
| define foo routine inputs tab trashes a, x, c, z, v, n {
|
||||
| ld x, 0
|
||||
| for x up to 8 {
|
||||
| for x up to 15 {
|
||||
| ld a, 0
|
||||
| }
|
||||
| }
|
||||
| }
|
||||
? ForbiddenWriteError
|
||||
|
||||
But nesting with two different variables is okay.
|
||||
|
||||
| byte table[16] tab
|
||||
|
|
||||
| define foo routine inputs tab trashes a, x, y, c, z, v, n {
|
||||
| ld x, 0
|
||||
| for x up to 8 {
|
||||
| ld a, x
|
||||
| ld y, a
|
||||
| for y up to 15 {
|
||||
| ld a, 0
|
||||
| }
|
||||
| }
|
||||
| }
|
||||
= ok
|
||||
|
||||
Inside the inner loop, the outer variable is still not writeable.
|
||||
|
||||
| byte table[16] tab
|
||||
|
|
||||
| define foo routine inputs tab trashes a, x, y, c, z, v, n {
|
||||
| ld x, 0
|
||||
| for x up to 8 {
|
||||
| ld a, x
|
||||
| ld y, a
|
||||
| for y up to 15 {
|
||||
| ld x, 0
|
||||
| }
|
||||
| }
|
||||
| }
|
||||
? ForbiddenWriteError
|
||||
|
||||
If the range isn't known to be smaller than the final value, you can't go up to it.
|
||||
|
||||
| byte table[32] tab
|
||||
|
|
||||
| define foo routine inputs tab trashes a, x, c, z, v, n {
|
||||
| ld x, 16
|
||||
| for x up to 15 {
|
||||
| ld a, tab + x
|
||||
| }
|
||||
| }
|
||||
? RangeExceededError
|
||||
|
||||
In a "for" loop (downward-counting variant), we know the exact range the loop variable takes on.
|
||||
|
||||
| byte table[16] tab
|
||||
|
|
||||
| define foo routine inputs tab trashes a, x, c, z, v, n {
|
||||
| ld x, 15
|
||||
| for x down to 0 {
|
||||
| ld a, tab + x
|
||||
| }
|
||||
| }
|
||||
= ok
|
||||
|
||||
| byte table[15] tab
|
||||
|
|
||||
| define foo routine inputs tab trashes a, x, c, z, v, n {
|
||||
| ld x, 15
|
||||
| for x down to 0 {
|
||||
| ld a, tab + x
|
||||
| }
|
||||
| }
|
||||
? RangeExceededError
|
||||
|
||||
You need to initialize the loop variable before a "for" loop (downward variant).
|
||||
|
||||
| byte table[16] tab
|
||||
|
|
||||
| define foo routine inputs tab trashes a, x, c, z, v, n {
|
||||
| for x down to 15 {
|
||||
| ld a, 0
|
||||
| }
|
||||
| }
|
||||
? UnmeaningfulReadError
|
||||
|
||||
You cannot modify the loop variable in a "for" loop (downward variant).
|
||||
|
||||
| byte table[16] tab
|
||||
|
|
||||
| define foo routine inputs tab trashes a, x, c, z, v, n {
|
||||
| ld x, 15
|
||||
| for x down to 0 {
|
||||
| ld x, 0
|
||||
| }
|
||||
| }
|
||||
? ForbiddenWriteError
|
||||
|
||||
If the range isn't known to be larger than the final value, you can't go down to it.
|
||||
|
||||
| byte table[32] tab
|
||||
|
|
||||
| define foo routine inputs tab trashes a, x, c, z, v, n {
|
||||
| ld x, 0
|
||||
| for x down to 0 {
|
||||
| ld a, tab + x
|
||||
| }
|
||||
| }
|
||||
? RangeExceededError
|
||||
|
||||
You can initialize something inside the loop that was uninitialized outside.
|
||||
|
||||
| routine main
|
||||
| outputs x, y, n, z
|
||||
| trashes c
|
||||
| {
|
||||
| ld x, 0
|
||||
| for x up to 15 {
|
||||
| ld y, 15
|
||||
| }
|
||||
| }
|
||||
= ok
|
||||
|
||||
But you can't UNinitialize something at the end of the loop that you need
|
||||
initialized at the start of that loop.
|
||||
|
||||
| routine foo
|
||||
| trashes y
|
||||
| {
|
||||
| }
|
||||
|
|
||||
| routine main
|
||||
| outputs x, y, n, z
|
||||
| trashes c
|
||||
| {
|
||||
| ld x, 0
|
||||
| ld y, 15
|
||||
| for x up to 15 {
|
||||
| inc y
|
||||
| call foo
|
||||
| }
|
||||
| }
|
||||
? UnmeaningfulReadError: y
|
||||
|
||||
### copy ###
|
||||
|
||||
Can't `copy` from a memory location that isn't initialized.
|
||||
|
@ -2181,6 +2379,52 @@ Calling the vector does indeed trash the things the vector says it does.
|
|||
| }
|
||||
? IllegalJumpError
|
||||
|
||||
| routine bar trashes x, z, n {
|
||||
| ld x, 200
|
||||
| }
|
||||
|
|
||||
| routine main trashes x, z, n {
|
||||
| ld x, 0
|
||||
| if z {
|
||||
| ld x, 1
|
||||
| goto bar
|
||||
| } else {
|
||||
| ld x, 0
|
||||
| goto bar
|
||||
| }
|
||||
| }
|
||||
= ok
|
||||
|
||||
| routine bar trashes x, z, n {
|
||||
| ld x, 200
|
||||
| }
|
||||
|
|
||||
| routine main trashes x, z, n {
|
||||
| ld x, 0
|
||||
| if z {
|
||||
| ld x, 1
|
||||
| goto bar
|
||||
| } else {
|
||||
| ld x, 0
|
||||
| }
|
||||
| }
|
||||
= ok
|
||||
|
||||
For the purposes of `goto`, the end of a loop is never tail position.
|
||||
|
||||
| routine bar trashes x, z, n {
|
||||
| ld x, 200
|
||||
| }
|
||||
|
|
||||
| routine main trashes x, z, n {
|
||||
| ld x, 0
|
||||
| repeat {
|
||||
| inc x
|
||||
| goto bar
|
||||
| } until z
|
||||
| }
|
||||
? IllegalJumpError
|
||||
|
||||
Can't `goto` a routine that outputs or trashes more than the current routine.
|
||||
|
||||
| routine bar trashes x, y, z, n {
|
||||
|
|
|
@ -351,6 +351,46 @@ The body of `repeat forever` can be empty.
|
|||
= $080D JMP $080D
|
||||
= $0810 RTS
|
||||
|
||||
Compiling `for ... up to`.
|
||||
|
||||
| byte table[256] tab
|
||||
|
|
||||
| define main routine
|
||||
| inputs tab
|
||||
| trashes a, x, c, z, v, n
|
||||
| {
|
||||
| ld x, 0
|
||||
| for x up to 15 {
|
||||
| ld a, tab + x
|
||||
| }
|
||||
| }
|
||||
= $080D LDX #$00
|
||||
= $080F LDA $0818,X
|
||||
= $0812 INX
|
||||
= $0813 CPX #$10
|
||||
= $0815 BNE $080F
|
||||
= $0817 RTS
|
||||
|
||||
Compiling `for ... down to`.
|
||||
|
||||
| byte table[256] tab
|
||||
|
|
||||
| define main routine
|
||||
| inputs tab
|
||||
| trashes a, x, c, z, v, n
|
||||
| {
|
||||
| ld x, 15
|
||||
| for x down to 0 {
|
||||
| ld a, tab + x
|
||||
| }
|
||||
| }
|
||||
= $080D LDX #$0F
|
||||
= $080F LDA $0818,X
|
||||
= $0812 DEX
|
||||
= $0813 CPX #$FF
|
||||
= $0815 BNE $080F
|
||||
= $0817 RTS
|
||||
|
||||
Indexed access.
|
||||
|
||||
| byte one
|
||||
|
|
|
@ -131,6 +131,22 @@ Repeat with not
|
|||
| }
|
||||
= ok
|
||||
|
||||
Basic "open-faced for" loops, up and down.
|
||||
|
||||
| byte table[256] tab
|
||||
|
|
||||
| routine foo trashes a, x, c, z, v {
|
||||
| ld x, 0
|
||||
| for x up to 15 {
|
||||
| ld a, tab + x
|
||||
| }
|
||||
| ld x, 15
|
||||
| for x down to 0 {
|
||||
| ld a, tab + x
|
||||
| }
|
||||
| }
|
||||
= ok
|
||||
|
||||
User-defined memory addresses of different types.
|
||||
|
||||
| byte byt
|
||||
|
|
Loading…
Reference in New Issue
Block a user