diff --git a/README.markdown b/README.markdown index db7884f..159d3b1 100644 --- a/README.markdown +++ b/README.markdown @@ -2,9 +2,9 @@ SixtyPical ========== SixtyPical is a very low-level programming language, similar to 6502 assembly, -with block structure and static analysis through abstract interpretation. +with static analysis through type-checking and abstract interpretation. -It is a work in progress, currently at the proof-of-concept stage. +It is a **work in progress**, currently at the **proof-of-concept** stage. It is expected that a common use case for SixtyPical would be retroprogramming for the Commodore 64 and other 6502-based computers such as the VIC-20. @@ -19,8 +19,8 @@ opcodes, but generate slightly different (safer, but intuitively related) sequences of opcodes. Et cetera. `sixtypical` is the reference implementation of SixtyPical. It is written in -Haskell. It can currently parse and analyze a SixtyPical program, and will -eventually be able to compile it to an Ophis assembler listing. +Haskell. It can currently parse and check a SixtyPical program, and can +emit an Ophis assembler listing for it. Concepts -------- @@ -131,11 +131,13 @@ Note to self, the `pl` opcodes *do* change flags. Instruction Support so far -------------------------- -A `X` indicates unsupported. A `!` indicates will-not-support. +A `X` indicates unsupported. Funny syntax indicates use of a special form. In these, `absolute` must be a `reserve`d or `locate`d address. +`immediate` must be a literal decimal or hexadecimal number +(or in future, a declared constant.) . adc #immediate @@ -268,472 +270,4 @@ TODO * give length for tables, must be there for reserved * Character tables ("strings" to everybody else) * Work out the analyses again and document them -* lda wordaddress --> is not legal. use lda wordaddr * Addressing modes; rename instructions to match - -Tests ------ - - -> 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)" - -`main` must be present. - - | routine main { - | nop - | } - = True - - | routine frog { - | nop - | } - ? missing 'main' routine - -A comment may appear at the start of a block. - - | routine main { - | ; this program does nothing - | nop - | } - = True - -A comment may appear after each command. - - | routine main { - | lda #1 ; we assemble the fnord using - | ldx #1 ; multiple lorem ipsums which - | ldy #1 - | lda #1 ; we - | ldx #1 ; found under the bridge by the old mill yesterday - | } - = True - -A comment may appear after each declaration. - - | reserve byte lives ; fnord - | assign byte gdcol 647 ; fnord - | external blastoff 4 ; fnnnnnnnnnnnnnnnnfffffffff - | - | routine main { - | nop - | } - = True - -A program may `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 table frequencies - | assign byte table screen 1024 - | routine main { - | nop - | } - = True - -A program may declare an `external`. - - | external blastoff 49152 - | routine main { - | jsr blastoff - | } - = True - -All declarations (`reserve`s and `assign`s) 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 - -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 to a vector. - - | reserve vector blah - | routine main { - | jmp blah - | } - = True - -We can't jump to a word. - - | reserve word blah - | routine main { - | jmp blah - | } - ? jmp to non-vector - -We can't jump to a byte. - - | assign byte screen 1024 - | routine main { - | jmp screen - | } - ? jmp to non-vector - -We can absolute-indexed a byte table. - - | assign byte table 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 - | } - = True - -We cannot absolute access a vector. - - | assign vector screen 1024 - | routine main { - | lda screen - | } - ? incompatible types 'Vector' and 'Byte' - - -> Tests for functionality "Emit ASM for SixtyPical program" - - -> Functionality "Emit ASM for SixtyPical program" is implemented by - -> shell command "bin/sixtypical emit %(test-file)" - - | reserve word vword - | reserve byte vbyte - | assign byte table table 1024 - | routine main { - | lda #4 - | ldx #0 - | ldy #$FF - | lda vbyte - | lda table, x - | lda table, y - | lda (vword), y - | lda vword - | inc vbyte - | tax - | inx - | dex - | stx vbyte - | tay - | iny - | dey - | sty vbyte - | cmp vbyte - | cmp #30 - | cmp vword - | ldx vbyte - | cpx vbyte - | cpx #31 - | txa - | ldy vbyte - | cpy vbyte - | cpy #32 - | tya - | sta vbyte - | sta table, x - | sta table, y - | sta (vword), y - | sta vword - | dec vbyte - | clc - | cld - | clv - | sec - | sed - | adc #8 - | adc vbyte - | and #8 - | and vbyte - | sbc #8 - | sbc vbyte - | ora #8 - | ora vbyte - | } - = main: - = lda #4 - = ldx #0 - = ldy #255 - = lda vbyte - = lda table, x - = lda table, y - = lda (vword), y - = lda vword - = lda vword+1 - = inc vbyte - = tax - = inx - = dex - = stx vbyte - = tay - = iny - = dey - = sty vbyte - = cmp vbyte - = cmp #30 - = cmp vword - = cmp vword+1 - = ldx vbyte - = cpx vbyte - = cpx #31 - = txa - = ldy vbyte - = cpy vbyte - = cpy #32 - = tya - = sta vbyte - = sta table, x - = sta table, y - = sta (vword), y - = sta vword - = sta vword+1 - = dec vbyte - = clc - = cld - = clv - = sec - = sed - = adc #8 - = adc vbyte - = and #8 - = and vbyte - = sbc #8 - = sbc vbyte - = ora #8 - = ora vbyte - = rts - = - = vword: .word 0 - = vbyte: .byte 0 - = .alias table 1024 - - | assign byte screen $0400 - | routine main { - | lda screen - | cmp screen - | if beq { - | tax - | } else { - | tay - | } - | sta screen - | } - = main: - = lda screen - = cmp screen - = BEQ _label_1 - = tay - = jmp _past_1 - = _label_1: - = tax - = _past_1: - = sta screen - = rts - = - = .alias screen 1024 - - | assign byte screen 1024 - | reserve byte zero - | routine main { - | ldy zero - | repeat bne { - | inc screen - | dey - | cpy zero - | } - | sty screen - | } - = main: - = ldy zero - = - = _repeat_1: - = inc screen - = dey - = cpy zero - = BNE _repeat_1 - = sty screen - = rts - = - = .alias screen 1024 - = zero: .byte 0 - -Nested ifs. - - | routine main { - | if beq { - | if bcc { - | lda #0 - | } else { - | if bvs { - | lda #1 - | } else { - | lda #2 - | } - | } - | } else { - | lda #3 - | } - | } - = main: - = BEQ _label_3 - = lda #3 - = jmp _past_3 - = _label_3: - = BCC _label_2 - = BVS _label_1 - = lda #2 - = jmp _past_1 - = _label_1: - = lda #1 - = _past_1: - = jmp _past_2 - = _label_2: - = lda #0 - = _past_2: - = _past_3: - = rts - -Installing an interrupt handler (at the Kernal level, i.e. with CINV) - - | assign byte screen 1024 - | assign vector cinv 788 - | reserve vector save_cinv - | - | routine main { - | sei { - | copy vector cinv to save_cinv - | copy routine our_cinv to cinv - | } - | } - | - | routine our_cinv { - | inc screen - | jmp save_cinv - | } - = main: - = sei - = lda cinv - = sta save_cinv - = lda cinv+1 - = sta save_cinv+1 - = lda #our_cinv - = sta cinv+1 - = cli - = rts - = - = our_cinv: - = inc screen - = jmp (save_cinv) - = rts - = - = .alias screen 1024 - = .alias cinv 788 - = save_cinv: .word 0 diff --git a/doc/Checking.markdown b/doc/Checking.markdown new file mode 100644 index 0000000..899fe48 --- /dev/null +++ b/doc/Checking.markdown @@ -0,0 +1,224 @@ +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)" + +`main` must be present. + + | routine main { + | nop + | } + = True + + | routine frog { + | nop + | } + ? missing 'main' routine + +A comment may appear at the start of a block. + + | routine main { + | ; this program does nothing + | nop + | } + = True + +A comment may appear after each command. + + | routine main { + | lda #1 ; we assemble the fnord using + | ldx #1 ; multiple lorem ipsums which + | ldy #1 + | lda #1 ; we + | ldx #1 ; found under the bridge by the old mill yesterday + | } + = True + +A comment may appear after each declaration. + + | reserve byte lives ; fnord + | assign byte gdcol 647 ; fnord + | external blastoff 4 ; fnnnnnnnnnnnnnnnnfffffffff + | + | routine main { + | nop + | } + = True + +A program may `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 table frequencies + | assign byte table screen 1024 + | routine main { + | nop + | } + = True + +A program may declare an `external`. + + | external blastoff 49152 + | routine main { + | jsr blastoff + | } + = True + +All declarations (`reserve`s and `assign`s) 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 + +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 to a vector. + + | reserve vector blah + | routine main { + | jmp blah + | } + = True + +We can't jump to a word. + + | reserve word blah + | routine main { + | jmp blah + | } + ? jmp to non-vector + +We can't jump to a byte. + + | assign byte screen 1024 + | routine main { + | jmp screen + | } + ? jmp to non-vector + +We can absolute-indexed a byte table. + + | assign byte table 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 + | } + = True + +We cannot absolute access a vector. + + | assign vector screen 1024 + | routine main { + | lda screen + | } + ? incompatible types 'Vector' and 'Byte' diff --git a/doc/Emitting.markdown b/doc/Emitting.markdown new file mode 100644 index 0000000..412618f --- /dev/null +++ b/doc/Emitting.markdown @@ -0,0 +1,244 @@ +Emitting Ophis from SixtyPical Programs +======================================= + + -> Tests for functionality "Emit ASM for SixtyPical program" + + -> Functionality "Emit ASM for SixtyPical program" is implemented by + -> shell command "bin/sixtypical emit %(test-file)" + + | reserve word vword + | reserve byte vbyte + | assign byte table table 1024 + | routine main { + | lda #4 + | ldx #0 + | ldy #$FF + | lda vbyte + | lda table, x + | lda table, y + | lda (vword), y + | lda vword + | inc vbyte + | tax + | inx + | dex + | stx vbyte + | tay + | iny + | dey + | sty vbyte + | cmp vbyte + | cmp #30 + | cmp vword + | ldx vbyte + | cpx vbyte + | cpx #31 + | txa + | ldy vbyte + | cpy vbyte + | cpy #32 + | tya + | sta vbyte + | sta table, x + | sta table, y + | sta (vword), y + | sta vword + | dec vbyte + | clc + | cld + | clv + | sec + | sed + | adc #8 + | adc vbyte + | and #8 + | and vbyte + | sbc #8 + | sbc vbyte + | ora #8 + | ora vbyte + | } + = main: + = lda #4 + = ldx #0 + = ldy #255 + = lda vbyte + = lda table, x + = lda table, y + = lda (vword), y + = lda vword + = lda vword+1 + = inc vbyte + = tax + = inx + = dex + = stx vbyte + = tay + = iny + = dey + = sty vbyte + = cmp vbyte + = cmp #30 + = cmp vword + = cmp vword+1 + = ldx vbyte + = cpx vbyte + = cpx #31 + = txa + = ldy vbyte + = cpy vbyte + = cpy #32 + = tya + = sta vbyte + = sta table, x + = sta table, y + = sta (vword), y + = sta vword + = sta vword+1 + = dec vbyte + = clc + = cld + = clv + = sec + = sed + = adc #8 + = adc vbyte + = and #8 + = and vbyte + = sbc #8 + = sbc vbyte + = ora #8 + = ora vbyte + = rts + = + = vword: .word 0 + = vbyte: .byte 0 + = .alias table 1024 + + | assign byte screen $0400 + | routine main { + | lda screen + | cmp screen + | if beq { + | tax + | } else { + | tay + | } + | sta screen + | } + = main: + = lda screen + = cmp screen + = BEQ _label_1 + = tay + = jmp _past_1 + = _label_1: + = tax + = _past_1: + = sta screen + = rts + = + = .alias screen 1024 + + | assign byte screen 1024 + | reserve byte zero + | routine main { + | ldy zero + | repeat bne { + | inc screen + | dey + | cpy zero + | } + | sty screen + | } + = main: + = ldy zero + = + = _repeat_1: + = inc screen + = dey + = cpy zero + = BNE _repeat_1 + = sty screen + = rts + = + = .alias screen 1024 + = zero: .byte 0 + +Nested ifs. + + | routine main { + | if beq { + | if bcc { + | lda #0 + | } else { + | if bvs { + | lda #1 + | } else { + | lda #2 + | } + | } + | } else { + | lda #3 + | } + | } + = main: + = BEQ _label_3 + = lda #3 + = jmp _past_3 + = _label_3: + = BCC _label_2 + = BVS _label_1 + = lda #2 + = jmp _past_1 + = _label_1: + = lda #1 + = _past_1: + = jmp _past_2 + = _label_2: + = lda #0 + = _past_2: + = _past_3: + = rts + +Installing an interrupt handler (at the Kernal level, i.e. with CINV) + + | assign byte screen 1024 + | assign vector cinv 788 + | reserve vector save_cinv + | + | routine main { + | sei { + | copy vector cinv to save_cinv + | copy routine our_cinv to cinv + | } + | } + | + | routine our_cinv { + | inc screen + | jmp save_cinv + | } + = main: + = sei + = lda cinv + = sta save_cinv + = lda cinv+1 + = sta save_cinv+1 + = lda #our_cinv + = sta cinv+1 + = cli + = rts + = + = our_cinv: + = inc screen + = jmp (save_cinv) + = rts + = + = .alias screen 1024 + = .alias cinv 788 + = save_cinv: .word 0 diff --git a/loadngo.sh b/loadngo.sh index 0ab05d1..23ba246 100755 --- a/loadngo.sh +++ b/loadngo.sh @@ -1,5 +1,6 @@ #!/bin/sh +./build.sh || exit 1 bin/sixtypical emit $1 > tmp.oph || exit 1 cat lib/basic_header.oph tmp.oph > tmp2.oph || exit 1 ophis tmp2.oph -o tmp.prg || exit 1 diff --git a/test.sh b/test.sh index fcb5822..ead4642 100755 --- a/test.sh +++ b/test.sh @@ -1,3 +1,4 @@ #!/bin/sh -./build.sh && falderal --substring-error README.markdown +FILES="doc/Checking.markdown doc/Emitting.markdown" +./build.sh && falderal --substring-error ${FILES}