git @ Cat's Eye Technologies SixtyPical / 0.1 doc / Checking.markdown
0.1

Tree @0.1 (Download .tar.gz)

Checking.markdown @0.1view markup · raw · history · blame

Checking SixtyPical Programs

-> Tests for functionality "Parse SixtyPical program"

-> Functionality "Parse SixtyPical program" is implemented by
-> shell command "bin/sixtypical parse %(test-file)"

-> Tests for functionality "Check SixtyPical program"

-> Functionality "Check SixtyPical program" is implemented by
-> shell command "bin/sixtypical check %(test-file)"

Some Basic Syntax

main must be present.

| routine main {
|    nop
| }
= True

| routine frog {
|    nop
| }
? missing 'main' routine

Each instruction need not appear on its own line. (Although you probably still want to write in that style, for consistency with assembly code.)

| routine main {
|    nop lda #1 ldx #1 nop
| }
= True

Javascript-style block and line comments are both supported. They may appear anywhere whitespace may appear.

| reserve byte lives      /* fnord */
| assign byte gdcol 647   // fnord
| external blastoff 4     // fnnnnnnnnnnnnnnnnfffffffff
| 
| routine /* hello */ main {
|    /* this routine does everything you need. */
|    lda #1   // we assemble the fnord using
|    ldx #1   // multiple lorem ipsums which
|    ldy #1
|    lda #1   /* we
|                found under the bridge by the old mill yesterday */
|    ldx #1
| }
= True

Addresses

An address may be declared with reserve, which is like .data or .bss in an assembler. This is an address into the program's data. It is global to all routines.

| reserve byte lives
| routine main {
|    lda #3
|    sta lives
| }
| routine died {
|    dec lives
| }
= True

An address declared with reserve may be given an initial value.

| reserve byte lives : 3
| routine main {
|    sta lives
| }
| routine died {
|    dec lives
| }
= True

A byte table declared with reserve may be given an initial value consisting of a sequence of bytes.

| reserve byte[4] table : (0 $40 $10 20)
| routine main {
|    ldy #0
|    lda table, y
| }
| routine died {
|    sta table, y
| }
= True

A byte table declared with reserve may be given an initial value consisting of a sequence of bytes represented as a character string.

| reserve byte[4] table : "What"
| routine main {
|    ldy #0
|    lda table, y
| }
| routine died {
|    sta table, y
| }
= True

When a byte table declared with reserve is given an initial value consisting of a sequence of bytes, it must be the same length as the table is declared.

| reserve byte[4] table : (0 $40 $10 20 60 70 90)
| routine main {
|    ldy #0
|    lda table, y
| }
| routine died {
|    sta table, y
| }
? initial table incorrect size

| reserve byte[4] table : "Hello, world!"
| routine main {
|    ldy #0
|    lda table, y
| }
| routine died {
|    sta table, y
| }
? initial table incorrect size

We can also define word and vector tables. These are each stored as two byte tables, one table of low bytes and one table of high bytes.

| reserve word[100] words
| reserve vector[100] vectors
| routine main {
|    lda #$04
|    sta <words
|    // sta <words, y
|    lda #$00
|    sta >words
|    // sta >words, y
|    // copy routine main to vectors, y
| }
= True

An address may be declared with locate, which is like .alias in an assembler, with the understanding that the value will be treated "like an address." This is generally an address into the operating system or hardware (e.g. kernal routine, I/O port, etc.)

| assign byte screen $0400
| routine main {
|    lda #0
|    sta screen
| }
= True

The body of a routine may not refer to an address literally. It must use a symbol that was declared previously with reserve or assign.

| routine main {
|    lda #0
|    sta $0400
| }
? unexpected

| assign byte screen $0400
| routine main {
|    lda #0
|    sta screen
| }
= True

Test for many combinations of reserve and assign.

| reserve byte lives
| assign byte gdcol 647
| reserve word score
| assign word memstr 641
| reserve vector v
| assign vector cinv 788
| reserve byte[16] frequencies
| assign byte[256] screen 1024
| routine main {
|    nop
| }
= True

reserve may be block-level.

| routine main {
|    reserve byte lives
|    lda lives
| }
= True

Block-level declarations are only visible in the block in which they are declared.

| routine main {
|    reserve byte lives
|    lda #3
|    sta lives
| }
| routine died {
|    dec lives
| }
? undeclared location 'lives'

A block-level reserve may not supply an initial value.

| routine main {
|    reserve byte lives : 3
|    lda lives
| }
? block-level 'lives' cannot supply initial value

A program may declare an external.

| external blastoff 49152
| routine main {
|    jsr blastoff
| }
= True

All declarations (reserves and assigns) must come before any routines.

| routine main {
|    lda score
| }
| reserve word score
? expecting "routine"

All locations used in all routines must be declared first.

| reserve byte score
| routine main {
|    lda score
|    cmp screen
| }
? undeclared location

Even in inner blocks.

| reserve byte score
| assign byte screen 1024
| routine main {
|    lda score
|    cmp screen
|    if beq {
|      lda score
|    } else {
|      lda fnord
|    }
| }
? undeclared location

Block-level declarations are visible in inner blocks.

| routine main {
|   reserve byte lives
|   with sei {
|     if beq {
|       lda #3
|       repeat bne {
|         sta lives
|       }
|     } else {
|       sta lives
|     }
|   }
| }
= True

A block-level reserve may not supply an initial value.

| routine main {
|    reserve byte lives : 3
|    lda lives
| }
? block-level 'lives' cannot supply initial value

All routines jsr'ed to must be defined, or external.

| routine main {
|    jsr blastoff
| }
? undeclared routine

No duplicate location names in declarations.

| reserve word score
| assign word score 4000
| routine main {
|    nop
| }
? duplicate location name

No duplicate routine names.

| routine main {
|    nop
| }
| routine main {
|    txa
| }
? duplicate routine name

No duplicate routine names, including externals.

| external main 7000
| routine main {
|    nop
| }
? duplicate routine name

We can jump indirectly through a vector.

| reserve vector blah
| routine main {
|    jmp (blah)
| }
= True

We can't jump indirectly through a word.

| reserve word blah
| routine main {
|    jmp (blah)
| }
? jmp to non-vector

We can't jump indirectly through a byte.

| assign byte screen 1024
| routine main {
|    jmp (screen)
| }
? jmp to non-vector

We can absolute-indexed a byte table.

| assign byte[256] screen 1024
| routine main {
|    sta screen, x
| }
= True

We cannot absolute-indexed a byte.

| assign byte screen 1024
| routine main {
|    sta screen, x
| }
? indexed access of non-table

We cannot absolute-indexed a word.

| assign word screen 1024
| routine main {
|    sta screen, x
| }
? indexed access of non-table

We cannot absolute access a word.

| assign word screen 1024
| routine main {
|    ldx screen
| }
? incompatible types 'Word' and 'Byte'

No, not even with ora.

| assign word screen 1024
| routine main {
|    ora screen
| }
? incompatible types 'Byte' and 'Word'

Instead, we have to do this.

| assign word screen 1024
| routine main {
|    lda <screen
|    lda >screen
| }
= True

We cannot absolute access a vector.

| assign vector screen 1024
| routine main {
|    lda screen
| }
? incompatible types 'Vector' and 'Byte'

Addresses

An address knows what kind of data is stored at the address:

  • byte: an 8-bit byte. not part of a word. not to be used as an address. (could be an index though.)
  • word: a 16-bit word. not to be used as an address.
  • vector: a 16-bit address of a routine. Only a handful of operations are supported on vectors:

    • copying the contents of one vector to another
    • copying the address of a routine into a vector
    • jumping indirectly to a vector (i.e. to the code at the address contained in the vector (and this can only happen at the end of a routine (NYI))
    • jsr'ing indirectly to a vector (which is done with a fun generated trick (NYI))
  • byte [SIZE]: a series of SIZE bytes contiguous in memory starting from the address. This is the only kind of address that can be used in indexed addressing. SIZE has a minimum of 1 and a maximum of 256.

Blocks

Each routine is a block. It may be composed of inner blocks, if those inner blocks are attached to certain instructions.

SixtyPical does not have instructions that map literally to the 6502 branch instructions. Instead, it has an if construct, with two blocks (for the "then" and else parts), and the branch instructions map to conditions for this construct.

Similarly, there is a repeat construct. The same branch instructions can be used in the condition to this construct. In this case, they branch back to the top of the repeat loop.

The abstract states of the machine at each of the different block exits are merged during analysis. If any register or memory location is treated inconsistently (e.g. updated in one branch of the test, but not the other,) that register cannot subsequently be used without a declaration to the effect that we know what's going on. (This is all a bit fuzzy right now.)

There is also no rts instruction. It is included at the end of a routine, but only when the routine is used as a subroutine. Also, if the routine ends by jsring another routine, it reserves the right to do a tail-call or even a fallthrough.

There are also with instructions, which are associated with three opcodes that have natural symmetrical opcodes: pha, php, and sei. These instructions take a block. The natural symmetrical opcode is inserted at the end of the block.